1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void TestIfPlayerTouchesCustomElement(int, int);
1062 static void TestIfElementTouchesCustomElement(int, int);
1063 static void TestIfElementHitsCustomElement(int, int, int);
1065 static void HandleElementChange(int, int, int);
1066 static void ExecuteCustomElementAction(int, int, int, int);
1067 static boolean ChangeElement(int, int, int, int);
1069 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1070 #define CheckTriggeredElementChange(x, y, e, ev) \
1071 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1072 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1073 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1074 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1075 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1076 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1077 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1078 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1079 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1081 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1082 #define CheckElementChange(x, y, e, te, ev) \
1083 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1084 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1085 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1086 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1087 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1088 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1089 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1091 static void PlayLevelSound(int, int, int);
1092 static void PlayLevelSoundNearest(int, int, int);
1093 static void PlayLevelSoundAction(int, int, int);
1094 static void PlayLevelSoundElementAction(int, int, int, int);
1095 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1096 static void PlayLevelSoundActionIfLoop(int, int, int);
1097 static void StopLevelSoundActionIfLoop(int, int, int);
1098 static void PlayLevelMusic(void);
1099 static void FadeLevelSoundsAndMusic(void);
1101 static void HandleGameButtons(struct GadgetInfo *);
1103 int AmoebaNeighbourNr(int, int);
1104 void AmoebaToDiamond(int, int);
1105 void ContinueMoving(int, int);
1106 void Bang(int, int);
1107 void InitMovDir(int, int);
1108 void InitAmoebaNr(int, int);
1109 void NewHighScore(int);
1111 void TestIfGoodThingHitsBadThing(int, int, int);
1112 void TestIfBadThingHitsGoodThing(int, int, int);
1113 void TestIfPlayerTouchesBadThing(int, int);
1114 void TestIfPlayerRunsIntoBadThing(int, int, int);
1115 void TestIfBadThingTouchesPlayer(int, int);
1116 void TestIfBadThingRunsIntoPlayer(int, int, int);
1117 void TestIfFriendTouchesBadThing(int, int);
1118 void TestIfBadThingTouchesFriend(int, int);
1119 void TestIfBadThingTouchesOtherBadThing(int, int);
1120 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1122 void KillPlayer(struct PlayerInfo *);
1123 void BuryPlayer(struct PlayerInfo *);
1124 void RemovePlayer(struct PlayerInfo *);
1125 void ExitPlayer(struct PlayerInfo *);
1127 static int getInvisibleActiveFromInvisibleElement(int);
1128 static int getInvisibleFromInvisibleActiveElement(int);
1130 static void TestFieldAfterSnapping(int, int, int, int, int);
1132 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1134 // for detection of endless loops, caused by custom element programming
1135 // (using maximal playfield width x 10 is just a rough approximation)
1136 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1138 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1140 if (recursion_loop_detected) \
1143 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1145 recursion_loop_detected = TRUE; \
1146 recursion_loop_element = (e); \
1149 recursion_loop_depth++; \
1152 #define RECURSION_LOOP_DETECTION_END() \
1154 recursion_loop_depth--; \
1157 static int recursion_loop_depth;
1158 static boolean recursion_loop_detected;
1159 static boolean recursion_loop_element;
1161 static int map_player_action[MAX_PLAYERS];
1164 // ----------------------------------------------------------------------------
1165 // definition of elements that automatically change to other elements after
1166 // a specified time, eventually calling a function when changing
1167 // ----------------------------------------------------------------------------
1169 // forward declaration for changer functions
1170 static void InitBuggyBase(int, int);
1171 static void WarnBuggyBase(int, int);
1173 static void InitTrap(int, int);
1174 static void ActivateTrap(int, int);
1175 static void ChangeActiveTrap(int, int);
1177 static void InitRobotWheel(int, int);
1178 static void RunRobotWheel(int, int);
1179 static void StopRobotWheel(int, int);
1181 static void InitTimegateWheel(int, int);
1182 static void RunTimegateWheel(int, int);
1184 static void InitMagicBallDelay(int, int);
1185 static void ActivateMagicBall(int, int);
1187 struct ChangingElementInfo
1192 void (*pre_change_function)(int x, int y);
1193 void (*change_function)(int x, int y);
1194 void (*post_change_function)(int x, int y);
1197 static struct ChangingElementInfo change_delay_list[] =
1232 EL_STEEL_EXIT_OPENING,
1240 EL_STEEL_EXIT_CLOSING,
1241 EL_STEEL_EXIT_CLOSED,
1264 EL_EM_STEEL_EXIT_OPENING,
1265 EL_EM_STEEL_EXIT_OPEN,
1272 EL_EM_STEEL_EXIT_CLOSING,
1296 EL_SWITCHGATE_OPENING,
1304 EL_SWITCHGATE_CLOSING,
1305 EL_SWITCHGATE_CLOSED,
1312 EL_TIMEGATE_OPENING,
1320 EL_TIMEGATE_CLOSING,
1329 EL_ACID_SPLASH_LEFT,
1337 EL_ACID_SPLASH_RIGHT,
1346 EL_SP_BUGGY_BASE_ACTIVATING,
1353 EL_SP_BUGGY_BASE_ACTIVATING,
1354 EL_SP_BUGGY_BASE_ACTIVE,
1361 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_ROBOT_WHEEL_ACTIVE,
1393 EL_TIMEGATE_SWITCH_ACTIVE,
1401 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1402 EL_DC_TIMEGATE_SWITCH,
1409 EL_EMC_MAGIC_BALL_ACTIVE,
1410 EL_EMC_MAGIC_BALL_ACTIVE,
1417 EL_EMC_SPRING_BUMPER_ACTIVE,
1418 EL_EMC_SPRING_BUMPER,
1425 EL_DIAGONAL_SHRINKING,
1433 EL_DIAGONAL_GROWING,
1454 int push_delay_fixed, push_delay_random;
1458 { EL_SPRING, 0, 0 },
1459 { EL_BALLOON, 0, 0 },
1461 { EL_SOKOBAN_OBJECT, 2, 0 },
1462 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1463 { EL_SATELLITE, 2, 0 },
1464 { EL_SP_DISK_YELLOW, 2, 0 },
1466 { EL_UNDEFINED, 0, 0 },
1474 move_stepsize_list[] =
1476 { EL_AMOEBA_DROP, 2 },
1477 { EL_AMOEBA_DROPPING, 2 },
1478 { EL_QUICKSAND_FILLING, 1 },
1479 { EL_QUICKSAND_EMPTYING, 1 },
1480 { EL_QUICKSAND_FAST_FILLING, 2 },
1481 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1482 { EL_MAGIC_WALL_FILLING, 2 },
1483 { EL_MAGIC_WALL_EMPTYING, 2 },
1484 { EL_BD_MAGIC_WALL_FILLING, 2 },
1485 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1486 { EL_DC_MAGIC_WALL_FILLING, 2 },
1487 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_UNDEFINED, 0 },
1497 collect_count_list[] =
1500 { EL_BD_DIAMOND, 1 },
1501 { EL_EMERALD_YELLOW, 1 },
1502 { EL_EMERALD_RED, 1 },
1503 { EL_EMERALD_PURPLE, 1 },
1505 { EL_SP_INFOTRON, 1 },
1509 { EL_UNDEFINED, 0 },
1517 access_direction_list[] =
1519 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1520 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1521 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1522 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1525 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1526 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1527 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1528 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1529 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1531 { EL_SP_PORT_LEFT, MV_RIGHT },
1532 { EL_SP_PORT_RIGHT, MV_LEFT },
1533 { EL_SP_PORT_UP, MV_DOWN },
1534 { EL_SP_PORT_DOWN, MV_UP },
1535 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1536 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1537 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1538 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1542 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1543 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1544 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1545 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1546 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1547 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1548 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1549 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1551 { EL_UNDEFINED, MV_NONE }
1554 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1556 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1557 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1558 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1559 IS_JUST_CHANGING(x, y))
1561 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1563 // static variables for playfield scan mode (scanning forward or backward)
1564 static int playfield_scan_start_x = 0;
1565 static int playfield_scan_start_y = 0;
1566 static int playfield_scan_delta_x = 1;
1567 static int playfield_scan_delta_y = 1;
1569 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1570 (y) >= 0 && (y) <= lev_fieldy - 1; \
1571 (y) += playfield_scan_delta_y) \
1572 for ((x) = playfield_scan_start_x; \
1573 (x) >= 0 && (x) <= lev_fieldx - 1; \
1574 (x) += playfield_scan_delta_x)
1577 void DEBUG_SetMaximumDynamite(void)
1581 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1582 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1583 local_player->inventory_element[local_player->inventory_size++] =
1588 static void InitPlayfieldScanModeVars(void)
1590 if (game.use_reverse_scan_direction)
1592 playfield_scan_start_x = lev_fieldx - 1;
1593 playfield_scan_start_y = lev_fieldy - 1;
1595 playfield_scan_delta_x = -1;
1596 playfield_scan_delta_y = -1;
1600 playfield_scan_start_x = 0;
1601 playfield_scan_start_y = 0;
1603 playfield_scan_delta_x = 1;
1604 playfield_scan_delta_y = 1;
1608 static void InitPlayfieldScanMode(int mode)
1610 game.use_reverse_scan_direction =
1611 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1613 InitPlayfieldScanModeVars();
1616 static int get_move_delay_from_stepsize(int move_stepsize)
1619 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1621 // make sure that stepsize value is always a power of 2
1622 move_stepsize = (1 << log_2(move_stepsize));
1624 return TILEX / move_stepsize;
1627 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1630 int player_nr = player->index_nr;
1631 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1632 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1634 // do no immediately change move delay -- the player might just be moving
1635 player->move_delay_value_next = move_delay;
1637 // information if player can move must be set separately
1638 player->cannot_move = cannot_move;
1642 player->move_delay = game.initial_move_delay[player_nr];
1643 player->move_delay_value = game.initial_move_delay_value[player_nr];
1645 player->move_delay_value_next = -1;
1647 player->move_delay_reset_counter = 0;
1651 void GetPlayerConfig(void)
1653 GameFrameDelay = setup.game_frame_delay;
1655 if (!audio.sound_available)
1656 setup.sound_simple = FALSE;
1658 if (!audio.loops_available)
1659 setup.sound_loops = FALSE;
1661 if (!audio.music_available)
1662 setup.sound_music = FALSE;
1664 if (!video.fullscreen_available)
1665 setup.fullscreen = FALSE;
1667 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1669 SetAudioMode(setup.sound);
1672 int GetElementFromGroupElement(int element)
1674 if (IS_GROUP_ELEMENT(element))
1676 struct ElementGroupInfo *group = element_info[element].group;
1677 int last_anim_random_frame = gfx.anim_random_frame;
1680 if (group->choice_mode == ANIM_RANDOM)
1681 gfx.anim_random_frame = RND(group->num_elements_resolved);
1683 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1684 group->choice_mode, 0,
1687 if (group->choice_mode == ANIM_RANDOM)
1688 gfx.anim_random_frame = last_anim_random_frame;
1690 group->choice_pos++;
1692 element = group->element_resolved[element_pos];
1698 static void IncrementSokobanFieldsNeeded(void)
1700 if (level.sb_fields_needed)
1701 game.sokoban_fields_still_needed++;
1704 static void IncrementSokobanObjectsNeeded(void)
1706 if (level.sb_objects_needed)
1707 game.sokoban_objects_still_needed++;
1710 static void DecrementSokobanFieldsNeeded(void)
1712 if (game.sokoban_fields_still_needed > 0)
1713 game.sokoban_fields_still_needed--;
1716 static void DecrementSokobanObjectsNeeded(void)
1718 if (game.sokoban_objects_still_needed > 0)
1719 game.sokoban_objects_still_needed--;
1722 static void InitPlayerField(int x, int y, int element, boolean init_game)
1724 if (element == EL_SP_MURPHY)
1728 if (stored_player[0].present)
1730 Tile[x][y] = EL_SP_MURPHY_CLONE;
1736 stored_player[0].initial_element = element;
1737 stored_player[0].use_murphy = TRUE;
1739 if (!level.use_artwork_element[0])
1740 stored_player[0].artwork_element = EL_SP_MURPHY;
1743 Tile[x][y] = EL_PLAYER_1;
1749 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1750 int jx = player->jx, jy = player->jy;
1752 player->present = TRUE;
1754 player->block_last_field = (element == EL_SP_MURPHY ?
1755 level.sp_block_last_field :
1756 level.block_last_field);
1758 // ---------- initialize player's last field block delay ------------------
1760 // always start with reliable default value (no adjustment needed)
1761 player->block_delay_adjustment = 0;
1763 // special case 1: in Supaplex, Murphy blocks last field one more frame
1764 if (player->block_last_field && element == EL_SP_MURPHY)
1765 player->block_delay_adjustment = 1;
1767 // special case 2: in game engines before 3.1.1, blocking was different
1768 if (game.use_block_last_field_bug)
1769 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1771 if (!network.enabled || player->connected_network)
1773 player->active = TRUE;
1775 // remove potentially duplicate players
1776 if (StorePlayer[jx][jy] == Tile[x][y])
1777 StorePlayer[jx][jy] = 0;
1779 StorePlayer[x][y] = Tile[x][y];
1781 #if DEBUG_INIT_PLAYER
1782 Debug("game:init:player", "- player element %d activated",
1783 player->element_nr);
1784 Debug("game:init:player", " (local player is %d and currently %s)",
1785 local_player->element_nr,
1786 local_player->active ? "active" : "not active");
1790 Tile[x][y] = EL_EMPTY;
1792 player->jx = player->last_jx = x;
1793 player->jy = player->last_jy = y;
1796 // always check if player was just killed and should be reanimated
1798 int player_nr = GET_PLAYER_NR(element);
1799 struct PlayerInfo *player = &stored_player[player_nr];
1801 if (player->active && player->killed)
1802 player->reanimated = TRUE; // if player was just killed, reanimate him
1806 static void InitField(int x, int y, boolean init_game)
1808 int element = Tile[x][y];
1817 InitPlayerField(x, y, element, init_game);
1820 case EL_SOKOBAN_FIELD_PLAYER:
1821 element = Tile[x][y] = EL_PLAYER_1;
1822 InitField(x, y, init_game);
1824 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1825 InitField(x, y, init_game);
1828 case EL_SOKOBAN_FIELD_EMPTY:
1829 IncrementSokobanFieldsNeeded();
1832 case EL_SOKOBAN_OBJECT:
1833 IncrementSokobanObjectsNeeded();
1837 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1838 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1839 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1840 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1841 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1842 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1843 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1844 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1845 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1846 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1855 case EL_SPACESHIP_RIGHT:
1856 case EL_SPACESHIP_UP:
1857 case EL_SPACESHIP_LEFT:
1858 case EL_SPACESHIP_DOWN:
1859 case EL_BD_BUTTERFLY:
1860 case EL_BD_BUTTERFLY_RIGHT:
1861 case EL_BD_BUTTERFLY_UP:
1862 case EL_BD_BUTTERFLY_LEFT:
1863 case EL_BD_BUTTERFLY_DOWN:
1865 case EL_BD_FIREFLY_RIGHT:
1866 case EL_BD_FIREFLY_UP:
1867 case EL_BD_FIREFLY_LEFT:
1868 case EL_BD_FIREFLY_DOWN:
1869 case EL_PACMAN_RIGHT:
1871 case EL_PACMAN_LEFT:
1872 case EL_PACMAN_DOWN:
1874 case EL_YAMYAM_LEFT:
1875 case EL_YAMYAM_RIGHT:
1877 case EL_YAMYAM_DOWN:
1878 case EL_DARK_YAMYAM:
1881 case EL_SP_SNIKSNAK:
1882 case EL_SP_ELECTRON:
1888 case EL_SPRING_LEFT:
1889 case EL_SPRING_RIGHT:
1893 case EL_AMOEBA_FULL:
1898 case EL_AMOEBA_DROP:
1899 if (y == lev_fieldy - 1)
1901 Tile[x][y] = EL_AMOEBA_GROWING;
1902 Store[x][y] = EL_AMOEBA_WET;
1906 case EL_DYNAMITE_ACTIVE:
1907 case EL_SP_DISK_RED_ACTIVE:
1908 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1909 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1910 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1912 MovDelay[x][y] = 96;
1915 case EL_EM_DYNAMITE_ACTIVE:
1916 MovDelay[x][y] = 32;
1920 game.lights_still_needed++;
1924 game.friends_still_needed++;
1929 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1932 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1933 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1934 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1935 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1946 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1947 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1948 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1950 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1952 game.belt_dir[belt_nr] = belt_dir;
1953 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1955 else // more than one switch -- set it like the first switch
1957 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1962 case EL_LIGHT_SWITCH_ACTIVE:
1964 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1967 case EL_INVISIBLE_STEELWALL:
1968 case EL_INVISIBLE_WALL:
1969 case EL_INVISIBLE_SAND:
1970 if (game.light_time_left > 0 ||
1971 game.lenses_time_left > 0)
1972 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1975 case EL_EMC_MAGIC_BALL:
1976 if (game.ball_active)
1977 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1980 case EL_EMC_MAGIC_BALL_SWITCH:
1981 if (game.ball_active)
1982 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1985 case EL_TRIGGER_PLAYER:
1986 case EL_TRIGGER_ELEMENT:
1987 case EL_TRIGGER_CE_VALUE:
1988 case EL_TRIGGER_CE_SCORE:
1990 case EL_ANY_ELEMENT:
1991 case EL_CURRENT_CE_VALUE:
1992 case EL_CURRENT_CE_SCORE:
2009 // reference elements should not be used on the playfield
2010 Tile[x][y] = EL_EMPTY;
2014 if (IS_CUSTOM_ELEMENT(element))
2016 if (CAN_MOVE(element))
2019 if (!element_info[element].use_last_ce_value || init_game)
2020 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2022 else if (IS_GROUP_ELEMENT(element))
2024 Tile[x][y] = GetElementFromGroupElement(element);
2026 InitField(x, y, init_game);
2033 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2036 static void InitField_WithBug1(int x, int y, boolean init_game)
2038 InitField(x, y, init_game);
2040 // not needed to call InitMovDir() -- already done by InitField()!
2041 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2042 CAN_MOVE(Tile[x][y]))
2046 static void InitField_WithBug2(int x, int y, boolean init_game)
2048 int old_element = Tile[x][y];
2050 InitField(x, y, init_game);
2052 // not needed to call InitMovDir() -- already done by InitField()!
2053 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2054 CAN_MOVE(old_element) &&
2055 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2058 /* this case is in fact a combination of not less than three bugs:
2059 first, it calls InitMovDir() for elements that can move, although this is
2060 already done by InitField(); then, it checks the element that was at this
2061 field _before_ the call to InitField() (which can change it); lastly, it
2062 was not called for "mole with direction" elements, which were treated as
2063 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2067 static int get_key_element_from_nr(int key_nr)
2069 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2070 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2071 EL_EM_KEY_1 : EL_KEY_1);
2073 return key_base_element + key_nr;
2076 static int get_next_dropped_element(struct PlayerInfo *player)
2078 return (player->inventory_size > 0 ?
2079 player->inventory_element[player->inventory_size - 1] :
2080 player->inventory_infinite_element != EL_UNDEFINED ?
2081 player->inventory_infinite_element :
2082 player->dynabombs_left > 0 ?
2083 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2087 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2089 // pos >= 0: get element from bottom of the stack;
2090 // pos < 0: get element from top of the stack
2094 int min_inventory_size = -pos;
2095 int inventory_pos = player->inventory_size - min_inventory_size;
2096 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2098 return (player->inventory_size >= min_inventory_size ?
2099 player->inventory_element[inventory_pos] :
2100 player->inventory_infinite_element != EL_UNDEFINED ?
2101 player->inventory_infinite_element :
2102 player->dynabombs_left >= min_dynabombs_left ?
2103 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 int min_dynabombs_left = pos + 1;
2109 int min_inventory_size = pos + 1 - player->dynabombs_left;
2110 int inventory_pos = pos - player->dynabombs_left;
2112 return (player->inventory_infinite_element != EL_UNDEFINED ?
2113 player->inventory_infinite_element :
2114 player->dynabombs_left >= min_dynabombs_left ?
2115 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2116 player->inventory_size >= min_inventory_size ?
2117 player->inventory_element[inventory_pos] :
2122 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2124 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2125 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2128 if (gpo1->sort_priority != gpo2->sort_priority)
2129 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2131 compare_result = gpo1->nr - gpo2->nr;
2133 return compare_result;
2136 int getPlayerInventorySize(int player_nr)
2138 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2139 return game_em.ply[player_nr]->dynamite;
2140 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2141 return game_sp.red_disk_count;
2143 return stored_player[player_nr].inventory_size;
2146 static void InitGameControlValues(void)
2150 for (i = 0; game_panel_controls[i].nr != -1; i++)
2152 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2153 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2154 struct TextPosInfo *pos = gpc->pos;
2156 int type = gpc->type;
2160 Error("'game_panel_controls' structure corrupted at %d", i);
2162 Fail("this should not happen -- please debug");
2165 // force update of game controls after initialization
2166 gpc->value = gpc->last_value = -1;
2167 gpc->frame = gpc->last_frame = -1;
2168 gpc->gfx_frame = -1;
2170 // determine panel value width for later calculation of alignment
2171 if (type == TYPE_INTEGER || type == TYPE_STRING)
2173 pos->width = pos->size * getFontWidth(pos->font);
2174 pos->height = getFontHeight(pos->font);
2176 else if (type == TYPE_ELEMENT)
2178 pos->width = pos->size;
2179 pos->height = pos->size;
2182 // fill structure for game panel draw order
2184 gpo->sort_priority = pos->sort_priority;
2187 // sort game panel controls according to sort_priority and control number
2188 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2189 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2192 static void UpdatePlayfieldElementCount(void)
2194 boolean use_element_count = FALSE;
2197 // first check if it is needed at all to calculate playfield element count
2198 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2199 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2200 use_element_count = TRUE;
2202 if (!use_element_count)
2205 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2206 element_info[i].element_count = 0;
2208 SCAN_PLAYFIELD(x, y)
2210 element_info[Tile[x][y]].element_count++;
2213 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2214 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2215 if (IS_IN_GROUP(j, i))
2216 element_info[EL_GROUP_START + i].element_count +=
2217 element_info[j].element_count;
2220 static void UpdateGameControlValues(void)
2223 int time = (game.LevelSolved ?
2224 game.LevelSolved_CountingTime :
2225 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 game_sp.time_played :
2229 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 game_mm.energy_left :
2231 game.no_time_limit ? TimePlayed : TimeLeft);
2232 int score = (game.LevelSolved ?
2233 game.LevelSolved_CountingScore :
2234 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2235 game_em.lev->score :
2236 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2242 game_em.lev->gems_needed :
2243 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2244 game_sp.infotrons_still_needed :
2245 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2246 game_mm.kettles_still_needed :
2247 game.gems_still_needed);
2248 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2249 game_em.lev->gems_needed > 0 :
2250 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2251 game_sp.infotrons_still_needed > 0 :
2252 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2253 game_mm.kettles_still_needed > 0 ||
2254 game_mm.lights_still_needed > 0 :
2255 game.gems_still_needed > 0 ||
2256 game.sokoban_fields_still_needed > 0 ||
2257 game.sokoban_objects_still_needed > 0 ||
2258 game.lights_still_needed > 0);
2259 int health = (game.LevelSolved ?
2260 game.LevelSolved_CountingHealth :
2261 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2262 MM_HEALTH(game_mm.laser_overload_value) :
2264 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2266 UpdatePlayfieldElementCount();
2268 // update game panel control values
2270 // used instead of "level_nr" (for network games)
2271 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2272 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2274 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2275 for (i = 0; i < MAX_NUM_KEYS; i++)
2276 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2277 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2278 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2280 if (game.centered_player_nr == -1)
2282 for (i = 0; i < MAX_PLAYERS; i++)
2284 // only one player in Supaplex game engine
2285 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2288 for (k = 0; k < MAX_NUM_KEYS; k++)
2290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2292 if (game_em.ply[i]->keys & (1 << k))
2293 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2294 get_key_element_from_nr(k);
2296 else if (stored_player[i].key[k])
2297 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2298 get_key_element_from_nr(k);
2301 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2302 getPlayerInventorySize(i);
2304 if (stored_player[i].num_white_keys > 0)
2305 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2308 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2309 stored_player[i].num_white_keys;
2314 int player_nr = game.centered_player_nr;
2316 for (k = 0; k < MAX_NUM_KEYS; k++)
2318 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2320 if (game_em.ply[player_nr]->keys & (1 << k))
2321 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2322 get_key_element_from_nr(k);
2324 else if (stored_player[player_nr].key[k])
2325 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2326 get_key_element_from_nr(k);
2329 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2330 getPlayerInventorySize(player_nr);
2332 if (stored_player[player_nr].num_white_keys > 0)
2333 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2335 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2336 stored_player[player_nr].num_white_keys;
2339 // re-arrange keys on game panel, if needed or if defined by style settings
2340 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2342 int nr = GAME_PANEL_KEY_1 + i;
2343 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2344 struct TextPosInfo *pos = gpc->pos;
2346 // skip check if key is not in the player's inventory
2347 if (gpc->value == EL_EMPTY)
2350 // check if keys should be arranged on panel from left to right
2351 if (pos->style == STYLE_LEFTMOST_POSITION)
2353 // check previous key positions (left from current key)
2354 for (k = 0; k < i; k++)
2356 int nr_new = GAME_PANEL_KEY_1 + k;
2358 if (game_panel_controls[nr_new].value == EL_EMPTY)
2360 game_panel_controls[nr_new].value = gpc->value;
2361 gpc->value = EL_EMPTY;
2368 // check if "undefined" keys can be placed at some other position
2369 if (pos->x == -1 && pos->y == -1)
2371 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2373 // 1st try: display key at the same position as normal or EM keys
2374 if (game_panel_controls[nr_new].value == EL_EMPTY)
2376 game_panel_controls[nr_new].value = gpc->value;
2380 // 2nd try: display key at the next free position in the key panel
2381 for (k = 0; k < STD_NUM_KEYS; k++)
2383 nr_new = GAME_PANEL_KEY_1 + k;
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2396 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2398 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2399 get_inventory_element_from_pos(local_player, i);
2400 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2401 get_inventory_element_from_pos(local_player, -i - 1);
2404 game_panel_controls[GAME_PANEL_SCORE].value = score;
2405 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2407 game_panel_controls[GAME_PANEL_TIME].value = time;
2409 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2410 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2411 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2413 if (level.time == 0)
2414 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2416 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2418 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2419 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2421 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2423 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2424 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2426 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2427 local_player->shield_normal_time_left;
2428 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2429 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2431 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2432 local_player->shield_deadly_time_left;
2434 game_panel_controls[GAME_PANEL_EXIT].value =
2435 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2437 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2438 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2439 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2440 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2441 EL_EMC_MAGIC_BALL_SWITCH);
2443 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2444 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2445 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2446 game.light_time_left;
2448 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2449 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2450 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2451 game.timegate_time_left;
2453 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2454 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2456 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2457 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2458 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2459 game.lenses_time_left;
2461 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2462 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2463 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2464 game.magnify_time_left;
2466 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2467 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2468 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2469 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2470 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2471 EL_BALLOON_SWITCH_NONE);
2473 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2474 local_player->dynabomb_count;
2475 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2476 local_player->dynabomb_size;
2477 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2478 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2480 game_panel_controls[GAME_PANEL_PENGUINS].value =
2481 game.friends_still_needed;
2483 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2484 game.sokoban_objects_still_needed;
2485 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2486 game.sokoban_fields_still_needed;
2488 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2489 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2491 for (i = 0; i < NUM_BELTS; i++)
2493 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2494 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2495 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2496 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2497 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2500 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2501 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2502 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2503 game.magic_wall_time_left;
2505 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2506 local_player->gravity;
2508 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2509 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2511 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2512 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2513 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2514 game.panel.element[i].id : EL_UNDEFINED);
2516 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2517 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2518 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2519 element_info[game.panel.element_count[i].id].element_count : 0);
2521 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2522 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2523 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2524 element_info[game.panel.ce_score[i].id].collect_score : 0);
2526 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2527 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2528 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2529 element_info[game.panel.ce_score_element[i].id].collect_score :
2532 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2533 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2534 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2536 // update game panel control frames
2538 for (i = 0; game_panel_controls[i].nr != -1; i++)
2540 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2542 if (gpc->type == TYPE_ELEMENT)
2544 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2546 int last_anim_random_frame = gfx.anim_random_frame;
2547 int element = gpc->value;
2548 int graphic = el2panelimg(element);
2549 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2550 sync_random_frame : INIT_GFX_RANDOM());
2552 if (gpc->value != gpc->last_value)
2555 gpc->gfx_random = init_gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2562 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2563 gpc->gfx_random = init_gfx_random;
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = gpc->gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2570 gpc->gfx_frame = element_info[element].collect_score;
2572 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2574 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2575 gfx.anim_random_frame = last_anim_random_frame;
2578 else if (gpc->type == TYPE_GRAPHIC)
2580 if (gpc->graphic != IMG_UNDEFINED)
2582 int last_anim_random_frame = gfx.anim_random_frame;
2583 int graphic = gpc->graphic;
2584 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2585 sync_random_frame : INIT_GFX_RANDOM());
2587 if (gpc->value != gpc->last_value)
2590 gpc->gfx_random = init_gfx_random;
2596 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2597 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2598 gpc->gfx_random = init_gfx_random;
2601 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2602 gfx.anim_random_frame = gpc->gfx_random;
2604 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2606 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2607 gfx.anim_random_frame = last_anim_random_frame;
2613 static void DisplayGameControlValues(void)
2615 boolean redraw_panel = FALSE;
2618 for (i = 0; game_panel_controls[i].nr != -1; i++)
2620 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2622 if (PANEL_DEACTIVATED(gpc->pos))
2625 if (gpc->value == gpc->last_value &&
2626 gpc->frame == gpc->last_frame)
2629 redraw_panel = TRUE;
2635 // copy default game door content to main double buffer
2637 // !!! CHECK AGAIN !!!
2638 SetPanelBackground();
2639 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2640 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2642 // redraw game control buttons
2643 RedrawGameButtons();
2645 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2647 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2649 int nr = game_panel_order[i].nr;
2650 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2651 struct TextPosInfo *pos = gpc->pos;
2652 int type = gpc->type;
2653 int value = gpc->value;
2654 int frame = gpc->frame;
2655 int size = pos->size;
2656 int font = pos->font;
2657 boolean draw_masked = pos->draw_masked;
2658 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2660 if (PANEL_DEACTIVATED(pos))
2663 if (pos->class == get_hash_from_key("extra_panel_items") &&
2664 !setup.prefer_extra_panel_items)
2667 gpc->last_value = value;
2668 gpc->last_frame = frame;
2670 if (type == TYPE_INTEGER)
2672 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2673 nr == GAME_PANEL_TIME)
2675 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2677 if (use_dynamic_size) // use dynamic number of digits
2679 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2680 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2681 int size2 = size1 + 1;
2682 int font1 = pos->font;
2683 int font2 = pos->font_alt;
2685 size = (value < value_change ? size1 : size2);
2686 font = (value < value_change ? font1 : font2);
2690 // correct text size if "digits" is zero or less
2692 size = strlen(int2str(value, size));
2694 // dynamically correct text alignment
2695 pos->width = size * getFontWidth(font);
2697 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2698 int2str(value, size), font, mask_mode);
2700 else if (type == TYPE_ELEMENT)
2702 int element, graphic;
2706 int dst_x = PANEL_XPOS(pos);
2707 int dst_y = PANEL_YPOS(pos);
2709 if (value != EL_UNDEFINED && value != EL_EMPTY)
2712 graphic = el2panelimg(value);
2715 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2716 element, EL_NAME(element), size);
2719 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2722 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2725 width = graphic_info[graphic].width * size / TILESIZE;
2726 height = graphic_info[graphic].height * size / TILESIZE;
2729 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2732 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2736 else if (type == TYPE_GRAPHIC)
2738 int graphic = gpc->graphic;
2739 int graphic_active = gpc->graphic_active;
2743 int dst_x = PANEL_XPOS(pos);
2744 int dst_y = PANEL_YPOS(pos);
2745 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2746 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2748 if (graphic != IMG_UNDEFINED && !skip)
2750 if (pos->style == STYLE_REVERSE)
2751 value = 100 - value;
2753 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2755 if (pos->direction & MV_HORIZONTAL)
2757 width = graphic_info[graphic_active].width * value / 100;
2758 height = graphic_info[graphic_active].height;
2760 if (pos->direction == MV_LEFT)
2762 src_x += graphic_info[graphic_active].width - width;
2763 dst_x += graphic_info[graphic_active].width - width;
2768 width = graphic_info[graphic_active].width;
2769 height = graphic_info[graphic_active].height * value / 100;
2771 if (pos->direction == MV_UP)
2773 src_y += graphic_info[graphic_active].height - height;
2774 dst_y += graphic_info[graphic_active].height - height;
2779 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2782 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2785 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2787 if (pos->direction & MV_HORIZONTAL)
2789 if (pos->direction == MV_RIGHT)
2796 dst_x = PANEL_XPOS(pos);
2799 width = graphic_info[graphic].width - width;
2803 if (pos->direction == MV_DOWN)
2810 dst_y = PANEL_YPOS(pos);
2813 height = graphic_info[graphic].height - height;
2817 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2820 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2824 else if (type == TYPE_STRING)
2826 boolean active = (value != 0);
2827 char *state_normal = "off";
2828 char *state_active = "on";
2829 char *state = (active ? state_active : state_normal);
2830 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2831 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2832 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2833 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2835 if (nr == GAME_PANEL_GRAVITY_STATE)
2837 int font1 = pos->font; // (used for normal state)
2838 int font2 = pos->font_alt; // (used for active state)
2840 font = (active ? font2 : font1);
2849 // don't truncate output if "chars" is zero or less
2852 // dynamically correct text alignment
2853 pos->width = size * getFontWidth(font);
2856 s_cut = getStringCopyN(s, size);
2858 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2859 s_cut, font, mask_mode);
2865 redraw_mask |= REDRAW_DOOR_1;
2868 SetGameStatus(GAME_MODE_PLAYING);
2871 void UpdateAndDisplayGameControlValues(void)
2873 if (tape.deactivate_display)
2876 UpdateGameControlValues();
2877 DisplayGameControlValues();
2880 void UpdateGameDoorValues(void)
2882 UpdateGameControlValues();
2885 void DrawGameDoorValues(void)
2887 DisplayGameControlValues();
2891 // ============================================================================
2893 // ----------------------------------------------------------------------------
2894 // initialize game engine due to level / tape version number
2895 // ============================================================================
2897 static void InitGameEngine(void)
2899 int i, j, k, l, x, y;
2901 // set game engine from tape file when re-playing, else from level file
2902 game.engine_version = (tape.playing ? tape.engine_version :
2903 level.game_version);
2905 // set single or multi-player game mode (needed for re-playing tapes)
2906 game.team_mode = setup.team_mode;
2910 int num_players = 0;
2912 for (i = 0; i < MAX_PLAYERS; i++)
2913 if (tape.player_participates[i])
2916 // multi-player tapes contain input data for more than one player
2917 game.team_mode = (num_players > 1);
2921 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2922 level.game_version);
2923 Debug("game:init:level", " tape.file_version == %06d",
2925 Debug("game:init:level", " tape.game_version == %06d",
2927 Debug("game:init:level", " tape.engine_version == %06d",
2928 tape.engine_version);
2929 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2930 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2933 // --------------------------------------------------------------------------
2934 // set flags for bugs and changes according to active game engine version
2935 // --------------------------------------------------------------------------
2939 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2941 Bug was introduced in version:
2944 Bug was fixed in version:
2948 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2949 but the property "can fall" was missing, which caused some levels to be
2950 unsolvable. This was fixed in version 4.2.0.0.
2952 Affected levels/tapes:
2953 An example for a tape that was fixed by this bugfix is tape 029 from the
2954 level set "rnd_sam_bateman".
2955 The wrong behaviour will still be used for all levels or tapes that were
2956 created/recorded with it. An example for this is tape 023 from the level
2957 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2960 boolean use_amoeba_dropping_cannot_fall_bug =
2961 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2962 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2964 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2965 tape.game_version < VERSION_IDENT(4,2,0,0)));
2968 Summary of bugfix/change:
2969 Fixed move speed of elements entering or leaving magic wall.
2971 Fixed/changed in version:
2975 Before 2.0.1, move speed of elements entering or leaving magic wall was
2976 twice as fast as it is now.
2977 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2979 Affected levels/tapes:
2980 The first condition is generally needed for all levels/tapes before version
2981 2.0.1, which might use the old behaviour before it was changed; known tapes
2982 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2983 The second condition is an exception from the above case and is needed for
2984 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2985 above, but before it was known that this change would break tapes like the
2986 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2987 although the engine version while recording maybe was before 2.0.1. There
2988 are a lot of tapes that are affected by this exception, like tape 006 from
2989 the level set "rnd_conor_mancone".
2992 boolean use_old_move_stepsize_for_magic_wall =
2993 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2995 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2996 tape.game_version < VERSION_IDENT(4,2,0,0)));
2999 Summary of bugfix/change:
3000 Fixed handling for custom elements that change when pushed by the player.
3002 Fixed/changed in version:
3006 Before 3.1.0, custom elements that "change when pushing" changed directly
3007 after the player started pushing them (until then handled in "DigField()").
3008 Since 3.1.0, these custom elements are not changed until the "pushing"
3009 move of the element is finished (now handled in "ContinueMoving()").
3011 Affected levels/tapes:
3012 The first condition is generally needed for all levels/tapes before version
3013 3.1.0, which might use the old behaviour before it was changed; known tapes
3014 that are affected are some tapes from the level set "Walpurgis Gardens" by
3016 The second condition is an exception from the above case and is needed for
3017 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3018 above (including some development versions of 3.1.0), but before it was
3019 known that this change would break tapes like the above and was fixed in
3020 3.1.1, so that the changed behaviour was active although the engine version
3021 while recording maybe was before 3.1.0. There is at least one tape that is
3022 affected by this exception, which is the tape for the one-level set "Bug
3023 Machine" by Juergen Bonhagen.
3026 game.use_change_when_pushing_bug =
3027 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3029 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3030 tape.game_version < VERSION_IDENT(3,1,1,0)));
3033 Summary of bugfix/change:
3034 Fixed handling for blocking the field the player leaves when moving.
3036 Fixed/changed in version:
3040 Before 3.1.1, when "block last field when moving" was enabled, the field
3041 the player is leaving when moving was blocked for the time of the move,
3042 and was directly unblocked afterwards. This resulted in the last field
3043 being blocked for exactly one less than the number of frames of one player
3044 move. Additionally, even when blocking was disabled, the last field was
3045 blocked for exactly one frame.
3046 Since 3.1.1, due to changes in player movement handling, the last field
3047 is not blocked at all when blocking is disabled. When blocking is enabled,
3048 the last field is blocked for exactly the number of frames of one player
3049 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3050 last field is blocked for exactly one more than the number of frames of
3053 Affected levels/tapes:
3054 (!!! yet to be determined -- probably many !!!)
3057 game.use_block_last_field_bug =
3058 (game.engine_version < VERSION_IDENT(3,1,1,0));
3060 /* various special flags and settings for native Emerald Mine game engine */
3062 game_em.use_single_button =
3063 (game.engine_version > VERSION_IDENT(4,0,0,2));
3065 game_em.use_snap_key_bug =
3066 (game.engine_version < VERSION_IDENT(4,0,1,0));
3068 game_em.use_random_bug =
3069 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3071 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3073 game_em.use_old_explosions = use_old_em_engine;
3074 game_em.use_old_android = use_old_em_engine;
3075 game_em.use_old_push_elements = use_old_em_engine;
3076 game_em.use_old_push_into_acid = use_old_em_engine;
3078 game_em.use_wrap_around = !use_old_em_engine;
3080 // --------------------------------------------------------------------------
3082 // set maximal allowed number of custom element changes per game frame
3083 game.max_num_changes_per_frame = 1;
3085 // default scan direction: scan playfield from top/left to bottom/right
3086 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3088 // dynamically adjust element properties according to game engine version
3089 InitElementPropertiesEngine(game.engine_version);
3091 // ---------- initialize special element properties -------------------------
3093 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3094 if (use_amoeba_dropping_cannot_fall_bug)
3095 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3097 // ---------- initialize player's initial move delay ------------------------
3099 // dynamically adjust player properties according to level information
3100 for (i = 0; i < MAX_PLAYERS; i++)
3101 game.initial_move_delay_value[i] =
3102 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3104 // dynamically adjust player properties according to game engine version
3105 for (i = 0; i < MAX_PLAYERS; i++)
3106 game.initial_move_delay[i] =
3107 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3108 game.initial_move_delay_value[i] : 0);
3110 // ---------- initialize player's initial push delay ------------------------
3112 // dynamically adjust player properties according to game engine version
3113 game.initial_push_delay_value =
3114 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3116 // ---------- initialize changing elements ----------------------------------
3118 // initialize changing elements information
3119 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3121 struct ElementInfo *ei = &element_info[i];
3123 // this pointer might have been changed in the level editor
3124 ei->change = &ei->change_page[0];
3126 if (!IS_CUSTOM_ELEMENT(i))
3128 ei->change->target_element = EL_EMPTY_SPACE;
3129 ei->change->delay_fixed = 0;
3130 ei->change->delay_random = 0;
3131 ei->change->delay_frames = 1;
3134 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3136 ei->has_change_event[j] = FALSE;
3138 ei->event_page_nr[j] = 0;
3139 ei->event_page[j] = &ei->change_page[0];
3143 // add changing elements from pre-defined list
3144 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3146 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3147 struct ElementInfo *ei = &element_info[ch_delay->element];
3149 ei->change->target_element = ch_delay->target_element;
3150 ei->change->delay_fixed = ch_delay->change_delay;
3152 ei->change->pre_change_function = ch_delay->pre_change_function;
3153 ei->change->change_function = ch_delay->change_function;
3154 ei->change->post_change_function = ch_delay->post_change_function;
3156 ei->change->can_change = TRUE;
3157 ei->change->can_change_or_has_action = TRUE;
3159 ei->has_change_event[CE_DELAY] = TRUE;
3161 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3162 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3165 // ---------- initialize internal run-time variables ------------------------
3167 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3169 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3171 for (j = 0; j < ei->num_change_pages; j++)
3173 ei->change_page[j].can_change_or_has_action =
3174 (ei->change_page[j].can_change |
3175 ei->change_page[j].has_action);
3179 // add change events from custom element configuration
3180 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3182 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3184 for (j = 0; j < ei->num_change_pages; j++)
3186 if (!ei->change_page[j].can_change_or_has_action)
3189 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3191 // only add event page for the first page found with this event
3192 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3194 ei->has_change_event[k] = TRUE;
3196 ei->event_page_nr[k] = j;
3197 ei->event_page[k] = &ei->change_page[j];
3203 // ---------- initialize reference elements in change conditions ------------
3205 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3207 int element = EL_CUSTOM_START + i;
3208 struct ElementInfo *ei = &element_info[element];
3210 for (j = 0; j < ei->num_change_pages; j++)
3212 int trigger_element = ei->change_page[j].initial_trigger_element;
3214 if (trigger_element >= EL_PREV_CE_8 &&
3215 trigger_element <= EL_NEXT_CE_8)
3216 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3218 ei->change_page[j].trigger_element = trigger_element;
3222 // ---------- initialize run-time trigger player and element ----------------
3224 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3226 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3228 for (j = 0; j < ei->num_change_pages; j++)
3230 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3231 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3232 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3233 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3234 ei->change_page[j].actual_trigger_ce_value = 0;
3235 ei->change_page[j].actual_trigger_ce_score = 0;
3239 // ---------- initialize trigger events -------------------------------------
3241 // initialize trigger events information
3242 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3243 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3244 trigger_events[i][j] = FALSE;
3246 // add trigger events from element change event properties
3247 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3249 struct ElementInfo *ei = &element_info[i];
3251 for (j = 0; j < ei->num_change_pages; j++)
3253 if (!ei->change_page[j].can_change_or_has_action)
3256 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3258 int trigger_element = ei->change_page[j].trigger_element;
3260 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3262 if (ei->change_page[j].has_event[k])
3264 if (IS_GROUP_ELEMENT(trigger_element))
3266 struct ElementGroupInfo *group =
3267 element_info[trigger_element].group;
3269 for (l = 0; l < group->num_elements_resolved; l++)
3270 trigger_events[group->element_resolved[l]][k] = TRUE;
3272 else if (trigger_element == EL_ANY_ELEMENT)
3273 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3274 trigger_events[l][k] = TRUE;
3276 trigger_events[trigger_element][k] = TRUE;
3283 // ---------- initialize push delay -----------------------------------------
3285 // initialize push delay values to default
3286 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3288 if (!IS_CUSTOM_ELEMENT(i))
3290 // set default push delay values (corrected since version 3.0.7-1)
3291 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3293 element_info[i].push_delay_fixed = 2;
3294 element_info[i].push_delay_random = 8;
3298 element_info[i].push_delay_fixed = 8;
3299 element_info[i].push_delay_random = 8;
3304 // set push delay value for certain elements from pre-defined list
3305 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3307 int e = push_delay_list[i].element;
3309 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3310 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3313 // set push delay value for Supaplex elements for newer engine versions
3314 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3316 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3318 if (IS_SP_ELEMENT(i))
3320 // set SP push delay to just enough to push under a falling zonk
3321 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3323 element_info[i].push_delay_fixed = delay;
3324 element_info[i].push_delay_random = 0;
3329 // ---------- initialize move stepsize --------------------------------------
3331 // initialize move stepsize values to default
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 if (!IS_CUSTOM_ELEMENT(i))
3334 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3336 // set move stepsize value for certain elements from pre-defined list
3337 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3339 int e = move_stepsize_list[i].element;
3341 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3343 // set move stepsize value for certain elements for older engine versions
3344 if (use_old_move_stepsize_for_magic_wall)
3346 if (e == EL_MAGIC_WALL_FILLING ||
3347 e == EL_MAGIC_WALL_EMPTYING ||
3348 e == EL_BD_MAGIC_WALL_FILLING ||
3349 e == EL_BD_MAGIC_WALL_EMPTYING)
3350 element_info[e].move_stepsize *= 2;
3354 // ---------- initialize collect score --------------------------------------
3356 // initialize collect score values for custom elements from initial value
3357 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3358 if (IS_CUSTOM_ELEMENT(i))
3359 element_info[i].collect_score = element_info[i].collect_score_initial;
3361 // ---------- initialize collect count --------------------------------------
3363 // initialize collect count values for non-custom elements
3364 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3365 if (!IS_CUSTOM_ELEMENT(i))
3366 element_info[i].collect_count_initial = 0;
3368 // add collect count values for all elements from pre-defined list
3369 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3370 element_info[collect_count_list[i].element].collect_count_initial =
3371 collect_count_list[i].count;
3373 // ---------- initialize access direction -----------------------------------
3375 // initialize access direction values to default (access from every side)
3376 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3377 if (!IS_CUSTOM_ELEMENT(i))
3378 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3380 // set access direction value for certain elements from pre-defined list
3381 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3382 element_info[access_direction_list[i].element].access_direction =
3383 access_direction_list[i].direction;
3385 // ---------- initialize explosion content ----------------------------------
3386 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3388 if (IS_CUSTOM_ELEMENT(i))
3391 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3393 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3395 element_info[i].content.e[x][y] =
3396 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3397 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3398 i == EL_PLAYER_3 ? EL_EMERALD :
3399 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3400 i == EL_MOLE ? EL_EMERALD_RED :
3401 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3402 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3403 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3404 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3405 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3406 i == EL_WALL_EMERALD ? EL_EMERALD :
3407 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3408 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3409 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3410 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3411 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3412 i == EL_WALL_PEARL ? EL_PEARL :
3413 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3418 // ---------- initialize recursion detection --------------------------------
3419 recursion_loop_depth = 0;
3420 recursion_loop_detected = FALSE;
3421 recursion_loop_element = EL_UNDEFINED;
3423 // ---------- initialize graphics engine ------------------------------------
3424 game.scroll_delay_value =
3425 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3426 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3427 !setup.forced_scroll_delay ? 0 :
3428 setup.scroll_delay ? setup.scroll_delay_value : 0);
3429 game.scroll_delay_value =
3430 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3432 // ---------- initialize game engine snapshots ------------------------------
3433 for (i = 0; i < MAX_PLAYERS; i++)
3434 game.snapshot.last_action[i] = 0;
3435 game.snapshot.changed_action = FALSE;
3436 game.snapshot.collected_item = FALSE;
3437 game.snapshot.mode =
3438 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3439 SNAPSHOT_MODE_EVERY_STEP :
3440 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3441 SNAPSHOT_MODE_EVERY_MOVE :
3442 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3443 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3444 game.snapshot.save_snapshot = FALSE;
3446 // ---------- initialize level time for Supaplex engine ---------------------
3447 // Supaplex levels with time limit currently unsupported -- should be added
3448 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3451 // ---------- initialize flags for handling game actions --------------------
3453 // set flags for game actions to default values
3454 game.use_key_actions = TRUE;
3455 game.use_mouse_actions = FALSE;
3457 // when using Mirror Magic game engine, handle mouse events only
3458 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3460 game.use_key_actions = FALSE;
3461 game.use_mouse_actions = TRUE;
3464 // check for custom elements with mouse click events
3465 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3467 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3469 int element = EL_CUSTOM_START + i;
3471 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3472 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3473 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3474 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3475 game.use_mouse_actions = TRUE;
3480 static int get_num_special_action(int element, int action_first,
3483 int num_special_action = 0;
3486 for (i = action_first; i <= action_last; i++)
3488 boolean found = FALSE;
3490 for (j = 0; j < NUM_DIRECTIONS; j++)
3491 if (el_act_dir2img(element, i, j) !=
3492 el_act_dir2img(element, ACTION_DEFAULT, j))
3496 num_special_action++;
3501 return num_special_action;
3505 // ============================================================================
3507 // ----------------------------------------------------------------------------
3508 // initialize and start new game
3509 // ============================================================================
3511 #if DEBUG_INIT_PLAYER
3512 static void DebugPrintPlayerStatus(char *message)
3519 Debug("game:init:player", "%s:", message);
3521 for (i = 0; i < MAX_PLAYERS; i++)
3523 struct PlayerInfo *player = &stored_player[i];
3525 Debug("game:init:player",
3526 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3530 player->connected_locally,
3531 player->connected_network,
3533 (local_player == player ? " (local player)" : ""));
3540 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3541 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3542 int fade_mask = REDRAW_FIELD;
3544 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3545 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3546 int initial_move_dir = MV_DOWN;
3549 // required here to update video display before fading (FIX THIS)
3550 DrawMaskedBorder(REDRAW_DOOR_2);
3552 if (!game.restart_level)
3553 CloseDoor(DOOR_CLOSE_1);
3555 SetGameStatus(GAME_MODE_PLAYING);
3557 if (level_editor_test_game)
3558 FadeSkipNextFadeOut();
3560 FadeSetEnterScreen();
3563 fade_mask = REDRAW_ALL;
3565 FadeLevelSoundsAndMusic();
3567 ExpireSoundLoops(TRUE);
3571 if (level_editor_test_game)
3572 FadeSkipNextFadeIn();
3574 // needed if different viewport properties defined for playing
3575 ChangeViewportPropertiesIfNeeded();
3579 DrawCompleteVideoDisplay();
3581 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3584 InitGameControlValues();
3588 // initialize tape actions from game when recording tape
3589 tape.use_key_actions = game.use_key_actions;
3590 tape.use_mouse_actions = game.use_mouse_actions;
3592 // initialize visible playfield size when recording tape (for team mode)
3593 tape.scr_fieldx = SCR_FIELDX;
3594 tape.scr_fieldy = SCR_FIELDY;
3597 // don't play tapes over network
3598 network_playing = (network.enabled && !tape.playing);
3600 for (i = 0; i < MAX_PLAYERS; i++)
3602 struct PlayerInfo *player = &stored_player[i];
3604 player->index_nr = i;
3605 player->index_bit = (1 << i);
3606 player->element_nr = EL_PLAYER_1 + i;
3608 player->present = FALSE;
3609 player->active = FALSE;
3610 player->mapped = FALSE;
3612 player->killed = FALSE;
3613 player->reanimated = FALSE;
3614 player->buried = FALSE;
3617 player->effective_action = 0;
3618 player->programmed_action = 0;
3619 player->snap_action = 0;
3621 player->mouse_action.lx = 0;
3622 player->mouse_action.ly = 0;
3623 player->mouse_action.button = 0;
3624 player->mouse_action.button_hint = 0;
3626 player->effective_mouse_action.lx = 0;
3627 player->effective_mouse_action.ly = 0;
3628 player->effective_mouse_action.button = 0;
3629 player->effective_mouse_action.button_hint = 0;
3631 for (j = 0; j < MAX_NUM_KEYS; j++)
3632 player->key[j] = FALSE;
3634 player->num_white_keys = 0;
3636 player->dynabomb_count = 0;
3637 player->dynabomb_size = 1;
3638 player->dynabombs_left = 0;
3639 player->dynabomb_xl = FALSE;
3641 player->MovDir = initial_move_dir;
3644 player->GfxDir = initial_move_dir;
3645 player->GfxAction = ACTION_DEFAULT;
3647 player->StepFrame = 0;
3649 player->initial_element = player->element_nr;
3650 player->artwork_element =
3651 (level.use_artwork_element[i] ? level.artwork_element[i] :
3652 player->element_nr);
3653 player->use_murphy = FALSE;
3655 player->block_last_field = FALSE; // initialized in InitPlayerField()
3656 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3658 player->gravity = level.initial_player_gravity[i];
3660 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3662 player->actual_frame_counter = 0;
3664 player->step_counter = 0;
3666 player->last_move_dir = initial_move_dir;
3668 player->is_active = FALSE;
3670 player->is_waiting = FALSE;
3671 player->is_moving = FALSE;
3672 player->is_auto_moving = FALSE;
3673 player->is_digging = FALSE;
3674 player->is_snapping = FALSE;
3675 player->is_collecting = FALSE;
3676 player->is_pushing = FALSE;
3677 player->is_switching = FALSE;
3678 player->is_dropping = FALSE;
3679 player->is_dropping_pressed = FALSE;
3681 player->is_bored = FALSE;
3682 player->is_sleeping = FALSE;
3684 player->was_waiting = TRUE;
3685 player->was_moving = FALSE;
3686 player->was_snapping = FALSE;
3687 player->was_dropping = FALSE;
3689 player->force_dropping = FALSE;
3691 player->frame_counter_bored = -1;
3692 player->frame_counter_sleeping = -1;
3694 player->anim_delay_counter = 0;
3695 player->post_delay_counter = 0;
3697 player->dir_waiting = initial_move_dir;
3698 player->action_waiting = ACTION_DEFAULT;
3699 player->last_action_waiting = ACTION_DEFAULT;
3700 player->special_action_bored = ACTION_DEFAULT;
3701 player->special_action_sleeping = ACTION_DEFAULT;
3703 player->switch_x = -1;
3704 player->switch_y = -1;
3706 player->drop_x = -1;
3707 player->drop_y = -1;
3709 player->show_envelope = 0;
3711 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3713 player->push_delay = -1; // initialized when pushing starts
3714 player->push_delay_value = game.initial_push_delay_value;
3716 player->drop_delay = 0;
3717 player->drop_pressed_delay = 0;
3719 player->last_jx = -1;
3720 player->last_jy = -1;
3724 player->shield_normal_time_left = 0;
3725 player->shield_deadly_time_left = 0;
3727 player->last_removed_element = EL_UNDEFINED;
3729 player->inventory_infinite_element = EL_UNDEFINED;
3730 player->inventory_size = 0;
3732 if (level.use_initial_inventory[i])
3734 for (j = 0; j < level.initial_inventory_size[i]; j++)
3736 int element = level.initial_inventory_content[i][j];
3737 int collect_count = element_info[element].collect_count_initial;
3740 if (!IS_CUSTOM_ELEMENT(element))
3743 if (collect_count == 0)
3744 player->inventory_infinite_element = element;
3746 for (k = 0; k < collect_count; k++)
3747 if (player->inventory_size < MAX_INVENTORY_SIZE)
3748 player->inventory_element[player->inventory_size++] = element;
3752 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3753 SnapField(player, 0, 0);
3755 map_player_action[i] = i;
3758 network_player_action_received = FALSE;
3760 // initial null action
3761 if (network_playing)
3762 SendToServer_MovePlayer(MV_NONE);
3767 TimeLeft = level.time;
3770 ScreenMovDir = MV_NONE;
3774 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3776 game.robot_wheel_x = -1;
3777 game.robot_wheel_y = -1;
3782 game.all_players_gone = FALSE;
3784 game.LevelSolved = FALSE;
3785 game.GameOver = FALSE;
3787 game.GamePlayed = !tape.playing;
3789 game.LevelSolved_GameWon = FALSE;
3790 game.LevelSolved_GameEnd = FALSE;
3791 game.LevelSolved_SaveTape = FALSE;
3792 game.LevelSolved_SaveScore = FALSE;
3794 game.LevelSolved_CountingTime = 0;
3795 game.LevelSolved_CountingScore = 0;
3796 game.LevelSolved_CountingHealth = 0;
3798 game.panel.active = TRUE;
3800 game.no_time_limit = (level.time == 0);
3802 game.yamyam_content_nr = 0;
3803 game.robot_wheel_active = FALSE;
3804 game.magic_wall_active = FALSE;
3805 game.magic_wall_time_left = 0;
3806 game.light_time_left = 0;
3807 game.timegate_time_left = 0;
3808 game.switchgate_pos = 0;
3809 game.wind_direction = level.wind_direction_initial;
3811 game.time_final = 0;
3812 game.score_time_final = 0;
3815 game.score_final = 0;
3817 game.health = MAX_HEALTH;
3818 game.health_final = MAX_HEALTH;
3820 game.gems_still_needed = level.gems_needed;
3821 game.sokoban_fields_still_needed = 0;
3822 game.sokoban_objects_still_needed = 0;
3823 game.lights_still_needed = 0;
3824 game.players_still_needed = 0;
3825 game.friends_still_needed = 0;
3827 game.lenses_time_left = 0;
3828 game.magnify_time_left = 0;
3830 game.ball_active = level.ball_active_initial;
3831 game.ball_content_nr = 0;
3833 game.explosions_delayed = TRUE;
3835 game.envelope_active = FALSE;
3837 for (i = 0; i < NUM_BELTS; i++)
3839 game.belt_dir[i] = MV_NONE;
3840 game.belt_dir_nr[i] = 3; // not moving, next moving left
3843 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3844 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3846 #if DEBUG_INIT_PLAYER
3847 DebugPrintPlayerStatus("Player status at level initialization");
3850 SCAN_PLAYFIELD(x, y)
3852 Tile[x][y] = Last[x][y] = level.field[x][y];
3853 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3854 ChangeDelay[x][y] = 0;
3855 ChangePage[x][y] = -1;
3856 CustomValue[x][y] = 0; // initialized in InitField()
3857 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3859 WasJustMoving[x][y] = 0;
3860 WasJustFalling[x][y] = 0;
3861 CheckCollision[x][y] = 0;
3862 CheckImpact[x][y] = 0;
3864 Pushed[x][y] = FALSE;
3866 ChangeCount[x][y] = 0;
3867 ChangeEvent[x][y] = -1;
3869 ExplodePhase[x][y] = 0;
3870 ExplodeDelay[x][y] = 0;
3871 ExplodeField[x][y] = EX_TYPE_NONE;
3873 RunnerVisit[x][y] = 0;
3874 PlayerVisit[x][y] = 0;
3877 GfxRandom[x][y] = INIT_GFX_RANDOM();
3878 GfxElement[x][y] = EL_UNDEFINED;
3879 GfxAction[x][y] = ACTION_DEFAULT;
3880 GfxDir[x][y] = MV_NONE;
3881 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3884 SCAN_PLAYFIELD(x, y)
3886 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3888 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3891 InitField(x, y, TRUE);
3893 ResetGfxAnimation(x, y);
3898 for (i = 0; i < MAX_PLAYERS; i++)
3900 struct PlayerInfo *player = &stored_player[i];
3902 // set number of special actions for bored and sleeping animation
3903 player->num_special_action_bored =
3904 get_num_special_action(player->artwork_element,
3905 ACTION_BORING_1, ACTION_BORING_LAST);
3906 player->num_special_action_sleeping =
3907 get_num_special_action(player->artwork_element,
3908 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3911 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3912 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3914 // initialize type of slippery elements
3915 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3917 if (!IS_CUSTOM_ELEMENT(i))
3919 // default: elements slip down either to the left or right randomly
3920 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3922 // SP style elements prefer to slip down on the left side
3923 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3924 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3926 // BD style elements prefer to slip down on the left side
3927 if (game.emulation == EMU_BOULDERDASH)
3928 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3932 // initialize explosion and ignition delay
3933 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3935 if (!IS_CUSTOM_ELEMENT(i))
3938 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3939 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3940 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3941 int last_phase = (num_phase + 1) * delay;
3942 int half_phase = (num_phase / 2) * delay;
3944 element_info[i].explosion_delay = last_phase - 1;
3945 element_info[i].ignition_delay = half_phase;
3947 if (i == EL_BLACK_ORB)
3948 element_info[i].ignition_delay = 1;
3952 // correct non-moving belts to start moving left
3953 for (i = 0; i < NUM_BELTS; i++)
3954 if (game.belt_dir[i] == MV_NONE)
3955 game.belt_dir_nr[i] = 3; // not moving, next moving left
3957 #if USE_NEW_PLAYER_ASSIGNMENTS
3958 // use preferred player also in local single-player mode
3959 if (!network.enabled && !game.team_mode)
3961 int new_index_nr = setup.network_player_nr;
3963 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3965 for (i = 0; i < MAX_PLAYERS; i++)
3966 stored_player[i].connected_locally = FALSE;
3968 stored_player[new_index_nr].connected_locally = TRUE;
3972 for (i = 0; i < MAX_PLAYERS; i++)
3974 stored_player[i].connected = FALSE;
3976 // in network game mode, the local player might not be the first player
3977 if (stored_player[i].connected_locally)
3978 local_player = &stored_player[i];
3981 if (!network.enabled)
3982 local_player->connected = TRUE;
3986 for (i = 0; i < MAX_PLAYERS; i++)
3987 stored_player[i].connected = tape.player_participates[i];
3989 else if (network.enabled)
3991 // add team mode players connected over the network (needed for correct
3992 // assignment of player figures from level to locally playing players)
3994 for (i = 0; i < MAX_PLAYERS; i++)
3995 if (stored_player[i].connected_network)
3996 stored_player[i].connected = TRUE;
3998 else if (game.team_mode)
4000 // try to guess locally connected team mode players (needed for correct
4001 // assignment of player figures from level to locally playing players)
4003 for (i = 0; i < MAX_PLAYERS; i++)
4004 if (setup.input[i].use_joystick ||
4005 setup.input[i].key.left != KSYM_UNDEFINED)
4006 stored_player[i].connected = TRUE;
4009 #if DEBUG_INIT_PLAYER
4010 DebugPrintPlayerStatus("Player status after level initialization");
4013 #if DEBUG_INIT_PLAYER
4014 Debug("game:init:player", "Reassigning players ...");
4017 // check if any connected player was not found in playfield
4018 for (i = 0; i < MAX_PLAYERS; i++)
4020 struct PlayerInfo *player = &stored_player[i];
4022 if (player->connected && !player->present)
4024 struct PlayerInfo *field_player = NULL;
4026 #if DEBUG_INIT_PLAYER
4027 Debug("game:init:player",
4028 "- looking for field player for player %d ...", i + 1);
4031 // assign first free player found that is present in the playfield
4033 // first try: look for unmapped playfield player that is not connected
4034 for (j = 0; j < MAX_PLAYERS; j++)
4035 if (field_player == NULL &&
4036 stored_player[j].present &&
4037 !stored_player[j].mapped &&
4038 !stored_player[j].connected)
4039 field_player = &stored_player[j];
4041 // second try: look for *any* unmapped playfield player
4042 for (j = 0; j < MAX_PLAYERS; j++)
4043 if (field_player == NULL &&
4044 stored_player[j].present &&
4045 !stored_player[j].mapped)
4046 field_player = &stored_player[j];
4048 if (field_player != NULL)
4050 int jx = field_player->jx, jy = field_player->jy;
4052 #if DEBUG_INIT_PLAYER
4053 Debug("game:init:player", "- found player %d",
4054 field_player->index_nr + 1);
4057 player->present = FALSE;
4058 player->active = FALSE;
4060 field_player->present = TRUE;
4061 field_player->active = TRUE;
4064 player->initial_element = field_player->initial_element;
4065 player->artwork_element = field_player->artwork_element;
4067 player->block_last_field = field_player->block_last_field;
4068 player->block_delay_adjustment = field_player->block_delay_adjustment;
4071 StorePlayer[jx][jy] = field_player->element_nr;
4073 field_player->jx = field_player->last_jx = jx;
4074 field_player->jy = field_player->last_jy = jy;
4076 if (local_player == player)
4077 local_player = field_player;
4079 map_player_action[field_player->index_nr] = i;
4081 field_player->mapped = TRUE;
4083 #if DEBUG_INIT_PLAYER
4084 Debug("game:init:player", "- map_player_action[%d] == %d",
4085 field_player->index_nr + 1, i + 1);
4090 if (player->connected && player->present)
4091 player->mapped = TRUE;
4094 #if DEBUG_INIT_PLAYER
4095 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4100 // check if any connected player was not found in playfield
4101 for (i = 0; i < MAX_PLAYERS; i++)
4103 struct PlayerInfo *player = &stored_player[i];
4105 if (player->connected && !player->present)
4107 for (j = 0; j < MAX_PLAYERS; j++)
4109 struct PlayerInfo *field_player = &stored_player[j];
4110 int jx = field_player->jx, jy = field_player->jy;
4112 // assign first free player found that is present in the playfield
4113 if (field_player->present && !field_player->connected)
4115 player->present = TRUE;
4116 player->active = TRUE;
4118 field_player->present = FALSE;
4119 field_player->active = FALSE;
4121 player->initial_element = field_player->initial_element;
4122 player->artwork_element = field_player->artwork_element;
4124 player->block_last_field = field_player->block_last_field;
4125 player->block_delay_adjustment = field_player->block_delay_adjustment;
4127 StorePlayer[jx][jy] = player->element_nr;
4129 player->jx = player->last_jx = jx;
4130 player->jy = player->last_jy = jy;
4140 Debug("game:init:player", "local_player->present == %d",
4141 local_player->present);
4144 // set focus to local player for network games, else to all players
4145 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4146 game.centered_player_nr_next = game.centered_player_nr;
4147 game.set_centered_player = FALSE;
4148 game.set_centered_player_wrap = FALSE;
4150 if (network_playing && tape.recording)
4152 // store client dependent player focus when recording network games
4153 tape.centered_player_nr_next = game.centered_player_nr_next;
4154 tape.set_centered_player = TRUE;
4159 // when playing a tape, eliminate all players who do not participate
4161 #if USE_NEW_PLAYER_ASSIGNMENTS
4163 if (!game.team_mode)
4165 for (i = 0; i < MAX_PLAYERS; i++)
4167 if (stored_player[i].active &&
4168 !tape.player_participates[map_player_action[i]])
4170 struct PlayerInfo *player = &stored_player[i];
4171 int jx = player->jx, jy = player->jy;
4173 #if DEBUG_INIT_PLAYER
4174 Debug("game:init:player", "Removing player %d at (%d, %d)",
4178 player->active = FALSE;
4179 StorePlayer[jx][jy] = 0;
4180 Tile[jx][jy] = EL_EMPTY;
4187 for (i = 0; i < MAX_PLAYERS; i++)
4189 if (stored_player[i].active &&
4190 !tape.player_participates[i])
4192 struct PlayerInfo *player = &stored_player[i];
4193 int jx = player->jx, jy = player->jy;
4195 player->active = FALSE;
4196 StorePlayer[jx][jy] = 0;
4197 Tile[jx][jy] = EL_EMPTY;
4202 else if (!network.enabled && !game.team_mode) // && !tape.playing
4204 // when in single player mode, eliminate all but the local player
4206 for (i = 0; i < MAX_PLAYERS; i++)
4208 struct PlayerInfo *player = &stored_player[i];
4210 if (player->active && player != local_player)
4212 int jx = player->jx, jy = player->jy;
4214 player->active = FALSE;
4215 player->present = FALSE;
4217 StorePlayer[jx][jy] = 0;
4218 Tile[jx][jy] = EL_EMPTY;
4223 for (i = 0; i < MAX_PLAYERS; i++)
4224 if (stored_player[i].active)
4225 game.players_still_needed++;
4227 if (level.solved_by_one_player)
4228 game.players_still_needed = 1;
4230 // when recording the game, store which players take part in the game
4233 #if USE_NEW_PLAYER_ASSIGNMENTS
4234 for (i = 0; i < MAX_PLAYERS; i++)
4235 if (stored_player[i].connected)
4236 tape.player_participates[i] = TRUE;
4238 for (i = 0; i < MAX_PLAYERS; i++)
4239 if (stored_player[i].active)
4240 tape.player_participates[i] = TRUE;
4244 #if DEBUG_INIT_PLAYER
4245 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4248 if (BorderElement == EL_EMPTY)
4251 SBX_Right = lev_fieldx - SCR_FIELDX;
4253 SBY_Lower = lev_fieldy - SCR_FIELDY;
4258 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4260 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4263 if (full_lev_fieldx <= SCR_FIELDX)
4264 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4265 if (full_lev_fieldy <= SCR_FIELDY)
4266 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4268 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4270 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4273 // if local player not found, look for custom element that might create
4274 // the player (make some assumptions about the right custom element)
4275 if (!local_player->present)
4277 int start_x = 0, start_y = 0;
4278 int found_rating = 0;
4279 int found_element = EL_UNDEFINED;
4280 int player_nr = local_player->index_nr;
4282 SCAN_PLAYFIELD(x, y)
4284 int element = Tile[x][y];
4289 if (level.use_start_element[player_nr] &&
4290 level.start_element[player_nr] == element &&
4297 found_element = element;
4300 if (!IS_CUSTOM_ELEMENT(element))
4303 if (CAN_CHANGE(element))
4305 for (i = 0; i < element_info[element].num_change_pages; i++)
4307 // check for player created from custom element as single target
4308 content = element_info[element].change_page[i].target_element;
4309 is_player = IS_PLAYER_ELEMENT(content);
4311 if (is_player && (found_rating < 3 ||
4312 (found_rating == 3 && element < found_element)))
4318 found_element = element;
4323 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4325 // check for player created from custom element as explosion content
4326 content = element_info[element].content.e[xx][yy];
4327 is_player = IS_PLAYER_ELEMENT(content);
4329 if (is_player && (found_rating < 2 ||
4330 (found_rating == 2 && element < found_element)))
4332 start_x = x + xx - 1;
4333 start_y = y + yy - 1;
4336 found_element = element;
4339 if (!CAN_CHANGE(element))
4342 for (i = 0; i < element_info[element].num_change_pages; i++)
4344 // check for player created from custom element as extended target
4346 element_info[element].change_page[i].target_content.e[xx][yy];
4348 is_player = IS_PLAYER_ELEMENT(content);
4350 if (is_player && (found_rating < 1 ||
4351 (found_rating == 1 && element < found_element)))
4353 start_x = x + xx - 1;
4354 start_y = y + yy - 1;
4357 found_element = element;
4363 scroll_x = SCROLL_POSITION_X(start_x);
4364 scroll_y = SCROLL_POSITION_Y(start_y);
4368 scroll_x = SCROLL_POSITION_X(local_player->jx);
4369 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4372 // !!! FIX THIS (START) !!!
4373 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4375 InitGameEngine_EM();
4377 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4379 InitGameEngine_SP();
4381 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4383 InitGameEngine_MM();
4387 DrawLevel(REDRAW_FIELD);
4390 // after drawing the level, correct some elements
4391 if (game.timegate_time_left == 0)
4392 CloseAllOpenTimegates();
4395 // blit playfield from scroll buffer to normal back buffer for fading in
4396 BlitScreenToBitmap(backbuffer);
4397 // !!! FIX THIS (END) !!!
4399 DrawMaskedBorder(fade_mask);
4404 // full screen redraw is required at this point in the following cases:
4405 // - special editor door undrawn when game was started from level editor
4406 // - drawing area (playfield) was changed and has to be removed completely
4407 redraw_mask = REDRAW_ALL;
4411 if (!game.restart_level)
4413 // copy default game door content to main double buffer
4415 // !!! CHECK AGAIN !!!
4416 SetPanelBackground();
4417 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4418 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4421 SetPanelBackground();
4422 SetDrawBackgroundMask(REDRAW_DOOR_1);
4424 UpdateAndDisplayGameControlValues();
4426 if (!game.restart_level)
4432 CreateGameButtons();
4437 // copy actual game door content to door double buffer for OpenDoor()
4438 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4440 OpenDoor(DOOR_OPEN_ALL);
4442 KeyboardAutoRepeatOffUnlessAutoplay();
4444 #if DEBUG_INIT_PLAYER
4445 DebugPrintPlayerStatus("Player status (final)");
4454 if (!game.restart_level && !tape.playing)
4456 LevelStats_incPlayed(level_nr);
4458 SaveLevelSetup_SeriesInfo();
4461 game.restart_level = FALSE;
4462 game.restart_game_message = NULL;
4464 game.request_active = FALSE;
4465 game.request_active_or_moving = FALSE;
4467 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4468 InitGameActions_MM();
4470 SaveEngineSnapshotToListInitial();
4472 if (!game.restart_level)
4474 PlaySound(SND_GAME_STARTING);
4476 if (setup.sound_music)
4481 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4482 int actual_player_x, int actual_player_y)
4484 // this is used for non-R'n'D game engines to update certain engine values
4486 // needed to determine if sounds are played within the visible screen area
4487 scroll_x = actual_scroll_x;
4488 scroll_y = actual_scroll_y;
4490 // needed to get player position for "follow finger" playing input method
4491 local_player->jx = actual_player_x;
4492 local_player->jy = actual_player_y;
4495 void InitMovDir(int x, int y)
4497 int i, element = Tile[x][y];
4498 static int xy[4][2] =
4505 static int direction[3][4] =
4507 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4508 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4509 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4518 Tile[x][y] = EL_BUG;
4519 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4522 case EL_SPACESHIP_RIGHT:
4523 case EL_SPACESHIP_UP:
4524 case EL_SPACESHIP_LEFT:
4525 case EL_SPACESHIP_DOWN:
4526 Tile[x][y] = EL_SPACESHIP;
4527 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4530 case EL_BD_BUTTERFLY_RIGHT:
4531 case EL_BD_BUTTERFLY_UP:
4532 case EL_BD_BUTTERFLY_LEFT:
4533 case EL_BD_BUTTERFLY_DOWN:
4534 Tile[x][y] = EL_BD_BUTTERFLY;
4535 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4538 case EL_BD_FIREFLY_RIGHT:
4539 case EL_BD_FIREFLY_UP:
4540 case EL_BD_FIREFLY_LEFT:
4541 case EL_BD_FIREFLY_DOWN:
4542 Tile[x][y] = EL_BD_FIREFLY;
4543 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4546 case EL_PACMAN_RIGHT:
4548 case EL_PACMAN_LEFT:
4549 case EL_PACMAN_DOWN:
4550 Tile[x][y] = EL_PACMAN;
4551 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4554 case EL_YAMYAM_LEFT:
4555 case EL_YAMYAM_RIGHT:
4557 case EL_YAMYAM_DOWN:
4558 Tile[x][y] = EL_YAMYAM;
4559 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4562 case EL_SP_SNIKSNAK:
4563 MovDir[x][y] = MV_UP;
4566 case EL_SP_ELECTRON:
4567 MovDir[x][y] = MV_LEFT;
4574 Tile[x][y] = EL_MOLE;
4575 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4578 case EL_SPRING_LEFT:
4579 case EL_SPRING_RIGHT:
4580 Tile[x][y] = EL_SPRING;
4581 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4585 if (IS_CUSTOM_ELEMENT(element))
4587 struct ElementInfo *ei = &element_info[element];
4588 int move_direction_initial = ei->move_direction_initial;
4589 int move_pattern = ei->move_pattern;
4591 if (move_direction_initial == MV_START_PREVIOUS)
4593 if (MovDir[x][y] != MV_NONE)
4596 move_direction_initial = MV_START_AUTOMATIC;
4599 if (move_direction_initial == MV_START_RANDOM)
4600 MovDir[x][y] = 1 << RND(4);
4601 else if (move_direction_initial & MV_ANY_DIRECTION)
4602 MovDir[x][y] = move_direction_initial;
4603 else if (move_pattern == MV_ALL_DIRECTIONS ||
4604 move_pattern == MV_TURNING_LEFT ||
4605 move_pattern == MV_TURNING_RIGHT ||
4606 move_pattern == MV_TURNING_LEFT_RIGHT ||
4607 move_pattern == MV_TURNING_RIGHT_LEFT ||
4608 move_pattern == MV_TURNING_RANDOM)
4609 MovDir[x][y] = 1 << RND(4);
4610 else if (move_pattern == MV_HORIZONTAL)
4611 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4612 else if (move_pattern == MV_VERTICAL)
4613 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4614 else if (move_pattern & MV_ANY_DIRECTION)
4615 MovDir[x][y] = element_info[element].move_pattern;
4616 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4617 move_pattern == MV_ALONG_RIGHT_SIDE)
4619 // use random direction as default start direction
4620 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4621 MovDir[x][y] = 1 << RND(4);
4623 for (i = 0; i < NUM_DIRECTIONS; i++)
4625 int x1 = x + xy[i][0];
4626 int y1 = y + xy[i][1];
4628 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4630 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4631 MovDir[x][y] = direction[0][i];
4633 MovDir[x][y] = direction[1][i];
4642 MovDir[x][y] = 1 << RND(4);
4644 if (element != EL_BUG &&
4645 element != EL_SPACESHIP &&
4646 element != EL_BD_BUTTERFLY &&
4647 element != EL_BD_FIREFLY)
4650 for (i = 0; i < NUM_DIRECTIONS; i++)
4652 int x1 = x + xy[i][0];
4653 int y1 = y + xy[i][1];
4655 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4657 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4659 MovDir[x][y] = direction[0][i];
4662 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4663 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4665 MovDir[x][y] = direction[1][i];
4674 GfxDir[x][y] = MovDir[x][y];
4677 void InitAmoebaNr(int x, int y)
4680 int group_nr = AmoebaNeighbourNr(x, y);
4684 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4686 if (AmoebaCnt[i] == 0)
4694 AmoebaNr[x][y] = group_nr;
4695 AmoebaCnt[group_nr]++;
4696 AmoebaCnt2[group_nr]++;
4699 static void LevelSolved_SetFinalGameValues(void)
4701 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4702 game.score_time_final = (level.use_step_counter ? TimePlayed :
4703 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4705 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4706 game_em.lev->score :
4707 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4711 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4712 MM_HEALTH(game_mm.laser_overload_value) :
4715 game.LevelSolved_CountingTime = game.time_final;
4716 game.LevelSolved_CountingScore = game.score_final;
4717 game.LevelSolved_CountingHealth = game.health_final;
4720 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4722 game.LevelSolved_CountingTime = time;
4723 game.LevelSolved_CountingScore = score;
4724 game.LevelSolved_CountingHealth = health;
4726 game_panel_controls[GAME_PANEL_TIME].value = time;
4727 game_panel_controls[GAME_PANEL_SCORE].value = score;
4728 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4730 DisplayGameControlValues();
4733 static void LevelSolved(void)
4735 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4736 game.players_still_needed > 0)
4739 game.LevelSolved = TRUE;
4740 game.GameOver = TRUE;
4742 // needed here to display correct panel values while player walks into exit
4743 LevelSolved_SetFinalGameValues();
4748 static int time_count_steps;
4749 static int time, time_final;
4750 static float score, score_final; // needed for time score < 10 for 10 seconds
4751 static int health, health_final;
4752 static int game_over_delay_1 = 0;
4753 static int game_over_delay_2 = 0;
4754 static int game_over_delay_3 = 0;
4755 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4756 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4758 if (!game.LevelSolved_GameWon)
4762 // do not start end game actions before the player stops moving (to exit)
4763 if (local_player->active && local_player->MovPos)
4766 // calculate final game values after player finished walking into exit
4767 LevelSolved_SetFinalGameValues();
4769 game.LevelSolved_GameWon = TRUE;
4770 game.LevelSolved_SaveTape = tape.recording;
4771 game.LevelSolved_SaveScore = !tape.playing;
4775 LevelStats_incSolved(level_nr);
4777 SaveLevelSetup_SeriesInfo();
4780 if (tape.auto_play) // tape might already be stopped here
4781 tape.auto_play_level_solved = TRUE;
4785 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4786 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4787 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4789 time = time_final = game.time_final;
4790 score = score_final = game.score_final;
4791 health = health_final = game.health_final;
4793 // if level has time score defined, calculate new final game values
4796 int time_final_max = 999;
4797 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4798 int time_frames = 0;
4799 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4800 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4805 time_frames = time_frames_left;
4807 else if (game.no_time_limit && TimePlayed < time_final_max)
4809 time_final = time_final_max;
4810 time_frames = time_frames_final_max - time_frames_played;
4813 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4815 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4817 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4820 score_final += health * time_score;
4823 game.score_final = score_final;
4824 game.health_final = health_final;
4827 // if not counting score after game, immediately update game panel values
4828 if (level_editor_test_game || !setup.count_score_after_game)
4831 score = score_final;
4833 LevelSolved_DisplayFinalGameValues(time, score, health);
4836 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4838 // check if last player has left the level
4839 if (game.exit_x >= 0 &&
4842 int x = game.exit_x;
4843 int y = game.exit_y;
4844 int element = Tile[x][y];
4846 // close exit door after last player
4847 if ((game.all_players_gone &&
4848 (element == EL_EXIT_OPEN ||
4849 element == EL_SP_EXIT_OPEN ||
4850 element == EL_STEEL_EXIT_OPEN)) ||
4851 element == EL_EM_EXIT_OPEN ||
4852 element == EL_EM_STEEL_EXIT_OPEN)
4856 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4857 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4858 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4859 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4860 EL_EM_STEEL_EXIT_CLOSING);
4862 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4865 // player disappears
4866 DrawLevelField(x, y);
4869 for (i = 0; i < MAX_PLAYERS; i++)
4871 struct PlayerInfo *player = &stored_player[i];
4873 if (player->present)
4875 RemovePlayer(player);
4877 // player disappears
4878 DrawLevelField(player->jx, player->jy);
4883 PlaySound(SND_GAME_WINNING);
4886 if (setup.count_score_after_game)
4888 if (time != time_final)
4890 if (game_over_delay_1 > 0)
4892 game_over_delay_1--;
4897 int time_to_go = ABS(time_final - time);
4898 int time_count_dir = (time < time_final ? +1 : -1);
4900 if (time_to_go < time_count_steps)
4901 time_count_steps = 1;
4903 time += time_count_steps * time_count_dir;
4904 score += time_count_steps * time_score;
4906 // set final score to correct rounding differences after counting score
4907 if (time == time_final)
4908 score = score_final;
4910 LevelSolved_DisplayFinalGameValues(time, score, health);
4912 if (time == time_final)
4913 StopSound(SND_GAME_LEVELTIME_BONUS);
4914 else if (setup.sound_loops)
4915 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4917 PlaySound(SND_GAME_LEVELTIME_BONUS);
4922 if (health != health_final)
4924 if (game_over_delay_2 > 0)
4926 game_over_delay_2--;
4931 int health_count_dir = (health < health_final ? +1 : -1);
4933 health += health_count_dir;
4934 score += time_score;
4936 LevelSolved_DisplayFinalGameValues(time, score, health);
4938 if (health == health_final)
4939 StopSound(SND_GAME_LEVELTIME_BONUS);
4940 else if (setup.sound_loops)
4941 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4943 PlaySound(SND_GAME_LEVELTIME_BONUS);
4949 game.panel.active = FALSE;
4951 if (game_over_delay_3 > 0)
4953 game_over_delay_3--;
4963 // used instead of "level_nr" (needed for network games)
4964 int last_level_nr = levelset.level_nr;
4966 game.LevelSolved_GameEnd = TRUE;
4968 if (game.LevelSolved_SaveTape)
4970 // make sure that request dialog to save tape does not open door again
4971 if (!global.use_envelope_request)
4972 CloseDoor(DOOR_CLOSE_1);
4974 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4976 // set unique basename for score tape (also saved in high score table)
4977 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4980 // if no tape is to be saved, close both doors simultaneously
4981 CloseDoor(DOOR_CLOSE_ALL);
4983 if (level_editor_test_game)
4985 SetGameStatus(GAME_MODE_MAIN);
4992 if (!game.LevelSolved_SaveScore)
4994 SetGameStatus(GAME_MODE_MAIN);
5001 if (level_nr == leveldir_current->handicap_level)
5003 leveldir_current->handicap_level++;
5005 SaveLevelSetup_SeriesInfo();
5008 // save score and score tape before potentially erasing tape below
5009 NewHighScore(last_level_nr);
5011 if (setup.increment_levels &&
5012 level_nr < leveldir_current->last_level &&
5015 level_nr++; // advance to next level
5016 TapeErase(); // start with empty tape
5018 if (setup.auto_play_next_level)
5020 LoadLevel(level_nr);
5022 SaveLevelSetup_SeriesInfo();
5026 if (scores.last_added >= 0 && setup.show_scores_after_game)
5028 SetGameStatus(GAME_MODE_SCORES);
5030 DrawHallOfFame(last_level_nr);
5032 else if (setup.auto_play_next_level && setup.increment_levels &&
5033 last_level_nr < leveldir_current->last_level &&
5036 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5040 SetGameStatus(GAME_MODE_MAIN);
5046 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5047 boolean one_score_entry_per_name)
5051 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5054 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5056 struct ScoreEntry *entry = &list->entry[i];
5057 boolean score_is_better = (new_entry->score > entry->score);
5058 boolean score_is_equal = (new_entry->score == entry->score);
5059 boolean time_is_better = (new_entry->time < entry->time);
5060 boolean time_is_equal = (new_entry->time == entry->time);
5061 boolean better_by_score = (score_is_better ||
5062 (score_is_equal && time_is_better));
5063 boolean better_by_time = (time_is_better ||
5064 (time_is_equal && score_is_better));
5065 boolean is_better = (level.rate_time_over_score ? better_by_time :
5067 boolean entry_is_empty = (entry->score == 0 &&
5070 // prevent adding server score entries if also existing in local score file
5071 // (special case: historic score entries have an empty tape basename entry)
5072 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5073 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5076 if (is_better || entry_is_empty)
5078 // player has made it to the hall of fame
5080 if (i < MAX_SCORE_ENTRIES - 1)
5082 int m = MAX_SCORE_ENTRIES - 1;
5085 if (one_score_entry_per_name)
5087 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5088 if (strEqual(list->entry[l].name, new_entry->name))
5091 if (m == i) // player's new highscore overwrites his old one
5095 for (l = m; l > i; l--)
5096 list->entry[l] = list->entry[l - 1];
5101 *entry = *new_entry;
5105 else if (one_score_entry_per_name &&
5106 strEqual(entry->name, new_entry->name))
5108 // player already in high score list with better score or time
5117 void NewHighScore(int level_nr)
5119 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5120 boolean one_per_name = FALSE;
5122 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5123 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5125 new_entry.score = game.score_final;
5126 new_entry.time = game.score_time_final;
5128 LoadScore(level_nr);
5130 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5132 if (scores.last_added >= 0)
5134 SaveScore(level_nr);
5136 // store last added local score entry (before merging server scores)
5137 scores.last_added_local = scores.last_added;
5140 if (game.LevelSolved_SaveTape)
5142 SaveScoreTape(level_nr);
5143 SaveServerScore(level_nr);
5147 void MergeServerScore(void)
5149 struct ScoreEntry last_added_entry;
5150 boolean one_per_name = FALSE;
5153 if (scores.last_added >= 0)
5154 last_added_entry = scores.entry[scores.last_added];
5156 for (i = 0; i < server_scores.num_entries; i++)
5158 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5160 if (pos >= 0 && pos <= scores.last_added)
5161 scores.last_added++;
5164 if (scores.last_added >= MAX_SCORE_ENTRIES)
5166 scores.last_added = MAX_SCORE_ENTRIES - 1;
5167 scores.force_last_added = TRUE;
5169 scores.entry[scores.last_added] = last_added_entry;
5173 static int getElementMoveStepsizeExt(int x, int y, int direction)
5175 int element = Tile[x][y];
5176 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5177 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5178 int horiz_move = (dx != 0);
5179 int sign = (horiz_move ? dx : dy);
5180 int step = sign * element_info[element].move_stepsize;
5182 // special values for move stepsize for spring and things on conveyor belt
5185 if (CAN_FALL(element) &&
5186 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5187 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5188 else if (element == EL_SPRING)
5189 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5195 static int getElementMoveStepsize(int x, int y)
5197 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5200 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5202 if (player->GfxAction != action || player->GfxDir != dir)
5204 player->GfxAction = action;
5205 player->GfxDir = dir;
5207 player->StepFrame = 0;
5211 static void ResetGfxFrame(int x, int y)
5213 // profiling showed that "autotest" spends 10~20% of its time in this function
5214 if (DrawingDeactivatedField())
5217 int element = Tile[x][y];
5218 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5220 if (graphic_info[graphic].anim_global_sync)
5221 GfxFrame[x][y] = FrameCounter;
5222 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5223 GfxFrame[x][y] = CustomValue[x][y];
5224 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5225 GfxFrame[x][y] = element_info[element].collect_score;
5226 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5227 GfxFrame[x][y] = ChangeDelay[x][y];
5230 static void ResetGfxAnimation(int x, int y)
5232 GfxAction[x][y] = ACTION_DEFAULT;
5233 GfxDir[x][y] = MovDir[x][y];
5236 ResetGfxFrame(x, y);
5239 static void ResetRandomAnimationValue(int x, int y)
5241 GfxRandom[x][y] = INIT_GFX_RANDOM();
5244 static void InitMovingField(int x, int y, int direction)
5246 int element = Tile[x][y];
5247 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5248 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5251 boolean is_moving_before, is_moving_after;
5253 // check if element was/is moving or being moved before/after mode change
5254 is_moving_before = (WasJustMoving[x][y] != 0);
5255 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5257 // reset animation only for moving elements which change direction of moving
5258 // or which just started or stopped moving
5259 // (else CEs with property "can move" / "not moving" are reset each frame)
5260 if (is_moving_before != is_moving_after ||
5261 direction != MovDir[x][y])
5262 ResetGfxAnimation(x, y);
5264 MovDir[x][y] = direction;
5265 GfxDir[x][y] = direction;
5267 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5268 direction == MV_DOWN && CAN_FALL(element) ?
5269 ACTION_FALLING : ACTION_MOVING);
5271 // this is needed for CEs with property "can move" / "not moving"
5273 if (is_moving_after)
5275 if (Tile[newx][newy] == EL_EMPTY)
5276 Tile[newx][newy] = EL_BLOCKED;
5278 MovDir[newx][newy] = MovDir[x][y];
5280 CustomValue[newx][newy] = CustomValue[x][y];
5282 GfxFrame[newx][newy] = GfxFrame[x][y];
5283 GfxRandom[newx][newy] = GfxRandom[x][y];
5284 GfxAction[newx][newy] = GfxAction[x][y];
5285 GfxDir[newx][newy] = GfxDir[x][y];
5289 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5291 int direction = MovDir[x][y];
5292 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5293 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5299 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5301 int oldx = x, oldy = y;
5302 int direction = MovDir[x][y];
5304 if (direction == MV_LEFT)
5306 else if (direction == MV_RIGHT)
5308 else if (direction == MV_UP)
5310 else if (direction == MV_DOWN)
5313 *comes_from_x = oldx;
5314 *comes_from_y = oldy;
5317 static int MovingOrBlocked2Element(int x, int y)
5319 int element = Tile[x][y];
5321 if (element == EL_BLOCKED)
5325 Blocked2Moving(x, y, &oldx, &oldy);
5326 return Tile[oldx][oldy];
5332 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5334 // like MovingOrBlocked2Element(), but if element is moving
5335 // and (x,y) is the field the moving element is just leaving,
5336 // return EL_BLOCKED instead of the element value
5337 int element = Tile[x][y];
5339 if (IS_MOVING(x, y))
5341 if (element == EL_BLOCKED)
5345 Blocked2Moving(x, y, &oldx, &oldy);
5346 return Tile[oldx][oldy];
5355 static void RemoveField(int x, int y)
5357 Tile[x][y] = EL_EMPTY;
5363 CustomValue[x][y] = 0;
5366 ChangeDelay[x][y] = 0;
5367 ChangePage[x][y] = -1;
5368 Pushed[x][y] = FALSE;
5370 GfxElement[x][y] = EL_UNDEFINED;
5371 GfxAction[x][y] = ACTION_DEFAULT;
5372 GfxDir[x][y] = MV_NONE;
5375 static void RemoveMovingField(int x, int y)
5377 int oldx = x, oldy = y, newx = x, newy = y;
5378 int element = Tile[x][y];
5379 int next_element = EL_UNDEFINED;
5381 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5384 if (IS_MOVING(x, y))
5386 Moving2Blocked(x, y, &newx, &newy);
5388 if (Tile[newx][newy] != EL_BLOCKED)
5390 // element is moving, but target field is not free (blocked), but
5391 // already occupied by something different (example: acid pool);
5392 // in this case, only remove the moving field, but not the target
5394 RemoveField(oldx, oldy);
5396 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5398 TEST_DrawLevelField(oldx, oldy);
5403 else if (element == EL_BLOCKED)
5405 Blocked2Moving(x, y, &oldx, &oldy);
5406 if (!IS_MOVING(oldx, oldy))
5410 if (element == EL_BLOCKED &&
5411 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5412 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5413 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5414 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5415 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5416 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5417 next_element = get_next_element(Tile[oldx][oldy]);
5419 RemoveField(oldx, oldy);
5420 RemoveField(newx, newy);
5422 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5424 if (next_element != EL_UNDEFINED)
5425 Tile[oldx][oldy] = next_element;
5427 TEST_DrawLevelField(oldx, oldy);
5428 TEST_DrawLevelField(newx, newy);
5431 void DrawDynamite(int x, int y)
5433 int sx = SCREENX(x), sy = SCREENY(y);
5434 int graphic = el2img(Tile[x][y]);
5437 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5440 if (IS_WALKABLE_INSIDE(Back[x][y]))
5444 DrawLevelElement(x, y, Back[x][y]);
5445 else if (Store[x][y])
5446 DrawLevelElement(x, y, Store[x][y]);
5447 else if (game.use_masked_elements)
5448 DrawLevelElement(x, y, EL_EMPTY);
5450 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5452 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5453 DrawGraphicThruMask(sx, sy, graphic, frame);
5455 DrawGraphic(sx, sy, graphic, frame);
5458 static void CheckDynamite(int x, int y)
5460 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5464 if (MovDelay[x][y] != 0)
5467 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5473 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5478 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5480 boolean num_checked_players = 0;
5483 for (i = 0; i < MAX_PLAYERS; i++)
5485 if (stored_player[i].active)
5487 int sx = stored_player[i].jx;
5488 int sy = stored_player[i].jy;
5490 if (num_checked_players == 0)
5497 *sx1 = MIN(*sx1, sx);
5498 *sy1 = MIN(*sy1, sy);
5499 *sx2 = MAX(*sx2, sx);
5500 *sy2 = MAX(*sy2, sy);
5503 num_checked_players++;
5508 static boolean checkIfAllPlayersFitToScreen_RND(void)
5510 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5512 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5514 return (sx2 - sx1 < SCR_FIELDX &&
5515 sy2 - sy1 < SCR_FIELDY);
5518 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5520 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5522 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5524 *sx = (sx1 + sx2) / 2;
5525 *sy = (sy1 + sy2) / 2;
5528 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5529 boolean center_screen, boolean quick_relocation)
5531 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5532 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5533 boolean no_delay = (tape.warp_forward);
5534 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5535 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5536 int new_scroll_x, new_scroll_y;
5538 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5540 // case 1: quick relocation inside visible screen (without scrolling)
5547 if (!level.shifted_relocation || center_screen)
5549 // relocation _with_ centering of screen
5551 new_scroll_x = SCROLL_POSITION_X(x);
5552 new_scroll_y = SCROLL_POSITION_Y(y);
5556 // relocation _without_ centering of screen
5558 int center_scroll_x = SCROLL_POSITION_X(old_x);
5559 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5560 int offset_x = x + (scroll_x - center_scroll_x);
5561 int offset_y = y + (scroll_y - center_scroll_y);
5563 // for new screen position, apply previous offset to center position
5564 new_scroll_x = SCROLL_POSITION_X(offset_x);
5565 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5568 if (quick_relocation)
5570 // case 2: quick relocation (redraw without visible scrolling)
5572 scroll_x = new_scroll_x;
5573 scroll_y = new_scroll_y;
5580 // case 3: visible relocation (with scrolling to new position)
5582 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5584 SetVideoFrameDelay(wait_delay_value);
5586 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5588 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5589 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5591 if (dx == 0 && dy == 0) // no scrolling needed at all
5597 // set values for horizontal/vertical screen scrolling (half tile size)
5598 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5599 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5600 int pos_x = dx * TILEX / 2;
5601 int pos_y = dy * TILEY / 2;
5602 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5603 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5605 ScrollLevel(dx, dy);
5608 // scroll in two steps of half tile size to make things smoother
5609 BlitScreenToBitmapExt_RND(window, fx, fy);
5611 // scroll second step to align at full tile size
5612 BlitScreenToBitmap(window);
5618 SetVideoFrameDelay(frame_delay_value_old);
5621 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5623 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5624 int player_nr = GET_PLAYER_NR(el_player);
5625 struct PlayerInfo *player = &stored_player[player_nr];
5626 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5627 boolean no_delay = (tape.warp_forward);
5628 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5629 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5630 int old_jx = player->jx;
5631 int old_jy = player->jy;
5632 int old_element = Tile[old_jx][old_jy];
5633 int element = Tile[jx][jy];
5634 boolean player_relocated = (old_jx != jx || old_jy != jy);
5636 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5637 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5638 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5639 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5640 int leave_side_horiz = move_dir_horiz;
5641 int leave_side_vert = move_dir_vert;
5642 int enter_side = enter_side_horiz | enter_side_vert;
5643 int leave_side = leave_side_horiz | leave_side_vert;
5645 if (player->buried) // do not reanimate dead player
5648 if (!player_relocated) // no need to relocate the player
5651 if (IS_PLAYER(jx, jy)) // player already placed at new position
5653 RemoveField(jx, jy); // temporarily remove newly placed player
5654 DrawLevelField(jx, jy);
5657 if (player->present)
5659 while (player->MovPos)
5661 ScrollPlayer(player, SCROLL_GO_ON);
5662 ScrollScreen(NULL, SCROLL_GO_ON);
5664 AdvanceFrameAndPlayerCounters(player->index_nr);
5668 BackToFront_WithFrameDelay(wait_delay_value);
5671 DrawPlayer(player); // needed here only to cleanup last field
5672 DrawLevelField(player->jx, player->jy); // remove player graphic
5674 player->is_moving = FALSE;
5677 if (IS_CUSTOM_ELEMENT(old_element))
5678 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5680 player->index_bit, leave_side);
5682 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5684 player->index_bit, leave_side);
5686 Tile[jx][jy] = el_player;
5687 InitPlayerField(jx, jy, el_player, TRUE);
5689 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5690 possible that the relocation target field did not contain a player element,
5691 but a walkable element, to which the new player was relocated -- in this
5692 case, restore that (already initialized!) element on the player field */
5693 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5695 Tile[jx][jy] = element; // restore previously existing element
5698 // only visually relocate centered player
5699 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5700 FALSE, level.instant_relocation);
5702 TestIfPlayerTouchesBadThing(jx, jy);
5703 TestIfPlayerTouchesCustomElement(jx, jy);
5705 if (IS_CUSTOM_ELEMENT(element))
5706 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5707 player->index_bit, enter_side);
5709 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5710 player->index_bit, enter_side);
5712 if (player->is_switching)
5714 /* ensure that relocation while still switching an element does not cause
5715 a new element to be treated as also switched directly after relocation
5716 (this is important for teleporter switches that teleport the player to
5717 a place where another teleporter switch is in the same direction, which
5718 would then incorrectly be treated as immediately switched before the
5719 direction key that caused the switch was released) */
5721 player->switch_x += jx - old_jx;
5722 player->switch_y += jy - old_jy;
5726 static void Explode(int ex, int ey, int phase, int mode)
5732 // !!! eliminate this variable !!!
5733 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5735 if (game.explosions_delayed)
5737 ExplodeField[ex][ey] = mode;
5741 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5743 int center_element = Tile[ex][ey];
5744 int artwork_element, explosion_element; // set these values later
5746 // remove things displayed in background while burning dynamite
5747 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5750 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5752 // put moving element to center field (and let it explode there)
5753 center_element = MovingOrBlocked2Element(ex, ey);
5754 RemoveMovingField(ex, ey);
5755 Tile[ex][ey] = center_element;
5758 // now "center_element" is finally determined -- set related values now
5759 artwork_element = center_element; // for custom player artwork
5760 explosion_element = center_element; // for custom player artwork
5762 if (IS_PLAYER(ex, ey))
5764 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5766 artwork_element = stored_player[player_nr].artwork_element;
5768 if (level.use_explosion_element[player_nr])
5770 explosion_element = level.explosion_element[player_nr];
5771 artwork_element = explosion_element;
5775 if (mode == EX_TYPE_NORMAL ||
5776 mode == EX_TYPE_CENTER ||
5777 mode == EX_TYPE_CROSS)
5778 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5780 last_phase = element_info[explosion_element].explosion_delay + 1;
5782 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5784 int xx = x - ex + 1;
5785 int yy = y - ey + 1;
5788 if (!IN_LEV_FIELD(x, y) ||
5789 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5790 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5793 element = Tile[x][y];
5795 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5797 element = MovingOrBlocked2Element(x, y);
5799 if (!IS_EXPLOSION_PROOF(element))
5800 RemoveMovingField(x, y);
5803 // indestructible elements can only explode in center (but not flames)
5804 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5805 mode == EX_TYPE_BORDER)) ||
5806 element == EL_FLAMES)
5809 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5810 behaviour, for example when touching a yamyam that explodes to rocks
5811 with active deadly shield, a rock is created under the player !!! */
5812 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5814 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5815 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5816 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5818 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5821 if (IS_ACTIVE_BOMB(element))
5823 // re-activate things under the bomb like gate or penguin
5824 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5831 // save walkable background elements while explosion on same tile
5832 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5833 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5834 Back[x][y] = element;
5836 // ignite explodable elements reached by other explosion
5837 if (element == EL_EXPLOSION)
5838 element = Store2[x][y];
5840 if (AmoebaNr[x][y] &&
5841 (element == EL_AMOEBA_FULL ||
5842 element == EL_BD_AMOEBA ||
5843 element == EL_AMOEBA_GROWING))
5845 AmoebaCnt[AmoebaNr[x][y]]--;
5846 AmoebaCnt2[AmoebaNr[x][y]]--;
5851 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5853 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5855 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5857 if (PLAYERINFO(ex, ey)->use_murphy)
5858 Store[x][y] = EL_EMPTY;
5861 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5862 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5863 else if (IS_PLAYER_ELEMENT(center_element))
5864 Store[x][y] = EL_EMPTY;
5865 else if (center_element == EL_YAMYAM)
5866 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5867 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5868 Store[x][y] = element_info[center_element].content.e[xx][yy];
5870 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5871 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5872 // otherwise) -- FIX THIS !!!
5873 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5874 Store[x][y] = element_info[element].content.e[1][1];
5876 else if (!CAN_EXPLODE(element))
5877 Store[x][y] = element_info[element].content.e[1][1];
5880 Store[x][y] = EL_EMPTY;
5882 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5883 center_element == EL_AMOEBA_TO_DIAMOND)
5884 Store2[x][y] = element;
5886 Tile[x][y] = EL_EXPLOSION;
5887 GfxElement[x][y] = artwork_element;
5889 ExplodePhase[x][y] = 1;
5890 ExplodeDelay[x][y] = last_phase;
5895 if (center_element == EL_YAMYAM)
5896 game.yamyam_content_nr =
5897 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5909 GfxFrame[x][y] = 0; // restart explosion animation
5911 last_phase = ExplodeDelay[x][y];
5913 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5915 // this can happen if the player leaves an explosion just in time
5916 if (GfxElement[x][y] == EL_UNDEFINED)
5917 GfxElement[x][y] = EL_EMPTY;
5919 border_element = Store2[x][y];
5920 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5921 border_element = StorePlayer[x][y];
5923 if (phase == element_info[border_element].ignition_delay ||
5924 phase == last_phase)
5926 boolean border_explosion = FALSE;
5928 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5929 !PLAYER_EXPLOSION_PROTECTED(x, y))
5931 KillPlayerUnlessExplosionProtected(x, y);
5932 border_explosion = TRUE;
5934 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5936 Tile[x][y] = Store2[x][y];
5939 border_explosion = TRUE;
5941 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5943 AmoebaToDiamond(x, y);
5945 border_explosion = TRUE;
5948 // if an element just explodes due to another explosion (chain-reaction),
5949 // do not immediately end the new explosion when it was the last frame of
5950 // the explosion (as it would be done in the following "if"-statement!)
5951 if (border_explosion && phase == last_phase)
5955 if (phase == last_phase)
5959 element = Tile[x][y] = Store[x][y];
5960 Store[x][y] = Store2[x][y] = 0;
5961 GfxElement[x][y] = EL_UNDEFINED;
5963 // player can escape from explosions and might therefore be still alive
5964 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5965 element <= EL_PLAYER_IS_EXPLODING_4)
5967 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5968 int explosion_element = EL_PLAYER_1 + player_nr;
5969 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5970 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5972 if (level.use_explosion_element[player_nr])
5973 explosion_element = level.explosion_element[player_nr];
5975 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5976 element_info[explosion_element].content.e[xx][yy]);
5979 // restore probably existing indestructible background element
5980 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5981 element = Tile[x][y] = Back[x][y];
5984 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5985 GfxDir[x][y] = MV_NONE;
5986 ChangeDelay[x][y] = 0;
5987 ChangePage[x][y] = -1;
5989 CustomValue[x][y] = 0;
5991 InitField_WithBug2(x, y, FALSE);
5993 TEST_DrawLevelField(x, y);
5995 TestIfElementTouchesCustomElement(x, y);
5997 if (GFX_CRUMBLED(element))
5998 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6000 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6001 StorePlayer[x][y] = 0;
6003 if (IS_PLAYER_ELEMENT(element))
6004 RelocatePlayer(x, y, element);
6006 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6008 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6009 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
6012 TEST_DrawLevelFieldCrumbled(x, y);
6014 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6016 DrawLevelElement(x, y, Back[x][y]);
6017 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6019 else if (IS_WALKABLE_UNDER(Back[x][y]))
6021 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6022 DrawLevelElementThruMask(x, y, Back[x][y]);
6024 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6025 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6029 static void DynaExplode(int ex, int ey)
6032 int dynabomb_element = Tile[ex][ey];
6033 int dynabomb_size = 1;
6034 boolean dynabomb_xl = FALSE;
6035 struct PlayerInfo *player;
6036 static int xy[4][2] =
6044 if (IS_ACTIVE_BOMB(dynabomb_element))
6046 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6047 dynabomb_size = player->dynabomb_size;
6048 dynabomb_xl = player->dynabomb_xl;
6049 player->dynabombs_left++;
6052 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6054 for (i = 0; i < NUM_DIRECTIONS; i++)
6056 for (j = 1; j <= dynabomb_size; j++)
6058 int x = ex + j * xy[i][0];
6059 int y = ey + j * xy[i][1];
6062 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6065 element = Tile[x][y];
6067 // do not restart explosions of fields with active bombs
6068 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6071 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6073 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6074 !IS_DIGGABLE(element) && !dynabomb_xl)
6080 void Bang(int x, int y)
6082 int element = MovingOrBlocked2Element(x, y);
6083 int explosion_type = EX_TYPE_NORMAL;
6085 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6087 struct PlayerInfo *player = PLAYERINFO(x, y);
6089 element = Tile[x][y] = player->initial_element;
6091 if (level.use_explosion_element[player->index_nr])
6093 int explosion_element = level.explosion_element[player->index_nr];
6095 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6096 explosion_type = EX_TYPE_CROSS;
6097 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6098 explosion_type = EX_TYPE_CENTER;
6106 case EL_BD_BUTTERFLY:
6109 case EL_DARK_YAMYAM:
6113 RaiseScoreElement(element);
6116 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6117 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6118 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6119 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6120 case EL_DYNABOMB_INCREASE_NUMBER:
6121 case EL_DYNABOMB_INCREASE_SIZE:
6122 case EL_DYNABOMB_INCREASE_POWER:
6123 explosion_type = EX_TYPE_DYNA;
6126 case EL_DC_LANDMINE:
6127 explosion_type = EX_TYPE_CENTER;
6132 case EL_LAMP_ACTIVE:
6133 case EL_AMOEBA_TO_DIAMOND:
6134 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6135 explosion_type = EX_TYPE_CENTER;
6139 if (element_info[element].explosion_type == EXPLODES_CROSS)
6140 explosion_type = EX_TYPE_CROSS;
6141 else if (element_info[element].explosion_type == EXPLODES_1X1)
6142 explosion_type = EX_TYPE_CENTER;
6146 if (explosion_type == EX_TYPE_DYNA)
6149 Explode(x, y, EX_PHASE_START, explosion_type);
6151 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6154 static void SplashAcid(int x, int y)
6156 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6157 (!IN_LEV_FIELD(x - 1, y - 2) ||
6158 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6159 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6161 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6162 (!IN_LEV_FIELD(x + 1, y - 2) ||
6163 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6164 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6166 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6169 static void InitBeltMovement(void)
6171 static int belt_base_element[4] =
6173 EL_CONVEYOR_BELT_1_LEFT,
6174 EL_CONVEYOR_BELT_2_LEFT,
6175 EL_CONVEYOR_BELT_3_LEFT,
6176 EL_CONVEYOR_BELT_4_LEFT
6178 static int belt_base_active_element[4] =
6180 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6181 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6182 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6183 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6188 // set frame order for belt animation graphic according to belt direction
6189 for (i = 0; i < NUM_BELTS; i++)
6193 for (j = 0; j < NUM_BELT_PARTS; j++)
6195 int element = belt_base_active_element[belt_nr] + j;
6196 int graphic_1 = el2img(element);
6197 int graphic_2 = el2panelimg(element);
6199 if (game.belt_dir[i] == MV_LEFT)
6201 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6202 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6206 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6207 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6212 SCAN_PLAYFIELD(x, y)
6214 int element = Tile[x][y];
6216 for (i = 0; i < NUM_BELTS; i++)
6218 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6220 int e_belt_nr = getBeltNrFromBeltElement(element);
6223 if (e_belt_nr == belt_nr)
6225 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6227 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6234 static void ToggleBeltSwitch(int x, int y)
6236 static int belt_base_element[4] =
6238 EL_CONVEYOR_BELT_1_LEFT,
6239 EL_CONVEYOR_BELT_2_LEFT,
6240 EL_CONVEYOR_BELT_3_LEFT,
6241 EL_CONVEYOR_BELT_4_LEFT
6243 static int belt_base_active_element[4] =
6245 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6246 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6247 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6248 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6250 static int belt_base_switch_element[4] =
6252 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6253 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6254 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6255 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6257 static int belt_move_dir[4] =
6265 int element = Tile[x][y];
6266 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6267 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6268 int belt_dir = belt_move_dir[belt_dir_nr];
6271 if (!IS_BELT_SWITCH(element))
6274 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6275 game.belt_dir[belt_nr] = belt_dir;
6277 if (belt_dir_nr == 3)
6280 // set frame order for belt animation graphic according to belt direction
6281 for (i = 0; i < NUM_BELT_PARTS; i++)
6283 int element = belt_base_active_element[belt_nr] + i;
6284 int graphic_1 = el2img(element);
6285 int graphic_2 = el2panelimg(element);
6287 if (belt_dir == MV_LEFT)
6289 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6290 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6294 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6295 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6299 SCAN_PLAYFIELD(xx, yy)
6301 int element = Tile[xx][yy];
6303 if (IS_BELT_SWITCH(element))
6305 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6307 if (e_belt_nr == belt_nr)
6309 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6310 TEST_DrawLevelField(xx, yy);
6313 else if (IS_BELT(element) && belt_dir != MV_NONE)
6315 int e_belt_nr = getBeltNrFromBeltElement(element);
6317 if (e_belt_nr == belt_nr)
6319 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6321 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6322 TEST_DrawLevelField(xx, yy);
6325 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6327 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6329 if (e_belt_nr == belt_nr)
6331 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6333 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6334 TEST_DrawLevelField(xx, yy);
6340 static void ToggleSwitchgateSwitch(int x, int y)
6344 game.switchgate_pos = !game.switchgate_pos;
6346 SCAN_PLAYFIELD(xx, yy)
6348 int element = Tile[xx][yy];
6350 if (element == EL_SWITCHGATE_SWITCH_UP)
6352 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6353 TEST_DrawLevelField(xx, yy);
6355 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6357 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6358 TEST_DrawLevelField(xx, yy);
6360 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6362 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6363 TEST_DrawLevelField(xx, yy);
6365 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6367 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6368 TEST_DrawLevelField(xx, yy);
6370 else if (element == EL_SWITCHGATE_OPEN ||
6371 element == EL_SWITCHGATE_OPENING)
6373 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6375 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6377 else if (element == EL_SWITCHGATE_CLOSED ||
6378 element == EL_SWITCHGATE_CLOSING)
6380 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6382 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6387 static int getInvisibleActiveFromInvisibleElement(int element)
6389 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6390 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6391 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6395 static int getInvisibleFromInvisibleActiveElement(int element)
6397 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6398 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6399 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6403 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6407 SCAN_PLAYFIELD(x, y)
6409 int element = Tile[x][y];
6411 if (element == EL_LIGHT_SWITCH &&
6412 game.light_time_left > 0)
6414 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6415 TEST_DrawLevelField(x, y);
6417 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6418 game.light_time_left == 0)
6420 Tile[x][y] = EL_LIGHT_SWITCH;
6421 TEST_DrawLevelField(x, y);
6423 else if (element == EL_EMC_DRIPPER &&
6424 game.light_time_left > 0)
6426 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6427 TEST_DrawLevelField(x, y);
6429 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6430 game.light_time_left == 0)
6432 Tile[x][y] = EL_EMC_DRIPPER;
6433 TEST_DrawLevelField(x, y);
6435 else if (element == EL_INVISIBLE_STEELWALL ||
6436 element == EL_INVISIBLE_WALL ||
6437 element == EL_INVISIBLE_SAND)
6439 if (game.light_time_left > 0)
6440 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6442 TEST_DrawLevelField(x, y);
6444 // uncrumble neighbour fields, if needed
6445 if (element == EL_INVISIBLE_SAND)
6446 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6448 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6449 element == EL_INVISIBLE_WALL_ACTIVE ||
6450 element == EL_INVISIBLE_SAND_ACTIVE)
6452 if (game.light_time_left == 0)
6453 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6455 TEST_DrawLevelField(x, y);
6457 // re-crumble neighbour fields, if needed
6458 if (element == EL_INVISIBLE_SAND)
6459 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6464 static void RedrawAllInvisibleElementsForLenses(void)
6468 SCAN_PLAYFIELD(x, y)
6470 int element = Tile[x][y];
6472 if (element == EL_EMC_DRIPPER &&
6473 game.lenses_time_left > 0)
6475 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6476 TEST_DrawLevelField(x, y);
6478 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6479 game.lenses_time_left == 0)
6481 Tile[x][y] = EL_EMC_DRIPPER;
6482 TEST_DrawLevelField(x, y);
6484 else if (element == EL_INVISIBLE_STEELWALL ||
6485 element == EL_INVISIBLE_WALL ||
6486 element == EL_INVISIBLE_SAND)
6488 if (game.lenses_time_left > 0)
6489 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6491 TEST_DrawLevelField(x, y);
6493 // uncrumble neighbour fields, if needed
6494 if (element == EL_INVISIBLE_SAND)
6495 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6497 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6498 element == EL_INVISIBLE_WALL_ACTIVE ||
6499 element == EL_INVISIBLE_SAND_ACTIVE)
6501 if (game.lenses_time_left == 0)
6502 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6504 TEST_DrawLevelField(x, y);
6506 // re-crumble neighbour fields, if needed
6507 if (element == EL_INVISIBLE_SAND)
6508 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6513 static void RedrawAllInvisibleElementsForMagnifier(void)
6517 SCAN_PLAYFIELD(x, y)
6519 int element = Tile[x][y];
6521 if (element == EL_EMC_FAKE_GRASS &&
6522 game.magnify_time_left > 0)
6524 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6525 TEST_DrawLevelField(x, y);
6527 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6528 game.magnify_time_left == 0)
6530 Tile[x][y] = EL_EMC_FAKE_GRASS;
6531 TEST_DrawLevelField(x, y);
6533 else if (IS_GATE_GRAY(element) &&
6534 game.magnify_time_left > 0)
6536 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6537 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6538 IS_EM_GATE_GRAY(element) ?
6539 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6540 IS_EMC_GATE_GRAY(element) ?
6541 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6542 IS_DC_GATE_GRAY(element) ?
6543 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6545 TEST_DrawLevelField(x, y);
6547 else if (IS_GATE_GRAY_ACTIVE(element) &&
6548 game.magnify_time_left == 0)
6550 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6551 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6552 IS_EM_GATE_GRAY_ACTIVE(element) ?
6553 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6554 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6555 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6556 IS_DC_GATE_GRAY_ACTIVE(element) ?
6557 EL_DC_GATE_WHITE_GRAY :
6559 TEST_DrawLevelField(x, y);
6564 static void ToggleLightSwitch(int x, int y)
6566 int element = Tile[x][y];
6568 game.light_time_left =
6569 (element == EL_LIGHT_SWITCH ?
6570 level.time_light * FRAMES_PER_SECOND : 0);
6572 RedrawAllLightSwitchesAndInvisibleElements();
6575 static void ActivateTimegateSwitch(int x, int y)
6579 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6581 SCAN_PLAYFIELD(xx, yy)
6583 int element = Tile[xx][yy];
6585 if (element == EL_TIMEGATE_CLOSED ||
6586 element == EL_TIMEGATE_CLOSING)
6588 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6589 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6593 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6595 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6596 TEST_DrawLevelField(xx, yy);
6602 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6603 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6606 static void Impact(int x, int y)
6608 boolean last_line = (y == lev_fieldy - 1);
6609 boolean object_hit = FALSE;
6610 boolean impact = (last_line || object_hit);
6611 int element = Tile[x][y];
6612 int smashed = EL_STEELWALL;
6614 if (!last_line) // check if element below was hit
6616 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6619 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6620 MovDir[x][y + 1] != MV_DOWN ||
6621 MovPos[x][y + 1] <= TILEY / 2));
6623 // do not smash moving elements that left the smashed field in time
6624 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6625 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6628 #if USE_QUICKSAND_IMPACT_BUGFIX
6629 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6631 RemoveMovingField(x, y + 1);
6632 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6633 Tile[x][y + 2] = EL_ROCK;
6634 TEST_DrawLevelField(x, y + 2);
6639 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6641 RemoveMovingField(x, y + 1);
6642 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6643 Tile[x][y + 2] = EL_ROCK;
6644 TEST_DrawLevelField(x, y + 2);
6651 smashed = MovingOrBlocked2Element(x, y + 1);
6653 impact = (last_line || object_hit);
6656 if (!last_line && smashed == EL_ACID) // element falls into acid
6658 SplashAcid(x, y + 1);
6662 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6663 // only reset graphic animation if graphic really changes after impact
6665 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6667 ResetGfxAnimation(x, y);
6668 TEST_DrawLevelField(x, y);
6671 if (impact && CAN_EXPLODE_IMPACT(element))
6676 else if (impact && element == EL_PEARL &&
6677 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6679 ResetGfxAnimation(x, y);
6681 Tile[x][y] = EL_PEARL_BREAKING;
6682 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6685 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6687 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6692 if (impact && element == EL_AMOEBA_DROP)
6694 if (object_hit && IS_PLAYER(x, y + 1))
6695 KillPlayerUnlessEnemyProtected(x, y + 1);
6696 else if (object_hit && smashed == EL_PENGUIN)
6700 Tile[x][y] = EL_AMOEBA_GROWING;
6701 Store[x][y] = EL_AMOEBA_WET;
6703 ResetRandomAnimationValue(x, y);
6708 if (object_hit) // check which object was hit
6710 if ((CAN_PASS_MAGIC_WALL(element) &&
6711 (smashed == EL_MAGIC_WALL ||
6712 smashed == EL_BD_MAGIC_WALL)) ||
6713 (CAN_PASS_DC_MAGIC_WALL(element) &&
6714 smashed == EL_DC_MAGIC_WALL))
6717 int activated_magic_wall =
6718 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6719 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6720 EL_DC_MAGIC_WALL_ACTIVE);
6722 // activate magic wall / mill
6723 SCAN_PLAYFIELD(xx, yy)
6725 if (Tile[xx][yy] == smashed)
6726 Tile[xx][yy] = activated_magic_wall;
6729 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6730 game.magic_wall_active = TRUE;
6732 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6733 SND_MAGIC_WALL_ACTIVATING :
6734 smashed == EL_BD_MAGIC_WALL ?
6735 SND_BD_MAGIC_WALL_ACTIVATING :
6736 SND_DC_MAGIC_WALL_ACTIVATING));
6739 if (IS_PLAYER(x, y + 1))
6741 if (CAN_SMASH_PLAYER(element))
6743 KillPlayerUnlessEnemyProtected(x, y + 1);
6747 else if (smashed == EL_PENGUIN)
6749 if (CAN_SMASH_PLAYER(element))
6755 else if (element == EL_BD_DIAMOND)
6757 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6763 else if (((element == EL_SP_INFOTRON ||
6764 element == EL_SP_ZONK) &&
6765 (smashed == EL_SP_SNIKSNAK ||
6766 smashed == EL_SP_ELECTRON ||
6767 smashed == EL_SP_DISK_ORANGE)) ||
6768 (element == EL_SP_INFOTRON &&
6769 smashed == EL_SP_DISK_YELLOW))
6774 else if (CAN_SMASH_EVERYTHING(element))
6776 if (IS_CLASSIC_ENEMY(smashed) ||
6777 CAN_EXPLODE_SMASHED(smashed))
6782 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6784 if (smashed == EL_LAMP ||
6785 smashed == EL_LAMP_ACTIVE)
6790 else if (smashed == EL_NUT)
6792 Tile[x][y + 1] = EL_NUT_BREAKING;
6793 PlayLevelSound(x, y, SND_NUT_BREAKING);
6794 RaiseScoreElement(EL_NUT);
6797 else if (smashed == EL_PEARL)
6799 ResetGfxAnimation(x, y);
6801 Tile[x][y + 1] = EL_PEARL_BREAKING;
6802 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6805 else if (smashed == EL_DIAMOND)
6807 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6808 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6811 else if (IS_BELT_SWITCH(smashed))
6813 ToggleBeltSwitch(x, y + 1);
6815 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6816 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6817 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6818 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6820 ToggleSwitchgateSwitch(x, y + 1);
6822 else if (smashed == EL_LIGHT_SWITCH ||
6823 smashed == EL_LIGHT_SWITCH_ACTIVE)
6825 ToggleLightSwitch(x, y + 1);
6829 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6831 CheckElementChangeBySide(x, y + 1, smashed, element,
6832 CE_SWITCHED, CH_SIDE_TOP);
6833 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6839 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6844 // play sound of magic wall / mill
6846 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6847 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6848 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6850 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6851 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6852 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6853 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6854 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6855 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6860 // play sound of object that hits the ground
6861 if (last_line || object_hit)
6862 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6865 static void TurnRoundExt(int x, int y)
6877 { 0, 0 }, { 0, 0 }, { 0, 0 },
6882 int left, right, back;
6886 { MV_DOWN, MV_UP, MV_RIGHT },
6887 { MV_UP, MV_DOWN, MV_LEFT },
6889 { MV_LEFT, MV_RIGHT, MV_DOWN },
6893 { MV_RIGHT, MV_LEFT, MV_UP }
6896 int element = Tile[x][y];
6897 int move_pattern = element_info[element].move_pattern;
6899 int old_move_dir = MovDir[x][y];
6900 int left_dir = turn[old_move_dir].left;
6901 int right_dir = turn[old_move_dir].right;
6902 int back_dir = turn[old_move_dir].back;
6904 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6905 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6906 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6907 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6909 int left_x = x + left_dx, left_y = y + left_dy;
6910 int right_x = x + right_dx, right_y = y + right_dy;
6911 int move_x = x + move_dx, move_y = y + move_dy;
6915 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6917 TestIfBadThingTouchesOtherBadThing(x, y);
6919 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6920 MovDir[x][y] = right_dir;
6921 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6922 MovDir[x][y] = left_dir;
6924 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6926 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6929 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6931 TestIfBadThingTouchesOtherBadThing(x, y);
6933 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6934 MovDir[x][y] = left_dir;
6935 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6936 MovDir[x][y] = right_dir;
6938 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6940 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6943 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6945 TestIfBadThingTouchesOtherBadThing(x, y);
6947 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6948 MovDir[x][y] = left_dir;
6949 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6950 MovDir[x][y] = right_dir;
6952 if (MovDir[x][y] != old_move_dir)
6955 else if (element == EL_YAMYAM)
6957 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6958 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6960 if (can_turn_left && can_turn_right)
6961 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6962 else if (can_turn_left)
6963 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6964 else if (can_turn_right)
6965 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6967 MovDir[x][y] = back_dir;
6969 MovDelay[x][y] = 16 + 16 * RND(3);
6971 else if (element == EL_DARK_YAMYAM)
6973 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6975 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6978 if (can_turn_left && can_turn_right)
6979 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6980 else if (can_turn_left)
6981 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6982 else if (can_turn_right)
6983 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6985 MovDir[x][y] = back_dir;
6987 MovDelay[x][y] = 16 + 16 * RND(3);
6989 else if (element == EL_PACMAN)
6991 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6992 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6994 if (can_turn_left && can_turn_right)
6995 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6996 else if (can_turn_left)
6997 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6998 else if (can_turn_right)
6999 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7001 MovDir[x][y] = back_dir;
7003 MovDelay[x][y] = 6 + RND(40);
7005 else if (element == EL_PIG)
7007 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7008 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7009 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7010 boolean should_turn_left, should_turn_right, should_move_on;
7012 int rnd = RND(rnd_value);
7014 should_turn_left = (can_turn_left &&
7016 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7017 y + back_dy + left_dy)));
7018 should_turn_right = (can_turn_right &&
7020 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7021 y + back_dy + right_dy)));
7022 should_move_on = (can_move_on &&
7025 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7026 y + move_dy + left_dy) ||
7027 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7028 y + move_dy + right_dy)));
7030 if (should_turn_left || should_turn_right || should_move_on)
7032 if (should_turn_left && should_turn_right && should_move_on)
7033 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7034 rnd < 2 * rnd_value / 3 ? right_dir :
7036 else if (should_turn_left && should_turn_right)
7037 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7038 else if (should_turn_left && should_move_on)
7039 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7040 else if (should_turn_right && should_move_on)
7041 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7042 else if (should_turn_left)
7043 MovDir[x][y] = left_dir;
7044 else if (should_turn_right)
7045 MovDir[x][y] = right_dir;
7046 else if (should_move_on)
7047 MovDir[x][y] = old_move_dir;
7049 else if (can_move_on && rnd > rnd_value / 8)
7050 MovDir[x][y] = old_move_dir;
7051 else if (can_turn_left && can_turn_right)
7052 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7053 else if (can_turn_left && rnd > rnd_value / 8)
7054 MovDir[x][y] = left_dir;
7055 else if (can_turn_right && rnd > rnd_value/8)
7056 MovDir[x][y] = right_dir;
7058 MovDir[x][y] = back_dir;
7060 xx = x + move_xy[MovDir[x][y]].dx;
7061 yy = y + move_xy[MovDir[x][y]].dy;
7063 if (!IN_LEV_FIELD(xx, yy) ||
7064 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7065 MovDir[x][y] = old_move_dir;
7069 else if (element == EL_DRAGON)
7071 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7072 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7073 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7075 int rnd = RND(rnd_value);
7077 if (can_move_on && rnd > rnd_value / 8)
7078 MovDir[x][y] = old_move_dir;
7079 else if (can_turn_left && can_turn_right)
7080 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7081 else if (can_turn_left && rnd > rnd_value / 8)
7082 MovDir[x][y] = left_dir;
7083 else if (can_turn_right && rnd > rnd_value / 8)
7084 MovDir[x][y] = right_dir;
7086 MovDir[x][y] = back_dir;
7088 xx = x + move_xy[MovDir[x][y]].dx;
7089 yy = y + move_xy[MovDir[x][y]].dy;
7091 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7092 MovDir[x][y] = old_move_dir;
7096 else if (element == EL_MOLE)
7098 boolean can_move_on =
7099 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7100 IS_AMOEBOID(Tile[move_x][move_y]) ||
7101 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7104 boolean can_turn_left =
7105 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7106 IS_AMOEBOID(Tile[left_x][left_y])));
7108 boolean can_turn_right =
7109 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7110 IS_AMOEBOID(Tile[right_x][right_y])));
7112 if (can_turn_left && can_turn_right)
7113 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7114 else if (can_turn_left)
7115 MovDir[x][y] = left_dir;
7117 MovDir[x][y] = right_dir;
7120 if (MovDir[x][y] != old_move_dir)
7123 else if (element == EL_BALLOON)
7125 MovDir[x][y] = game.wind_direction;
7128 else if (element == EL_SPRING)
7130 if (MovDir[x][y] & MV_HORIZONTAL)
7132 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7133 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7135 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7136 ResetGfxAnimation(move_x, move_y);
7137 TEST_DrawLevelField(move_x, move_y);
7139 MovDir[x][y] = back_dir;
7141 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7142 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7143 MovDir[x][y] = MV_NONE;
7148 else if (element == EL_ROBOT ||
7149 element == EL_SATELLITE ||
7150 element == EL_PENGUIN ||
7151 element == EL_EMC_ANDROID)
7153 int attr_x = -1, attr_y = -1;
7155 if (game.all_players_gone)
7157 attr_x = game.exit_x;
7158 attr_y = game.exit_y;
7164 for (i = 0; i < MAX_PLAYERS; i++)
7166 struct PlayerInfo *player = &stored_player[i];
7167 int jx = player->jx, jy = player->jy;
7169 if (!player->active)
7173 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7181 if (element == EL_ROBOT &&
7182 game.robot_wheel_x >= 0 &&
7183 game.robot_wheel_y >= 0 &&
7184 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7185 game.engine_version < VERSION_IDENT(3,1,0,0)))
7187 attr_x = game.robot_wheel_x;
7188 attr_y = game.robot_wheel_y;
7191 if (element == EL_PENGUIN)
7194 static int xy[4][2] =
7202 for (i = 0; i < NUM_DIRECTIONS; i++)
7204 int ex = x + xy[i][0];
7205 int ey = y + xy[i][1];
7207 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7208 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7209 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7210 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7219 MovDir[x][y] = MV_NONE;
7221 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7222 else if (attr_x > x)
7223 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7225 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7226 else if (attr_y > y)
7227 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7229 if (element == EL_ROBOT)
7233 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7234 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7235 Moving2Blocked(x, y, &newx, &newy);
7237 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7238 MovDelay[x][y] = 8 + 8 * !RND(3);
7240 MovDelay[x][y] = 16;
7242 else if (element == EL_PENGUIN)
7248 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7250 boolean first_horiz = RND(2);
7251 int new_move_dir = MovDir[x][y];
7254 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7255 Moving2Blocked(x, y, &newx, &newy);
7257 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7261 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7262 Moving2Blocked(x, y, &newx, &newy);
7264 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7267 MovDir[x][y] = old_move_dir;
7271 else if (element == EL_SATELLITE)
7277 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7279 boolean first_horiz = RND(2);
7280 int new_move_dir = MovDir[x][y];
7283 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7284 Moving2Blocked(x, y, &newx, &newy);
7286 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7290 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7291 Moving2Blocked(x, y, &newx, &newy);
7293 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7296 MovDir[x][y] = old_move_dir;
7300 else if (element == EL_EMC_ANDROID)
7302 static int check_pos[16] =
7304 -1, // 0 => (invalid)
7307 -1, // 3 => (invalid)
7309 0, // 5 => MV_LEFT | MV_UP
7310 2, // 6 => MV_RIGHT | MV_UP
7311 -1, // 7 => (invalid)
7313 6, // 9 => MV_LEFT | MV_DOWN
7314 4, // 10 => MV_RIGHT | MV_DOWN
7315 -1, // 11 => (invalid)
7316 -1, // 12 => (invalid)
7317 -1, // 13 => (invalid)
7318 -1, // 14 => (invalid)
7319 -1, // 15 => (invalid)
7327 { -1, -1, MV_LEFT | MV_UP },
7329 { +1, -1, MV_RIGHT | MV_UP },
7330 { +1, 0, MV_RIGHT },
7331 { +1, +1, MV_RIGHT | MV_DOWN },
7333 { -1, +1, MV_LEFT | MV_DOWN },
7336 int start_pos, check_order;
7337 boolean can_clone = FALSE;
7340 // check if there is any free field around current position
7341 for (i = 0; i < 8; i++)
7343 int newx = x + check_xy[i].dx;
7344 int newy = y + check_xy[i].dy;
7346 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7354 if (can_clone) // randomly find an element to clone
7358 start_pos = check_pos[RND(8)];
7359 check_order = (RND(2) ? -1 : +1);
7361 for (i = 0; i < 8; i++)
7363 int pos_raw = start_pos + i * check_order;
7364 int pos = (pos_raw + 8) % 8;
7365 int newx = x + check_xy[pos].dx;
7366 int newy = y + check_xy[pos].dy;
7368 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7370 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7371 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7373 Store[x][y] = Tile[newx][newy];
7382 if (can_clone) // randomly find a direction to move
7386 start_pos = check_pos[RND(8)];
7387 check_order = (RND(2) ? -1 : +1);
7389 for (i = 0; i < 8; i++)
7391 int pos_raw = start_pos + i * check_order;
7392 int pos = (pos_raw + 8) % 8;
7393 int newx = x + check_xy[pos].dx;
7394 int newy = y + check_xy[pos].dy;
7395 int new_move_dir = check_xy[pos].dir;
7397 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7399 MovDir[x][y] = new_move_dir;
7400 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7409 if (can_clone) // cloning and moving successful
7412 // cannot clone -- try to move towards player
7414 start_pos = check_pos[MovDir[x][y] & 0x0f];
7415 check_order = (RND(2) ? -1 : +1);
7417 for (i = 0; i < 3; i++)
7419 // first check start_pos, then previous/next or (next/previous) pos
7420 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7421 int pos = (pos_raw + 8) % 8;
7422 int newx = x + check_xy[pos].dx;
7423 int newy = y + check_xy[pos].dy;
7424 int new_move_dir = check_xy[pos].dir;
7426 if (IS_PLAYER(newx, newy))
7429 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7431 MovDir[x][y] = new_move_dir;
7432 MovDelay[x][y] = level.android_move_time * 8 + 1;
7439 else if (move_pattern == MV_TURNING_LEFT ||
7440 move_pattern == MV_TURNING_RIGHT ||
7441 move_pattern == MV_TURNING_LEFT_RIGHT ||
7442 move_pattern == MV_TURNING_RIGHT_LEFT ||
7443 move_pattern == MV_TURNING_RANDOM ||
7444 move_pattern == MV_ALL_DIRECTIONS)
7446 boolean can_turn_left =
7447 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7448 boolean can_turn_right =
7449 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7451 if (element_info[element].move_stepsize == 0) // "not moving"
7454 if (move_pattern == MV_TURNING_LEFT)
7455 MovDir[x][y] = left_dir;
7456 else if (move_pattern == MV_TURNING_RIGHT)
7457 MovDir[x][y] = right_dir;
7458 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7459 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7460 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7461 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7462 else if (move_pattern == MV_TURNING_RANDOM)
7463 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7464 can_turn_right && !can_turn_left ? right_dir :
7465 RND(2) ? left_dir : right_dir);
7466 else if (can_turn_left && can_turn_right)
7467 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7468 else if (can_turn_left)
7469 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7470 else if (can_turn_right)
7471 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7473 MovDir[x][y] = back_dir;
7475 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7477 else if (move_pattern == MV_HORIZONTAL ||
7478 move_pattern == MV_VERTICAL)
7480 if (move_pattern & old_move_dir)
7481 MovDir[x][y] = back_dir;
7482 else if (move_pattern == MV_HORIZONTAL)
7483 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7484 else if (move_pattern == MV_VERTICAL)
7485 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7487 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7489 else if (move_pattern & MV_ANY_DIRECTION)
7491 MovDir[x][y] = move_pattern;
7492 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7494 else if (move_pattern & MV_WIND_DIRECTION)
7496 MovDir[x][y] = game.wind_direction;
7497 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7499 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7501 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7502 MovDir[x][y] = left_dir;
7503 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7504 MovDir[x][y] = right_dir;
7506 if (MovDir[x][y] != old_move_dir)
7507 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7509 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7511 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7512 MovDir[x][y] = right_dir;
7513 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7514 MovDir[x][y] = left_dir;
7516 if (MovDir[x][y] != old_move_dir)
7517 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7519 else if (move_pattern == MV_TOWARDS_PLAYER ||
7520 move_pattern == MV_AWAY_FROM_PLAYER)
7522 int attr_x = -1, attr_y = -1;
7524 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7526 if (game.all_players_gone)
7528 attr_x = game.exit_x;
7529 attr_y = game.exit_y;
7535 for (i = 0; i < MAX_PLAYERS; i++)
7537 struct PlayerInfo *player = &stored_player[i];
7538 int jx = player->jx, jy = player->jy;
7540 if (!player->active)
7544 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7552 MovDir[x][y] = MV_NONE;
7554 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7555 else if (attr_x > x)
7556 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7558 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7559 else if (attr_y > y)
7560 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7562 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7564 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7566 boolean first_horiz = RND(2);
7567 int new_move_dir = MovDir[x][y];
7569 if (element_info[element].move_stepsize == 0) // "not moving"
7571 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7572 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7578 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7579 Moving2Blocked(x, y, &newx, &newy);
7581 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7585 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7586 Moving2Blocked(x, y, &newx, &newy);
7588 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7591 MovDir[x][y] = old_move_dir;
7594 else if (move_pattern == MV_WHEN_PUSHED ||
7595 move_pattern == MV_WHEN_DROPPED)
7597 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7598 MovDir[x][y] = MV_NONE;
7602 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7604 static int test_xy[7][2] =
7614 static int test_dir[7] =
7624 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7625 int move_preference = -1000000; // start with very low preference
7626 int new_move_dir = MV_NONE;
7627 int start_test = RND(4);
7630 for (i = 0; i < NUM_DIRECTIONS; i++)
7632 int move_dir = test_dir[start_test + i];
7633 int move_dir_preference;
7635 xx = x + test_xy[start_test + i][0];
7636 yy = y + test_xy[start_test + i][1];
7638 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7639 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7641 new_move_dir = move_dir;
7646 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7649 move_dir_preference = -1 * RunnerVisit[xx][yy];
7650 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7651 move_dir_preference = PlayerVisit[xx][yy];
7653 if (move_dir_preference > move_preference)
7655 // prefer field that has not been visited for the longest time
7656 move_preference = move_dir_preference;
7657 new_move_dir = move_dir;
7659 else if (move_dir_preference == move_preference &&
7660 move_dir == old_move_dir)
7662 // prefer last direction when all directions are preferred equally
7663 move_preference = move_dir_preference;
7664 new_move_dir = move_dir;
7668 MovDir[x][y] = new_move_dir;
7669 if (old_move_dir != new_move_dir)
7670 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7674 static void TurnRound(int x, int y)
7676 int direction = MovDir[x][y];
7680 GfxDir[x][y] = MovDir[x][y];
7682 if (direction != MovDir[x][y])
7686 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7688 ResetGfxFrame(x, y);
7691 static boolean JustBeingPushed(int x, int y)
7695 for (i = 0; i < MAX_PLAYERS; i++)
7697 struct PlayerInfo *player = &stored_player[i];
7699 if (player->active && player->is_pushing && player->MovPos)
7701 int next_jx = player->jx + (player->jx - player->last_jx);
7702 int next_jy = player->jy + (player->jy - player->last_jy);
7704 if (x == next_jx && y == next_jy)
7712 static void StartMoving(int x, int y)
7714 boolean started_moving = FALSE; // some elements can fall _and_ move
7715 int element = Tile[x][y];
7720 if (MovDelay[x][y] == 0)
7721 GfxAction[x][y] = ACTION_DEFAULT;
7723 if (CAN_FALL(element) && y < lev_fieldy - 1)
7725 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7726 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7727 if (JustBeingPushed(x, y))
7730 if (element == EL_QUICKSAND_FULL)
7732 if (IS_FREE(x, y + 1))
7734 InitMovingField(x, y, MV_DOWN);
7735 started_moving = TRUE;
7737 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7738 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7739 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7740 Store[x][y] = EL_ROCK;
7742 Store[x][y] = EL_ROCK;
7745 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7747 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7749 if (!MovDelay[x][y])
7751 MovDelay[x][y] = TILEY + 1;
7753 ResetGfxAnimation(x, y);
7754 ResetGfxAnimation(x, y + 1);
7759 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7760 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7767 Tile[x][y] = EL_QUICKSAND_EMPTY;
7768 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7769 Store[x][y + 1] = Store[x][y];
7772 PlayLevelSoundAction(x, y, ACTION_FILLING);
7774 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7776 if (!MovDelay[x][y])
7778 MovDelay[x][y] = TILEY + 1;
7780 ResetGfxAnimation(x, y);
7781 ResetGfxAnimation(x, y + 1);
7786 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7787 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7794 Tile[x][y] = EL_QUICKSAND_EMPTY;
7795 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7796 Store[x][y + 1] = Store[x][y];
7799 PlayLevelSoundAction(x, y, ACTION_FILLING);
7802 else if (element == EL_QUICKSAND_FAST_FULL)
7804 if (IS_FREE(x, y + 1))
7806 InitMovingField(x, y, MV_DOWN);
7807 started_moving = TRUE;
7809 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7810 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7811 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7812 Store[x][y] = EL_ROCK;
7814 Store[x][y] = EL_ROCK;
7817 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7819 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7821 if (!MovDelay[x][y])
7823 MovDelay[x][y] = TILEY + 1;
7825 ResetGfxAnimation(x, y);
7826 ResetGfxAnimation(x, y + 1);
7831 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7832 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7839 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7840 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7841 Store[x][y + 1] = Store[x][y];
7844 PlayLevelSoundAction(x, y, ACTION_FILLING);
7846 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7848 if (!MovDelay[x][y])
7850 MovDelay[x][y] = TILEY + 1;
7852 ResetGfxAnimation(x, y);
7853 ResetGfxAnimation(x, y + 1);
7858 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7859 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7866 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7867 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7868 Store[x][y + 1] = Store[x][y];
7871 PlayLevelSoundAction(x, y, ACTION_FILLING);
7874 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7875 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7877 InitMovingField(x, y, MV_DOWN);
7878 started_moving = TRUE;
7880 Tile[x][y] = EL_QUICKSAND_FILLING;
7881 Store[x][y] = element;
7883 PlayLevelSoundAction(x, y, ACTION_FILLING);
7885 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7886 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7888 InitMovingField(x, y, MV_DOWN);
7889 started_moving = TRUE;
7891 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7892 Store[x][y] = element;
7894 PlayLevelSoundAction(x, y, ACTION_FILLING);
7896 else if (element == EL_MAGIC_WALL_FULL)
7898 if (IS_FREE(x, y + 1))
7900 InitMovingField(x, y, MV_DOWN);
7901 started_moving = TRUE;
7903 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7904 Store[x][y] = EL_CHANGED(Store[x][y]);
7906 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7908 if (!MovDelay[x][y])
7909 MovDelay[x][y] = TILEY / 4 + 1;
7918 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7919 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7920 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7924 else if (element == EL_BD_MAGIC_WALL_FULL)
7926 if (IS_FREE(x, y + 1))
7928 InitMovingField(x, y, MV_DOWN);
7929 started_moving = TRUE;
7931 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7932 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7934 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7936 if (!MovDelay[x][y])
7937 MovDelay[x][y] = TILEY / 4 + 1;
7946 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7947 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7948 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7952 else if (element == EL_DC_MAGIC_WALL_FULL)
7954 if (IS_FREE(x, y + 1))
7956 InitMovingField(x, y, MV_DOWN);
7957 started_moving = TRUE;
7959 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7960 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7962 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7964 if (!MovDelay[x][y])
7965 MovDelay[x][y] = TILEY / 4 + 1;
7974 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7975 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7976 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7980 else if ((CAN_PASS_MAGIC_WALL(element) &&
7981 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7982 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7983 (CAN_PASS_DC_MAGIC_WALL(element) &&
7984 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7987 InitMovingField(x, y, MV_DOWN);
7988 started_moving = TRUE;
7991 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7992 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7993 EL_DC_MAGIC_WALL_FILLING);
7994 Store[x][y] = element;
7996 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7998 SplashAcid(x, y + 1);
8000 InitMovingField(x, y, MV_DOWN);
8001 started_moving = TRUE;
8003 Store[x][y] = EL_ACID;
8006 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8007 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8008 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8009 CAN_FALL(element) && WasJustFalling[x][y] &&
8010 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8012 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8013 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8014 (Tile[x][y + 1] == EL_BLOCKED)))
8016 /* this is needed for a special case not covered by calling "Impact()"
8017 from "ContinueMoving()": if an element moves to a tile directly below
8018 another element which was just falling on that tile (which was empty
8019 in the previous frame), the falling element above would just stop
8020 instead of smashing the element below (in previous version, the above
8021 element was just checked for "moving" instead of "falling", resulting
8022 in incorrect smashes caused by horizontal movement of the above
8023 element; also, the case of the player being the element to smash was
8024 simply not covered here... :-/ ) */
8026 CheckCollision[x][y] = 0;
8027 CheckImpact[x][y] = 0;
8031 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8033 if (MovDir[x][y] == MV_NONE)
8035 InitMovingField(x, y, MV_DOWN);
8036 started_moving = TRUE;
8039 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8041 if (WasJustFalling[x][y]) // prevent animation from being restarted
8042 MovDir[x][y] = MV_DOWN;
8044 InitMovingField(x, y, MV_DOWN);
8045 started_moving = TRUE;
8047 else if (element == EL_AMOEBA_DROP)
8049 Tile[x][y] = EL_AMOEBA_GROWING;
8050 Store[x][y] = EL_AMOEBA_WET;
8052 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8053 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8054 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8055 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8057 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8058 (IS_FREE(x - 1, y + 1) ||
8059 Tile[x - 1][y + 1] == EL_ACID));
8060 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8061 (IS_FREE(x + 1, y + 1) ||
8062 Tile[x + 1][y + 1] == EL_ACID));
8063 boolean can_fall_any = (can_fall_left || can_fall_right);
8064 boolean can_fall_both = (can_fall_left && can_fall_right);
8065 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8067 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8069 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8070 can_fall_right = FALSE;
8071 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8072 can_fall_left = FALSE;
8073 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8074 can_fall_right = FALSE;
8075 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8076 can_fall_left = FALSE;
8078 can_fall_any = (can_fall_left || can_fall_right);
8079 can_fall_both = FALSE;
8084 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8085 can_fall_right = FALSE; // slip down on left side
8087 can_fall_left = !(can_fall_right = RND(2));
8089 can_fall_both = FALSE;
8094 // if not determined otherwise, prefer left side for slipping down
8095 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8096 started_moving = TRUE;
8099 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8101 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8102 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8103 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8104 int belt_dir = game.belt_dir[belt_nr];
8106 if ((belt_dir == MV_LEFT && left_is_free) ||
8107 (belt_dir == MV_RIGHT && right_is_free))
8109 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8111 InitMovingField(x, y, belt_dir);
8112 started_moving = TRUE;
8114 Pushed[x][y] = TRUE;
8115 Pushed[nextx][y] = TRUE;
8117 GfxAction[x][y] = ACTION_DEFAULT;
8121 MovDir[x][y] = 0; // if element was moving, stop it
8126 // not "else if" because of elements that can fall and move (EL_SPRING)
8127 if (CAN_MOVE(element) && !started_moving)
8129 int move_pattern = element_info[element].move_pattern;
8132 Moving2Blocked(x, y, &newx, &newy);
8134 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8137 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8138 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8140 WasJustMoving[x][y] = 0;
8141 CheckCollision[x][y] = 0;
8143 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8145 if (Tile[x][y] != element) // element has changed
8149 if (!MovDelay[x][y]) // start new movement phase
8151 // all objects that can change their move direction after each step
8152 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8154 if (element != EL_YAMYAM &&
8155 element != EL_DARK_YAMYAM &&
8156 element != EL_PACMAN &&
8157 !(move_pattern & MV_ANY_DIRECTION) &&
8158 move_pattern != MV_TURNING_LEFT &&
8159 move_pattern != MV_TURNING_RIGHT &&
8160 move_pattern != MV_TURNING_LEFT_RIGHT &&
8161 move_pattern != MV_TURNING_RIGHT_LEFT &&
8162 move_pattern != MV_TURNING_RANDOM)
8166 if (MovDelay[x][y] && (element == EL_BUG ||
8167 element == EL_SPACESHIP ||
8168 element == EL_SP_SNIKSNAK ||
8169 element == EL_SP_ELECTRON ||
8170 element == EL_MOLE))
8171 TEST_DrawLevelField(x, y);
8175 if (MovDelay[x][y]) // wait some time before next movement
8179 if (element == EL_ROBOT ||
8180 element == EL_YAMYAM ||
8181 element == EL_DARK_YAMYAM)
8183 DrawLevelElementAnimationIfNeeded(x, y, element);
8184 PlayLevelSoundAction(x, y, ACTION_WAITING);
8186 else if (element == EL_SP_ELECTRON)
8187 DrawLevelElementAnimationIfNeeded(x, y, element);
8188 else if (element == EL_DRAGON)
8191 int dir = MovDir[x][y];
8192 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8193 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8194 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8195 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8196 dir == MV_UP ? IMG_FLAMES_1_UP :
8197 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8198 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8200 GfxAction[x][y] = ACTION_ATTACKING;
8202 if (IS_PLAYER(x, y))
8203 DrawPlayerField(x, y);
8205 TEST_DrawLevelField(x, y);
8207 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8209 for (i = 1; i <= 3; i++)
8211 int xx = x + i * dx;
8212 int yy = y + i * dy;
8213 int sx = SCREENX(xx);
8214 int sy = SCREENY(yy);
8215 int flame_graphic = graphic + (i - 1);
8217 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8222 int flamed = MovingOrBlocked2Element(xx, yy);
8224 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8227 RemoveMovingField(xx, yy);
8229 ChangeDelay[xx][yy] = 0;
8231 Tile[xx][yy] = EL_FLAMES;
8233 if (IN_SCR_FIELD(sx, sy))
8235 TEST_DrawLevelFieldCrumbled(xx, yy);
8236 DrawGraphic(sx, sy, flame_graphic, frame);
8241 if (Tile[xx][yy] == EL_FLAMES)
8242 Tile[xx][yy] = EL_EMPTY;
8243 TEST_DrawLevelField(xx, yy);
8248 if (MovDelay[x][y]) // element still has to wait some time
8250 PlayLevelSoundAction(x, y, ACTION_WAITING);
8256 // now make next step
8258 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8260 if (DONT_COLLIDE_WITH(element) &&
8261 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8262 !PLAYER_ENEMY_PROTECTED(newx, newy))
8264 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8269 else if (CAN_MOVE_INTO_ACID(element) &&
8270 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8271 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8272 (MovDir[x][y] == MV_DOWN ||
8273 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8275 SplashAcid(newx, newy);
8276 Store[x][y] = EL_ACID;
8278 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8280 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8281 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8282 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8283 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8286 TEST_DrawLevelField(x, y);
8288 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8289 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8290 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8292 game.friends_still_needed--;
8293 if (!game.friends_still_needed &&
8295 game.all_players_gone)
8300 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8302 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8303 TEST_DrawLevelField(newx, newy);
8305 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8307 else if (!IS_FREE(newx, newy))
8309 GfxAction[x][y] = ACTION_WAITING;
8311 if (IS_PLAYER(x, y))
8312 DrawPlayerField(x, y);
8314 TEST_DrawLevelField(x, y);
8319 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8321 if (IS_FOOD_PIG(Tile[newx][newy]))
8323 if (IS_MOVING(newx, newy))
8324 RemoveMovingField(newx, newy);
8327 Tile[newx][newy] = EL_EMPTY;
8328 TEST_DrawLevelField(newx, newy);
8331 PlayLevelSound(x, y, SND_PIG_DIGGING);
8333 else if (!IS_FREE(newx, newy))
8335 if (IS_PLAYER(x, y))
8336 DrawPlayerField(x, y);
8338 TEST_DrawLevelField(x, y);
8343 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8345 if (Store[x][y] != EL_EMPTY)
8347 boolean can_clone = FALSE;
8350 // check if element to clone is still there
8351 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8353 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8361 // cannot clone or target field not free anymore -- do not clone
8362 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8363 Store[x][y] = EL_EMPTY;
8366 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8368 if (IS_MV_DIAGONAL(MovDir[x][y]))
8370 int diagonal_move_dir = MovDir[x][y];
8371 int stored = Store[x][y];
8372 int change_delay = 8;
8375 // android is moving diagonally
8377 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8379 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8380 GfxElement[x][y] = EL_EMC_ANDROID;
8381 GfxAction[x][y] = ACTION_SHRINKING;
8382 GfxDir[x][y] = diagonal_move_dir;
8383 ChangeDelay[x][y] = change_delay;
8385 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8388 DrawLevelGraphicAnimation(x, y, graphic);
8389 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8391 if (Tile[newx][newy] == EL_ACID)
8393 SplashAcid(newx, newy);
8398 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8400 Store[newx][newy] = EL_EMC_ANDROID;
8401 GfxElement[newx][newy] = EL_EMC_ANDROID;
8402 GfxAction[newx][newy] = ACTION_GROWING;
8403 GfxDir[newx][newy] = diagonal_move_dir;
8404 ChangeDelay[newx][newy] = change_delay;
8406 graphic = el_act_dir2img(GfxElement[newx][newy],
8407 GfxAction[newx][newy], GfxDir[newx][newy]);
8409 DrawLevelGraphicAnimation(newx, newy, graphic);
8410 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8416 Tile[newx][newy] = EL_EMPTY;
8417 TEST_DrawLevelField(newx, newy);
8419 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8422 else if (!IS_FREE(newx, newy))
8427 else if (IS_CUSTOM_ELEMENT(element) &&
8428 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8430 if (!DigFieldByCE(newx, newy, element))
8433 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8435 RunnerVisit[x][y] = FrameCounter;
8436 PlayerVisit[x][y] /= 8; // expire player visit path
8439 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8441 if (!IS_FREE(newx, newy))
8443 if (IS_PLAYER(x, y))
8444 DrawPlayerField(x, y);
8446 TEST_DrawLevelField(x, y);
8452 boolean wanna_flame = !RND(10);
8453 int dx = newx - x, dy = newy - y;
8454 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8455 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8456 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8457 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8458 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8459 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8462 IS_CLASSIC_ENEMY(element1) ||
8463 IS_CLASSIC_ENEMY(element2)) &&
8464 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8465 element1 != EL_FLAMES && element2 != EL_FLAMES)
8467 ResetGfxAnimation(x, y);
8468 GfxAction[x][y] = ACTION_ATTACKING;
8470 if (IS_PLAYER(x, y))
8471 DrawPlayerField(x, y);
8473 TEST_DrawLevelField(x, y);
8475 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8477 MovDelay[x][y] = 50;
8479 Tile[newx][newy] = EL_FLAMES;
8480 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8481 Tile[newx1][newy1] = EL_FLAMES;
8482 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8483 Tile[newx2][newy2] = EL_FLAMES;
8489 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8490 Tile[newx][newy] == EL_DIAMOND)
8492 if (IS_MOVING(newx, newy))
8493 RemoveMovingField(newx, newy);
8496 Tile[newx][newy] = EL_EMPTY;
8497 TEST_DrawLevelField(newx, newy);
8500 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8502 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8503 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8505 if (AmoebaNr[newx][newy])
8507 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8508 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8509 Tile[newx][newy] == EL_BD_AMOEBA)
8510 AmoebaCnt[AmoebaNr[newx][newy]]--;
8513 if (IS_MOVING(newx, newy))
8515 RemoveMovingField(newx, newy);
8519 Tile[newx][newy] = EL_EMPTY;
8520 TEST_DrawLevelField(newx, newy);
8523 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8525 else if ((element == EL_PACMAN || element == EL_MOLE)
8526 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8528 if (AmoebaNr[newx][newy])
8530 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8531 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8532 Tile[newx][newy] == EL_BD_AMOEBA)
8533 AmoebaCnt[AmoebaNr[newx][newy]]--;
8536 if (element == EL_MOLE)
8538 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8539 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8541 ResetGfxAnimation(x, y);
8542 GfxAction[x][y] = ACTION_DIGGING;
8543 TEST_DrawLevelField(x, y);
8545 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8547 return; // wait for shrinking amoeba
8549 else // element == EL_PACMAN
8551 Tile[newx][newy] = EL_EMPTY;
8552 TEST_DrawLevelField(newx, newy);
8553 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8556 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8557 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8558 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8560 // wait for shrinking amoeba to completely disappear
8563 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8565 // object was running against a wall
8569 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8570 DrawLevelElementAnimation(x, y, element);
8572 if (DONT_TOUCH(element))
8573 TestIfBadThingTouchesPlayer(x, y);
8578 InitMovingField(x, y, MovDir[x][y]);
8580 PlayLevelSoundAction(x, y, ACTION_MOVING);
8584 ContinueMoving(x, y);
8587 void ContinueMoving(int x, int y)
8589 int element = Tile[x][y];
8590 struct ElementInfo *ei = &element_info[element];
8591 int direction = MovDir[x][y];
8592 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8593 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8594 int newx = x + dx, newy = y + dy;
8595 int stored = Store[x][y];
8596 int stored_new = Store[newx][newy];
8597 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8598 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8599 boolean last_line = (newy == lev_fieldy - 1);
8600 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8602 if (pushed_by_player) // special case: moving object pushed by player
8604 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8606 else if (use_step_delay) // special case: moving object has step delay
8608 if (!MovDelay[x][y])
8609 MovPos[x][y] += getElementMoveStepsize(x, y);
8614 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8618 TEST_DrawLevelField(x, y);
8620 return; // element is still waiting
8623 else // normal case: generically moving object
8625 MovPos[x][y] += getElementMoveStepsize(x, y);
8628 if (ABS(MovPos[x][y]) < TILEX)
8630 TEST_DrawLevelField(x, y);
8632 return; // element is still moving
8635 // element reached destination field
8637 Tile[x][y] = EL_EMPTY;
8638 Tile[newx][newy] = element;
8639 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8641 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8643 element = Tile[newx][newy] = EL_ACID;
8645 else if (element == EL_MOLE)
8647 Tile[x][y] = EL_SAND;
8649 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8651 else if (element == EL_QUICKSAND_FILLING)
8653 element = Tile[newx][newy] = get_next_element(element);
8654 Store[newx][newy] = Store[x][y];
8656 else if (element == EL_QUICKSAND_EMPTYING)
8658 Tile[x][y] = get_next_element(element);
8659 element = Tile[newx][newy] = Store[x][y];
8661 else if (element == EL_QUICKSAND_FAST_FILLING)
8663 element = Tile[newx][newy] = get_next_element(element);
8664 Store[newx][newy] = Store[x][y];
8666 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8668 Tile[x][y] = get_next_element(element);
8669 element = Tile[newx][newy] = Store[x][y];
8671 else if (element == EL_MAGIC_WALL_FILLING)
8673 element = Tile[newx][newy] = get_next_element(element);
8674 if (!game.magic_wall_active)
8675 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8676 Store[newx][newy] = Store[x][y];
8678 else if (element == EL_MAGIC_WALL_EMPTYING)
8680 Tile[x][y] = get_next_element(element);
8681 if (!game.magic_wall_active)
8682 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8683 element = Tile[newx][newy] = Store[x][y];
8685 InitField(newx, newy, FALSE);
8687 else if (element == EL_BD_MAGIC_WALL_FILLING)
8689 element = Tile[newx][newy] = get_next_element(element);
8690 if (!game.magic_wall_active)
8691 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8692 Store[newx][newy] = Store[x][y];
8694 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8696 Tile[x][y] = get_next_element(element);
8697 if (!game.magic_wall_active)
8698 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8699 element = Tile[newx][newy] = Store[x][y];
8701 InitField(newx, newy, FALSE);
8703 else if (element == EL_DC_MAGIC_WALL_FILLING)
8705 element = Tile[newx][newy] = get_next_element(element);
8706 if (!game.magic_wall_active)
8707 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8708 Store[newx][newy] = Store[x][y];
8710 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8712 Tile[x][y] = get_next_element(element);
8713 if (!game.magic_wall_active)
8714 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8715 element = Tile[newx][newy] = Store[x][y];
8717 InitField(newx, newy, FALSE);
8719 else if (element == EL_AMOEBA_DROPPING)
8721 Tile[x][y] = get_next_element(element);
8722 element = Tile[newx][newy] = Store[x][y];
8724 else if (element == EL_SOKOBAN_OBJECT)
8727 Tile[x][y] = Back[x][y];
8729 if (Back[newx][newy])
8730 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8732 Back[x][y] = Back[newx][newy] = 0;
8735 Store[x][y] = EL_EMPTY;
8740 MovDelay[newx][newy] = 0;
8742 if (CAN_CHANGE_OR_HAS_ACTION(element))
8744 // copy element change control values to new field
8745 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8746 ChangePage[newx][newy] = ChangePage[x][y];
8747 ChangeCount[newx][newy] = ChangeCount[x][y];
8748 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8751 CustomValue[newx][newy] = CustomValue[x][y];
8753 ChangeDelay[x][y] = 0;
8754 ChangePage[x][y] = -1;
8755 ChangeCount[x][y] = 0;
8756 ChangeEvent[x][y] = -1;
8758 CustomValue[x][y] = 0;
8760 // copy animation control values to new field
8761 GfxFrame[newx][newy] = GfxFrame[x][y];
8762 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8763 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8764 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8766 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8768 // some elements can leave other elements behind after moving
8769 if (ei->move_leave_element != EL_EMPTY &&
8770 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8771 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8773 int move_leave_element = ei->move_leave_element;
8775 // this makes it possible to leave the removed element again
8776 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8777 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8779 Tile[x][y] = move_leave_element;
8781 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8782 MovDir[x][y] = direction;
8784 InitField(x, y, FALSE);
8786 if (GFX_CRUMBLED(Tile[x][y]))
8787 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8789 if (IS_PLAYER_ELEMENT(move_leave_element))
8790 RelocatePlayer(x, y, move_leave_element);
8793 // do this after checking for left-behind element
8794 ResetGfxAnimation(x, y); // reset animation values for old field
8796 if (!CAN_MOVE(element) ||
8797 (CAN_FALL(element) && direction == MV_DOWN &&
8798 (element == EL_SPRING ||
8799 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8800 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8801 GfxDir[x][y] = MovDir[newx][newy] = 0;
8803 TEST_DrawLevelField(x, y);
8804 TEST_DrawLevelField(newx, newy);
8806 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8808 // prevent pushed element from moving on in pushed direction
8809 if (pushed_by_player && CAN_MOVE(element) &&
8810 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8811 !(element_info[element].move_pattern & direction))
8812 TurnRound(newx, newy);
8814 // prevent elements on conveyor belt from moving on in last direction
8815 if (pushed_by_conveyor && CAN_FALL(element) &&
8816 direction & MV_HORIZONTAL)
8817 MovDir[newx][newy] = 0;
8819 if (!pushed_by_player)
8821 int nextx = newx + dx, nexty = newy + dy;
8822 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8824 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8826 if (CAN_FALL(element) && direction == MV_DOWN)
8827 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8829 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8830 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8832 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8833 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8836 if (DONT_TOUCH(element)) // object may be nasty to player or others
8838 TestIfBadThingTouchesPlayer(newx, newy);
8839 TestIfBadThingTouchesFriend(newx, newy);
8841 if (!IS_CUSTOM_ELEMENT(element))
8842 TestIfBadThingTouchesOtherBadThing(newx, newy);
8844 else if (element == EL_PENGUIN)
8845 TestIfFriendTouchesBadThing(newx, newy);
8847 if (DONT_GET_HIT_BY(element))
8849 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8852 // give the player one last chance (one more frame) to move away
8853 if (CAN_FALL(element) && direction == MV_DOWN &&
8854 (last_line || (!IS_FREE(x, newy + 1) &&
8855 (!IS_PLAYER(x, newy + 1) ||
8856 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8859 if (pushed_by_player && !game.use_change_when_pushing_bug)
8861 int push_side = MV_DIR_OPPOSITE(direction);
8862 struct PlayerInfo *player = PLAYERINFO(x, y);
8864 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8865 player->index_bit, push_side);
8866 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8867 player->index_bit, push_side);
8870 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8871 MovDelay[newx][newy] = 1;
8873 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8875 TestIfElementTouchesCustomElement(x, y); // empty or new element
8876 TestIfElementHitsCustomElement(newx, newy, direction);
8877 TestIfPlayerTouchesCustomElement(newx, newy);
8878 TestIfElementTouchesCustomElement(newx, newy);
8880 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8881 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8882 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8883 MV_DIR_OPPOSITE(direction));
8886 int AmoebaNeighbourNr(int ax, int ay)
8889 int element = Tile[ax][ay];
8891 static int xy[4][2] =
8899 for (i = 0; i < NUM_DIRECTIONS; i++)
8901 int x = ax + xy[i][0];
8902 int y = ay + xy[i][1];
8904 if (!IN_LEV_FIELD(x, y))
8907 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8908 group_nr = AmoebaNr[x][y];
8914 static void AmoebaMerge(int ax, int ay)
8916 int i, x, y, xx, yy;
8917 int new_group_nr = AmoebaNr[ax][ay];
8918 static int xy[4][2] =
8926 if (new_group_nr == 0)
8929 for (i = 0; i < NUM_DIRECTIONS; i++)
8934 if (!IN_LEV_FIELD(x, y))
8937 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8938 Tile[x][y] == EL_BD_AMOEBA ||
8939 Tile[x][y] == EL_AMOEBA_DEAD) &&
8940 AmoebaNr[x][y] != new_group_nr)
8942 int old_group_nr = AmoebaNr[x][y];
8944 if (old_group_nr == 0)
8947 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8948 AmoebaCnt[old_group_nr] = 0;
8949 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8950 AmoebaCnt2[old_group_nr] = 0;
8952 SCAN_PLAYFIELD(xx, yy)
8954 if (AmoebaNr[xx][yy] == old_group_nr)
8955 AmoebaNr[xx][yy] = new_group_nr;
8961 void AmoebaToDiamond(int ax, int ay)
8965 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8967 int group_nr = AmoebaNr[ax][ay];
8972 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8973 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8979 SCAN_PLAYFIELD(x, y)
8981 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8984 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8988 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8989 SND_AMOEBA_TURNING_TO_GEM :
8990 SND_AMOEBA_TURNING_TO_ROCK));
8995 static int xy[4][2] =
9003 for (i = 0; i < NUM_DIRECTIONS; i++)
9008 if (!IN_LEV_FIELD(x, y))
9011 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9013 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9014 SND_AMOEBA_TURNING_TO_GEM :
9015 SND_AMOEBA_TURNING_TO_ROCK));
9022 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9025 int group_nr = AmoebaNr[ax][ay];
9026 boolean done = FALSE;
9031 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9032 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9038 SCAN_PLAYFIELD(x, y)
9040 if (AmoebaNr[x][y] == group_nr &&
9041 (Tile[x][y] == EL_AMOEBA_DEAD ||
9042 Tile[x][y] == EL_BD_AMOEBA ||
9043 Tile[x][y] == EL_AMOEBA_GROWING))
9046 Tile[x][y] = new_element;
9047 InitField(x, y, FALSE);
9048 TEST_DrawLevelField(x, y);
9054 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9055 SND_BD_AMOEBA_TURNING_TO_ROCK :
9056 SND_BD_AMOEBA_TURNING_TO_GEM));
9059 static void AmoebaGrowing(int x, int y)
9061 static unsigned int sound_delay = 0;
9062 static unsigned int sound_delay_value = 0;
9064 if (!MovDelay[x][y]) // start new growing cycle
9068 if (DelayReached(&sound_delay, sound_delay_value))
9070 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9071 sound_delay_value = 30;
9075 if (MovDelay[x][y]) // wait some time before growing bigger
9078 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9080 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9081 6 - MovDelay[x][y]);
9083 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9086 if (!MovDelay[x][y])
9088 Tile[x][y] = Store[x][y];
9090 TEST_DrawLevelField(x, y);
9095 static void AmoebaShrinking(int x, int y)
9097 static unsigned int sound_delay = 0;
9098 static unsigned int sound_delay_value = 0;
9100 if (!MovDelay[x][y]) // start new shrinking cycle
9104 if (DelayReached(&sound_delay, sound_delay_value))
9105 sound_delay_value = 30;
9108 if (MovDelay[x][y]) // wait some time before shrinking
9111 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9113 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9114 6 - MovDelay[x][y]);
9116 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9119 if (!MovDelay[x][y])
9121 Tile[x][y] = EL_EMPTY;
9122 TEST_DrawLevelField(x, y);
9124 // don't let mole enter this field in this cycle;
9125 // (give priority to objects falling to this field from above)
9131 static void AmoebaReproduce(int ax, int ay)
9134 int element = Tile[ax][ay];
9135 int graphic = el2img(element);
9136 int newax = ax, neway = ay;
9137 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9138 static int xy[4][2] =
9146 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9148 Tile[ax][ay] = EL_AMOEBA_DEAD;
9149 TEST_DrawLevelField(ax, ay);
9153 if (IS_ANIMATED(graphic))
9154 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9156 if (!MovDelay[ax][ay]) // start making new amoeba field
9157 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9159 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9162 if (MovDelay[ax][ay])
9166 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9169 int x = ax + xy[start][0];
9170 int y = ay + xy[start][1];
9172 if (!IN_LEV_FIELD(x, y))
9175 if (IS_FREE(x, y) ||
9176 CAN_GROW_INTO(Tile[x][y]) ||
9177 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9178 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9184 if (newax == ax && neway == ay)
9187 else // normal or "filled" (BD style) amoeba
9190 boolean waiting_for_player = FALSE;
9192 for (i = 0; i < NUM_DIRECTIONS; i++)
9194 int j = (start + i) % 4;
9195 int x = ax + xy[j][0];
9196 int y = ay + xy[j][1];
9198 if (!IN_LEV_FIELD(x, y))
9201 if (IS_FREE(x, y) ||
9202 CAN_GROW_INTO(Tile[x][y]) ||
9203 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9204 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9210 else if (IS_PLAYER(x, y))
9211 waiting_for_player = TRUE;
9214 if (newax == ax && neway == ay) // amoeba cannot grow
9216 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9218 Tile[ax][ay] = EL_AMOEBA_DEAD;
9219 TEST_DrawLevelField(ax, ay);
9220 AmoebaCnt[AmoebaNr[ax][ay]]--;
9222 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9224 if (element == EL_AMOEBA_FULL)
9225 AmoebaToDiamond(ax, ay);
9226 else if (element == EL_BD_AMOEBA)
9227 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9232 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9234 // amoeba gets larger by growing in some direction
9236 int new_group_nr = AmoebaNr[ax][ay];
9239 if (new_group_nr == 0)
9241 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9243 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9249 AmoebaNr[newax][neway] = new_group_nr;
9250 AmoebaCnt[new_group_nr]++;
9251 AmoebaCnt2[new_group_nr]++;
9253 // if amoeba touches other amoeba(s) after growing, unify them
9254 AmoebaMerge(newax, neway);
9256 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9258 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9264 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9265 (neway == lev_fieldy - 1 && newax != ax))
9267 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9268 Store[newax][neway] = element;
9270 else if (neway == ay || element == EL_EMC_DRIPPER)
9272 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9274 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9278 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9279 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9280 Store[ax][ay] = EL_AMOEBA_DROP;
9281 ContinueMoving(ax, ay);
9285 TEST_DrawLevelField(newax, neway);
9288 static void Life(int ax, int ay)
9292 int element = Tile[ax][ay];
9293 int graphic = el2img(element);
9294 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9296 boolean changed = FALSE;
9298 if (IS_ANIMATED(graphic))
9299 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9304 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9305 MovDelay[ax][ay] = life_time;
9307 if (MovDelay[ax][ay]) // wait some time before next cycle
9310 if (MovDelay[ax][ay])
9314 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9316 int xx = ax+x1, yy = ay+y1;
9317 int old_element = Tile[xx][yy];
9318 int num_neighbours = 0;
9320 if (!IN_LEV_FIELD(xx, yy))
9323 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9325 int x = xx+x2, y = yy+y2;
9327 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9330 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9331 boolean is_neighbour = FALSE;
9333 if (level.use_life_bugs)
9335 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9336 (IS_FREE(x, y) && Stop[x][y]));
9339 (Last[x][y] == element || is_player_cell);
9345 boolean is_free = FALSE;
9347 if (level.use_life_bugs)
9348 is_free = (IS_FREE(xx, yy));
9350 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9352 if (xx == ax && yy == ay) // field in the middle
9354 if (num_neighbours < life_parameter[0] ||
9355 num_neighbours > life_parameter[1])
9357 Tile[xx][yy] = EL_EMPTY;
9358 if (Tile[xx][yy] != old_element)
9359 TEST_DrawLevelField(xx, yy);
9360 Stop[xx][yy] = TRUE;
9364 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9365 { // free border field
9366 if (num_neighbours >= life_parameter[2] &&
9367 num_neighbours <= life_parameter[3])
9369 Tile[xx][yy] = element;
9370 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9371 if (Tile[xx][yy] != old_element)
9372 TEST_DrawLevelField(xx, yy);
9373 Stop[xx][yy] = TRUE;
9380 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9381 SND_GAME_OF_LIFE_GROWING);
9384 static void InitRobotWheel(int x, int y)
9386 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9389 static void RunRobotWheel(int x, int y)
9391 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9394 static void StopRobotWheel(int x, int y)
9396 if (game.robot_wheel_x == x &&
9397 game.robot_wheel_y == y)
9399 game.robot_wheel_x = -1;
9400 game.robot_wheel_y = -1;
9401 game.robot_wheel_active = FALSE;
9405 static void InitTimegateWheel(int x, int y)
9407 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9410 static void RunTimegateWheel(int x, int y)
9412 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9415 static void InitMagicBallDelay(int x, int y)
9417 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9420 static void ActivateMagicBall(int bx, int by)
9424 if (level.ball_random)
9426 int pos_border = RND(8); // select one of the eight border elements
9427 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9428 int xx = pos_content % 3;
9429 int yy = pos_content / 3;
9434 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9435 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9439 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9441 int xx = x - bx + 1;
9442 int yy = y - by + 1;
9444 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9445 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9449 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9452 static void CheckExit(int x, int y)
9454 if (game.gems_still_needed > 0 ||
9455 game.sokoban_fields_still_needed > 0 ||
9456 game.sokoban_objects_still_needed > 0 ||
9457 game.lights_still_needed > 0)
9459 int element = Tile[x][y];
9460 int graphic = el2img(element);
9462 if (IS_ANIMATED(graphic))
9463 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9468 // do not re-open exit door closed after last player
9469 if (game.all_players_gone)
9472 Tile[x][y] = EL_EXIT_OPENING;
9474 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9477 static void CheckExitEM(int x, int y)
9479 if (game.gems_still_needed > 0 ||
9480 game.sokoban_fields_still_needed > 0 ||
9481 game.sokoban_objects_still_needed > 0 ||
9482 game.lights_still_needed > 0)
9484 int element = Tile[x][y];
9485 int graphic = el2img(element);
9487 if (IS_ANIMATED(graphic))
9488 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9493 // do not re-open exit door closed after last player
9494 if (game.all_players_gone)
9497 Tile[x][y] = EL_EM_EXIT_OPENING;
9499 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9502 static void CheckExitSteel(int x, int y)
9504 if (game.gems_still_needed > 0 ||
9505 game.sokoban_fields_still_needed > 0 ||
9506 game.sokoban_objects_still_needed > 0 ||
9507 game.lights_still_needed > 0)
9509 int element = Tile[x][y];
9510 int graphic = el2img(element);
9512 if (IS_ANIMATED(graphic))
9513 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9518 // do not re-open exit door closed after last player
9519 if (game.all_players_gone)
9522 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9524 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9527 static void CheckExitSteelEM(int x, int y)
9529 if (game.gems_still_needed > 0 ||
9530 game.sokoban_fields_still_needed > 0 ||
9531 game.sokoban_objects_still_needed > 0 ||
9532 game.lights_still_needed > 0)
9534 int element = Tile[x][y];
9535 int graphic = el2img(element);
9537 if (IS_ANIMATED(graphic))
9538 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9543 // do not re-open exit door closed after last player
9544 if (game.all_players_gone)
9547 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9549 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9552 static void CheckExitSP(int x, int y)
9554 if (game.gems_still_needed > 0)
9556 int element = Tile[x][y];
9557 int graphic = el2img(element);
9559 if (IS_ANIMATED(graphic))
9560 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9565 // do not re-open exit door closed after last player
9566 if (game.all_players_gone)
9569 Tile[x][y] = EL_SP_EXIT_OPENING;
9571 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9574 static void CloseAllOpenTimegates(void)
9578 SCAN_PLAYFIELD(x, y)
9580 int element = Tile[x][y];
9582 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9584 Tile[x][y] = EL_TIMEGATE_CLOSING;
9586 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9591 static void DrawTwinkleOnField(int x, int y)
9593 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9596 if (Tile[x][y] == EL_BD_DIAMOND)
9599 if (MovDelay[x][y] == 0) // next animation frame
9600 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9602 if (MovDelay[x][y] != 0) // wait some time before next frame
9606 DrawLevelElementAnimation(x, y, Tile[x][y]);
9608 if (MovDelay[x][y] != 0)
9610 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9611 10 - MovDelay[x][y]);
9613 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9618 static void MauerWaechst(int x, int y)
9622 if (!MovDelay[x][y]) // next animation frame
9623 MovDelay[x][y] = 3 * delay;
9625 if (MovDelay[x][y]) // wait some time before next frame
9629 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9631 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9632 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9634 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9637 if (!MovDelay[x][y])
9639 if (MovDir[x][y] == MV_LEFT)
9641 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9642 TEST_DrawLevelField(x - 1, y);
9644 else if (MovDir[x][y] == MV_RIGHT)
9646 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9647 TEST_DrawLevelField(x + 1, y);
9649 else if (MovDir[x][y] == MV_UP)
9651 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9652 TEST_DrawLevelField(x, y - 1);
9656 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9657 TEST_DrawLevelField(x, y + 1);
9660 Tile[x][y] = Store[x][y];
9662 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9663 TEST_DrawLevelField(x, y);
9668 static void MauerAbleger(int ax, int ay)
9670 int element = Tile[ax][ay];
9671 int graphic = el2img(element);
9672 boolean oben_frei = FALSE, unten_frei = FALSE;
9673 boolean links_frei = FALSE, rechts_frei = FALSE;
9674 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9675 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9676 boolean new_wall = FALSE;
9678 if (IS_ANIMATED(graphic))
9679 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9681 if (!MovDelay[ax][ay]) // start building new wall
9682 MovDelay[ax][ay] = 6;
9684 if (MovDelay[ax][ay]) // wait some time before building new wall
9687 if (MovDelay[ax][ay])
9691 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9693 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9695 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9697 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9700 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9701 element == EL_EXPANDABLE_WALL_ANY)
9705 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9706 Store[ax][ay-1] = element;
9707 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9708 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9709 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9710 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9715 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9716 Store[ax][ay+1] = element;
9717 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9718 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9719 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9720 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9725 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9726 element == EL_EXPANDABLE_WALL_ANY ||
9727 element == EL_EXPANDABLE_WALL ||
9728 element == EL_BD_EXPANDABLE_WALL)
9732 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9733 Store[ax-1][ay] = element;
9734 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9735 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9736 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9737 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9743 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9744 Store[ax+1][ay] = element;
9745 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9746 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9747 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9748 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9753 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9754 TEST_DrawLevelField(ax, ay);
9756 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9758 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9759 unten_massiv = TRUE;
9760 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9761 links_massiv = TRUE;
9762 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9763 rechts_massiv = TRUE;
9765 if (((oben_massiv && unten_massiv) ||
9766 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9767 element == EL_EXPANDABLE_WALL) &&
9768 ((links_massiv && rechts_massiv) ||
9769 element == EL_EXPANDABLE_WALL_VERTICAL))
9770 Tile[ax][ay] = EL_WALL;
9773 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9776 static void MauerAblegerStahl(int ax, int ay)
9778 int element = Tile[ax][ay];
9779 int graphic = el2img(element);
9780 boolean oben_frei = FALSE, unten_frei = FALSE;
9781 boolean links_frei = FALSE, rechts_frei = FALSE;
9782 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9783 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9784 boolean new_wall = FALSE;
9786 if (IS_ANIMATED(graphic))
9787 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9789 if (!MovDelay[ax][ay]) // start building new wall
9790 MovDelay[ax][ay] = 6;
9792 if (MovDelay[ax][ay]) // wait some time before building new wall
9795 if (MovDelay[ax][ay])
9799 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9801 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9803 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9805 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9808 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9809 element == EL_EXPANDABLE_STEELWALL_ANY)
9813 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9814 Store[ax][ay-1] = element;
9815 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9816 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9817 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9818 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9823 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9824 Store[ax][ay+1] = element;
9825 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9826 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9827 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9828 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9833 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9834 element == EL_EXPANDABLE_STEELWALL_ANY)
9838 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9839 Store[ax-1][ay] = element;
9840 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9841 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9842 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9843 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9849 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9850 Store[ax+1][ay] = element;
9851 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9852 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9853 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9854 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9859 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9861 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9862 unten_massiv = TRUE;
9863 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9864 links_massiv = TRUE;
9865 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9866 rechts_massiv = TRUE;
9868 if (((oben_massiv && unten_massiv) ||
9869 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9870 ((links_massiv && rechts_massiv) ||
9871 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9872 Tile[ax][ay] = EL_STEELWALL;
9875 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9878 static void CheckForDragon(int x, int y)
9881 boolean dragon_found = FALSE;
9882 static int xy[4][2] =
9890 for (i = 0; i < NUM_DIRECTIONS; i++)
9892 for (j = 0; j < 4; j++)
9894 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9896 if (IN_LEV_FIELD(xx, yy) &&
9897 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9899 if (Tile[xx][yy] == EL_DRAGON)
9900 dragon_found = TRUE;
9909 for (i = 0; i < NUM_DIRECTIONS; i++)
9911 for (j = 0; j < 3; j++)
9913 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9915 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9917 Tile[xx][yy] = EL_EMPTY;
9918 TEST_DrawLevelField(xx, yy);
9927 static void InitBuggyBase(int x, int y)
9929 int element = Tile[x][y];
9930 int activating_delay = FRAMES_PER_SECOND / 4;
9933 (element == EL_SP_BUGGY_BASE ?
9934 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9935 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9937 element == EL_SP_BUGGY_BASE_ACTIVE ?
9938 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9941 static void WarnBuggyBase(int x, int y)
9944 static int xy[4][2] =
9952 for (i = 0; i < NUM_DIRECTIONS; i++)
9954 int xx = x + xy[i][0];
9955 int yy = y + xy[i][1];
9957 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9959 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9966 static void InitTrap(int x, int y)
9968 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9971 static void ActivateTrap(int x, int y)
9973 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9976 static void ChangeActiveTrap(int x, int y)
9978 int graphic = IMG_TRAP_ACTIVE;
9980 // if new animation frame was drawn, correct crumbled sand border
9981 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9982 TEST_DrawLevelFieldCrumbled(x, y);
9985 static int getSpecialActionElement(int element, int number, int base_element)
9987 return (element != EL_EMPTY ? element :
9988 number != -1 ? base_element + number - 1 :
9992 static int getModifiedActionNumber(int value_old, int operator, int operand,
9993 int value_min, int value_max)
9995 int value_new = (operator == CA_MODE_SET ? operand :
9996 operator == CA_MODE_ADD ? value_old + operand :
9997 operator == CA_MODE_SUBTRACT ? value_old - operand :
9998 operator == CA_MODE_MULTIPLY ? value_old * operand :
9999 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10000 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10003 return (value_new < value_min ? value_min :
10004 value_new > value_max ? value_max :
10008 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10010 struct ElementInfo *ei = &element_info[element];
10011 struct ElementChangeInfo *change = &ei->change_page[page];
10012 int target_element = change->target_element;
10013 int action_type = change->action_type;
10014 int action_mode = change->action_mode;
10015 int action_arg = change->action_arg;
10016 int action_element = change->action_element;
10019 if (!change->has_action)
10022 // ---------- determine action paramater values -----------------------------
10024 int level_time_value =
10025 (level.time > 0 ? TimeLeft :
10028 int action_arg_element_raw =
10029 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10030 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10031 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10032 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10033 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10034 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10035 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10037 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10039 int action_arg_direction =
10040 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10041 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10042 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10043 change->actual_trigger_side :
10044 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10045 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10048 int action_arg_number_min =
10049 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10052 int action_arg_number_max =
10053 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10054 action_type == CA_SET_LEVEL_GEMS ? 999 :
10055 action_type == CA_SET_LEVEL_TIME ? 9999 :
10056 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10057 action_type == CA_SET_CE_VALUE ? 9999 :
10058 action_type == CA_SET_CE_SCORE ? 9999 :
10061 int action_arg_number_reset =
10062 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10063 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10064 action_type == CA_SET_LEVEL_TIME ? level.time :
10065 action_type == CA_SET_LEVEL_SCORE ? 0 :
10066 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10067 action_type == CA_SET_CE_SCORE ? 0 :
10070 int action_arg_number =
10071 (action_arg <= CA_ARG_MAX ? action_arg :
10072 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10073 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10074 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10075 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10076 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10077 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10078 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10079 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10080 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10081 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10082 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10083 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10084 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10085 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10086 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10087 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10088 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10089 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10090 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10091 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10092 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10095 int action_arg_number_old =
10096 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10097 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10098 action_type == CA_SET_LEVEL_SCORE ? game.score :
10099 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10100 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10103 int action_arg_number_new =
10104 getModifiedActionNumber(action_arg_number_old,
10105 action_mode, action_arg_number,
10106 action_arg_number_min, action_arg_number_max);
10108 int trigger_player_bits =
10109 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10110 change->actual_trigger_player_bits : change->trigger_player);
10112 int action_arg_player_bits =
10113 (action_arg >= CA_ARG_PLAYER_1 &&
10114 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10115 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10116 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10119 // ---------- execute action -----------------------------------------------
10121 switch (action_type)
10128 // ---------- level actions ----------------------------------------------
10130 case CA_RESTART_LEVEL:
10132 game.restart_level = TRUE;
10137 case CA_SHOW_ENVELOPE:
10139 int element = getSpecialActionElement(action_arg_element,
10140 action_arg_number, EL_ENVELOPE_1);
10142 if (IS_ENVELOPE(element))
10143 local_player->show_envelope = element;
10148 case CA_SET_LEVEL_TIME:
10150 if (level.time > 0) // only modify limited time value
10152 TimeLeft = action_arg_number_new;
10154 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10156 DisplayGameControlValues();
10158 if (!TimeLeft && setup.time_limit)
10159 for (i = 0; i < MAX_PLAYERS; i++)
10160 KillPlayer(&stored_player[i]);
10166 case CA_SET_LEVEL_SCORE:
10168 game.score = action_arg_number_new;
10170 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10172 DisplayGameControlValues();
10177 case CA_SET_LEVEL_GEMS:
10179 game.gems_still_needed = action_arg_number_new;
10181 game.snapshot.collected_item = TRUE;
10183 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10185 DisplayGameControlValues();
10190 case CA_SET_LEVEL_WIND:
10192 game.wind_direction = action_arg_direction;
10197 case CA_SET_LEVEL_RANDOM_SEED:
10199 // ensure that setting a new random seed while playing is predictable
10200 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10205 // ---------- player actions ---------------------------------------------
10207 case CA_MOVE_PLAYER:
10208 case CA_MOVE_PLAYER_NEW:
10210 // automatically move to the next field in specified direction
10211 for (i = 0; i < MAX_PLAYERS; i++)
10212 if (trigger_player_bits & (1 << i))
10213 if (action_type == CA_MOVE_PLAYER ||
10214 stored_player[i].MovPos == 0)
10215 stored_player[i].programmed_action = action_arg_direction;
10220 case CA_EXIT_PLAYER:
10222 for (i = 0; i < MAX_PLAYERS; i++)
10223 if (action_arg_player_bits & (1 << i))
10224 ExitPlayer(&stored_player[i]);
10226 if (game.players_still_needed == 0)
10232 case CA_KILL_PLAYER:
10234 for (i = 0; i < MAX_PLAYERS; i++)
10235 if (action_arg_player_bits & (1 << i))
10236 KillPlayer(&stored_player[i]);
10241 case CA_SET_PLAYER_KEYS:
10243 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10244 int element = getSpecialActionElement(action_arg_element,
10245 action_arg_number, EL_KEY_1);
10247 if (IS_KEY(element))
10249 for (i = 0; i < MAX_PLAYERS; i++)
10251 if (trigger_player_bits & (1 << i))
10253 stored_player[i].key[KEY_NR(element)] = key_state;
10255 DrawGameDoorValues();
10263 case CA_SET_PLAYER_SPEED:
10265 for (i = 0; i < MAX_PLAYERS; i++)
10267 if (trigger_player_bits & (1 << i))
10269 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10271 if (action_arg == CA_ARG_SPEED_FASTER &&
10272 stored_player[i].cannot_move)
10274 action_arg_number = STEPSIZE_VERY_SLOW;
10276 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10277 action_arg == CA_ARG_SPEED_FASTER)
10279 action_arg_number = 2;
10280 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10283 else if (action_arg == CA_ARG_NUMBER_RESET)
10285 action_arg_number = level.initial_player_stepsize[i];
10289 getModifiedActionNumber(move_stepsize,
10292 action_arg_number_min,
10293 action_arg_number_max);
10295 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10302 case CA_SET_PLAYER_SHIELD:
10304 for (i = 0; i < MAX_PLAYERS; i++)
10306 if (trigger_player_bits & (1 << i))
10308 if (action_arg == CA_ARG_SHIELD_OFF)
10310 stored_player[i].shield_normal_time_left = 0;
10311 stored_player[i].shield_deadly_time_left = 0;
10313 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10315 stored_player[i].shield_normal_time_left = 999999;
10317 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10319 stored_player[i].shield_normal_time_left = 999999;
10320 stored_player[i].shield_deadly_time_left = 999999;
10328 case CA_SET_PLAYER_GRAVITY:
10330 for (i = 0; i < MAX_PLAYERS; i++)
10332 if (trigger_player_bits & (1 << i))
10334 stored_player[i].gravity =
10335 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10336 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10337 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10338 stored_player[i].gravity);
10345 case CA_SET_PLAYER_ARTWORK:
10347 for (i = 0; i < MAX_PLAYERS; i++)
10349 if (trigger_player_bits & (1 << i))
10351 int artwork_element = action_arg_element;
10353 if (action_arg == CA_ARG_ELEMENT_RESET)
10355 (level.use_artwork_element[i] ? level.artwork_element[i] :
10356 stored_player[i].element_nr);
10358 if (stored_player[i].artwork_element != artwork_element)
10359 stored_player[i].Frame = 0;
10361 stored_player[i].artwork_element = artwork_element;
10363 SetPlayerWaiting(&stored_player[i], FALSE);
10365 // set number of special actions for bored and sleeping animation
10366 stored_player[i].num_special_action_bored =
10367 get_num_special_action(artwork_element,
10368 ACTION_BORING_1, ACTION_BORING_LAST);
10369 stored_player[i].num_special_action_sleeping =
10370 get_num_special_action(artwork_element,
10371 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10378 case CA_SET_PLAYER_INVENTORY:
10380 for (i = 0; i < MAX_PLAYERS; i++)
10382 struct PlayerInfo *player = &stored_player[i];
10385 if (trigger_player_bits & (1 << i))
10387 int inventory_element = action_arg_element;
10389 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10390 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10391 action_arg == CA_ARG_ELEMENT_ACTION)
10393 int element = inventory_element;
10394 int collect_count = element_info[element].collect_count_initial;
10396 if (!IS_CUSTOM_ELEMENT(element))
10399 if (collect_count == 0)
10400 player->inventory_infinite_element = element;
10402 for (k = 0; k < collect_count; k++)
10403 if (player->inventory_size < MAX_INVENTORY_SIZE)
10404 player->inventory_element[player->inventory_size++] =
10407 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10408 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10409 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10411 if (player->inventory_infinite_element != EL_UNDEFINED &&
10412 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10413 action_arg_element_raw))
10414 player->inventory_infinite_element = EL_UNDEFINED;
10416 for (k = 0, j = 0; j < player->inventory_size; j++)
10418 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10419 action_arg_element_raw))
10420 player->inventory_element[k++] = player->inventory_element[j];
10423 player->inventory_size = k;
10425 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10427 if (player->inventory_size > 0)
10429 for (j = 0; j < player->inventory_size - 1; j++)
10430 player->inventory_element[j] = player->inventory_element[j + 1];
10432 player->inventory_size--;
10435 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10437 if (player->inventory_size > 0)
10438 player->inventory_size--;
10440 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10442 player->inventory_infinite_element = EL_UNDEFINED;
10443 player->inventory_size = 0;
10445 else if (action_arg == CA_ARG_INVENTORY_RESET)
10447 player->inventory_infinite_element = EL_UNDEFINED;
10448 player->inventory_size = 0;
10450 if (level.use_initial_inventory[i])
10452 for (j = 0; j < level.initial_inventory_size[i]; j++)
10454 int element = level.initial_inventory_content[i][j];
10455 int collect_count = element_info[element].collect_count_initial;
10457 if (!IS_CUSTOM_ELEMENT(element))
10460 if (collect_count == 0)
10461 player->inventory_infinite_element = element;
10463 for (k = 0; k < collect_count; k++)
10464 if (player->inventory_size < MAX_INVENTORY_SIZE)
10465 player->inventory_element[player->inventory_size++] =
10476 // ---------- CE actions -------------------------------------------------
10478 case CA_SET_CE_VALUE:
10480 int last_ce_value = CustomValue[x][y];
10482 CustomValue[x][y] = action_arg_number_new;
10484 if (CustomValue[x][y] != last_ce_value)
10486 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10487 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10489 if (CustomValue[x][y] == 0)
10491 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10492 ChangeCount[x][y] = 0; // allow at least one more change
10494 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10495 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10502 case CA_SET_CE_SCORE:
10504 int last_ce_score = ei->collect_score;
10506 ei->collect_score = action_arg_number_new;
10508 if (ei->collect_score != last_ce_score)
10510 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10511 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10513 if (ei->collect_score == 0)
10517 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10518 ChangeCount[x][y] = 0; // allow at least one more change
10520 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10521 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10524 This is a very special case that seems to be a mixture between
10525 CheckElementChange() and CheckTriggeredElementChange(): while
10526 the first one only affects single elements that are triggered
10527 directly, the second one affects multiple elements in the playfield
10528 that are triggered indirectly by another element. This is a third
10529 case: Changing the CE score always affects multiple identical CEs,
10530 so every affected CE must be checked, not only the single CE for
10531 which the CE score was changed in the first place (as every instance
10532 of that CE shares the same CE score, and therefore also can change)!
10534 SCAN_PLAYFIELD(xx, yy)
10536 if (Tile[xx][yy] == element)
10537 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10538 CE_SCORE_GETS_ZERO);
10546 case CA_SET_CE_ARTWORK:
10548 int artwork_element = action_arg_element;
10549 boolean reset_frame = FALSE;
10552 if (action_arg == CA_ARG_ELEMENT_RESET)
10553 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10556 if (ei->gfx_element != artwork_element)
10557 reset_frame = TRUE;
10559 ei->gfx_element = artwork_element;
10561 SCAN_PLAYFIELD(xx, yy)
10563 if (Tile[xx][yy] == element)
10567 ResetGfxAnimation(xx, yy);
10568 ResetRandomAnimationValue(xx, yy);
10571 TEST_DrawLevelField(xx, yy);
10578 // ---------- engine actions ---------------------------------------------
10580 case CA_SET_ENGINE_SCAN_MODE:
10582 InitPlayfieldScanMode(action_arg);
10592 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10594 int old_element = Tile[x][y];
10595 int new_element = GetElementFromGroupElement(element);
10596 int previous_move_direction = MovDir[x][y];
10597 int last_ce_value = CustomValue[x][y];
10598 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10599 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10600 boolean add_player_onto_element = (new_element_is_player &&
10601 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10602 IS_WALKABLE(old_element));
10604 if (!add_player_onto_element)
10606 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10607 RemoveMovingField(x, y);
10611 Tile[x][y] = new_element;
10613 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10614 MovDir[x][y] = previous_move_direction;
10616 if (element_info[new_element].use_last_ce_value)
10617 CustomValue[x][y] = last_ce_value;
10619 InitField_WithBug1(x, y, FALSE);
10621 new_element = Tile[x][y]; // element may have changed
10623 ResetGfxAnimation(x, y);
10624 ResetRandomAnimationValue(x, y);
10626 TEST_DrawLevelField(x, y);
10628 if (GFX_CRUMBLED(new_element))
10629 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10632 // check if element under the player changes from accessible to unaccessible
10633 // (needed for special case of dropping element which then changes)
10634 // (must be checked after creating new element for walkable group elements)
10635 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10636 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10643 // "ChangeCount" not set yet to allow "entered by player" change one time
10644 if (new_element_is_player)
10645 RelocatePlayer(x, y, new_element);
10648 ChangeCount[x][y]++; // count number of changes in the same frame
10650 TestIfBadThingTouchesPlayer(x, y);
10651 TestIfPlayerTouchesCustomElement(x, y);
10652 TestIfElementTouchesCustomElement(x, y);
10655 static void CreateField(int x, int y, int element)
10657 CreateFieldExt(x, y, element, FALSE);
10660 static void CreateElementFromChange(int x, int y, int element)
10662 element = GET_VALID_RUNTIME_ELEMENT(element);
10664 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10666 int old_element = Tile[x][y];
10668 // prevent changed element from moving in same engine frame
10669 // unless both old and new element can either fall or move
10670 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10671 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10675 CreateFieldExt(x, y, element, TRUE);
10678 static boolean ChangeElement(int x, int y, int element, int page)
10680 struct ElementInfo *ei = &element_info[element];
10681 struct ElementChangeInfo *change = &ei->change_page[page];
10682 int ce_value = CustomValue[x][y];
10683 int ce_score = ei->collect_score;
10684 int target_element;
10685 int old_element = Tile[x][y];
10687 // always use default change event to prevent running into a loop
10688 if (ChangeEvent[x][y] == -1)
10689 ChangeEvent[x][y] = CE_DELAY;
10691 if (ChangeEvent[x][y] == CE_DELAY)
10693 // reset actual trigger element, trigger player and action element
10694 change->actual_trigger_element = EL_EMPTY;
10695 change->actual_trigger_player = EL_EMPTY;
10696 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10697 change->actual_trigger_side = CH_SIDE_NONE;
10698 change->actual_trigger_ce_value = 0;
10699 change->actual_trigger_ce_score = 0;
10702 // do not change elements more than a specified maximum number of changes
10703 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10706 ChangeCount[x][y]++; // count number of changes in the same frame
10708 if (change->explode)
10715 if (change->use_target_content)
10717 boolean complete_replace = TRUE;
10718 boolean can_replace[3][3];
10721 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10724 boolean is_walkable;
10725 boolean is_diggable;
10726 boolean is_collectible;
10727 boolean is_removable;
10728 boolean is_destructible;
10729 int ex = x + xx - 1;
10730 int ey = y + yy - 1;
10731 int content_element = change->target_content.e[xx][yy];
10734 can_replace[xx][yy] = TRUE;
10736 if (ex == x && ey == y) // do not check changing element itself
10739 if (content_element == EL_EMPTY_SPACE)
10741 can_replace[xx][yy] = FALSE; // do not replace border with space
10746 if (!IN_LEV_FIELD(ex, ey))
10748 can_replace[xx][yy] = FALSE;
10749 complete_replace = FALSE;
10756 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10757 e = MovingOrBlocked2Element(ex, ey);
10759 is_empty = (IS_FREE(ex, ey) ||
10760 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10762 is_walkable = (is_empty || IS_WALKABLE(e));
10763 is_diggable = (is_empty || IS_DIGGABLE(e));
10764 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10765 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10766 is_removable = (is_diggable || is_collectible);
10768 can_replace[xx][yy] =
10769 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10770 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10771 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10772 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10773 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10774 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10775 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10777 if (!can_replace[xx][yy])
10778 complete_replace = FALSE;
10781 if (!change->only_if_complete || complete_replace)
10783 boolean something_has_changed = FALSE;
10785 if (change->only_if_complete && change->use_random_replace &&
10786 RND(100) < change->random_percentage)
10789 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10791 int ex = x + xx - 1;
10792 int ey = y + yy - 1;
10793 int content_element;
10795 if (can_replace[xx][yy] && (!change->use_random_replace ||
10796 RND(100) < change->random_percentage))
10798 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10799 RemoveMovingField(ex, ey);
10801 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10803 content_element = change->target_content.e[xx][yy];
10804 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10805 ce_value, ce_score);
10807 CreateElementFromChange(ex, ey, target_element);
10809 something_has_changed = TRUE;
10811 // for symmetry reasons, freeze newly created border elements
10812 if (ex != x || ey != y)
10813 Stop[ex][ey] = TRUE; // no more moving in this frame
10817 if (something_has_changed)
10819 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10820 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10826 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10827 ce_value, ce_score);
10829 if (element == EL_DIAGONAL_GROWING ||
10830 element == EL_DIAGONAL_SHRINKING)
10832 target_element = Store[x][y];
10834 Store[x][y] = EL_EMPTY;
10837 // special case: element changes to player (and may be kept if walkable)
10838 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10839 CreateElementFromChange(x, y, EL_EMPTY);
10841 CreateElementFromChange(x, y, target_element);
10843 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10844 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10847 // this uses direct change before indirect change
10848 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10853 static void HandleElementChange(int x, int y, int page)
10855 int element = MovingOrBlocked2Element(x, y);
10856 struct ElementInfo *ei = &element_info[element];
10857 struct ElementChangeInfo *change = &ei->change_page[page];
10858 boolean handle_action_before_change = FALSE;
10861 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10862 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10864 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10865 x, y, element, element_info[element].token_name);
10866 Debug("game:playing:HandleElementChange", "This should never happen!");
10870 // this can happen with classic bombs on walkable, changing elements
10871 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10876 if (ChangeDelay[x][y] == 0) // initialize element change
10878 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10880 if (change->can_change)
10882 // !!! not clear why graphic animation should be reset at all here !!!
10883 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10884 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10887 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10889 When using an animation frame delay of 1 (this only happens with
10890 "sp_zonk.moving.left/right" in the classic graphics), the default
10891 (non-moving) animation shows wrong animation frames (while the
10892 moving animation, like "sp_zonk.moving.left/right", is correct,
10893 so this graphical bug never shows up with the classic graphics).
10894 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10895 be drawn instead of the correct frames 0,1,2,3. This is caused by
10896 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10897 an element change: First when the change delay ("ChangeDelay[][]")
10898 counter has reached zero after decrementing, then a second time in
10899 the next frame (after "GfxFrame[][]" was already incremented) when
10900 "ChangeDelay[][]" is reset to the initial delay value again.
10902 This causes frame 0 to be drawn twice, while the last frame won't
10903 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10905 As some animations may already be cleverly designed around this bug
10906 (at least the "Snake Bite" snake tail animation does this), it cannot
10907 simply be fixed here without breaking such existing animations.
10908 Unfortunately, it cannot easily be detected if a graphics set was
10909 designed "before" or "after" the bug was fixed. As a workaround,
10910 a new graphics set option "game.graphics_engine_version" was added
10911 to be able to specify the game's major release version for which the
10912 graphics set was designed, which can then be used to decide if the
10913 bugfix should be used (version 4 and above) or not (version 3 or
10914 below, or if no version was specified at all, as with old sets).
10916 (The wrong/fixed animation frames can be tested with the test level set
10917 "test_gfxframe" and level "000", which contains a specially prepared
10918 custom element at level position (x/y) == (11/9) which uses the zonk
10919 animation mentioned above. Using "game.graphics_engine_version: 4"
10920 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10921 This can also be seen from the debug output for this test element.)
10924 // when a custom element is about to change (for example by change delay),
10925 // do not reset graphic animation when the custom element is moving
10926 if (game.graphics_engine_version < 4 &&
10929 ResetGfxAnimation(x, y);
10930 ResetRandomAnimationValue(x, y);
10933 if (change->pre_change_function)
10934 change->pre_change_function(x, y);
10938 ChangeDelay[x][y]--;
10940 if (ChangeDelay[x][y] != 0) // continue element change
10942 if (change->can_change)
10944 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10946 if (IS_ANIMATED(graphic))
10947 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10949 if (change->change_function)
10950 change->change_function(x, y);
10953 else // finish element change
10955 if (ChangePage[x][y] != -1) // remember page from delayed change
10957 page = ChangePage[x][y];
10958 ChangePage[x][y] = -1;
10960 change = &ei->change_page[page];
10963 if (IS_MOVING(x, y)) // never change a running system ;-)
10965 ChangeDelay[x][y] = 1; // try change after next move step
10966 ChangePage[x][y] = page; // remember page to use for change
10971 // special case: set new level random seed before changing element
10972 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10973 handle_action_before_change = TRUE;
10975 if (change->has_action && handle_action_before_change)
10976 ExecuteCustomElementAction(x, y, element, page);
10978 if (change->can_change)
10980 if (ChangeElement(x, y, element, page))
10982 if (change->post_change_function)
10983 change->post_change_function(x, y);
10987 if (change->has_action && !handle_action_before_change)
10988 ExecuteCustomElementAction(x, y, element, page);
10992 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10993 int trigger_element,
10995 int trigger_player,
10999 boolean change_done_any = FALSE;
11000 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11003 if (!(trigger_events[trigger_element][trigger_event]))
11006 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11008 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11010 int element = EL_CUSTOM_START + i;
11011 boolean change_done = FALSE;
11014 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11015 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11018 for (p = 0; p < element_info[element].num_change_pages; p++)
11020 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11022 if (change->can_change_or_has_action &&
11023 change->has_event[trigger_event] &&
11024 change->trigger_side & trigger_side &&
11025 change->trigger_player & trigger_player &&
11026 change->trigger_page & trigger_page_bits &&
11027 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11029 change->actual_trigger_element = trigger_element;
11030 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11031 change->actual_trigger_player_bits = trigger_player;
11032 change->actual_trigger_side = trigger_side;
11033 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11034 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11036 if ((change->can_change && !change_done) || change->has_action)
11040 SCAN_PLAYFIELD(x, y)
11042 if (Tile[x][y] == element)
11044 if (change->can_change && !change_done)
11046 // if element already changed in this frame, not only prevent
11047 // another element change (checked in ChangeElement()), but
11048 // also prevent additional element actions for this element
11050 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11051 !level.use_action_after_change_bug)
11054 ChangeDelay[x][y] = 1;
11055 ChangeEvent[x][y] = trigger_event;
11057 HandleElementChange(x, y, p);
11059 else if (change->has_action)
11061 // if element already changed in this frame, not only prevent
11062 // another element change (checked in ChangeElement()), but
11063 // also prevent additional element actions for this element
11065 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11066 !level.use_action_after_change_bug)
11069 ExecuteCustomElementAction(x, y, element, p);
11070 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11075 if (change->can_change)
11077 change_done = TRUE;
11078 change_done_any = TRUE;
11085 RECURSION_LOOP_DETECTION_END();
11087 return change_done_any;
11090 static boolean CheckElementChangeExt(int x, int y,
11092 int trigger_element,
11094 int trigger_player,
11097 boolean change_done = FALSE;
11100 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11101 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11104 if (Tile[x][y] == EL_BLOCKED)
11106 Blocked2Moving(x, y, &x, &y);
11107 element = Tile[x][y];
11110 // check if element has already changed or is about to change after moving
11111 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11112 Tile[x][y] != element) ||
11114 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11115 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11116 ChangePage[x][y] != -1)))
11119 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11121 for (p = 0; p < element_info[element].num_change_pages; p++)
11123 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11125 /* check trigger element for all events where the element that is checked
11126 for changing interacts with a directly adjacent element -- this is
11127 different to element changes that affect other elements to change on the
11128 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11129 boolean check_trigger_element =
11130 (trigger_event == CE_TOUCHING_X ||
11131 trigger_event == CE_HITTING_X ||
11132 trigger_event == CE_HIT_BY_X ||
11133 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11135 if (change->can_change_or_has_action &&
11136 change->has_event[trigger_event] &&
11137 change->trigger_side & trigger_side &&
11138 change->trigger_player & trigger_player &&
11139 (!check_trigger_element ||
11140 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11142 change->actual_trigger_element = trigger_element;
11143 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11144 change->actual_trigger_player_bits = trigger_player;
11145 change->actual_trigger_side = trigger_side;
11146 change->actual_trigger_ce_value = CustomValue[x][y];
11147 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11149 // special case: trigger element not at (x,y) position for some events
11150 if (check_trigger_element)
11162 { 0, 0 }, { 0, 0 }, { 0, 0 },
11166 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11167 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11169 change->actual_trigger_ce_value = CustomValue[xx][yy];
11170 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11173 if (change->can_change && !change_done)
11175 ChangeDelay[x][y] = 1;
11176 ChangeEvent[x][y] = trigger_event;
11178 HandleElementChange(x, y, p);
11180 change_done = TRUE;
11182 else if (change->has_action)
11184 ExecuteCustomElementAction(x, y, element, p);
11185 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11190 RECURSION_LOOP_DETECTION_END();
11192 return change_done;
11195 static void PlayPlayerSound(struct PlayerInfo *player)
11197 int jx = player->jx, jy = player->jy;
11198 int sound_element = player->artwork_element;
11199 int last_action = player->last_action_waiting;
11200 int action = player->action_waiting;
11202 if (player->is_waiting)
11204 if (action != last_action)
11205 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11207 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11211 if (action != last_action)
11212 StopSound(element_info[sound_element].sound[last_action]);
11214 if (last_action == ACTION_SLEEPING)
11215 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11219 static void PlayAllPlayersSound(void)
11223 for (i = 0; i < MAX_PLAYERS; i++)
11224 if (stored_player[i].active)
11225 PlayPlayerSound(&stored_player[i]);
11228 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11230 boolean last_waiting = player->is_waiting;
11231 int move_dir = player->MovDir;
11233 player->dir_waiting = move_dir;
11234 player->last_action_waiting = player->action_waiting;
11238 if (!last_waiting) // not waiting -> waiting
11240 player->is_waiting = TRUE;
11242 player->frame_counter_bored =
11244 game.player_boring_delay_fixed +
11245 GetSimpleRandom(game.player_boring_delay_random);
11246 player->frame_counter_sleeping =
11248 game.player_sleeping_delay_fixed +
11249 GetSimpleRandom(game.player_sleeping_delay_random);
11251 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11254 if (game.player_sleeping_delay_fixed +
11255 game.player_sleeping_delay_random > 0 &&
11256 player->anim_delay_counter == 0 &&
11257 player->post_delay_counter == 0 &&
11258 FrameCounter >= player->frame_counter_sleeping)
11259 player->is_sleeping = TRUE;
11260 else if (game.player_boring_delay_fixed +
11261 game.player_boring_delay_random > 0 &&
11262 FrameCounter >= player->frame_counter_bored)
11263 player->is_bored = TRUE;
11265 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11266 player->is_bored ? ACTION_BORING :
11269 if (player->is_sleeping && player->use_murphy)
11271 // special case for sleeping Murphy when leaning against non-free tile
11273 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11274 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11275 !IS_MOVING(player->jx - 1, player->jy)))
11276 move_dir = MV_LEFT;
11277 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11278 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11279 !IS_MOVING(player->jx + 1, player->jy)))
11280 move_dir = MV_RIGHT;
11282 player->is_sleeping = FALSE;
11284 player->dir_waiting = move_dir;
11287 if (player->is_sleeping)
11289 if (player->num_special_action_sleeping > 0)
11291 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11293 int last_special_action = player->special_action_sleeping;
11294 int num_special_action = player->num_special_action_sleeping;
11295 int special_action =
11296 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11297 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11298 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11299 last_special_action + 1 : ACTION_SLEEPING);
11300 int special_graphic =
11301 el_act_dir2img(player->artwork_element, special_action, move_dir);
11303 player->anim_delay_counter =
11304 graphic_info[special_graphic].anim_delay_fixed +
11305 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11306 player->post_delay_counter =
11307 graphic_info[special_graphic].post_delay_fixed +
11308 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11310 player->special_action_sleeping = special_action;
11313 if (player->anim_delay_counter > 0)
11315 player->action_waiting = player->special_action_sleeping;
11316 player->anim_delay_counter--;
11318 else if (player->post_delay_counter > 0)
11320 player->post_delay_counter--;
11324 else if (player->is_bored)
11326 if (player->num_special_action_bored > 0)
11328 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11330 int special_action =
11331 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11332 int special_graphic =
11333 el_act_dir2img(player->artwork_element, special_action, move_dir);
11335 player->anim_delay_counter =
11336 graphic_info[special_graphic].anim_delay_fixed +
11337 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11338 player->post_delay_counter =
11339 graphic_info[special_graphic].post_delay_fixed +
11340 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11342 player->special_action_bored = special_action;
11345 if (player->anim_delay_counter > 0)
11347 player->action_waiting = player->special_action_bored;
11348 player->anim_delay_counter--;
11350 else if (player->post_delay_counter > 0)
11352 player->post_delay_counter--;
11357 else if (last_waiting) // waiting -> not waiting
11359 player->is_waiting = FALSE;
11360 player->is_bored = FALSE;
11361 player->is_sleeping = FALSE;
11363 player->frame_counter_bored = -1;
11364 player->frame_counter_sleeping = -1;
11366 player->anim_delay_counter = 0;
11367 player->post_delay_counter = 0;
11369 player->dir_waiting = player->MovDir;
11370 player->action_waiting = ACTION_DEFAULT;
11372 player->special_action_bored = ACTION_DEFAULT;
11373 player->special_action_sleeping = ACTION_DEFAULT;
11377 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11379 if ((!player->is_moving && player->was_moving) ||
11380 (player->MovPos == 0 && player->was_moving) ||
11381 (player->is_snapping && !player->was_snapping) ||
11382 (player->is_dropping && !player->was_dropping))
11384 if (!CheckSaveEngineSnapshotToList())
11387 player->was_moving = FALSE;
11388 player->was_snapping = TRUE;
11389 player->was_dropping = TRUE;
11393 if (player->is_moving)
11394 player->was_moving = TRUE;
11396 if (!player->is_snapping)
11397 player->was_snapping = FALSE;
11399 if (!player->is_dropping)
11400 player->was_dropping = FALSE;
11403 static struct MouseActionInfo mouse_action_last = { 0 };
11404 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11405 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11408 CheckSaveEngineSnapshotToList();
11410 mouse_action_last = mouse_action;
11413 static void CheckSingleStepMode(struct PlayerInfo *player)
11415 if (tape.single_step && tape.recording && !tape.pausing)
11417 // as it is called "single step mode", just return to pause mode when the
11418 // player stopped moving after one tile (or never starts moving at all)
11419 // (reverse logic needed here in case single step mode used in team mode)
11420 if (player->is_moving ||
11421 player->is_pushing ||
11422 player->is_dropping_pressed ||
11423 player->effective_mouse_action.button)
11424 game.enter_single_step_mode = FALSE;
11427 CheckSaveEngineSnapshot(player);
11430 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11432 int left = player_action & JOY_LEFT;
11433 int right = player_action & JOY_RIGHT;
11434 int up = player_action & JOY_UP;
11435 int down = player_action & JOY_DOWN;
11436 int button1 = player_action & JOY_BUTTON_1;
11437 int button2 = player_action & JOY_BUTTON_2;
11438 int dx = (left ? -1 : right ? 1 : 0);
11439 int dy = (up ? -1 : down ? 1 : 0);
11441 if (!player->active || tape.pausing)
11447 SnapField(player, dx, dy);
11451 DropElement(player);
11453 MovePlayer(player, dx, dy);
11456 CheckSingleStepMode(player);
11458 SetPlayerWaiting(player, FALSE);
11460 return player_action;
11464 // no actions for this player (no input at player's configured device)
11466 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11467 SnapField(player, 0, 0);
11468 CheckGravityMovementWhenNotMoving(player);
11470 if (player->MovPos == 0)
11471 SetPlayerWaiting(player, TRUE);
11473 if (player->MovPos == 0) // needed for tape.playing
11474 player->is_moving = FALSE;
11476 player->is_dropping = FALSE;
11477 player->is_dropping_pressed = FALSE;
11478 player->drop_pressed_delay = 0;
11480 CheckSingleStepMode(player);
11486 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11489 if (!tape.use_mouse_actions)
11492 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11493 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11494 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11497 static void SetTapeActionFromMouseAction(byte *tape_action,
11498 struct MouseActionInfo *mouse_action)
11500 if (!tape.use_mouse_actions)
11503 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11504 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11505 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11508 static void CheckLevelSolved(void)
11510 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11512 if (game_em.level_solved &&
11513 !game_em.game_over) // game won
11517 game_em.game_over = TRUE;
11519 game.all_players_gone = TRUE;
11522 if (game_em.game_over) // game lost
11523 game.all_players_gone = TRUE;
11525 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11527 if (game_sp.level_solved &&
11528 !game_sp.game_over) // game won
11532 game_sp.game_over = TRUE;
11534 game.all_players_gone = TRUE;
11537 if (game_sp.game_over) // game lost
11538 game.all_players_gone = TRUE;
11540 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11542 if (game_mm.level_solved &&
11543 !game_mm.game_over) // game won
11547 game_mm.game_over = TRUE;
11549 game.all_players_gone = TRUE;
11552 if (game_mm.game_over) // game lost
11553 game.all_players_gone = TRUE;
11557 static void CheckLevelTime(void)
11561 if (TimeFrames >= FRAMES_PER_SECOND)
11566 for (i = 0; i < MAX_PLAYERS; i++)
11568 struct PlayerInfo *player = &stored_player[i];
11570 if (SHIELD_ON(player))
11572 player->shield_normal_time_left--;
11574 if (player->shield_deadly_time_left > 0)
11575 player->shield_deadly_time_left--;
11579 if (!game.LevelSolved && !level.use_step_counter)
11587 if (TimeLeft <= 10 && setup.time_limit)
11588 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11590 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11591 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11593 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11595 if (!TimeLeft && setup.time_limit)
11597 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11598 game_em.lev->killed_out_of_time = TRUE;
11600 for (i = 0; i < MAX_PLAYERS; i++)
11601 KillPlayer(&stored_player[i]);
11604 else if (game.no_time_limit && !game.all_players_gone)
11606 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11609 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11612 if (tape.recording || tape.playing)
11613 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11616 if (tape.recording || tape.playing)
11617 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11619 UpdateAndDisplayGameControlValues();
11622 void AdvanceFrameAndPlayerCounters(int player_nr)
11626 // advance frame counters (global frame counter and time frame counter)
11630 // advance player counters (counters for move delay, move animation etc.)
11631 for (i = 0; i < MAX_PLAYERS; i++)
11633 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11634 int move_delay_value = stored_player[i].move_delay_value;
11635 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11637 if (!advance_player_counters) // not all players may be affected
11640 if (move_frames == 0) // less than one move per game frame
11642 int stepsize = TILEX / move_delay_value;
11643 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11644 int count = (stored_player[i].is_moving ?
11645 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11647 if (count % delay == 0)
11651 stored_player[i].Frame += move_frames;
11653 if (stored_player[i].MovPos != 0)
11654 stored_player[i].StepFrame += move_frames;
11656 if (stored_player[i].move_delay > 0)
11657 stored_player[i].move_delay--;
11659 // due to bugs in previous versions, counter must count up, not down
11660 if (stored_player[i].push_delay != -1)
11661 stored_player[i].push_delay++;
11663 if (stored_player[i].drop_delay > 0)
11664 stored_player[i].drop_delay--;
11666 if (stored_player[i].is_dropping_pressed)
11667 stored_player[i].drop_pressed_delay++;
11671 void StartGameActions(boolean init_network_game, boolean record_tape,
11674 unsigned int new_random_seed = InitRND(random_seed);
11677 TapeStartRecording(new_random_seed);
11679 if (init_network_game)
11681 SendToServer_LevelFile();
11682 SendToServer_StartPlaying();
11690 static void GameActionsExt(void)
11693 static unsigned int game_frame_delay = 0;
11695 unsigned int game_frame_delay_value;
11696 byte *recorded_player_action;
11697 byte summarized_player_action = 0;
11698 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11701 // detect endless loops, caused by custom element programming
11702 if (recursion_loop_detected && recursion_loop_depth == 0)
11704 char *message = getStringCat3("Internal Error! Element ",
11705 EL_NAME(recursion_loop_element),
11706 " caused endless loop! Quit the game?");
11708 Warn("element '%s' caused endless loop in game engine",
11709 EL_NAME(recursion_loop_element));
11711 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11713 recursion_loop_detected = FALSE; // if game should be continued
11720 if (game.restart_level)
11721 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11723 CheckLevelSolved();
11725 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11728 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11731 if (game_status != GAME_MODE_PLAYING) // status might have changed
11734 game_frame_delay_value =
11735 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11737 if (tape.playing && tape.warp_forward && !tape.pausing)
11738 game_frame_delay_value = 0;
11740 SetVideoFrameDelay(game_frame_delay_value);
11742 // (de)activate virtual buttons depending on current game status
11743 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11745 if (game.all_players_gone) // if no players there to be controlled anymore
11746 SetOverlayActive(FALSE);
11747 else if (!tape.playing) // if game continues after tape stopped playing
11748 SetOverlayActive(TRUE);
11753 // ---------- main game synchronization point ----------
11755 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11757 Debug("game:playing:skip", "skip == %d", skip);
11760 // ---------- main game synchronization point ----------
11762 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11766 if (network_playing && !network_player_action_received)
11768 // try to get network player actions in time
11770 // last chance to get network player actions without main loop delay
11771 HandleNetworking();
11773 // game was quit by network peer
11774 if (game_status != GAME_MODE_PLAYING)
11777 // check if network player actions still missing and game still running
11778 if (!network_player_action_received && !checkGameEnded())
11779 return; // failed to get network player actions in time
11781 // do not yet reset "network_player_action_received" (for tape.pausing)
11787 // at this point we know that we really continue executing the game
11789 network_player_action_received = FALSE;
11791 // when playing tape, read previously recorded player input from tape data
11792 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11794 local_player->effective_mouse_action = local_player->mouse_action;
11796 if (recorded_player_action != NULL)
11797 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11798 recorded_player_action);
11800 // TapePlayAction() may return NULL when toggling to "pause before death"
11804 if (tape.set_centered_player)
11806 game.centered_player_nr_next = tape.centered_player_nr_next;
11807 game.set_centered_player = TRUE;
11810 for (i = 0; i < MAX_PLAYERS; i++)
11812 summarized_player_action |= stored_player[i].action;
11814 if (!network_playing && (game.team_mode || tape.playing))
11815 stored_player[i].effective_action = stored_player[i].action;
11818 if (network_playing && !checkGameEnded())
11819 SendToServer_MovePlayer(summarized_player_action);
11821 // summarize all actions at local players mapped input device position
11822 // (this allows using different input devices in single player mode)
11823 if (!network.enabled && !game.team_mode)
11824 stored_player[map_player_action[local_player->index_nr]].effective_action =
11825 summarized_player_action;
11827 // summarize all actions at centered player in local team mode
11828 if (tape.recording &&
11829 setup.team_mode && !network.enabled &&
11830 setup.input_on_focus &&
11831 game.centered_player_nr != -1)
11833 for (i = 0; i < MAX_PLAYERS; i++)
11834 stored_player[map_player_action[i]].effective_action =
11835 (i == game.centered_player_nr ? summarized_player_action : 0);
11838 if (recorded_player_action != NULL)
11839 for (i = 0; i < MAX_PLAYERS; i++)
11840 stored_player[i].effective_action = recorded_player_action[i];
11842 for (i = 0; i < MAX_PLAYERS; i++)
11844 tape_action[i] = stored_player[i].effective_action;
11846 /* (this may happen in the RND game engine if a player was not present on
11847 the playfield on level start, but appeared later from a custom element */
11848 if (setup.team_mode &&
11851 !tape.player_participates[i])
11852 tape.player_participates[i] = TRUE;
11855 SetTapeActionFromMouseAction(tape_action,
11856 &local_player->effective_mouse_action);
11858 // only record actions from input devices, but not programmed actions
11859 if (tape.recording)
11860 TapeRecordAction(tape_action);
11862 // remember if game was played (especially after tape stopped playing)
11863 if (!tape.playing && summarized_player_action)
11864 game.GamePlayed = TRUE;
11866 #if USE_NEW_PLAYER_ASSIGNMENTS
11867 // !!! also map player actions in single player mode !!!
11868 // if (game.team_mode)
11871 byte mapped_action[MAX_PLAYERS];
11873 #if DEBUG_PLAYER_ACTIONS
11874 for (i = 0; i < MAX_PLAYERS; i++)
11875 DebugContinued("", "%d, ", stored_player[i].effective_action);
11878 for (i = 0; i < MAX_PLAYERS; i++)
11879 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11881 for (i = 0; i < MAX_PLAYERS; i++)
11882 stored_player[i].effective_action = mapped_action[i];
11884 #if DEBUG_PLAYER_ACTIONS
11885 DebugContinued("", "=> ");
11886 for (i = 0; i < MAX_PLAYERS; i++)
11887 DebugContinued("", "%d, ", stored_player[i].effective_action);
11888 DebugContinued("game:playing:player", "\n");
11891 #if DEBUG_PLAYER_ACTIONS
11894 for (i = 0; i < MAX_PLAYERS; i++)
11895 DebugContinued("", "%d, ", stored_player[i].effective_action);
11896 DebugContinued("game:playing:player", "\n");
11901 for (i = 0; i < MAX_PLAYERS; i++)
11903 // allow engine snapshot in case of changed movement attempt
11904 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11905 (stored_player[i].effective_action & KEY_MOTION))
11906 game.snapshot.changed_action = TRUE;
11908 // allow engine snapshot in case of snapping/dropping attempt
11909 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11910 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11911 game.snapshot.changed_action = TRUE;
11913 game.snapshot.last_action[i] = stored_player[i].effective_action;
11916 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11918 GameActions_EM_Main();
11920 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11922 GameActions_SP_Main();
11924 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11926 GameActions_MM_Main();
11930 GameActions_RND_Main();
11933 BlitScreenToBitmap(backbuffer);
11935 CheckLevelSolved();
11938 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11940 if (global.show_frames_per_second)
11942 static unsigned int fps_counter = 0;
11943 static int fps_frames = 0;
11944 unsigned int fps_delay_ms = Counter() - fps_counter;
11948 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11950 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11953 fps_counter = Counter();
11955 // always draw FPS to screen after FPS value was updated
11956 redraw_mask |= REDRAW_FPS;
11959 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11960 if (GetDrawDeactivationMask() == REDRAW_NONE)
11961 redraw_mask |= REDRAW_FPS;
11965 static void GameActions_CheckSaveEngineSnapshot(void)
11967 if (!game.snapshot.save_snapshot)
11970 // clear flag for saving snapshot _before_ saving snapshot
11971 game.snapshot.save_snapshot = FALSE;
11973 SaveEngineSnapshotToList();
11976 void GameActions(void)
11980 GameActions_CheckSaveEngineSnapshot();
11983 void GameActions_EM_Main(void)
11985 byte effective_action[MAX_PLAYERS];
11986 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11989 for (i = 0; i < MAX_PLAYERS; i++)
11990 effective_action[i] = stored_player[i].effective_action;
11992 GameActions_EM(effective_action, warp_mode);
11995 void GameActions_SP_Main(void)
11997 byte effective_action[MAX_PLAYERS];
11998 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12001 for (i = 0; i < MAX_PLAYERS; i++)
12002 effective_action[i] = stored_player[i].effective_action;
12004 GameActions_SP(effective_action, warp_mode);
12006 for (i = 0; i < MAX_PLAYERS; i++)
12008 if (stored_player[i].force_dropping)
12009 stored_player[i].action |= KEY_BUTTON_DROP;
12011 stored_player[i].force_dropping = FALSE;
12015 void GameActions_MM_Main(void)
12017 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12019 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12022 void GameActions_RND_Main(void)
12027 void GameActions_RND(void)
12029 static struct MouseActionInfo mouse_action_last = { 0 };
12030 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12031 int magic_wall_x = 0, magic_wall_y = 0;
12032 int i, x, y, element, graphic, last_gfx_frame;
12034 InitPlayfieldScanModeVars();
12036 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12038 SCAN_PLAYFIELD(x, y)
12040 ChangeCount[x][y] = 0;
12041 ChangeEvent[x][y] = -1;
12045 if (game.set_centered_player)
12047 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12049 // switching to "all players" only possible if all players fit to screen
12050 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12052 game.centered_player_nr_next = game.centered_player_nr;
12053 game.set_centered_player = FALSE;
12056 // do not switch focus to non-existing (or non-active) player
12057 if (game.centered_player_nr_next >= 0 &&
12058 !stored_player[game.centered_player_nr_next].active)
12060 game.centered_player_nr_next = game.centered_player_nr;
12061 game.set_centered_player = FALSE;
12065 if (game.set_centered_player &&
12066 ScreenMovPos == 0) // screen currently aligned at tile position
12070 if (game.centered_player_nr_next == -1)
12072 setScreenCenteredToAllPlayers(&sx, &sy);
12076 sx = stored_player[game.centered_player_nr_next].jx;
12077 sy = stored_player[game.centered_player_nr_next].jy;
12080 game.centered_player_nr = game.centered_player_nr_next;
12081 game.set_centered_player = FALSE;
12083 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12084 DrawGameDoorValues();
12087 // check single step mode (set flag and clear again if any player is active)
12088 game.enter_single_step_mode =
12089 (tape.single_step && tape.recording && !tape.pausing);
12091 for (i = 0; i < MAX_PLAYERS; i++)
12093 int actual_player_action = stored_player[i].effective_action;
12096 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12097 - rnd_equinox_tetrachloride 048
12098 - rnd_equinox_tetrachloride_ii 096
12099 - rnd_emanuel_schmieg 002
12100 - doctor_sloan_ww 001, 020
12102 if (stored_player[i].MovPos == 0)
12103 CheckGravityMovement(&stored_player[i]);
12106 // overwrite programmed action with tape action
12107 if (stored_player[i].programmed_action)
12108 actual_player_action = stored_player[i].programmed_action;
12110 PlayerActions(&stored_player[i], actual_player_action);
12112 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12115 // single step pause mode may already have been toggled by "ScrollPlayer()"
12116 if (game.enter_single_step_mode && !tape.pausing)
12117 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12119 ScrollScreen(NULL, SCROLL_GO_ON);
12121 /* for backwards compatibility, the following code emulates a fixed bug that
12122 occured when pushing elements (causing elements that just made their last
12123 pushing step to already (if possible) make their first falling step in the
12124 same game frame, which is bad); this code is also needed to use the famous
12125 "spring push bug" which is used in older levels and might be wanted to be
12126 used also in newer levels, but in this case the buggy pushing code is only
12127 affecting the "spring" element and no other elements */
12129 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12131 for (i = 0; i < MAX_PLAYERS; i++)
12133 struct PlayerInfo *player = &stored_player[i];
12134 int x = player->jx;
12135 int y = player->jy;
12137 if (player->active && player->is_pushing && player->is_moving &&
12139 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12140 Tile[x][y] == EL_SPRING))
12142 ContinueMoving(x, y);
12144 // continue moving after pushing (this is actually a bug)
12145 if (!IS_MOVING(x, y))
12146 Stop[x][y] = FALSE;
12151 SCAN_PLAYFIELD(x, y)
12153 Last[x][y] = Tile[x][y];
12155 ChangeCount[x][y] = 0;
12156 ChangeEvent[x][y] = -1;
12158 // this must be handled before main playfield loop
12159 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12162 if (MovDelay[x][y] <= 0)
12166 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12169 if (MovDelay[x][y] <= 0)
12171 int element = Store[x][y];
12172 int move_direction = MovDir[x][y];
12173 int player_index_bit = Store2[x][y];
12179 TEST_DrawLevelField(x, y);
12181 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12183 if (IS_ENVELOPE(element))
12184 local_player->show_envelope = element;
12189 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12191 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12193 Debug("game:playing:GameActions_RND", "This should never happen!");
12195 ChangePage[x][y] = -1;
12199 Stop[x][y] = FALSE;
12200 if (WasJustMoving[x][y] > 0)
12201 WasJustMoving[x][y]--;
12202 if (WasJustFalling[x][y] > 0)
12203 WasJustFalling[x][y]--;
12204 if (CheckCollision[x][y] > 0)
12205 CheckCollision[x][y]--;
12206 if (CheckImpact[x][y] > 0)
12207 CheckImpact[x][y]--;
12211 /* reset finished pushing action (not done in ContinueMoving() to allow
12212 continuous pushing animation for elements with zero push delay) */
12213 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12215 ResetGfxAnimation(x, y);
12216 TEST_DrawLevelField(x, y);
12220 if (IS_BLOCKED(x, y))
12224 Blocked2Moving(x, y, &oldx, &oldy);
12225 if (!IS_MOVING(oldx, oldy))
12227 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12228 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12229 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12230 Debug("game:playing:GameActions_RND", "This should never happen!");
12236 if (mouse_action.button)
12238 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12239 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12241 x = mouse_action.lx;
12242 y = mouse_action.ly;
12243 element = Tile[x][y];
12247 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12248 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12252 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12253 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12257 SCAN_PLAYFIELD(x, y)
12259 element = Tile[x][y];
12260 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12261 last_gfx_frame = GfxFrame[x][y];
12263 ResetGfxFrame(x, y);
12265 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12266 DrawLevelGraphicAnimation(x, y, graphic);
12268 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12269 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12270 ResetRandomAnimationValue(x, y);
12272 SetRandomAnimationValue(x, y);
12274 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12276 if (IS_INACTIVE(element))
12278 if (IS_ANIMATED(graphic))
12279 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12284 // this may take place after moving, so 'element' may have changed
12285 if (IS_CHANGING(x, y) &&
12286 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12288 int page = element_info[element].event_page_nr[CE_DELAY];
12290 HandleElementChange(x, y, page);
12292 element = Tile[x][y];
12293 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12296 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12300 element = Tile[x][y];
12301 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12303 if (IS_ANIMATED(graphic) &&
12304 !IS_MOVING(x, y) &&
12306 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12308 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12309 TEST_DrawTwinkleOnField(x, y);
12311 else if (element == EL_ACID)
12314 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12316 else if ((element == EL_EXIT_OPEN ||
12317 element == EL_EM_EXIT_OPEN ||
12318 element == EL_SP_EXIT_OPEN ||
12319 element == EL_STEEL_EXIT_OPEN ||
12320 element == EL_EM_STEEL_EXIT_OPEN ||
12321 element == EL_SP_TERMINAL ||
12322 element == EL_SP_TERMINAL_ACTIVE ||
12323 element == EL_EXTRA_TIME ||
12324 element == EL_SHIELD_NORMAL ||
12325 element == EL_SHIELD_DEADLY) &&
12326 IS_ANIMATED(graphic))
12327 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12328 else if (IS_MOVING(x, y))
12329 ContinueMoving(x, y);
12330 else if (IS_ACTIVE_BOMB(element))
12331 CheckDynamite(x, y);
12332 else if (element == EL_AMOEBA_GROWING)
12333 AmoebaGrowing(x, y);
12334 else if (element == EL_AMOEBA_SHRINKING)
12335 AmoebaShrinking(x, y);
12337 #if !USE_NEW_AMOEBA_CODE
12338 else if (IS_AMOEBALIVE(element))
12339 AmoebaReproduce(x, y);
12342 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12344 else if (element == EL_EXIT_CLOSED)
12346 else if (element == EL_EM_EXIT_CLOSED)
12348 else if (element == EL_STEEL_EXIT_CLOSED)
12349 CheckExitSteel(x, y);
12350 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12351 CheckExitSteelEM(x, y);
12352 else if (element == EL_SP_EXIT_CLOSED)
12354 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12355 element == EL_EXPANDABLE_STEELWALL_GROWING)
12356 MauerWaechst(x, y);
12357 else if (element == EL_EXPANDABLE_WALL ||
12358 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12359 element == EL_EXPANDABLE_WALL_VERTICAL ||
12360 element == EL_EXPANDABLE_WALL_ANY ||
12361 element == EL_BD_EXPANDABLE_WALL)
12362 MauerAbleger(x, y);
12363 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12364 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12365 element == EL_EXPANDABLE_STEELWALL_ANY)
12366 MauerAblegerStahl(x, y);
12367 else if (element == EL_FLAMES)
12368 CheckForDragon(x, y);
12369 else if (element == EL_EXPLOSION)
12370 ; // drawing of correct explosion animation is handled separately
12371 else if (element == EL_ELEMENT_SNAPPING ||
12372 element == EL_DIAGONAL_SHRINKING ||
12373 element == EL_DIAGONAL_GROWING)
12375 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12377 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12379 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12380 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12382 if (IS_BELT_ACTIVE(element))
12383 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12385 if (game.magic_wall_active)
12387 int jx = local_player->jx, jy = local_player->jy;
12389 // play the element sound at the position nearest to the player
12390 if ((element == EL_MAGIC_WALL_FULL ||
12391 element == EL_MAGIC_WALL_ACTIVE ||
12392 element == EL_MAGIC_WALL_EMPTYING ||
12393 element == EL_BD_MAGIC_WALL_FULL ||
12394 element == EL_BD_MAGIC_WALL_ACTIVE ||
12395 element == EL_BD_MAGIC_WALL_EMPTYING ||
12396 element == EL_DC_MAGIC_WALL_FULL ||
12397 element == EL_DC_MAGIC_WALL_ACTIVE ||
12398 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12399 ABS(x - jx) + ABS(y - jy) <
12400 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12408 #if USE_NEW_AMOEBA_CODE
12409 // new experimental amoeba growth stuff
12410 if (!(FrameCounter % 8))
12412 static unsigned int random = 1684108901;
12414 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12416 x = RND(lev_fieldx);
12417 y = RND(lev_fieldy);
12418 element = Tile[x][y];
12420 if (!IS_PLAYER(x,y) &&
12421 (element == EL_EMPTY ||
12422 CAN_GROW_INTO(element) ||
12423 element == EL_QUICKSAND_EMPTY ||
12424 element == EL_QUICKSAND_FAST_EMPTY ||
12425 element == EL_ACID_SPLASH_LEFT ||
12426 element == EL_ACID_SPLASH_RIGHT))
12428 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12429 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12430 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12431 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12432 Tile[x][y] = EL_AMOEBA_DROP;
12435 random = random * 129 + 1;
12440 game.explosions_delayed = FALSE;
12442 SCAN_PLAYFIELD(x, y)
12444 element = Tile[x][y];
12446 if (ExplodeField[x][y])
12447 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12448 else if (element == EL_EXPLOSION)
12449 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12451 ExplodeField[x][y] = EX_TYPE_NONE;
12454 game.explosions_delayed = TRUE;
12456 if (game.magic_wall_active)
12458 if (!(game.magic_wall_time_left % 4))
12460 int element = Tile[magic_wall_x][magic_wall_y];
12462 if (element == EL_BD_MAGIC_WALL_FULL ||
12463 element == EL_BD_MAGIC_WALL_ACTIVE ||
12464 element == EL_BD_MAGIC_WALL_EMPTYING)
12465 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12466 else if (element == EL_DC_MAGIC_WALL_FULL ||
12467 element == EL_DC_MAGIC_WALL_ACTIVE ||
12468 element == EL_DC_MAGIC_WALL_EMPTYING)
12469 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12471 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12474 if (game.magic_wall_time_left > 0)
12476 game.magic_wall_time_left--;
12478 if (!game.magic_wall_time_left)
12480 SCAN_PLAYFIELD(x, y)
12482 element = Tile[x][y];
12484 if (element == EL_MAGIC_WALL_ACTIVE ||
12485 element == EL_MAGIC_WALL_FULL)
12487 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12488 TEST_DrawLevelField(x, y);
12490 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12491 element == EL_BD_MAGIC_WALL_FULL)
12493 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12494 TEST_DrawLevelField(x, y);
12496 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12497 element == EL_DC_MAGIC_WALL_FULL)
12499 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12500 TEST_DrawLevelField(x, y);
12504 game.magic_wall_active = FALSE;
12509 if (game.light_time_left > 0)
12511 game.light_time_left--;
12513 if (game.light_time_left == 0)
12514 RedrawAllLightSwitchesAndInvisibleElements();
12517 if (game.timegate_time_left > 0)
12519 game.timegate_time_left--;
12521 if (game.timegate_time_left == 0)
12522 CloseAllOpenTimegates();
12525 if (game.lenses_time_left > 0)
12527 game.lenses_time_left--;
12529 if (game.lenses_time_left == 0)
12530 RedrawAllInvisibleElementsForLenses();
12533 if (game.magnify_time_left > 0)
12535 game.magnify_time_left--;
12537 if (game.magnify_time_left == 0)
12538 RedrawAllInvisibleElementsForMagnifier();
12541 for (i = 0; i < MAX_PLAYERS; i++)
12543 struct PlayerInfo *player = &stored_player[i];
12545 if (SHIELD_ON(player))
12547 if (player->shield_deadly_time_left)
12548 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12549 else if (player->shield_normal_time_left)
12550 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12554 #if USE_DELAYED_GFX_REDRAW
12555 SCAN_PLAYFIELD(x, y)
12557 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12559 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12560 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12562 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12563 DrawLevelField(x, y);
12565 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12566 DrawLevelFieldCrumbled(x, y);
12568 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12569 DrawLevelFieldCrumbledNeighbours(x, y);
12571 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12572 DrawTwinkleOnField(x, y);
12575 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12580 PlayAllPlayersSound();
12582 for (i = 0; i < MAX_PLAYERS; i++)
12584 struct PlayerInfo *player = &stored_player[i];
12586 if (player->show_envelope != 0 && (!player->active ||
12587 player->MovPos == 0))
12589 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12591 player->show_envelope = 0;
12595 // use random number generator in every frame to make it less predictable
12596 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12599 mouse_action_last = mouse_action;
12602 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12604 int min_x = x, min_y = y, max_x = x, max_y = y;
12605 int scr_fieldx = getScreenFieldSizeX();
12606 int scr_fieldy = getScreenFieldSizeY();
12609 for (i = 0; i < MAX_PLAYERS; i++)
12611 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12613 if (!stored_player[i].active || &stored_player[i] == player)
12616 min_x = MIN(min_x, jx);
12617 min_y = MIN(min_y, jy);
12618 max_x = MAX(max_x, jx);
12619 max_y = MAX(max_y, jy);
12622 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12625 static boolean AllPlayersInVisibleScreen(void)
12629 for (i = 0; i < MAX_PLAYERS; i++)
12631 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12633 if (!stored_player[i].active)
12636 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12643 void ScrollLevel(int dx, int dy)
12645 int scroll_offset = 2 * TILEX_VAR;
12648 BlitBitmap(drawto_field, drawto_field,
12649 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12650 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12651 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12652 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12653 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12654 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12658 x = (dx == 1 ? BX1 : BX2);
12659 for (y = BY1; y <= BY2; y++)
12660 DrawScreenField(x, y);
12665 y = (dy == 1 ? BY1 : BY2);
12666 for (x = BX1; x <= BX2; x++)
12667 DrawScreenField(x, y);
12670 redraw_mask |= REDRAW_FIELD;
12673 static boolean canFallDown(struct PlayerInfo *player)
12675 int jx = player->jx, jy = player->jy;
12677 return (IN_LEV_FIELD(jx, jy + 1) &&
12678 (IS_FREE(jx, jy + 1) ||
12679 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12680 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12681 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12684 static boolean canPassField(int x, int y, int move_dir)
12686 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12687 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12688 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12689 int nextx = x + dx;
12690 int nexty = y + dy;
12691 int element = Tile[x][y];
12693 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12694 !CAN_MOVE(element) &&
12695 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12696 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12697 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12700 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12702 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12703 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12704 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12708 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12709 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12710 (IS_DIGGABLE(Tile[newx][newy]) ||
12711 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12712 canPassField(newx, newy, move_dir)));
12715 static void CheckGravityMovement(struct PlayerInfo *player)
12717 if (player->gravity && !player->programmed_action)
12719 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12720 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12721 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12722 int jx = player->jx, jy = player->jy;
12723 boolean player_is_moving_to_valid_field =
12724 (!player_is_snapping &&
12725 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12726 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12727 boolean player_can_fall_down = canFallDown(player);
12729 if (player_can_fall_down &&
12730 !player_is_moving_to_valid_field)
12731 player->programmed_action = MV_DOWN;
12735 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12737 return CheckGravityMovement(player);
12739 if (player->gravity && !player->programmed_action)
12741 int jx = player->jx, jy = player->jy;
12742 boolean field_under_player_is_free =
12743 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12744 boolean player_is_standing_on_valid_field =
12745 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12746 (IS_WALKABLE(Tile[jx][jy]) &&
12747 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12749 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12750 player->programmed_action = MV_DOWN;
12755 MovePlayerOneStep()
12756 -----------------------------------------------------------------------------
12757 dx, dy: direction (non-diagonal) to try to move the player to
12758 real_dx, real_dy: direction as read from input device (can be diagonal)
12761 boolean MovePlayerOneStep(struct PlayerInfo *player,
12762 int dx, int dy, int real_dx, int real_dy)
12764 int jx = player->jx, jy = player->jy;
12765 int new_jx = jx + dx, new_jy = jy + dy;
12767 boolean player_can_move = !player->cannot_move;
12769 if (!player->active || (!dx && !dy))
12770 return MP_NO_ACTION;
12772 player->MovDir = (dx < 0 ? MV_LEFT :
12773 dx > 0 ? MV_RIGHT :
12775 dy > 0 ? MV_DOWN : MV_NONE);
12777 if (!IN_LEV_FIELD(new_jx, new_jy))
12778 return MP_NO_ACTION;
12780 if (!player_can_move)
12782 if (player->MovPos == 0)
12784 player->is_moving = FALSE;
12785 player->is_digging = FALSE;
12786 player->is_collecting = FALSE;
12787 player->is_snapping = FALSE;
12788 player->is_pushing = FALSE;
12792 if (!network.enabled && game.centered_player_nr == -1 &&
12793 !AllPlayersInSight(player, new_jx, new_jy))
12794 return MP_NO_ACTION;
12796 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12797 if (can_move != MP_MOVING)
12800 // check if DigField() has caused relocation of the player
12801 if (player->jx != jx || player->jy != jy)
12802 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12804 StorePlayer[jx][jy] = 0;
12805 player->last_jx = jx;
12806 player->last_jy = jy;
12807 player->jx = new_jx;
12808 player->jy = new_jy;
12809 StorePlayer[new_jx][new_jy] = player->element_nr;
12811 if (player->move_delay_value_next != -1)
12813 player->move_delay_value = player->move_delay_value_next;
12814 player->move_delay_value_next = -1;
12818 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12820 player->step_counter++;
12822 PlayerVisit[jx][jy] = FrameCounter;
12824 player->is_moving = TRUE;
12827 // should better be called in MovePlayer(), but this breaks some tapes
12828 ScrollPlayer(player, SCROLL_INIT);
12834 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12836 int jx = player->jx, jy = player->jy;
12837 int old_jx = jx, old_jy = jy;
12838 int moved = MP_NO_ACTION;
12840 if (!player->active)
12845 if (player->MovPos == 0)
12847 player->is_moving = FALSE;
12848 player->is_digging = FALSE;
12849 player->is_collecting = FALSE;
12850 player->is_snapping = FALSE;
12851 player->is_pushing = FALSE;
12857 if (player->move_delay > 0)
12860 player->move_delay = -1; // set to "uninitialized" value
12862 // store if player is automatically moved to next field
12863 player->is_auto_moving = (player->programmed_action != MV_NONE);
12865 // remove the last programmed player action
12866 player->programmed_action = 0;
12868 if (player->MovPos)
12870 // should only happen if pre-1.2 tape recordings are played
12871 // this is only for backward compatibility
12873 int original_move_delay_value = player->move_delay_value;
12876 Debug("game:playing:MovePlayer",
12877 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12881 // scroll remaining steps with finest movement resolution
12882 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12884 while (player->MovPos)
12886 ScrollPlayer(player, SCROLL_GO_ON);
12887 ScrollScreen(NULL, SCROLL_GO_ON);
12889 AdvanceFrameAndPlayerCounters(player->index_nr);
12892 BackToFront_WithFrameDelay(0);
12895 player->move_delay_value = original_move_delay_value;
12898 player->is_active = FALSE;
12900 if (player->last_move_dir & MV_HORIZONTAL)
12902 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12903 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12907 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12908 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12911 if (!moved && !player->is_active)
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;
12923 if (moved & MP_MOVING && !ScreenMovPos &&
12924 (player->index_nr == game.centered_player_nr ||
12925 game.centered_player_nr == -1))
12927 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12929 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12931 // actual player has left the screen -- scroll in that direction
12932 if (jx != old_jx) // player has moved horizontally
12933 scroll_x += (jx - old_jx);
12934 else // player has moved vertically
12935 scroll_y += (jy - old_jy);
12939 int offset_raw = game.scroll_delay_value;
12941 if (jx != old_jx) // player has moved horizontally
12943 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12944 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12945 int new_scroll_x = jx - MIDPOSX + offset_x;
12947 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12948 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12949 scroll_x = new_scroll_x;
12951 // don't scroll over playfield boundaries
12952 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12954 // don't scroll more than one field at a time
12955 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12957 // don't scroll against the player's moving direction
12958 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12959 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12960 scroll_x = old_scroll_x;
12962 else // player has moved vertically
12964 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12965 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12966 int new_scroll_y = jy - MIDPOSY + offset_y;
12968 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12969 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12970 scroll_y = new_scroll_y;
12972 // don't scroll over playfield boundaries
12973 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12975 // don't scroll more than one field at a time
12976 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12978 // don't scroll against the player's moving direction
12979 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12980 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12981 scroll_y = old_scroll_y;
12985 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12987 if (!network.enabled && game.centered_player_nr == -1 &&
12988 !AllPlayersInVisibleScreen())
12990 scroll_x = old_scroll_x;
12991 scroll_y = old_scroll_y;
12995 ScrollScreen(player, SCROLL_INIT);
12996 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13001 player->StepFrame = 0;
13003 if (moved & MP_MOVING)
13005 if (old_jx != jx && old_jy == jy)
13006 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13007 else if (old_jx == jx && old_jy != jy)
13008 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13010 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13012 player->last_move_dir = player->MovDir;
13013 player->is_moving = TRUE;
13014 player->is_snapping = FALSE;
13015 player->is_switching = FALSE;
13016 player->is_dropping = FALSE;
13017 player->is_dropping_pressed = FALSE;
13018 player->drop_pressed_delay = 0;
13021 // should better be called here than above, but this breaks some tapes
13022 ScrollPlayer(player, SCROLL_INIT);
13027 CheckGravityMovementWhenNotMoving(player);
13029 player->is_moving = FALSE;
13031 /* at this point, the player is allowed to move, but cannot move right now
13032 (e.g. because of something blocking the way) -- ensure that the player
13033 is also allowed to move in the next frame (in old versions before 3.1.1,
13034 the player was forced to wait again for eight frames before next try) */
13036 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13037 player->move_delay = 0; // allow direct movement in the next frame
13040 if (player->move_delay == -1) // not yet initialized by DigField()
13041 player->move_delay = player->move_delay_value;
13043 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13045 TestIfPlayerTouchesBadThing(jx, jy);
13046 TestIfPlayerTouchesCustomElement(jx, jy);
13049 if (!player->active)
13050 RemovePlayer(player);
13055 void ScrollPlayer(struct PlayerInfo *player, int mode)
13057 int jx = player->jx, jy = player->jy;
13058 int last_jx = player->last_jx, last_jy = player->last_jy;
13059 int move_stepsize = TILEX / player->move_delay_value;
13061 if (!player->active)
13064 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13067 if (mode == SCROLL_INIT)
13069 player->actual_frame_counter = FrameCounter;
13070 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13072 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13073 Tile[last_jx][last_jy] == EL_EMPTY)
13075 int last_field_block_delay = 0; // start with no blocking at all
13076 int block_delay_adjustment = player->block_delay_adjustment;
13078 // if player blocks last field, add delay for exactly one move
13079 if (player->block_last_field)
13081 last_field_block_delay += player->move_delay_value;
13083 // when blocking enabled, prevent moving up despite gravity
13084 if (player->gravity && player->MovDir == MV_UP)
13085 block_delay_adjustment = -1;
13088 // add block delay adjustment (also possible when not blocking)
13089 last_field_block_delay += block_delay_adjustment;
13091 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13092 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13095 if (player->MovPos != 0) // player has not yet reached destination
13098 else if (!FrameReached(&player->actual_frame_counter, 1))
13101 if (player->MovPos != 0)
13103 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13104 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13106 // before DrawPlayer() to draw correct player graphic for this case
13107 if (player->MovPos == 0)
13108 CheckGravityMovement(player);
13111 if (player->MovPos == 0) // player reached destination field
13113 if (player->move_delay_reset_counter > 0)
13115 player->move_delay_reset_counter--;
13117 if (player->move_delay_reset_counter == 0)
13119 // continue with normal speed after quickly moving through gate
13120 HALVE_PLAYER_SPEED(player);
13122 // be able to make the next move without delay
13123 player->move_delay = 0;
13127 player->last_jx = jx;
13128 player->last_jy = jy;
13130 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13131 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13132 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13133 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13134 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13135 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13136 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13137 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13139 ExitPlayer(player);
13141 if (game.players_still_needed == 0 &&
13142 (game.friends_still_needed == 0 ||
13143 IS_SP_ELEMENT(Tile[jx][jy])))
13147 // this breaks one level: "machine", level 000
13149 int move_direction = player->MovDir;
13150 int enter_side = MV_DIR_OPPOSITE(move_direction);
13151 int leave_side = move_direction;
13152 int old_jx = last_jx;
13153 int old_jy = last_jy;
13154 int old_element = Tile[old_jx][old_jy];
13155 int new_element = Tile[jx][jy];
13157 if (IS_CUSTOM_ELEMENT(old_element))
13158 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13160 player->index_bit, leave_side);
13162 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13163 CE_PLAYER_LEAVES_X,
13164 player->index_bit, leave_side);
13166 if (IS_CUSTOM_ELEMENT(new_element))
13167 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13168 player->index_bit, enter_side);
13170 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13171 CE_PLAYER_ENTERS_X,
13172 player->index_bit, enter_side);
13174 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13175 CE_MOVE_OF_X, move_direction);
13178 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13180 TestIfPlayerTouchesBadThing(jx, jy);
13181 TestIfPlayerTouchesCustomElement(jx, jy);
13183 /* needed because pushed element has not yet reached its destination,
13184 so it would trigger a change event at its previous field location */
13185 if (!player->is_pushing)
13186 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13188 if (level.finish_dig_collect &&
13189 (player->is_digging || player->is_collecting))
13191 int last_element = player->last_removed_element;
13192 int move_direction = player->MovDir;
13193 int enter_side = MV_DIR_OPPOSITE(move_direction);
13194 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13195 CE_PLAYER_COLLECTS_X);
13197 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13198 player->index_bit, enter_side);
13200 player->last_removed_element = EL_UNDEFINED;
13203 if (!player->active)
13204 RemovePlayer(player);
13207 if (level.use_step_counter)
13217 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13218 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13220 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13222 DisplayGameControlValues();
13224 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13225 for (i = 0; i < MAX_PLAYERS; i++)
13226 KillPlayer(&stored_player[i]);
13228 else if (game.no_time_limit && !game.all_players_gone)
13230 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13232 DisplayGameControlValues();
13236 if (tape.single_step && tape.recording && !tape.pausing &&
13237 !player->programmed_action)
13238 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13240 if (!player->programmed_action)
13241 CheckSaveEngineSnapshot(player);
13245 void ScrollScreen(struct PlayerInfo *player, int mode)
13247 static unsigned int screen_frame_counter = 0;
13249 if (mode == SCROLL_INIT)
13251 // set scrolling step size according to actual player's moving speed
13252 ScrollStepSize = TILEX / player->move_delay_value;
13254 screen_frame_counter = FrameCounter;
13255 ScreenMovDir = player->MovDir;
13256 ScreenMovPos = player->MovPos;
13257 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13260 else if (!FrameReached(&screen_frame_counter, 1))
13265 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13266 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13267 redraw_mask |= REDRAW_FIELD;
13270 ScreenMovDir = MV_NONE;
13273 void TestIfPlayerTouchesCustomElement(int x, int y)
13275 static int xy[4][2] =
13282 static int trigger_sides[4][2] =
13284 // center side border side
13285 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13286 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13287 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13288 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13290 static int touch_dir[4] =
13292 MV_LEFT | MV_RIGHT,
13297 int center_element = Tile[x][y]; // should always be non-moving!
13300 for (i = 0; i < NUM_DIRECTIONS; i++)
13302 int xx = x + xy[i][0];
13303 int yy = y + xy[i][1];
13304 int center_side = trigger_sides[i][0];
13305 int border_side = trigger_sides[i][1];
13306 int border_element;
13308 if (!IN_LEV_FIELD(xx, yy))
13311 if (IS_PLAYER(x, y)) // player found at center element
13313 struct PlayerInfo *player = PLAYERINFO(x, y);
13315 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13316 border_element = Tile[xx][yy]; // may be moving!
13317 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13318 border_element = Tile[xx][yy];
13319 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13320 border_element = MovingOrBlocked2Element(xx, yy);
13322 continue; // center and border element do not touch
13324 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13325 player->index_bit, border_side);
13326 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13327 CE_PLAYER_TOUCHES_X,
13328 player->index_bit, border_side);
13331 /* use player element that is initially defined in the level playfield,
13332 not the player element that corresponds to the runtime player number
13333 (example: a level that contains EL_PLAYER_3 as the only player would
13334 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13335 int player_element = PLAYERINFO(x, y)->initial_element;
13337 CheckElementChangeBySide(xx, yy, border_element, player_element,
13338 CE_TOUCHING_X, border_side);
13341 else if (IS_PLAYER(xx, yy)) // player found at border element
13343 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13345 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13347 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13348 continue; // center and border element do not touch
13351 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13352 player->index_bit, center_side);
13353 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13354 CE_PLAYER_TOUCHES_X,
13355 player->index_bit, center_side);
13358 /* use player element that is initially defined in the level playfield,
13359 not the player element that corresponds to the runtime player number
13360 (example: a level that contains EL_PLAYER_3 as the only player would
13361 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13362 int player_element = PLAYERINFO(xx, yy)->initial_element;
13364 CheckElementChangeBySide(x, y, center_element, player_element,
13365 CE_TOUCHING_X, center_side);
13373 void TestIfElementTouchesCustomElement(int x, int y)
13375 static int xy[4][2] =
13382 static int trigger_sides[4][2] =
13384 // center side border side
13385 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13386 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13387 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13388 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13390 static int touch_dir[4] =
13392 MV_LEFT | MV_RIGHT,
13397 boolean change_center_element = FALSE;
13398 int center_element = Tile[x][y]; // should always be non-moving!
13399 int border_element_old[NUM_DIRECTIONS];
13402 for (i = 0; i < NUM_DIRECTIONS; i++)
13404 int xx = x + xy[i][0];
13405 int yy = y + xy[i][1];
13406 int border_element;
13408 border_element_old[i] = -1;
13410 if (!IN_LEV_FIELD(xx, yy))
13413 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13414 border_element = Tile[xx][yy]; // may be moving!
13415 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13416 border_element = Tile[xx][yy];
13417 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13418 border_element = MovingOrBlocked2Element(xx, yy);
13420 continue; // center and border element do not touch
13422 border_element_old[i] = border_element;
13425 for (i = 0; i < NUM_DIRECTIONS; i++)
13427 int xx = x + xy[i][0];
13428 int yy = y + xy[i][1];
13429 int center_side = trigger_sides[i][0];
13430 int border_element = border_element_old[i];
13432 if (border_element == -1)
13435 // check for change of border element
13436 CheckElementChangeBySide(xx, yy, border_element, center_element,
13437 CE_TOUCHING_X, center_side);
13439 // (center element cannot be player, so we dont have to check this here)
13442 for (i = 0; i < NUM_DIRECTIONS; i++)
13444 int xx = x + xy[i][0];
13445 int yy = y + xy[i][1];
13446 int border_side = trigger_sides[i][1];
13447 int border_element = border_element_old[i];
13449 if (border_element == -1)
13452 // check for change of center element (but change it only once)
13453 if (!change_center_element)
13454 change_center_element =
13455 CheckElementChangeBySide(x, y, center_element, border_element,
13456 CE_TOUCHING_X, border_side);
13458 if (IS_PLAYER(xx, yy))
13460 /* use player element that is initially defined in the level playfield,
13461 not the player element that corresponds to the runtime player number
13462 (example: a level that contains EL_PLAYER_3 as the only player would
13463 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13464 int player_element = PLAYERINFO(xx, yy)->initial_element;
13466 CheckElementChangeBySide(x, y, center_element, player_element,
13467 CE_TOUCHING_X, border_side);
13472 void TestIfElementHitsCustomElement(int x, int y, int direction)
13474 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13475 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13476 int hitx = x + dx, hity = y + dy;
13477 int hitting_element = Tile[x][y];
13478 int touched_element;
13480 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13483 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13484 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13486 if (IN_LEV_FIELD(hitx, hity))
13488 int opposite_direction = MV_DIR_OPPOSITE(direction);
13489 int hitting_side = direction;
13490 int touched_side = opposite_direction;
13491 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13492 MovDir[hitx][hity] != direction ||
13493 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13499 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13500 CE_HITTING_X, touched_side);
13502 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13503 CE_HIT_BY_X, hitting_side);
13505 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13506 CE_HIT_BY_SOMETHING, opposite_direction);
13508 if (IS_PLAYER(hitx, hity))
13510 /* use player element that is initially defined in the level playfield,
13511 not the player element that corresponds to the runtime player number
13512 (example: a level that contains EL_PLAYER_3 as the only player would
13513 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13514 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13516 CheckElementChangeBySide(x, y, hitting_element, player_element,
13517 CE_HITTING_X, touched_side);
13522 // "hitting something" is also true when hitting the playfield border
13523 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13524 CE_HITTING_SOMETHING, direction);
13527 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13529 int i, kill_x = -1, kill_y = -1;
13531 int bad_element = -1;
13532 static int test_xy[4][2] =
13539 static int test_dir[4] =
13547 for (i = 0; i < NUM_DIRECTIONS; i++)
13549 int test_x, test_y, test_move_dir, test_element;
13551 test_x = good_x + test_xy[i][0];
13552 test_y = good_y + test_xy[i][1];
13554 if (!IN_LEV_FIELD(test_x, test_y))
13558 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13560 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13562 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13563 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13565 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13566 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13570 bad_element = test_element;
13576 if (kill_x != -1 || kill_y != -1)
13578 if (IS_PLAYER(good_x, good_y))
13580 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13582 if (player->shield_deadly_time_left > 0 &&
13583 !IS_INDESTRUCTIBLE(bad_element))
13584 Bang(kill_x, kill_y);
13585 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13586 KillPlayer(player);
13589 Bang(good_x, good_y);
13593 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13595 int i, kill_x = -1, kill_y = -1;
13596 int bad_element = Tile[bad_x][bad_y];
13597 static int test_xy[4][2] =
13604 static int touch_dir[4] =
13606 MV_LEFT | MV_RIGHT,
13611 static int test_dir[4] =
13619 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13622 for (i = 0; i < NUM_DIRECTIONS; i++)
13624 int test_x, test_y, test_move_dir, test_element;
13626 test_x = bad_x + test_xy[i][0];
13627 test_y = bad_y + test_xy[i][1];
13629 if (!IN_LEV_FIELD(test_x, test_y))
13633 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13635 test_element = Tile[test_x][test_y];
13637 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13638 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13640 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13641 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13643 // good thing is player or penguin that does not move away
13644 if (IS_PLAYER(test_x, test_y))
13646 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13648 if (bad_element == EL_ROBOT && player->is_moving)
13649 continue; // robot does not kill player if he is moving
13651 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13653 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13654 continue; // center and border element do not touch
13662 else if (test_element == EL_PENGUIN)
13672 if (kill_x != -1 || kill_y != -1)
13674 if (IS_PLAYER(kill_x, kill_y))
13676 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13678 if (player->shield_deadly_time_left > 0 &&
13679 !IS_INDESTRUCTIBLE(bad_element))
13680 Bang(bad_x, bad_y);
13681 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13682 KillPlayer(player);
13685 Bang(kill_x, kill_y);
13689 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13691 int bad_element = Tile[bad_x][bad_y];
13692 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13693 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13694 int test_x = bad_x + dx, test_y = bad_y + dy;
13695 int test_move_dir, test_element;
13696 int kill_x = -1, kill_y = -1;
13698 if (!IN_LEV_FIELD(test_x, test_y))
13702 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13704 test_element = Tile[test_x][test_y];
13706 if (test_move_dir != bad_move_dir)
13708 // good thing can be player or penguin that does not move away
13709 if (IS_PLAYER(test_x, test_y))
13711 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13713 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13714 player as being hit when he is moving towards the bad thing, because
13715 the "get hit by" condition would be lost after the player stops) */
13716 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13717 return; // player moves away from bad thing
13722 else if (test_element == EL_PENGUIN)
13729 if (kill_x != -1 || kill_y != -1)
13731 if (IS_PLAYER(kill_x, kill_y))
13733 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13735 if (player->shield_deadly_time_left > 0 &&
13736 !IS_INDESTRUCTIBLE(bad_element))
13737 Bang(bad_x, bad_y);
13738 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13739 KillPlayer(player);
13742 Bang(kill_x, kill_y);
13746 void TestIfPlayerTouchesBadThing(int x, int y)
13748 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13751 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13753 TestIfGoodThingHitsBadThing(x, y, move_dir);
13756 void TestIfBadThingTouchesPlayer(int x, int y)
13758 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13761 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13763 TestIfBadThingHitsGoodThing(x, y, move_dir);
13766 void TestIfFriendTouchesBadThing(int x, int y)
13768 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13771 void TestIfBadThingTouchesFriend(int x, int y)
13773 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13776 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13778 int i, kill_x = bad_x, kill_y = bad_y;
13779 static int xy[4][2] =
13787 for (i = 0; i < NUM_DIRECTIONS; i++)
13791 x = bad_x + xy[i][0];
13792 y = bad_y + xy[i][1];
13793 if (!IN_LEV_FIELD(x, y))
13796 element = Tile[x][y];
13797 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13798 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13806 if (kill_x != bad_x || kill_y != bad_y)
13807 Bang(bad_x, bad_y);
13810 void KillPlayer(struct PlayerInfo *player)
13812 int jx = player->jx, jy = player->jy;
13814 if (!player->active)
13818 Debug("game:playing:KillPlayer",
13819 "0: killed == %d, active == %d, reanimated == %d",
13820 player->killed, player->active, player->reanimated);
13823 /* the following code was introduced to prevent an infinite loop when calling
13825 -> CheckTriggeredElementChangeExt()
13826 -> ExecuteCustomElementAction()
13828 -> (infinitely repeating the above sequence of function calls)
13829 which occurs when killing the player while having a CE with the setting
13830 "kill player X when explosion of <player X>"; the solution using a new
13831 field "player->killed" was chosen for backwards compatibility, although
13832 clever use of the fields "player->active" etc. would probably also work */
13834 if (player->killed)
13838 player->killed = TRUE;
13840 // remove accessible field at the player's position
13841 Tile[jx][jy] = EL_EMPTY;
13843 // deactivate shield (else Bang()/Explode() would not work right)
13844 player->shield_normal_time_left = 0;
13845 player->shield_deadly_time_left = 0;
13848 Debug("game:playing:KillPlayer",
13849 "1: killed == %d, active == %d, reanimated == %d",
13850 player->killed, player->active, player->reanimated);
13856 Debug("game:playing:KillPlayer",
13857 "2: killed == %d, active == %d, reanimated == %d",
13858 player->killed, player->active, player->reanimated);
13861 if (player->reanimated) // killed player may have been reanimated
13862 player->killed = player->reanimated = FALSE;
13864 BuryPlayer(player);
13867 static void KillPlayerUnlessEnemyProtected(int x, int y)
13869 if (!PLAYER_ENEMY_PROTECTED(x, y))
13870 KillPlayer(PLAYERINFO(x, y));
13873 static void KillPlayerUnlessExplosionProtected(int x, int y)
13875 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13876 KillPlayer(PLAYERINFO(x, y));
13879 void BuryPlayer(struct PlayerInfo *player)
13881 int jx = player->jx, jy = player->jy;
13883 if (!player->active)
13886 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13887 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13889 RemovePlayer(player);
13891 player->buried = TRUE;
13893 if (game.all_players_gone)
13894 game.GameOver = TRUE;
13897 void RemovePlayer(struct PlayerInfo *player)
13899 int jx = player->jx, jy = player->jy;
13900 int i, found = FALSE;
13902 player->present = FALSE;
13903 player->active = FALSE;
13905 // required for some CE actions (even if the player is not active anymore)
13906 player->MovPos = 0;
13908 if (!ExplodeField[jx][jy])
13909 StorePlayer[jx][jy] = 0;
13911 if (player->is_moving)
13912 TEST_DrawLevelField(player->last_jx, player->last_jy);
13914 for (i = 0; i < MAX_PLAYERS; i++)
13915 if (stored_player[i].active)
13920 game.all_players_gone = TRUE;
13921 game.GameOver = TRUE;
13924 game.exit_x = game.robot_wheel_x = jx;
13925 game.exit_y = game.robot_wheel_y = jy;
13928 void ExitPlayer(struct PlayerInfo *player)
13930 DrawPlayer(player); // needed here only to cleanup last field
13931 RemovePlayer(player);
13933 if (game.players_still_needed > 0)
13934 game.players_still_needed--;
13937 static void SetFieldForSnapping(int x, int y, int element, int direction,
13938 int player_index_bit)
13940 struct ElementInfo *ei = &element_info[element];
13941 int direction_bit = MV_DIR_TO_BIT(direction);
13942 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13943 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13944 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13946 Tile[x][y] = EL_ELEMENT_SNAPPING;
13947 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13948 MovDir[x][y] = direction;
13949 Store[x][y] = element;
13950 Store2[x][y] = player_index_bit;
13952 ResetGfxAnimation(x, y);
13954 GfxElement[x][y] = element;
13955 GfxAction[x][y] = action;
13956 GfxDir[x][y] = direction;
13957 GfxFrame[x][y] = -1;
13960 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13961 int player_index_bit)
13963 TestIfElementTouchesCustomElement(x, y); // for empty space
13965 if (level.finish_dig_collect)
13967 int dig_side = MV_DIR_OPPOSITE(direction);
13969 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13970 player_index_bit, dig_side);
13975 =============================================================================
13976 checkDiagonalPushing()
13977 -----------------------------------------------------------------------------
13978 check if diagonal input device direction results in pushing of object
13979 (by checking if the alternative direction is walkable, diggable, ...)
13980 =============================================================================
13983 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13984 int x, int y, int real_dx, int real_dy)
13986 int jx, jy, dx, dy, xx, yy;
13988 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13991 // diagonal direction: check alternative direction
13996 xx = jx + (dx == 0 ? real_dx : 0);
13997 yy = jy + (dy == 0 ? real_dy : 0);
13999 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14003 =============================================================================
14005 -----------------------------------------------------------------------------
14006 x, y: field next to player (non-diagonal) to try to dig to
14007 real_dx, real_dy: direction as read from input device (can be diagonal)
14008 =============================================================================
14011 static int DigField(struct PlayerInfo *player,
14012 int oldx, int oldy, int x, int y,
14013 int real_dx, int real_dy, int mode)
14015 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14016 boolean player_was_pushing = player->is_pushing;
14017 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14018 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14019 int jx = oldx, jy = oldy;
14020 int dx = x - jx, dy = y - jy;
14021 int nextx = x + dx, nexty = y + dy;
14022 int move_direction = (dx == -1 ? MV_LEFT :
14023 dx == +1 ? MV_RIGHT :
14025 dy == +1 ? MV_DOWN : MV_NONE);
14026 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14027 int dig_side = MV_DIR_OPPOSITE(move_direction);
14028 int old_element = Tile[jx][jy];
14029 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14032 if (is_player) // function can also be called by EL_PENGUIN
14034 if (player->MovPos == 0)
14036 player->is_digging = FALSE;
14037 player->is_collecting = FALSE;
14040 if (player->MovPos == 0) // last pushing move finished
14041 player->is_pushing = FALSE;
14043 if (mode == DF_NO_PUSH) // player just stopped pushing
14045 player->is_switching = FALSE;
14046 player->push_delay = -1;
14048 return MP_NO_ACTION;
14052 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14053 old_element = Back[jx][jy];
14055 // in case of element dropped at player position, check background
14056 else if (Back[jx][jy] != EL_EMPTY &&
14057 game.engine_version >= VERSION_IDENT(2,2,0,0))
14058 old_element = Back[jx][jy];
14060 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14061 return MP_NO_ACTION; // field has no opening in this direction
14063 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14064 return MP_NO_ACTION; // field has no opening in this direction
14066 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14070 Tile[jx][jy] = player->artwork_element;
14071 InitMovingField(jx, jy, MV_DOWN);
14072 Store[jx][jy] = EL_ACID;
14073 ContinueMoving(jx, jy);
14074 BuryPlayer(player);
14076 return MP_DONT_RUN_INTO;
14079 if (player_can_move && DONT_RUN_INTO(element))
14081 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14083 return MP_DONT_RUN_INTO;
14086 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14087 return MP_NO_ACTION;
14089 collect_count = element_info[element].collect_count_initial;
14091 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14092 return MP_NO_ACTION;
14094 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14095 player_can_move = player_can_move_or_snap;
14097 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14098 game.engine_version >= VERSION_IDENT(2,2,0,0))
14100 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14101 player->index_bit, dig_side);
14102 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14103 player->index_bit, dig_side);
14105 if (element == EL_DC_LANDMINE)
14108 if (Tile[x][y] != element) // field changed by snapping
14111 return MP_NO_ACTION;
14114 if (player->gravity && is_player && !player->is_auto_moving &&
14115 canFallDown(player) && move_direction != MV_DOWN &&
14116 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14117 return MP_NO_ACTION; // player cannot walk here due to gravity
14119 if (player_can_move &&
14120 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14122 int sound_element = SND_ELEMENT(element);
14123 int sound_action = ACTION_WALKING;
14125 if (IS_RND_GATE(element))
14127 if (!player->key[RND_GATE_NR(element)])
14128 return MP_NO_ACTION;
14130 else if (IS_RND_GATE_GRAY(element))
14132 if (!player->key[RND_GATE_GRAY_NR(element)])
14133 return MP_NO_ACTION;
14135 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14137 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14138 return MP_NO_ACTION;
14140 else if (element == EL_EXIT_OPEN ||
14141 element == EL_EM_EXIT_OPEN ||
14142 element == EL_EM_EXIT_OPENING ||
14143 element == EL_STEEL_EXIT_OPEN ||
14144 element == EL_EM_STEEL_EXIT_OPEN ||
14145 element == EL_EM_STEEL_EXIT_OPENING ||
14146 element == EL_SP_EXIT_OPEN ||
14147 element == EL_SP_EXIT_OPENING)
14149 sound_action = ACTION_PASSING; // player is passing exit
14151 else if (element == EL_EMPTY)
14153 sound_action = ACTION_MOVING; // nothing to walk on
14156 // play sound from background or player, whatever is available
14157 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14158 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14160 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14162 else if (player_can_move &&
14163 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14165 if (!ACCESS_FROM(element, opposite_direction))
14166 return MP_NO_ACTION; // field not accessible from this direction
14168 if (CAN_MOVE(element)) // only fixed elements can be passed!
14169 return MP_NO_ACTION;
14171 if (IS_EM_GATE(element))
14173 if (!player->key[EM_GATE_NR(element)])
14174 return MP_NO_ACTION;
14176 else if (IS_EM_GATE_GRAY(element))
14178 if (!player->key[EM_GATE_GRAY_NR(element)])
14179 return MP_NO_ACTION;
14181 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14183 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14184 return MP_NO_ACTION;
14186 else if (IS_EMC_GATE(element))
14188 if (!player->key[EMC_GATE_NR(element)])
14189 return MP_NO_ACTION;
14191 else if (IS_EMC_GATE_GRAY(element))
14193 if (!player->key[EMC_GATE_GRAY_NR(element)])
14194 return MP_NO_ACTION;
14196 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14198 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14199 return MP_NO_ACTION;
14201 else if (element == EL_DC_GATE_WHITE ||
14202 element == EL_DC_GATE_WHITE_GRAY ||
14203 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14205 if (player->num_white_keys == 0)
14206 return MP_NO_ACTION;
14208 player->num_white_keys--;
14210 else if (IS_SP_PORT(element))
14212 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14213 element == EL_SP_GRAVITY_PORT_RIGHT ||
14214 element == EL_SP_GRAVITY_PORT_UP ||
14215 element == EL_SP_GRAVITY_PORT_DOWN)
14216 player->gravity = !player->gravity;
14217 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14218 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14219 element == EL_SP_GRAVITY_ON_PORT_UP ||
14220 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14221 player->gravity = TRUE;
14222 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14223 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14224 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14225 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14226 player->gravity = FALSE;
14229 // automatically move to the next field with double speed
14230 player->programmed_action = move_direction;
14232 if (player->move_delay_reset_counter == 0)
14234 player->move_delay_reset_counter = 2; // two double speed steps
14236 DOUBLE_PLAYER_SPEED(player);
14239 PlayLevelSoundAction(x, y, ACTION_PASSING);
14241 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14245 if (mode != DF_SNAP)
14247 GfxElement[x][y] = GFX_ELEMENT(element);
14248 player->is_digging = TRUE;
14251 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14253 // use old behaviour for old levels (digging)
14254 if (!level.finish_dig_collect)
14256 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14257 player->index_bit, dig_side);
14259 // if digging triggered player relocation, finish digging tile
14260 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14261 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14264 if (mode == DF_SNAP)
14266 if (level.block_snap_field)
14267 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14269 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14271 // use old behaviour for old levels (snapping)
14272 if (!level.finish_dig_collect)
14273 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14274 player->index_bit, dig_side);
14277 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14281 if (is_player && mode != DF_SNAP)
14283 GfxElement[x][y] = element;
14284 player->is_collecting = TRUE;
14287 if (element == EL_SPEED_PILL)
14289 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14291 else if (element == EL_EXTRA_TIME && level.time > 0)
14293 TimeLeft += level.extra_time;
14295 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14297 DisplayGameControlValues();
14299 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14301 player->shield_normal_time_left += level.shield_normal_time;
14302 if (element == EL_SHIELD_DEADLY)
14303 player->shield_deadly_time_left += level.shield_deadly_time;
14305 else if (element == EL_DYNAMITE ||
14306 element == EL_EM_DYNAMITE ||
14307 element == EL_SP_DISK_RED)
14309 if (player->inventory_size < MAX_INVENTORY_SIZE)
14310 player->inventory_element[player->inventory_size++] = element;
14312 DrawGameDoorValues();
14314 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14316 player->dynabomb_count++;
14317 player->dynabombs_left++;
14319 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14321 player->dynabomb_size++;
14323 else if (element == EL_DYNABOMB_INCREASE_POWER)
14325 player->dynabomb_xl = TRUE;
14327 else if (IS_KEY(element))
14329 player->key[KEY_NR(element)] = TRUE;
14331 DrawGameDoorValues();
14333 else if (element == EL_DC_KEY_WHITE)
14335 player->num_white_keys++;
14337 // display white keys?
14338 // DrawGameDoorValues();
14340 else if (IS_ENVELOPE(element))
14342 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14344 if (!wait_for_snapping)
14345 player->show_envelope = element;
14347 else if (element == EL_EMC_LENSES)
14349 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14351 RedrawAllInvisibleElementsForLenses();
14353 else if (element == EL_EMC_MAGNIFIER)
14355 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14357 RedrawAllInvisibleElementsForMagnifier();
14359 else if (IS_DROPPABLE(element) ||
14360 IS_THROWABLE(element)) // can be collected and dropped
14364 if (collect_count == 0)
14365 player->inventory_infinite_element = element;
14367 for (i = 0; i < collect_count; i++)
14368 if (player->inventory_size < MAX_INVENTORY_SIZE)
14369 player->inventory_element[player->inventory_size++] = element;
14371 DrawGameDoorValues();
14373 else if (collect_count > 0)
14375 game.gems_still_needed -= collect_count;
14376 if (game.gems_still_needed < 0)
14377 game.gems_still_needed = 0;
14379 game.snapshot.collected_item = TRUE;
14381 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14383 DisplayGameControlValues();
14386 RaiseScoreElement(element);
14387 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14389 // use old behaviour for old levels (collecting)
14390 if (!level.finish_dig_collect && is_player)
14392 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14393 player->index_bit, dig_side);
14395 // if collecting triggered player relocation, finish collecting tile
14396 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14397 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14400 if (mode == DF_SNAP)
14402 if (level.block_snap_field)
14403 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14405 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14407 // use old behaviour for old levels (snapping)
14408 if (!level.finish_dig_collect)
14409 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14410 player->index_bit, dig_side);
14413 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14415 if (mode == DF_SNAP && element != EL_BD_ROCK)
14416 return MP_NO_ACTION;
14418 if (CAN_FALL(element) && dy)
14419 return MP_NO_ACTION;
14421 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14422 !(element == EL_SPRING && level.use_spring_bug))
14423 return MP_NO_ACTION;
14425 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14426 ((move_direction & MV_VERTICAL &&
14427 ((element_info[element].move_pattern & MV_LEFT &&
14428 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14429 (element_info[element].move_pattern & MV_RIGHT &&
14430 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14431 (move_direction & MV_HORIZONTAL &&
14432 ((element_info[element].move_pattern & MV_UP &&
14433 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14434 (element_info[element].move_pattern & MV_DOWN &&
14435 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14436 return MP_NO_ACTION;
14438 // do not push elements already moving away faster than player
14439 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14440 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14441 return MP_NO_ACTION;
14443 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14445 if (player->push_delay_value == -1 || !player_was_pushing)
14446 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14448 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14450 if (player->push_delay_value == -1)
14451 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14453 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14455 if (!player->is_pushing)
14456 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14459 player->is_pushing = TRUE;
14460 player->is_active = TRUE;
14462 if (!(IN_LEV_FIELD(nextx, nexty) &&
14463 (IS_FREE(nextx, nexty) ||
14464 (IS_SB_ELEMENT(element) &&
14465 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14466 (IS_CUSTOM_ELEMENT(element) &&
14467 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14468 return MP_NO_ACTION;
14470 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14471 return MP_NO_ACTION;
14473 if (player->push_delay == -1) // new pushing; restart delay
14474 player->push_delay = 0;
14476 if (player->push_delay < player->push_delay_value &&
14477 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14478 element != EL_SPRING && element != EL_BALLOON)
14480 // make sure that there is no move delay before next try to push
14481 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14482 player->move_delay = 0;
14484 return MP_NO_ACTION;
14487 if (IS_CUSTOM_ELEMENT(element) &&
14488 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14490 if (!DigFieldByCE(nextx, nexty, element))
14491 return MP_NO_ACTION;
14494 if (IS_SB_ELEMENT(element))
14496 boolean sokoban_task_solved = FALSE;
14498 if (element == EL_SOKOBAN_FIELD_FULL)
14500 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14502 IncrementSokobanFieldsNeeded();
14503 IncrementSokobanObjectsNeeded();
14506 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14508 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14510 DecrementSokobanFieldsNeeded();
14511 DecrementSokobanObjectsNeeded();
14513 // sokoban object was pushed from empty field to sokoban field
14514 if (Back[x][y] == EL_EMPTY)
14515 sokoban_task_solved = TRUE;
14518 Tile[x][y] = EL_SOKOBAN_OBJECT;
14520 if (Back[x][y] == Back[nextx][nexty])
14521 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14522 else if (Back[x][y] != 0)
14523 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14526 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14529 if (sokoban_task_solved &&
14530 game.sokoban_fields_still_needed == 0 &&
14531 game.sokoban_objects_still_needed == 0 &&
14532 level.auto_exit_sokoban)
14534 game.players_still_needed = 0;
14538 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14542 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14544 InitMovingField(x, y, move_direction);
14545 GfxAction[x][y] = ACTION_PUSHING;
14547 if (mode == DF_SNAP)
14548 ContinueMoving(x, y);
14550 MovPos[x][y] = (dx != 0 ? dx : dy);
14552 Pushed[x][y] = TRUE;
14553 Pushed[nextx][nexty] = TRUE;
14555 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14556 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14558 player->push_delay_value = -1; // get new value later
14560 // check for element change _after_ element has been pushed
14561 if (game.use_change_when_pushing_bug)
14563 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14564 player->index_bit, dig_side);
14565 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14566 player->index_bit, dig_side);
14569 else if (IS_SWITCHABLE(element))
14571 if (PLAYER_SWITCHING(player, x, y))
14573 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14574 player->index_bit, dig_side);
14579 player->is_switching = TRUE;
14580 player->switch_x = x;
14581 player->switch_y = y;
14583 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14585 if (element == EL_ROBOT_WHEEL)
14587 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14589 game.robot_wheel_x = x;
14590 game.robot_wheel_y = y;
14591 game.robot_wheel_active = TRUE;
14593 TEST_DrawLevelField(x, y);
14595 else if (element == EL_SP_TERMINAL)
14599 SCAN_PLAYFIELD(xx, yy)
14601 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14605 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14607 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14609 ResetGfxAnimation(xx, yy);
14610 TEST_DrawLevelField(xx, yy);
14614 else if (IS_BELT_SWITCH(element))
14616 ToggleBeltSwitch(x, y);
14618 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14619 element == EL_SWITCHGATE_SWITCH_DOWN ||
14620 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14621 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14623 ToggleSwitchgateSwitch(x, y);
14625 else if (element == EL_LIGHT_SWITCH ||
14626 element == EL_LIGHT_SWITCH_ACTIVE)
14628 ToggleLightSwitch(x, y);
14630 else if (element == EL_TIMEGATE_SWITCH ||
14631 element == EL_DC_TIMEGATE_SWITCH)
14633 ActivateTimegateSwitch(x, y);
14635 else if (element == EL_BALLOON_SWITCH_LEFT ||
14636 element == EL_BALLOON_SWITCH_RIGHT ||
14637 element == EL_BALLOON_SWITCH_UP ||
14638 element == EL_BALLOON_SWITCH_DOWN ||
14639 element == EL_BALLOON_SWITCH_NONE ||
14640 element == EL_BALLOON_SWITCH_ANY)
14642 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14643 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14644 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14645 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14646 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14649 else if (element == EL_LAMP)
14651 Tile[x][y] = EL_LAMP_ACTIVE;
14652 game.lights_still_needed--;
14654 ResetGfxAnimation(x, y);
14655 TEST_DrawLevelField(x, y);
14657 else if (element == EL_TIME_ORB_FULL)
14659 Tile[x][y] = EL_TIME_ORB_EMPTY;
14661 if (level.time > 0 || level.use_time_orb_bug)
14663 TimeLeft += level.time_orb_time;
14664 game.no_time_limit = FALSE;
14666 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14668 DisplayGameControlValues();
14671 ResetGfxAnimation(x, y);
14672 TEST_DrawLevelField(x, y);
14674 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14675 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14679 game.ball_active = !game.ball_active;
14681 SCAN_PLAYFIELD(xx, yy)
14683 int e = Tile[xx][yy];
14685 if (game.ball_active)
14687 if (e == EL_EMC_MAGIC_BALL)
14688 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14689 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14690 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14694 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14695 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14696 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14697 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14702 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14703 player->index_bit, dig_side);
14705 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14706 player->index_bit, dig_side);
14708 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14709 player->index_bit, dig_side);
14715 if (!PLAYER_SWITCHING(player, x, y))
14717 player->is_switching = TRUE;
14718 player->switch_x = x;
14719 player->switch_y = y;
14721 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14722 player->index_bit, dig_side);
14723 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14724 player->index_bit, dig_side);
14726 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14727 player->index_bit, dig_side);
14728 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14729 player->index_bit, dig_side);
14732 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14733 player->index_bit, dig_side);
14734 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14735 player->index_bit, dig_side);
14737 return MP_NO_ACTION;
14740 player->push_delay = -1;
14742 if (is_player) // function can also be called by EL_PENGUIN
14744 if (Tile[x][y] != element) // really digged/collected something
14746 player->is_collecting = !player->is_digging;
14747 player->is_active = TRUE;
14749 player->last_removed_element = element;
14756 static boolean DigFieldByCE(int x, int y, int digging_element)
14758 int element = Tile[x][y];
14760 if (!IS_FREE(x, y))
14762 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14763 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14766 // no element can dig solid indestructible elements
14767 if (IS_INDESTRUCTIBLE(element) &&
14768 !IS_DIGGABLE(element) &&
14769 !IS_COLLECTIBLE(element))
14772 if (AmoebaNr[x][y] &&
14773 (element == EL_AMOEBA_FULL ||
14774 element == EL_BD_AMOEBA ||
14775 element == EL_AMOEBA_GROWING))
14777 AmoebaCnt[AmoebaNr[x][y]]--;
14778 AmoebaCnt2[AmoebaNr[x][y]]--;
14781 if (IS_MOVING(x, y))
14782 RemoveMovingField(x, y);
14786 TEST_DrawLevelField(x, y);
14789 // if digged element was about to explode, prevent the explosion
14790 ExplodeField[x][y] = EX_TYPE_NONE;
14792 PlayLevelSoundAction(x, y, action);
14795 Store[x][y] = EL_EMPTY;
14797 // this makes it possible to leave the removed element again
14798 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14799 Store[x][y] = element;
14804 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14806 int jx = player->jx, jy = player->jy;
14807 int x = jx + dx, y = jy + dy;
14808 int snap_direction = (dx == -1 ? MV_LEFT :
14809 dx == +1 ? MV_RIGHT :
14811 dy == +1 ? MV_DOWN : MV_NONE);
14812 boolean can_continue_snapping = (level.continuous_snapping &&
14813 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14815 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14818 if (!player->active || !IN_LEV_FIELD(x, y))
14826 if (player->MovPos == 0)
14827 player->is_pushing = FALSE;
14829 player->is_snapping = FALSE;
14831 if (player->MovPos == 0)
14833 player->is_moving = FALSE;
14834 player->is_digging = FALSE;
14835 player->is_collecting = FALSE;
14841 // prevent snapping with already pressed snap key when not allowed
14842 if (player->is_snapping && !can_continue_snapping)
14845 player->MovDir = snap_direction;
14847 if (player->MovPos == 0)
14849 player->is_moving = FALSE;
14850 player->is_digging = FALSE;
14851 player->is_collecting = FALSE;
14854 player->is_dropping = FALSE;
14855 player->is_dropping_pressed = FALSE;
14856 player->drop_pressed_delay = 0;
14858 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14861 player->is_snapping = TRUE;
14862 player->is_active = TRUE;
14864 if (player->MovPos == 0)
14866 player->is_moving = FALSE;
14867 player->is_digging = FALSE;
14868 player->is_collecting = FALSE;
14871 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14872 TEST_DrawLevelField(player->last_jx, player->last_jy);
14874 TEST_DrawLevelField(x, y);
14879 static boolean DropElement(struct PlayerInfo *player)
14881 int old_element, new_element;
14882 int dropx = player->jx, dropy = player->jy;
14883 int drop_direction = player->MovDir;
14884 int drop_side = drop_direction;
14885 int drop_element = get_next_dropped_element(player);
14887 /* do not drop an element on top of another element; when holding drop key
14888 pressed without moving, dropped element must move away before the next
14889 element can be dropped (this is especially important if the next element
14890 is dynamite, which can be placed on background for historical reasons) */
14891 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14894 if (IS_THROWABLE(drop_element))
14896 dropx += GET_DX_FROM_DIR(drop_direction);
14897 dropy += GET_DY_FROM_DIR(drop_direction);
14899 if (!IN_LEV_FIELD(dropx, dropy))
14903 old_element = Tile[dropx][dropy]; // old element at dropping position
14904 new_element = drop_element; // default: no change when dropping
14906 // check if player is active, not moving and ready to drop
14907 if (!player->active || player->MovPos || player->drop_delay > 0)
14910 // check if player has anything that can be dropped
14911 if (new_element == EL_UNDEFINED)
14914 // only set if player has anything that can be dropped
14915 player->is_dropping_pressed = TRUE;
14917 // check if drop key was pressed long enough for EM style dynamite
14918 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14921 // check if anything can be dropped at the current position
14922 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14925 // collected custom elements can only be dropped on empty fields
14926 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14929 if (old_element != EL_EMPTY)
14930 Back[dropx][dropy] = old_element; // store old element on this field
14932 ResetGfxAnimation(dropx, dropy);
14933 ResetRandomAnimationValue(dropx, dropy);
14935 if (player->inventory_size > 0 ||
14936 player->inventory_infinite_element != EL_UNDEFINED)
14938 if (player->inventory_size > 0)
14940 player->inventory_size--;
14942 DrawGameDoorValues();
14944 if (new_element == EL_DYNAMITE)
14945 new_element = EL_DYNAMITE_ACTIVE;
14946 else if (new_element == EL_EM_DYNAMITE)
14947 new_element = EL_EM_DYNAMITE_ACTIVE;
14948 else if (new_element == EL_SP_DISK_RED)
14949 new_element = EL_SP_DISK_RED_ACTIVE;
14952 Tile[dropx][dropy] = new_element;
14954 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14955 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14956 el2img(Tile[dropx][dropy]), 0);
14958 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14960 // needed if previous element just changed to "empty" in the last frame
14961 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14963 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14964 player->index_bit, drop_side);
14965 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14967 player->index_bit, drop_side);
14969 TestIfElementTouchesCustomElement(dropx, dropy);
14971 else // player is dropping a dyna bomb
14973 player->dynabombs_left--;
14975 Tile[dropx][dropy] = new_element;
14977 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14978 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14979 el2img(Tile[dropx][dropy]), 0);
14981 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14984 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14985 InitField_WithBug1(dropx, dropy, FALSE);
14987 new_element = Tile[dropx][dropy]; // element might have changed
14989 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14990 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14992 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14993 MovDir[dropx][dropy] = drop_direction;
14995 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14997 // do not cause impact style collision by dropping elements that can fall
14998 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15001 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15002 player->is_dropping = TRUE;
15004 player->drop_pressed_delay = 0;
15005 player->is_dropping_pressed = FALSE;
15007 player->drop_x = dropx;
15008 player->drop_y = dropy;
15013 // ----------------------------------------------------------------------------
15014 // game sound playing functions
15015 // ----------------------------------------------------------------------------
15017 static int *loop_sound_frame = NULL;
15018 static int *loop_sound_volume = NULL;
15020 void InitPlayLevelSound(void)
15022 int num_sounds = getSoundListSize();
15024 checked_free(loop_sound_frame);
15025 checked_free(loop_sound_volume);
15027 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15028 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15031 static void PlayLevelSound(int x, int y, int nr)
15033 int sx = SCREENX(x), sy = SCREENY(y);
15034 int volume, stereo_position;
15035 int max_distance = 8;
15036 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15038 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15039 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15042 if (!IN_LEV_FIELD(x, y) ||
15043 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15044 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15047 volume = SOUND_MAX_VOLUME;
15049 if (!IN_SCR_FIELD(sx, sy))
15051 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15052 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15054 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15057 stereo_position = (SOUND_MAX_LEFT +
15058 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15059 (SCR_FIELDX + 2 * max_distance));
15061 if (IS_LOOP_SOUND(nr))
15063 /* This assures that quieter loop sounds do not overwrite louder ones,
15064 while restarting sound volume comparison with each new game frame. */
15066 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15069 loop_sound_volume[nr] = volume;
15070 loop_sound_frame[nr] = FrameCounter;
15073 PlaySoundExt(nr, volume, stereo_position, type);
15076 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15078 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15079 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15080 y < LEVELY(BY1) ? LEVELY(BY1) :
15081 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15085 static void PlayLevelSoundAction(int x, int y, int action)
15087 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15090 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15092 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15094 if (sound_effect != SND_UNDEFINED)
15095 PlayLevelSound(x, y, sound_effect);
15098 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15101 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15103 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15104 PlayLevelSound(x, y, sound_effect);
15107 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15109 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15111 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15112 PlayLevelSound(x, y, sound_effect);
15115 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15117 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15119 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15120 StopSound(sound_effect);
15123 static int getLevelMusicNr(void)
15125 if (levelset.music[level_nr] != MUS_UNDEFINED)
15126 return levelset.music[level_nr]; // from config file
15128 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15131 static void FadeLevelSounds(void)
15136 static void FadeLevelMusic(void)
15138 int music_nr = getLevelMusicNr();
15139 char *curr_music = getCurrentlyPlayingMusicFilename();
15140 char *next_music = getMusicInfoEntryFilename(music_nr);
15142 if (!strEqual(curr_music, next_music))
15146 void FadeLevelSoundsAndMusic(void)
15152 static void PlayLevelMusic(void)
15154 int music_nr = getLevelMusicNr();
15155 char *curr_music = getCurrentlyPlayingMusicFilename();
15156 char *next_music = getMusicInfoEntryFilename(music_nr);
15158 if (!strEqual(curr_music, next_music))
15159 PlayMusicLoop(music_nr);
15162 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15164 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15166 int x = xx - offset;
15167 int y = yy - offset;
15172 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15176 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15180 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15184 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15188 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15192 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15196 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15199 case SOUND_android_clone:
15200 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15203 case SOUND_android_move:
15204 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15208 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15212 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15216 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15219 case SOUND_eater_eat:
15220 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15224 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15227 case SOUND_collect:
15228 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15231 case SOUND_diamond:
15232 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15236 // !!! CHECK THIS !!!
15238 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15240 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15244 case SOUND_wonderfall:
15245 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15249 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15253 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15257 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15261 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15265 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15269 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15273 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15277 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15280 case SOUND_exit_open:
15281 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15284 case SOUND_exit_leave:
15285 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15288 case SOUND_dynamite:
15289 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15293 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15297 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15301 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15305 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15309 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15313 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15317 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15322 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15324 int element = map_element_SP_to_RND(element_sp);
15325 int action = map_action_SP_to_RND(action_sp);
15326 int offset = (setup.sp_show_border_elements ? 0 : 1);
15327 int x = xx - offset;
15328 int y = yy - offset;
15330 PlayLevelSoundElementAction(x, y, element, action);
15333 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15335 int element = map_element_MM_to_RND(element_mm);
15336 int action = map_action_MM_to_RND(action_mm);
15338 int x = xx - offset;
15339 int y = yy - offset;
15341 if (!IS_MM_ELEMENT(element))
15342 element = EL_MM_DEFAULT;
15344 PlayLevelSoundElementAction(x, y, element, action);
15347 void PlaySound_MM(int sound_mm)
15349 int sound = map_sound_MM_to_RND(sound_mm);
15351 if (sound == SND_UNDEFINED)
15357 void PlaySoundLoop_MM(int sound_mm)
15359 int sound = map_sound_MM_to_RND(sound_mm);
15361 if (sound == SND_UNDEFINED)
15364 PlaySoundLoop(sound);
15367 void StopSound_MM(int sound_mm)
15369 int sound = map_sound_MM_to_RND(sound_mm);
15371 if (sound == SND_UNDEFINED)
15377 void RaiseScore(int value)
15379 game.score += value;
15381 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15383 DisplayGameControlValues();
15386 void RaiseScoreElement(int element)
15391 case EL_BD_DIAMOND:
15392 case EL_EMERALD_YELLOW:
15393 case EL_EMERALD_RED:
15394 case EL_EMERALD_PURPLE:
15395 case EL_SP_INFOTRON:
15396 RaiseScore(level.score[SC_EMERALD]);
15399 RaiseScore(level.score[SC_DIAMOND]);
15402 RaiseScore(level.score[SC_CRYSTAL]);
15405 RaiseScore(level.score[SC_PEARL]);
15408 case EL_BD_BUTTERFLY:
15409 case EL_SP_ELECTRON:
15410 RaiseScore(level.score[SC_BUG]);
15413 case EL_BD_FIREFLY:
15414 case EL_SP_SNIKSNAK:
15415 RaiseScore(level.score[SC_SPACESHIP]);
15418 case EL_DARK_YAMYAM:
15419 RaiseScore(level.score[SC_YAMYAM]);
15422 RaiseScore(level.score[SC_ROBOT]);
15425 RaiseScore(level.score[SC_PACMAN]);
15428 RaiseScore(level.score[SC_NUT]);
15431 case EL_EM_DYNAMITE:
15432 case EL_SP_DISK_RED:
15433 case EL_DYNABOMB_INCREASE_NUMBER:
15434 case EL_DYNABOMB_INCREASE_SIZE:
15435 case EL_DYNABOMB_INCREASE_POWER:
15436 RaiseScore(level.score[SC_DYNAMITE]);
15438 case EL_SHIELD_NORMAL:
15439 case EL_SHIELD_DEADLY:
15440 RaiseScore(level.score[SC_SHIELD]);
15442 case EL_EXTRA_TIME:
15443 RaiseScore(level.extra_time_score);
15457 case EL_DC_KEY_WHITE:
15458 RaiseScore(level.score[SC_KEY]);
15461 RaiseScore(element_info[element].collect_score);
15466 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15468 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15472 // prevent short reactivation of overlay buttons while closing door
15473 SetOverlayActive(FALSE);
15475 // door may still be open due to skipped or envelope style request
15476 CloseDoor(DOOR_CLOSE_1);
15479 if (network.enabled)
15480 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15484 FadeSkipNextFadeIn();
15486 SetGameStatus(GAME_MODE_MAIN);
15491 else // continue playing the game
15493 if (tape.playing && tape.deactivate_display)
15494 TapeDeactivateDisplayOff(TRUE);
15496 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15498 if (tape.playing && tape.deactivate_display)
15499 TapeDeactivateDisplayOn();
15503 void RequestQuitGame(boolean escape_key_pressed)
15505 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15506 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15507 level_editor_test_game);
15508 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15511 RequestQuitGameExt(skip_request, quick_quit,
15512 "Do you really want to quit the game?");
15515 void RequestRestartGame(char *message)
15517 game.restart_game_message = NULL;
15519 boolean has_started_game = hasStartedNetworkGame();
15520 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15522 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15524 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15528 // needed in case of envelope request to close game panel
15529 CloseDoor(DOOR_CLOSE_1);
15531 SetGameStatus(GAME_MODE_MAIN);
15537 void CheckGameOver(void)
15539 static boolean last_game_over = FALSE;
15540 static int game_over_delay = 0;
15541 int game_over_delay_value = 50;
15542 boolean game_over = checkGameFailed();
15544 // do not handle game over if request dialog is already active
15545 if (game.request_active)
15548 // do not ask to play again if game was never actually played
15549 if (!game.GamePlayed)
15554 last_game_over = FALSE;
15555 game_over_delay = game_over_delay_value;
15560 if (game_over_delay > 0)
15567 if (last_game_over != game_over)
15568 game.restart_game_message = (hasStartedNetworkGame() ?
15569 "Game over! Play it again?" :
15572 last_game_over = game_over;
15575 boolean checkGameSolved(void)
15577 // set for all game engines if level was solved
15578 return game.LevelSolved_GameEnd;
15581 boolean checkGameFailed(void)
15583 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15584 return (game_em.game_over && !game_em.level_solved);
15585 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15586 return (game_sp.game_over && !game_sp.level_solved);
15587 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15588 return (game_mm.game_over && !game_mm.level_solved);
15589 else // GAME_ENGINE_TYPE_RND
15590 return (game.GameOver && !game.LevelSolved);
15593 boolean checkGameEnded(void)
15595 return (checkGameSolved() || checkGameFailed());
15599 // ----------------------------------------------------------------------------
15600 // random generator functions
15601 // ----------------------------------------------------------------------------
15603 unsigned int InitEngineRandom_RND(int seed)
15605 game.num_random_calls = 0;
15607 return InitEngineRandom(seed);
15610 unsigned int RND(int max)
15614 game.num_random_calls++;
15616 return GetEngineRandom(max);
15623 // ----------------------------------------------------------------------------
15624 // game engine snapshot handling functions
15625 // ----------------------------------------------------------------------------
15627 struct EngineSnapshotInfo
15629 // runtime values for custom element collect score
15630 int collect_score[NUM_CUSTOM_ELEMENTS];
15632 // runtime values for group element choice position
15633 int choice_pos[NUM_GROUP_ELEMENTS];
15635 // runtime values for belt position animations
15636 int belt_graphic[4][NUM_BELT_PARTS];
15637 int belt_anim_mode[4][NUM_BELT_PARTS];
15640 static struct EngineSnapshotInfo engine_snapshot_rnd;
15641 static char *snapshot_level_identifier = NULL;
15642 static int snapshot_level_nr = -1;
15644 static void SaveEngineSnapshotValues_RND(void)
15646 static int belt_base_active_element[4] =
15648 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15649 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15650 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15651 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15655 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15657 int element = EL_CUSTOM_START + i;
15659 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15662 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15664 int element = EL_GROUP_START + i;
15666 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15669 for (i = 0; i < 4; i++)
15671 for (j = 0; j < NUM_BELT_PARTS; j++)
15673 int element = belt_base_active_element[i] + j;
15674 int graphic = el2img(element);
15675 int anim_mode = graphic_info[graphic].anim_mode;
15677 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15678 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15683 static void LoadEngineSnapshotValues_RND(void)
15685 unsigned int num_random_calls = game.num_random_calls;
15688 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15690 int element = EL_CUSTOM_START + i;
15692 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15695 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15697 int element = EL_GROUP_START + i;
15699 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15702 for (i = 0; i < 4; i++)
15704 for (j = 0; j < NUM_BELT_PARTS; j++)
15706 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15707 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15709 graphic_info[graphic].anim_mode = anim_mode;
15713 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15715 InitRND(tape.random_seed);
15716 for (i = 0; i < num_random_calls; i++)
15720 if (game.num_random_calls != num_random_calls)
15722 Error("number of random calls out of sync");
15723 Error("number of random calls should be %d", num_random_calls);
15724 Error("number of random calls is %d", game.num_random_calls);
15726 Fail("this should not happen -- please debug");
15730 void FreeEngineSnapshotSingle(void)
15732 FreeSnapshotSingle();
15734 setString(&snapshot_level_identifier, NULL);
15735 snapshot_level_nr = -1;
15738 void FreeEngineSnapshotList(void)
15740 FreeSnapshotList();
15743 static ListNode *SaveEngineSnapshotBuffers(void)
15745 ListNode *buffers = NULL;
15747 // copy some special values to a structure better suited for the snapshot
15749 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15750 SaveEngineSnapshotValues_RND();
15751 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15752 SaveEngineSnapshotValues_EM();
15753 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15754 SaveEngineSnapshotValues_SP(&buffers);
15755 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15756 SaveEngineSnapshotValues_MM(&buffers);
15758 // save values stored in special snapshot structure
15760 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15761 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15762 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15763 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15764 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15765 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15766 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15767 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15769 // save further RND engine values
15771 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15772 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15773 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15775 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15776 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15777 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15778 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15779 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15781 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15782 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15783 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15785 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15787 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15788 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15790 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15791 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15792 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15793 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15794 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15795 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15796 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15797 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15798 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15799 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15800 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15801 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15802 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15803 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15804 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15805 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15806 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15807 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15809 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15810 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15812 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15813 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15814 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15816 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15817 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15819 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15820 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15821 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15822 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15823 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15825 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15826 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15829 ListNode *node = engine_snapshot_list_rnd;
15832 while (node != NULL)
15834 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15839 Debug("game:playing:SaveEngineSnapshotBuffers",
15840 "size of engine snapshot: %d bytes", num_bytes);
15846 void SaveEngineSnapshotSingle(void)
15848 ListNode *buffers = SaveEngineSnapshotBuffers();
15850 // finally save all snapshot buffers to single snapshot
15851 SaveSnapshotSingle(buffers);
15853 // save level identification information
15854 setString(&snapshot_level_identifier, leveldir_current->identifier);
15855 snapshot_level_nr = level_nr;
15858 boolean CheckSaveEngineSnapshotToList(void)
15860 boolean save_snapshot =
15861 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15862 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15863 game.snapshot.changed_action) ||
15864 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15865 game.snapshot.collected_item));
15867 game.snapshot.changed_action = FALSE;
15868 game.snapshot.collected_item = FALSE;
15869 game.snapshot.save_snapshot = save_snapshot;
15871 return save_snapshot;
15874 void SaveEngineSnapshotToList(void)
15876 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15880 ListNode *buffers = SaveEngineSnapshotBuffers();
15882 // finally save all snapshot buffers to snapshot list
15883 SaveSnapshotToList(buffers);
15886 void SaveEngineSnapshotToListInitial(void)
15888 FreeEngineSnapshotList();
15890 SaveEngineSnapshotToList();
15893 static void LoadEngineSnapshotValues(void)
15895 // restore special values from snapshot structure
15897 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15898 LoadEngineSnapshotValues_RND();
15899 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15900 LoadEngineSnapshotValues_EM();
15901 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15902 LoadEngineSnapshotValues_SP();
15903 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15904 LoadEngineSnapshotValues_MM();
15907 void LoadEngineSnapshotSingle(void)
15909 LoadSnapshotSingle();
15911 LoadEngineSnapshotValues();
15914 static void LoadEngineSnapshot_Undo(int steps)
15916 LoadSnapshotFromList_Older(steps);
15918 LoadEngineSnapshotValues();
15921 static void LoadEngineSnapshot_Redo(int steps)
15923 LoadSnapshotFromList_Newer(steps);
15925 LoadEngineSnapshotValues();
15928 boolean CheckEngineSnapshotSingle(void)
15930 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15931 snapshot_level_nr == level_nr);
15934 boolean CheckEngineSnapshotList(void)
15936 return CheckSnapshotList();
15940 // ---------- new game button stuff -------------------------------------------
15947 boolean *setup_value;
15948 boolean allowed_on_tape;
15949 boolean is_touch_button;
15951 } gamebutton_info[NUM_GAME_BUTTONS] =
15954 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15955 GAME_CTRL_ID_STOP, NULL,
15956 TRUE, FALSE, "stop game"
15959 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15960 GAME_CTRL_ID_PAUSE, NULL,
15961 TRUE, FALSE, "pause game"
15964 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15965 GAME_CTRL_ID_PLAY, NULL,
15966 TRUE, FALSE, "play game"
15969 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15970 GAME_CTRL_ID_UNDO, NULL,
15971 TRUE, FALSE, "undo step"
15974 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15975 GAME_CTRL_ID_REDO, NULL,
15976 TRUE, FALSE, "redo step"
15979 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15980 GAME_CTRL_ID_SAVE, NULL,
15981 TRUE, FALSE, "save game"
15984 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15985 GAME_CTRL_ID_PAUSE2, NULL,
15986 TRUE, FALSE, "pause game"
15989 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15990 GAME_CTRL_ID_LOAD, NULL,
15991 TRUE, FALSE, "load game"
15994 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15995 GAME_CTRL_ID_PANEL_STOP, NULL,
15996 FALSE, FALSE, "stop game"
15999 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16000 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16001 FALSE, FALSE, "pause game"
16004 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16005 GAME_CTRL_ID_PANEL_PLAY, NULL,
16006 FALSE, FALSE, "play game"
16009 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16010 GAME_CTRL_ID_TOUCH_STOP, NULL,
16011 FALSE, TRUE, "stop game"
16014 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16015 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16016 FALSE, TRUE, "pause game"
16019 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16020 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16021 TRUE, FALSE, "background music on/off"
16024 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16025 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16026 TRUE, FALSE, "sound loops on/off"
16029 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16030 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16031 TRUE, FALSE, "normal sounds on/off"
16034 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16035 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16036 FALSE, FALSE, "background music on/off"
16039 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16040 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16041 FALSE, FALSE, "sound loops on/off"
16044 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16045 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16046 FALSE, FALSE, "normal sounds on/off"
16050 void CreateGameButtons(void)
16054 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16056 int graphic = gamebutton_info[i].graphic;
16057 struct GraphicInfo *gfx = &graphic_info[graphic];
16058 struct XY *pos = gamebutton_info[i].pos;
16059 struct GadgetInfo *gi;
16062 unsigned int event_mask;
16063 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16064 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16065 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16066 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16067 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16068 int gd_x = gfx->src_x;
16069 int gd_y = gfx->src_y;
16070 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16071 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16072 int gd_xa = gfx->src_x + gfx->active_xoffset;
16073 int gd_ya = gfx->src_y + gfx->active_yoffset;
16074 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16075 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16076 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16077 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16080 if (gfx->bitmap == NULL)
16082 game_gadget[id] = NULL;
16087 if (id == GAME_CTRL_ID_STOP ||
16088 id == GAME_CTRL_ID_PANEL_STOP ||
16089 id == GAME_CTRL_ID_TOUCH_STOP ||
16090 id == GAME_CTRL_ID_PLAY ||
16091 id == GAME_CTRL_ID_PANEL_PLAY ||
16092 id == GAME_CTRL_ID_SAVE ||
16093 id == GAME_CTRL_ID_LOAD)
16095 button_type = GD_TYPE_NORMAL_BUTTON;
16097 event_mask = GD_EVENT_RELEASED;
16099 else if (id == GAME_CTRL_ID_UNDO ||
16100 id == GAME_CTRL_ID_REDO)
16102 button_type = GD_TYPE_NORMAL_BUTTON;
16104 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16108 button_type = GD_TYPE_CHECK_BUTTON;
16109 checked = (gamebutton_info[i].setup_value != NULL ?
16110 *gamebutton_info[i].setup_value : FALSE);
16111 event_mask = GD_EVENT_PRESSED;
16114 gi = CreateGadget(GDI_CUSTOM_ID, id,
16115 GDI_IMAGE_ID, graphic,
16116 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16119 GDI_WIDTH, gfx->width,
16120 GDI_HEIGHT, gfx->height,
16121 GDI_TYPE, button_type,
16122 GDI_STATE, GD_BUTTON_UNPRESSED,
16123 GDI_CHECKED, checked,
16124 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16125 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16126 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16127 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16128 GDI_DIRECT_DRAW, FALSE,
16129 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16130 GDI_EVENT_MASK, event_mask,
16131 GDI_CALLBACK_ACTION, HandleGameButtons,
16135 Fail("cannot create gadget");
16137 game_gadget[id] = gi;
16141 void FreeGameButtons(void)
16145 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16146 FreeGadget(game_gadget[i]);
16149 static void UnmapGameButtonsAtSamePosition(int id)
16153 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16155 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16156 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16157 UnmapGadget(game_gadget[i]);
16160 static void UnmapGameButtonsAtSamePosition_All(void)
16162 if (setup.show_snapshot_buttons)
16164 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16165 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16166 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16170 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16171 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16172 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16174 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16175 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16176 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16180 static void MapGameButtonsAtSamePosition(int id)
16184 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16186 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16187 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16188 MapGadget(game_gadget[i]);
16190 UnmapGameButtonsAtSamePosition_All();
16193 void MapUndoRedoButtons(void)
16195 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16196 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16198 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16199 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16202 void UnmapUndoRedoButtons(void)
16204 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16205 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16207 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16208 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16211 void ModifyPauseButtons(void)
16215 GAME_CTRL_ID_PAUSE,
16216 GAME_CTRL_ID_PAUSE2,
16217 GAME_CTRL_ID_PANEL_PAUSE,
16218 GAME_CTRL_ID_TOUCH_PAUSE,
16223 for (i = 0; ids[i] > -1; i++)
16224 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16227 static void MapGameButtonsExt(boolean on_tape)
16231 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16232 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16233 i != GAME_CTRL_ID_UNDO &&
16234 i != GAME_CTRL_ID_REDO)
16235 MapGadget(game_gadget[i]);
16237 UnmapGameButtonsAtSamePosition_All();
16239 RedrawGameButtons();
16242 static void UnmapGameButtonsExt(boolean on_tape)
16246 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16247 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16248 UnmapGadget(game_gadget[i]);
16251 static void RedrawGameButtonsExt(boolean on_tape)
16255 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16256 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16257 RedrawGadget(game_gadget[i]);
16260 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16265 gi->checked = state;
16268 static void RedrawSoundButtonGadget(int id)
16270 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16271 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16272 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16273 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16274 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16275 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16278 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16279 RedrawGadget(game_gadget[id2]);
16282 void MapGameButtons(void)
16284 MapGameButtonsExt(FALSE);
16287 void UnmapGameButtons(void)
16289 UnmapGameButtonsExt(FALSE);
16292 void RedrawGameButtons(void)
16294 RedrawGameButtonsExt(FALSE);
16297 void MapGameButtonsOnTape(void)
16299 MapGameButtonsExt(TRUE);
16302 void UnmapGameButtonsOnTape(void)
16304 UnmapGameButtonsExt(TRUE);
16307 void RedrawGameButtonsOnTape(void)
16309 RedrawGameButtonsExt(TRUE);
16312 static void GameUndoRedoExt(void)
16314 ClearPlayerAction();
16316 tape.pausing = TRUE;
16319 UpdateAndDisplayGameControlValues();
16321 DrawCompleteVideoDisplay();
16322 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16323 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16324 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16329 static void GameUndo(int steps)
16331 if (!CheckEngineSnapshotList())
16334 LoadEngineSnapshot_Undo(steps);
16339 static void GameRedo(int steps)
16341 if (!CheckEngineSnapshotList())
16344 LoadEngineSnapshot_Redo(steps);
16349 static void HandleGameButtonsExt(int id, int button)
16351 static boolean game_undo_executed = FALSE;
16352 int steps = BUTTON_STEPSIZE(button);
16353 boolean handle_game_buttons =
16354 (game_status == GAME_MODE_PLAYING ||
16355 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16357 if (!handle_game_buttons)
16362 case GAME_CTRL_ID_STOP:
16363 case GAME_CTRL_ID_PANEL_STOP:
16364 case GAME_CTRL_ID_TOUCH_STOP:
16365 if (game_status == GAME_MODE_MAIN)
16371 RequestQuitGame(FALSE);
16375 case GAME_CTRL_ID_PAUSE:
16376 case GAME_CTRL_ID_PAUSE2:
16377 case GAME_CTRL_ID_PANEL_PAUSE:
16378 case GAME_CTRL_ID_TOUCH_PAUSE:
16379 if (network.enabled && game_status == GAME_MODE_PLAYING)
16382 SendToServer_ContinuePlaying();
16384 SendToServer_PausePlaying();
16387 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16389 game_undo_executed = FALSE;
16393 case GAME_CTRL_ID_PLAY:
16394 case GAME_CTRL_ID_PANEL_PLAY:
16395 if (game_status == GAME_MODE_MAIN)
16397 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16399 else if (tape.pausing)
16401 if (network.enabled)
16402 SendToServer_ContinuePlaying();
16404 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16408 case GAME_CTRL_ID_UNDO:
16409 // Important: When using "save snapshot when collecting an item" mode,
16410 // load last (current) snapshot for first "undo" after pressing "pause"
16411 // (else the last-but-one snapshot would be loaded, because the snapshot
16412 // pointer already points to the last snapshot when pressing "pause",
16413 // which is fine for "every step/move" mode, but not for "every collect")
16414 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16415 !game_undo_executed)
16418 game_undo_executed = TRUE;
16423 case GAME_CTRL_ID_REDO:
16427 case GAME_CTRL_ID_SAVE:
16431 case GAME_CTRL_ID_LOAD:
16435 case SOUND_CTRL_ID_MUSIC:
16436 case SOUND_CTRL_ID_PANEL_MUSIC:
16437 if (setup.sound_music)
16439 setup.sound_music = FALSE;
16443 else if (audio.music_available)
16445 setup.sound = setup.sound_music = TRUE;
16447 SetAudioMode(setup.sound);
16449 if (game_status == GAME_MODE_PLAYING)
16453 RedrawSoundButtonGadget(id);
16457 case SOUND_CTRL_ID_LOOPS:
16458 case SOUND_CTRL_ID_PANEL_LOOPS:
16459 if (setup.sound_loops)
16460 setup.sound_loops = FALSE;
16461 else if (audio.loops_available)
16463 setup.sound = setup.sound_loops = TRUE;
16465 SetAudioMode(setup.sound);
16468 RedrawSoundButtonGadget(id);
16472 case SOUND_CTRL_ID_SIMPLE:
16473 case SOUND_CTRL_ID_PANEL_SIMPLE:
16474 if (setup.sound_simple)
16475 setup.sound_simple = FALSE;
16476 else if (audio.sound_available)
16478 setup.sound = setup.sound_simple = TRUE;
16480 SetAudioMode(setup.sound);
16483 RedrawSoundButtonGadget(id);
16492 static void HandleGameButtons(struct GadgetInfo *gi)
16494 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16497 void HandleSoundButtonKeys(Key key)
16499 if (key == setup.shortcut.sound_simple)
16500 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16501 else if (key == setup.shortcut.sound_loops)
16502 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16503 else if (key == setup.shortcut.sound_music)
16504 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);