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_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Tile[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Tile[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Tile[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
971 Tile[x][y] == EL_EM_EXIT_OPEN || \
972 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
973 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Tile[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
986 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define GAME_CTRL_ID_TOUCH_STOP 11
1020 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1021 #define SOUND_CTRL_ID_MUSIC 13
1022 #define SOUND_CTRL_ID_LOOPS 14
1023 #define SOUND_CTRL_ID_SIMPLE 15
1024 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1025 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1026 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1028 #define NUM_GAME_BUTTONS 19
1031 // forward declaration for internal use
1033 static void CreateField(int, int, int);
1035 static void ResetGfxAnimation(int, int);
1037 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1038 static void AdvanceFrameAndPlayerCounters(int);
1040 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1041 static boolean MovePlayer(struct PlayerInfo *, int, int);
1042 static void ScrollPlayer(struct PlayerInfo *, int);
1043 static void ScrollScreen(struct PlayerInfo *, int);
1045 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1046 static boolean DigFieldByCE(int, int, int);
1047 static boolean SnapField(struct PlayerInfo *, int, int);
1048 static boolean DropElement(struct PlayerInfo *);
1050 static void InitBeltMovement(void);
1051 static void CloseAllOpenTimegates(void);
1052 static void CheckGravityMovement(struct PlayerInfo *);
1053 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1054 static void KillPlayerUnlessEnemyProtected(int, int);
1055 static void KillPlayerUnlessExplosionProtected(int, int);
1057 static void TestIfPlayerTouchesCustomElement(int, int);
1058 static void TestIfElementTouchesCustomElement(int, int);
1059 static void TestIfElementHitsCustomElement(int, int, int);
1061 static void HandleElementChange(int, int, int);
1062 static void ExecuteCustomElementAction(int, int, int, int);
1063 static boolean ChangeElement(int, int, int, int);
1065 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1066 #define CheckTriggeredElementChange(x, y, e, ev) \
1067 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1068 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1070 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1071 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1072 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1073 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1075 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckElementChange(x, y, e, te, ev) \
1077 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1078 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1080 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1083 static void PlayLevelSound(int, int, int);
1084 static void PlayLevelSoundNearest(int, int, int);
1085 static void PlayLevelSoundAction(int, int, int);
1086 static void PlayLevelSoundElementAction(int, int, int, int);
1087 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1088 static void PlayLevelSoundActionIfLoop(int, int, int);
1089 static void StopLevelSoundActionIfLoop(int, int, int);
1090 static void PlayLevelMusic(void);
1091 static void FadeLevelSoundsAndMusic(void);
1093 static void HandleGameButtons(struct GadgetInfo *);
1095 int AmoebaNeighbourNr(int, int);
1096 void AmoebaToDiamond(int, int);
1097 void ContinueMoving(int, int);
1098 void Bang(int, int);
1099 void InitMovDir(int, int);
1100 void InitAmoebaNr(int, int);
1101 int NewHiScore(int);
1103 void TestIfGoodThingHitsBadThing(int, int, int);
1104 void TestIfBadThingHitsGoodThing(int, int, int);
1105 void TestIfPlayerTouchesBadThing(int, int);
1106 void TestIfPlayerRunsIntoBadThing(int, int, int);
1107 void TestIfBadThingTouchesPlayer(int, int);
1108 void TestIfBadThingRunsIntoPlayer(int, int, int);
1109 void TestIfFriendTouchesBadThing(int, int);
1110 void TestIfBadThingTouchesFriend(int, int);
1111 void TestIfBadThingTouchesOtherBadThing(int, int);
1112 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1114 void KillPlayer(struct PlayerInfo *);
1115 void BuryPlayer(struct PlayerInfo *);
1116 void RemovePlayer(struct PlayerInfo *);
1117 void ExitPlayer(struct PlayerInfo *);
1119 static int getInvisibleActiveFromInvisibleElement(int);
1120 static int getInvisibleFromInvisibleActiveElement(int);
1122 static void TestFieldAfterSnapping(int, int, int, int, int);
1124 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1126 // for detection of endless loops, caused by custom element programming
1127 // (using maximal playfield width x 10 is just a rough approximation)
1128 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1130 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1132 if (recursion_loop_detected) \
1135 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1137 recursion_loop_detected = TRUE; \
1138 recursion_loop_element = (e); \
1141 recursion_loop_depth++; \
1144 #define RECURSION_LOOP_DETECTION_END() \
1146 recursion_loop_depth--; \
1149 static int recursion_loop_depth;
1150 static boolean recursion_loop_detected;
1151 static boolean recursion_loop_element;
1153 static int map_player_action[MAX_PLAYERS];
1156 // ----------------------------------------------------------------------------
1157 // definition of elements that automatically change to other elements after
1158 // a specified time, eventually calling a function when changing
1159 // ----------------------------------------------------------------------------
1161 // forward declaration for changer functions
1162 static void InitBuggyBase(int, int);
1163 static void WarnBuggyBase(int, int);
1165 static void InitTrap(int, int);
1166 static void ActivateTrap(int, int);
1167 static void ChangeActiveTrap(int, int);
1169 static void InitRobotWheel(int, int);
1170 static void RunRobotWheel(int, int);
1171 static void StopRobotWheel(int, int);
1173 static void InitTimegateWheel(int, int);
1174 static void RunTimegateWheel(int, int);
1176 static void InitMagicBallDelay(int, int);
1177 static void ActivateMagicBall(int, int);
1179 struct ChangingElementInfo
1184 void (*pre_change_function)(int x, int y);
1185 void (*change_function)(int x, int y);
1186 void (*post_change_function)(int x, int y);
1189 static struct ChangingElementInfo change_delay_list[] =
1224 EL_STEEL_EXIT_OPENING,
1232 EL_STEEL_EXIT_CLOSING,
1233 EL_STEEL_EXIT_CLOSED,
1256 EL_EM_STEEL_EXIT_OPENING,
1257 EL_EM_STEEL_EXIT_OPEN,
1264 EL_EM_STEEL_EXIT_CLOSING,
1288 EL_SWITCHGATE_OPENING,
1296 EL_SWITCHGATE_CLOSING,
1297 EL_SWITCHGATE_CLOSED,
1304 EL_TIMEGATE_OPENING,
1312 EL_TIMEGATE_CLOSING,
1321 EL_ACID_SPLASH_LEFT,
1329 EL_ACID_SPLASH_RIGHT,
1338 EL_SP_BUGGY_BASE_ACTIVATING,
1345 EL_SP_BUGGY_BASE_ACTIVATING,
1346 EL_SP_BUGGY_BASE_ACTIVE,
1353 EL_SP_BUGGY_BASE_ACTIVE,
1377 EL_ROBOT_WHEEL_ACTIVE,
1385 EL_TIMEGATE_SWITCH_ACTIVE,
1393 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1394 EL_DC_TIMEGATE_SWITCH,
1401 EL_EMC_MAGIC_BALL_ACTIVE,
1402 EL_EMC_MAGIC_BALL_ACTIVE,
1409 EL_EMC_SPRING_BUMPER_ACTIVE,
1410 EL_EMC_SPRING_BUMPER,
1417 EL_DIAGONAL_SHRINKING,
1425 EL_DIAGONAL_GROWING,
1446 int push_delay_fixed, push_delay_random;
1450 { EL_SPRING, 0, 0 },
1451 { EL_BALLOON, 0, 0 },
1453 { EL_SOKOBAN_OBJECT, 2, 0 },
1454 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1455 { EL_SATELLITE, 2, 0 },
1456 { EL_SP_DISK_YELLOW, 2, 0 },
1458 { EL_UNDEFINED, 0, 0 },
1466 move_stepsize_list[] =
1468 { EL_AMOEBA_DROP, 2 },
1469 { EL_AMOEBA_DROPPING, 2 },
1470 { EL_QUICKSAND_FILLING, 1 },
1471 { EL_QUICKSAND_EMPTYING, 1 },
1472 { EL_QUICKSAND_FAST_FILLING, 2 },
1473 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1474 { EL_MAGIC_WALL_FILLING, 2 },
1475 { EL_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_BD_MAGIC_WALL_FILLING, 2 },
1477 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1478 { EL_DC_MAGIC_WALL_FILLING, 2 },
1479 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1481 { EL_UNDEFINED, 0 },
1489 collect_count_list[] =
1492 { EL_BD_DIAMOND, 1 },
1493 { EL_EMERALD_YELLOW, 1 },
1494 { EL_EMERALD_RED, 1 },
1495 { EL_EMERALD_PURPLE, 1 },
1497 { EL_SP_INFOTRON, 1 },
1501 { EL_UNDEFINED, 0 },
1509 access_direction_list[] =
1511 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1512 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1513 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1514 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1515 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1516 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1517 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1518 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1519 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1520 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1521 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1523 { EL_SP_PORT_LEFT, MV_RIGHT },
1524 { EL_SP_PORT_RIGHT, MV_LEFT },
1525 { EL_SP_PORT_UP, MV_DOWN },
1526 { EL_SP_PORT_DOWN, MV_UP },
1527 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1528 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1529 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1530 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1531 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1532 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1533 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1534 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1537 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1538 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1543 { EL_UNDEFINED, MV_NONE }
1546 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1548 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1549 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1550 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1551 IS_JUST_CHANGING(x, y))
1553 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1555 // static variables for playfield scan mode (scanning forward or backward)
1556 static int playfield_scan_start_x = 0;
1557 static int playfield_scan_start_y = 0;
1558 static int playfield_scan_delta_x = 1;
1559 static int playfield_scan_delta_y = 1;
1561 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1562 (y) >= 0 && (y) <= lev_fieldy - 1; \
1563 (y) += playfield_scan_delta_y) \
1564 for ((x) = playfield_scan_start_x; \
1565 (x) >= 0 && (x) <= lev_fieldx - 1; \
1566 (x) += playfield_scan_delta_x)
1569 void DEBUG_SetMaximumDynamite(void)
1573 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1574 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1575 local_player->inventory_element[local_player->inventory_size++] =
1580 static void InitPlayfieldScanModeVars(void)
1582 if (game.use_reverse_scan_direction)
1584 playfield_scan_start_x = lev_fieldx - 1;
1585 playfield_scan_start_y = lev_fieldy - 1;
1587 playfield_scan_delta_x = -1;
1588 playfield_scan_delta_y = -1;
1592 playfield_scan_start_x = 0;
1593 playfield_scan_start_y = 0;
1595 playfield_scan_delta_x = 1;
1596 playfield_scan_delta_y = 1;
1600 static void InitPlayfieldScanMode(int mode)
1602 game.use_reverse_scan_direction =
1603 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1605 InitPlayfieldScanModeVars();
1608 static int get_move_delay_from_stepsize(int move_stepsize)
1611 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1613 // make sure that stepsize value is always a power of 2
1614 move_stepsize = (1 << log_2(move_stepsize));
1616 return TILEX / move_stepsize;
1619 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1622 int player_nr = player->index_nr;
1623 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1624 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1626 // do no immediately change move delay -- the player might just be moving
1627 player->move_delay_value_next = move_delay;
1629 // information if player can move must be set separately
1630 player->cannot_move = cannot_move;
1634 player->move_delay = game.initial_move_delay[player_nr];
1635 player->move_delay_value = game.initial_move_delay_value[player_nr];
1637 player->move_delay_value_next = -1;
1639 player->move_delay_reset_counter = 0;
1643 void GetPlayerConfig(void)
1645 GameFrameDelay = setup.game_frame_delay;
1647 if (!audio.sound_available)
1648 setup.sound_simple = FALSE;
1650 if (!audio.loops_available)
1651 setup.sound_loops = FALSE;
1653 if (!audio.music_available)
1654 setup.sound_music = FALSE;
1656 if (!video.fullscreen_available)
1657 setup.fullscreen = FALSE;
1659 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1661 SetAudioMode(setup.sound);
1664 int GetElementFromGroupElement(int element)
1666 if (IS_GROUP_ELEMENT(element))
1668 struct ElementGroupInfo *group = element_info[element].group;
1669 int last_anim_random_frame = gfx.anim_random_frame;
1672 if (group->choice_mode == ANIM_RANDOM)
1673 gfx.anim_random_frame = RND(group->num_elements_resolved);
1675 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1676 group->choice_mode, 0,
1679 if (group->choice_mode == ANIM_RANDOM)
1680 gfx.anim_random_frame = last_anim_random_frame;
1682 group->choice_pos++;
1684 element = group->element_resolved[element_pos];
1690 static void IncrementSokobanFieldsNeeded(void)
1692 if (level.sb_fields_needed)
1693 game.sokoban_fields_still_needed++;
1696 static void IncrementSokobanObjectsNeeded(void)
1698 if (level.sb_objects_needed)
1699 game.sokoban_objects_still_needed++;
1702 static void DecrementSokobanFieldsNeeded(void)
1704 if (game.sokoban_fields_still_needed > 0)
1705 game.sokoban_fields_still_needed--;
1708 static void DecrementSokobanObjectsNeeded(void)
1710 if (game.sokoban_objects_still_needed > 0)
1711 game.sokoban_objects_still_needed--;
1714 static void InitPlayerField(int x, int y, int element, boolean init_game)
1716 if (element == EL_SP_MURPHY)
1720 if (stored_player[0].present)
1722 Tile[x][y] = EL_SP_MURPHY_CLONE;
1728 stored_player[0].initial_element = element;
1729 stored_player[0].use_murphy = TRUE;
1731 if (!level.use_artwork_element[0])
1732 stored_player[0].artwork_element = EL_SP_MURPHY;
1735 Tile[x][y] = EL_PLAYER_1;
1741 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1742 int jx = player->jx, jy = player->jy;
1744 player->present = TRUE;
1746 player->block_last_field = (element == EL_SP_MURPHY ?
1747 level.sp_block_last_field :
1748 level.block_last_field);
1750 // ---------- initialize player's last field block delay ------------------
1752 // always start with reliable default value (no adjustment needed)
1753 player->block_delay_adjustment = 0;
1755 // special case 1: in Supaplex, Murphy blocks last field one more frame
1756 if (player->block_last_field && element == EL_SP_MURPHY)
1757 player->block_delay_adjustment = 1;
1759 // special case 2: in game engines before 3.1.1, blocking was different
1760 if (game.use_block_last_field_bug)
1761 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1763 if (!network.enabled || player->connected_network)
1765 player->active = TRUE;
1767 // remove potentially duplicate players
1768 if (StorePlayer[jx][jy] == Tile[x][y])
1769 StorePlayer[jx][jy] = 0;
1771 StorePlayer[x][y] = Tile[x][y];
1773 #if DEBUG_INIT_PLAYER
1774 Debug("game:init:player", "- player element %d activated",
1775 player->element_nr);
1776 Debug("game:init:player", " (local player is %d and currently %s)",
1777 local_player->element_nr,
1778 local_player->active ? "active" : "not active");
1782 Tile[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1788 // always check if player was just killed and should be reanimated
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Tile[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Tile[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1830 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1832 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1836 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1880 case EL_SPRING_LEFT:
1881 case EL_SPRING_RIGHT:
1885 case EL_AMOEBA_FULL:
1890 case EL_AMOEBA_DROP:
1891 if (y == lev_fieldy - 1)
1893 Tile[x][y] = EL_AMOEBA_GROWING;
1894 Store[x][y] = EL_AMOEBA_WET;
1898 case EL_DYNAMITE_ACTIVE:
1899 case EL_SP_DISK_RED_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1902 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1903 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1904 MovDelay[x][y] = 96;
1907 case EL_EM_DYNAMITE_ACTIVE:
1908 MovDelay[x][y] = 32;
1912 game.lights_still_needed++;
1916 game.friends_still_needed++;
1921 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1924 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1925 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1926 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1927 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1928 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1929 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1930 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1931 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1932 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1933 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1934 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1935 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1938 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1939 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1940 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1942 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1944 game.belt_dir[belt_nr] = belt_dir;
1945 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1947 else // more than one switch -- set it like the first switch
1949 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1954 case EL_LIGHT_SWITCH_ACTIVE:
1956 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1959 case EL_INVISIBLE_STEELWALL:
1960 case EL_INVISIBLE_WALL:
1961 case EL_INVISIBLE_SAND:
1962 if (game.light_time_left > 0 ||
1963 game.lenses_time_left > 0)
1964 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1967 case EL_EMC_MAGIC_BALL:
1968 if (game.ball_active)
1969 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1972 case EL_EMC_MAGIC_BALL_SWITCH:
1973 if (game.ball_active)
1974 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1977 case EL_TRIGGER_PLAYER:
1978 case EL_TRIGGER_ELEMENT:
1979 case EL_TRIGGER_CE_VALUE:
1980 case EL_TRIGGER_CE_SCORE:
1982 case EL_ANY_ELEMENT:
1983 case EL_CURRENT_CE_VALUE:
1984 case EL_CURRENT_CE_SCORE:
2001 // reference elements should not be used on the playfield
2002 Tile[x][y] = EL_EMPTY;
2006 if (IS_CUSTOM_ELEMENT(element))
2008 if (CAN_MOVE(element))
2011 if (!element_info[element].use_last_ce_value || init_game)
2012 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2014 else if (IS_GROUP_ELEMENT(element))
2016 Tile[x][y] = GetElementFromGroupElement(element);
2018 InitField(x, y, init_game);
2025 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2028 static void InitField_WithBug1(int x, int y, boolean init_game)
2030 InitField(x, y, init_game);
2032 // not needed to call InitMovDir() -- already done by InitField()!
2033 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2034 CAN_MOVE(Tile[x][y]))
2038 static void InitField_WithBug2(int x, int y, boolean init_game)
2040 int old_element = Tile[x][y];
2042 InitField(x, y, init_game);
2044 // not needed to call InitMovDir() -- already done by InitField()!
2045 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2046 CAN_MOVE(old_element) &&
2047 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2050 /* this case is in fact a combination of not less than three bugs:
2051 first, it calls InitMovDir() for elements that can move, although this is
2052 already done by InitField(); then, it checks the element that was at this
2053 field _before_ the call to InitField() (which can change it); lastly, it
2054 was not called for "mole with direction" elements, which were treated as
2055 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2059 static int get_key_element_from_nr(int key_nr)
2061 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2062 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2063 EL_EM_KEY_1 : EL_KEY_1);
2065 return key_base_element + key_nr;
2068 static int get_next_dropped_element(struct PlayerInfo *player)
2070 return (player->inventory_size > 0 ?
2071 player->inventory_element[player->inventory_size - 1] :
2072 player->inventory_infinite_element != EL_UNDEFINED ?
2073 player->inventory_infinite_element :
2074 player->dynabombs_left > 0 ?
2075 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2079 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2081 // pos >= 0: get element from bottom of the stack;
2082 // pos < 0: get element from top of the stack
2086 int min_inventory_size = -pos;
2087 int inventory_pos = player->inventory_size - min_inventory_size;
2088 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2090 return (player->inventory_size >= min_inventory_size ?
2091 player->inventory_element[inventory_pos] :
2092 player->inventory_infinite_element != EL_UNDEFINED ?
2093 player->inventory_infinite_element :
2094 player->dynabombs_left >= min_dynabombs_left ?
2095 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2100 int min_dynabombs_left = pos + 1;
2101 int min_inventory_size = pos + 1 - player->dynabombs_left;
2102 int inventory_pos = pos - player->dynabombs_left;
2104 return (player->inventory_infinite_element != EL_UNDEFINED ?
2105 player->inventory_infinite_element :
2106 player->dynabombs_left >= min_dynabombs_left ?
2107 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 player->inventory_size >= min_inventory_size ?
2109 player->inventory_element[inventory_pos] :
2114 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2116 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2117 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2120 if (gpo1->sort_priority != gpo2->sort_priority)
2121 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2123 compare_result = gpo1->nr - gpo2->nr;
2125 return compare_result;
2128 int getPlayerInventorySize(int player_nr)
2130 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2131 return game_em.ply[player_nr]->dynamite;
2132 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2133 return game_sp.red_disk_count;
2135 return stored_player[player_nr].inventory_size;
2138 static void InitGameControlValues(void)
2142 for (i = 0; game_panel_controls[i].nr != -1; i++)
2144 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2145 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2146 struct TextPosInfo *pos = gpc->pos;
2148 int type = gpc->type;
2152 Error("'game_panel_controls' structure corrupted at %d", i);
2154 Fail("this should not happen -- please debug");
2157 // force update of game controls after initialization
2158 gpc->value = gpc->last_value = -1;
2159 gpc->frame = gpc->last_frame = -1;
2160 gpc->gfx_frame = -1;
2162 // determine panel value width for later calculation of alignment
2163 if (type == TYPE_INTEGER || type == TYPE_STRING)
2165 pos->width = pos->size * getFontWidth(pos->font);
2166 pos->height = getFontHeight(pos->font);
2168 else if (type == TYPE_ELEMENT)
2170 pos->width = pos->size;
2171 pos->height = pos->size;
2174 // fill structure for game panel draw order
2176 gpo->sort_priority = pos->sort_priority;
2179 // sort game panel controls according to sort_priority and control number
2180 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2181 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2184 static void UpdatePlayfieldElementCount(void)
2186 boolean use_element_count = FALSE;
2189 // first check if it is needed at all to calculate playfield element count
2190 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2191 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2192 use_element_count = TRUE;
2194 if (!use_element_count)
2197 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2198 element_info[i].element_count = 0;
2200 SCAN_PLAYFIELD(x, y)
2202 element_info[Tile[x][y]].element_count++;
2205 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2206 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2207 if (IS_IN_GROUP(j, i))
2208 element_info[EL_GROUP_START + i].element_count +=
2209 element_info[j].element_count;
2212 static void UpdateGameControlValues(void)
2215 int time = (game.LevelSolved ?
2216 game.LevelSolved_CountingTime :
2217 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2219 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2220 game_sp.time_played :
2221 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2222 game_mm.energy_left :
2223 game.no_time_limit ? TimePlayed : TimeLeft);
2224 int score = (game.LevelSolved ?
2225 game.LevelSolved_CountingScore :
2226 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 game_em.lev->score :
2228 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2230 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2233 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2234 game_em.lev->gems_needed :
2235 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2236 game_sp.infotrons_still_needed :
2237 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2238 game_mm.kettles_still_needed :
2239 game.gems_still_needed);
2240 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2241 game_em.lev->gems_needed > 0 :
2242 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2243 game_sp.infotrons_still_needed > 0 :
2244 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2245 game_mm.kettles_still_needed > 0 ||
2246 game_mm.lights_still_needed > 0 :
2247 game.gems_still_needed > 0 ||
2248 game.sokoban_fields_still_needed > 0 ||
2249 game.sokoban_objects_still_needed > 0 ||
2250 game.lights_still_needed > 0);
2251 int health = (game.LevelSolved ?
2252 game.LevelSolved_CountingHealth :
2253 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2254 MM_HEALTH(game_mm.laser_overload_value) :
2256 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2258 UpdatePlayfieldElementCount();
2260 // update game panel control values
2262 // used instead of "level_nr" (for network games)
2263 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2264 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2266 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2267 for (i = 0; i < MAX_NUM_KEYS; i++)
2268 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2269 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2270 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2272 if (game.centered_player_nr == -1)
2274 for (i = 0; i < MAX_PLAYERS; i++)
2276 // only one player in Supaplex game engine
2277 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2280 for (k = 0; k < MAX_NUM_KEYS; k++)
2282 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2284 if (game_em.ply[i]->keys & (1 << k))
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2288 else if (stored_player[i].key[k])
2289 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2290 get_key_element_from_nr(k);
2293 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2294 getPlayerInventorySize(i);
2296 if (stored_player[i].num_white_keys > 0)
2297 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2300 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2301 stored_player[i].num_white_keys;
2306 int player_nr = game.centered_player_nr;
2308 for (k = 0; k < MAX_NUM_KEYS; k++)
2310 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2312 if (game_em.ply[player_nr]->keys & (1 << k))
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2316 else if (stored_player[player_nr].key[k])
2317 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2318 get_key_element_from_nr(k);
2321 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2322 getPlayerInventorySize(player_nr);
2324 if (stored_player[player_nr].num_white_keys > 0)
2325 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2327 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2328 stored_player[player_nr].num_white_keys;
2331 // re-arrange keys on game panel, if needed or if defined by style settings
2332 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2334 int nr = GAME_PANEL_KEY_1 + i;
2335 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2336 struct TextPosInfo *pos = gpc->pos;
2338 // skip check if key is not in the player's inventory
2339 if (gpc->value == EL_EMPTY)
2342 // check if keys should be arranged on panel from left to right
2343 if (pos->style == STYLE_LEFTMOST_POSITION)
2345 // check previous key positions (left from current key)
2346 for (k = 0; k < i; k++)
2348 int nr_new = GAME_PANEL_KEY_1 + k;
2350 if (game_panel_controls[nr_new].value == EL_EMPTY)
2352 game_panel_controls[nr_new].value = gpc->value;
2353 gpc->value = EL_EMPTY;
2360 // check if "undefined" keys can be placed at some other position
2361 if (pos->x == -1 && pos->y == -1)
2363 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2365 // 1st try: display key at the same position as normal or EM keys
2366 if (game_panel_controls[nr_new].value == EL_EMPTY)
2368 game_panel_controls[nr_new].value = gpc->value;
2372 // 2nd try: display key at the next free position in the key panel
2373 for (k = 0; k < STD_NUM_KEYS; k++)
2375 nr_new = GAME_PANEL_KEY_1 + k;
2377 if (game_panel_controls[nr_new].value == EL_EMPTY)
2379 game_panel_controls[nr_new].value = gpc->value;
2388 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2390 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2391 get_inventory_element_from_pos(local_player, i);
2392 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2393 get_inventory_element_from_pos(local_player, -i - 1);
2396 game_panel_controls[GAME_PANEL_SCORE].value = score;
2397 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2399 game_panel_controls[GAME_PANEL_TIME].value = time;
2401 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2402 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2403 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2405 if (level.time == 0)
2406 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2408 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2410 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2411 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2413 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2415 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2416 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2418 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2419 local_player->shield_normal_time_left;
2420 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2421 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2423 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2424 local_player->shield_deadly_time_left;
2426 game_panel_controls[GAME_PANEL_EXIT].value =
2427 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2429 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2430 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2431 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2432 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2433 EL_EMC_MAGIC_BALL_SWITCH);
2435 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2436 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2437 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2438 game.light_time_left;
2440 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2441 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2442 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2443 game.timegate_time_left;
2445 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2446 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2448 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2449 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2450 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2451 game.lenses_time_left;
2453 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2454 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2455 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2456 game.magnify_time_left;
2458 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2459 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2460 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2461 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2462 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2463 EL_BALLOON_SWITCH_NONE);
2465 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2466 local_player->dynabomb_count;
2467 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2468 local_player->dynabomb_size;
2469 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2470 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2472 game_panel_controls[GAME_PANEL_PENGUINS].value =
2473 game.friends_still_needed;
2475 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2476 game.sokoban_objects_still_needed;
2477 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2478 game.sokoban_fields_still_needed;
2480 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2481 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2483 for (i = 0; i < NUM_BELTS; i++)
2485 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2486 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2487 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2488 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2489 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2492 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2493 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2494 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2495 game.magic_wall_time_left;
2497 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2498 local_player->gravity;
2500 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2501 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2503 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2504 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2505 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2506 game.panel.element[i].id : EL_UNDEFINED);
2508 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2509 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2510 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2511 element_info[game.panel.element_count[i].id].element_count : 0);
2513 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2514 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2515 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2516 element_info[game.panel.ce_score[i].id].collect_score : 0);
2518 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2519 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2520 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2521 element_info[game.panel.ce_score_element[i].id].collect_score :
2524 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2525 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2526 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2528 // update game panel control frames
2530 for (i = 0; game_panel_controls[i].nr != -1; i++)
2532 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2534 if (gpc->type == TYPE_ELEMENT)
2536 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2538 int last_anim_random_frame = gfx.anim_random_frame;
2539 int element = gpc->value;
2540 int graphic = el2panelimg(element);
2541 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2542 sync_random_frame : INIT_GFX_RANDOM());
2544 if (gpc->value != gpc->last_value)
2547 gpc->gfx_random = init_gfx_random;
2553 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2554 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2555 gpc->gfx_random = init_gfx_random;
2558 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2559 gfx.anim_random_frame = gpc->gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2562 gpc->gfx_frame = element_info[element].collect_score;
2564 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = last_anim_random_frame;
2570 else if (gpc->type == TYPE_GRAPHIC)
2572 if (gpc->graphic != IMG_UNDEFINED)
2574 int last_anim_random_frame = gfx.anim_random_frame;
2575 int graphic = gpc->graphic;
2576 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2577 sync_random_frame : INIT_GFX_RANDOM());
2579 if (gpc->value != gpc->last_value)
2582 gpc->gfx_random = init_gfx_random;
2588 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2589 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2590 gpc->gfx_random = init_gfx_random;
2593 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2594 gfx.anim_random_frame = gpc->gfx_random;
2596 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2598 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2599 gfx.anim_random_frame = last_anim_random_frame;
2605 static void DisplayGameControlValues(void)
2607 boolean redraw_panel = FALSE;
2610 for (i = 0; game_panel_controls[i].nr != -1; i++)
2612 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2614 if (PANEL_DEACTIVATED(gpc->pos))
2617 if (gpc->value == gpc->last_value &&
2618 gpc->frame == gpc->last_frame)
2621 redraw_panel = TRUE;
2627 // copy default game door content to main double buffer
2629 // !!! CHECK AGAIN !!!
2630 SetPanelBackground();
2631 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2632 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2634 // redraw game control buttons
2635 RedrawGameButtons();
2637 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2639 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2641 int nr = game_panel_order[i].nr;
2642 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2643 struct TextPosInfo *pos = gpc->pos;
2644 int type = gpc->type;
2645 int value = gpc->value;
2646 int frame = gpc->frame;
2647 int size = pos->size;
2648 int font = pos->font;
2649 boolean draw_masked = pos->draw_masked;
2650 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2652 if (PANEL_DEACTIVATED(pos))
2655 if (pos->class == get_hash_from_key("extra_panel_items") &&
2656 !setup.prefer_extra_panel_items)
2659 gpc->last_value = value;
2660 gpc->last_frame = frame;
2662 if (type == TYPE_INTEGER)
2664 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2665 nr == GAME_PANEL_TIME)
2667 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2669 if (use_dynamic_size) // use dynamic number of digits
2671 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2672 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2673 int size2 = size1 + 1;
2674 int font1 = pos->font;
2675 int font2 = pos->font_alt;
2677 size = (value < value_change ? size1 : size2);
2678 font = (value < value_change ? font1 : font2);
2682 // correct text size if "digits" is zero or less
2684 size = strlen(int2str(value, size));
2686 // dynamically correct text alignment
2687 pos->width = size * getFontWidth(font);
2689 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2690 int2str(value, size), font, mask_mode);
2692 else if (type == TYPE_ELEMENT)
2694 int element, graphic;
2698 int dst_x = PANEL_XPOS(pos);
2699 int dst_y = PANEL_YPOS(pos);
2701 if (value != EL_UNDEFINED && value != EL_EMPTY)
2704 graphic = el2panelimg(value);
2707 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2708 element, EL_NAME(element), size);
2711 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2714 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2717 width = graphic_info[graphic].width * size / TILESIZE;
2718 height = graphic_info[graphic].height * size / TILESIZE;
2721 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2724 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2728 else if (type == TYPE_GRAPHIC)
2730 int graphic = gpc->graphic;
2731 int graphic_active = gpc->graphic_active;
2735 int dst_x = PANEL_XPOS(pos);
2736 int dst_y = PANEL_YPOS(pos);
2737 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2738 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2740 if (graphic != IMG_UNDEFINED && !skip)
2742 if (pos->style == STYLE_REVERSE)
2743 value = 100 - value;
2745 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2747 if (pos->direction & MV_HORIZONTAL)
2749 width = graphic_info[graphic_active].width * value / 100;
2750 height = graphic_info[graphic_active].height;
2752 if (pos->direction == MV_LEFT)
2754 src_x += graphic_info[graphic_active].width - width;
2755 dst_x += graphic_info[graphic_active].width - width;
2760 width = graphic_info[graphic_active].width;
2761 height = graphic_info[graphic_active].height * value / 100;
2763 if (pos->direction == MV_UP)
2765 src_y += graphic_info[graphic_active].height - height;
2766 dst_y += graphic_info[graphic_active].height - height;
2771 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2774 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2777 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2779 if (pos->direction & MV_HORIZONTAL)
2781 if (pos->direction == MV_RIGHT)
2788 dst_x = PANEL_XPOS(pos);
2791 width = graphic_info[graphic].width - width;
2795 if (pos->direction == MV_DOWN)
2802 dst_y = PANEL_YPOS(pos);
2805 height = graphic_info[graphic].height - height;
2809 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2812 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2816 else if (type == TYPE_STRING)
2818 boolean active = (value != 0);
2819 char *state_normal = "off";
2820 char *state_active = "on";
2821 char *state = (active ? state_active : state_normal);
2822 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2823 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2824 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2825 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2827 if (nr == GAME_PANEL_GRAVITY_STATE)
2829 int font1 = pos->font; // (used for normal state)
2830 int font2 = pos->font_alt; // (used for active state)
2832 font = (active ? font2 : font1);
2841 // don't truncate output if "chars" is zero or less
2844 // dynamically correct text alignment
2845 pos->width = size * getFontWidth(font);
2848 s_cut = getStringCopyN(s, size);
2850 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2851 s_cut, font, mask_mode);
2857 redraw_mask |= REDRAW_DOOR_1;
2860 SetGameStatus(GAME_MODE_PLAYING);
2863 void UpdateAndDisplayGameControlValues(void)
2865 if (tape.deactivate_display)
2868 UpdateGameControlValues();
2869 DisplayGameControlValues();
2872 void UpdateGameDoorValues(void)
2874 UpdateGameControlValues();
2877 void DrawGameDoorValues(void)
2879 DisplayGameControlValues();
2883 // ============================================================================
2885 // ----------------------------------------------------------------------------
2886 // initialize game engine due to level / tape version number
2887 // ============================================================================
2889 static void InitGameEngine(void)
2891 int i, j, k, l, x, y;
2893 // set game engine from tape file when re-playing, else from level file
2894 game.engine_version = (tape.playing ? tape.engine_version :
2895 level.game_version);
2897 // set single or multi-player game mode (needed for re-playing tapes)
2898 game.team_mode = setup.team_mode;
2902 int num_players = 0;
2904 for (i = 0; i < MAX_PLAYERS; i++)
2905 if (tape.player_participates[i])
2908 // multi-player tapes contain input data for more than one player
2909 game.team_mode = (num_players > 1);
2913 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2914 level.game_version);
2915 Debug("game:init:level", " tape.file_version == %06d",
2917 Debug("game:init:level", " tape.game_version == %06d",
2919 Debug("game:init:level", " tape.engine_version == %06d",
2920 tape.engine_version);
2921 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2922 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2925 // --------------------------------------------------------------------------
2926 // set flags for bugs and changes according to active game engine version
2927 // --------------------------------------------------------------------------
2931 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2933 Bug was introduced in version:
2936 Bug was fixed in version:
2940 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2941 but the property "can fall" was missing, which caused some levels to be
2942 unsolvable. This was fixed in version 4.2.0.0.
2944 Affected levels/tapes:
2945 An example for a tape that was fixed by this bugfix is tape 029 from the
2946 level set "rnd_sam_bateman".
2947 The wrong behaviour will still be used for all levels or tapes that were
2948 created/recorded with it. An example for this is tape 023 from the level
2949 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2952 boolean use_amoeba_dropping_cannot_fall_bug =
2953 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2954 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2956 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2957 tape.game_version < VERSION_IDENT(4,2,0,0)));
2960 Summary of bugfix/change:
2961 Fixed move speed of elements entering or leaving magic wall.
2963 Fixed/changed in version:
2967 Before 2.0.1, move speed of elements entering or leaving magic wall was
2968 twice as fast as it is now.
2969 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2971 Affected levels/tapes:
2972 The first condition is generally needed for all levels/tapes before version
2973 2.0.1, which might use the old behaviour before it was changed; known tapes
2974 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2975 The second condition is an exception from the above case and is needed for
2976 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2977 above, but before it was known that this change would break tapes like the
2978 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2979 although the engine version while recording maybe was before 2.0.1. There
2980 are a lot of tapes that are affected by this exception, like tape 006 from
2981 the level set "rnd_conor_mancone".
2984 boolean use_old_move_stepsize_for_magic_wall =
2985 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2987 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2988 tape.game_version < VERSION_IDENT(4,2,0,0)));
2991 Summary of bugfix/change:
2992 Fixed handling for custom elements that change when pushed by the player.
2994 Fixed/changed in version:
2998 Before 3.1.0, custom elements that "change when pushing" changed directly
2999 after the player started pushing them (until then handled in "DigField()").
3000 Since 3.1.0, these custom elements are not changed until the "pushing"
3001 move of the element is finished (now handled in "ContinueMoving()").
3003 Affected levels/tapes:
3004 The first condition is generally needed for all levels/tapes before version
3005 3.1.0, which might use the old behaviour before it was changed; known tapes
3006 that are affected are some tapes from the level set "Walpurgis Gardens" by
3008 The second condition is an exception from the above case and is needed for
3009 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3010 above (including some development versions of 3.1.0), but before it was
3011 known that this change would break tapes like the above and was fixed in
3012 3.1.1, so that the changed behaviour was active although the engine version
3013 while recording maybe was before 3.1.0. There is at least one tape that is
3014 affected by this exception, which is the tape for the one-level set "Bug
3015 Machine" by Juergen Bonhagen.
3018 game.use_change_when_pushing_bug =
3019 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3021 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3022 tape.game_version < VERSION_IDENT(3,1,1,0)));
3025 Summary of bugfix/change:
3026 Fixed handling for blocking the field the player leaves when moving.
3028 Fixed/changed in version:
3032 Before 3.1.1, when "block last field when moving" was enabled, the field
3033 the player is leaving when moving was blocked for the time of the move,
3034 and was directly unblocked afterwards. This resulted in the last field
3035 being blocked for exactly one less than the number of frames of one player
3036 move. Additionally, even when blocking was disabled, the last field was
3037 blocked for exactly one frame.
3038 Since 3.1.1, due to changes in player movement handling, the last field
3039 is not blocked at all when blocking is disabled. When blocking is enabled,
3040 the last field is blocked for exactly the number of frames of one player
3041 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3042 last field is blocked for exactly one more than the number of frames of
3045 Affected levels/tapes:
3046 (!!! yet to be determined -- probably many !!!)
3049 game.use_block_last_field_bug =
3050 (game.engine_version < VERSION_IDENT(3,1,1,0));
3052 /* various special flags and settings for native Emerald Mine game engine */
3054 game_em.use_single_button =
3055 (game.engine_version > VERSION_IDENT(4,0,0,2));
3057 game_em.use_snap_key_bug =
3058 (game.engine_version < VERSION_IDENT(4,0,1,0));
3060 game_em.use_random_bug =
3061 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3063 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3065 game_em.use_old_explosions = use_old_em_engine;
3066 game_em.use_old_android = use_old_em_engine;
3067 game_em.use_old_push_elements = use_old_em_engine;
3068 game_em.use_old_push_into_acid = use_old_em_engine;
3070 game_em.use_wrap_around = !use_old_em_engine;
3072 // --------------------------------------------------------------------------
3074 // set maximal allowed number of custom element changes per game frame
3075 game.max_num_changes_per_frame = 1;
3077 // default scan direction: scan playfield from top/left to bottom/right
3078 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3080 // dynamically adjust element properties according to game engine version
3081 InitElementPropertiesEngine(game.engine_version);
3083 // ---------- initialize special element properties -------------------------
3085 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3086 if (use_amoeba_dropping_cannot_fall_bug)
3087 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3089 // ---------- initialize player's initial move delay ------------------------
3091 // dynamically adjust player properties according to level information
3092 for (i = 0; i < MAX_PLAYERS; i++)
3093 game.initial_move_delay_value[i] =
3094 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3096 // dynamically adjust player properties according to game engine version
3097 for (i = 0; i < MAX_PLAYERS; i++)
3098 game.initial_move_delay[i] =
3099 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3100 game.initial_move_delay_value[i] : 0);
3102 // ---------- initialize player's initial push delay ------------------------
3104 // dynamically adjust player properties according to game engine version
3105 game.initial_push_delay_value =
3106 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3108 // ---------- initialize changing elements ----------------------------------
3110 // initialize changing elements information
3111 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3113 struct ElementInfo *ei = &element_info[i];
3115 // this pointer might have been changed in the level editor
3116 ei->change = &ei->change_page[0];
3118 if (!IS_CUSTOM_ELEMENT(i))
3120 ei->change->target_element = EL_EMPTY_SPACE;
3121 ei->change->delay_fixed = 0;
3122 ei->change->delay_random = 0;
3123 ei->change->delay_frames = 1;
3126 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3128 ei->has_change_event[j] = FALSE;
3130 ei->event_page_nr[j] = 0;
3131 ei->event_page[j] = &ei->change_page[0];
3135 // add changing elements from pre-defined list
3136 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3138 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3139 struct ElementInfo *ei = &element_info[ch_delay->element];
3141 ei->change->target_element = ch_delay->target_element;
3142 ei->change->delay_fixed = ch_delay->change_delay;
3144 ei->change->pre_change_function = ch_delay->pre_change_function;
3145 ei->change->change_function = ch_delay->change_function;
3146 ei->change->post_change_function = ch_delay->post_change_function;
3148 ei->change->can_change = TRUE;
3149 ei->change->can_change_or_has_action = TRUE;
3151 ei->has_change_event[CE_DELAY] = TRUE;
3153 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3154 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3157 // ---------- initialize internal run-time variables ------------------------
3159 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3161 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3163 for (j = 0; j < ei->num_change_pages; j++)
3165 ei->change_page[j].can_change_or_has_action =
3166 (ei->change_page[j].can_change |
3167 ei->change_page[j].has_action);
3171 // add change events from custom element configuration
3172 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3174 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3176 for (j = 0; j < ei->num_change_pages; j++)
3178 if (!ei->change_page[j].can_change_or_has_action)
3181 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3183 // only add event page for the first page found with this event
3184 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3186 ei->has_change_event[k] = TRUE;
3188 ei->event_page_nr[k] = j;
3189 ei->event_page[k] = &ei->change_page[j];
3195 // ---------- initialize reference elements in change conditions ------------
3197 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3199 int element = EL_CUSTOM_START + i;
3200 struct ElementInfo *ei = &element_info[element];
3202 for (j = 0; j < ei->num_change_pages; j++)
3204 int trigger_element = ei->change_page[j].initial_trigger_element;
3206 if (trigger_element >= EL_PREV_CE_8 &&
3207 trigger_element <= EL_NEXT_CE_8)
3208 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3210 ei->change_page[j].trigger_element = trigger_element;
3214 // ---------- initialize run-time trigger player and element ----------------
3216 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3218 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3220 for (j = 0; j < ei->num_change_pages; j++)
3222 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3223 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3224 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3225 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3226 ei->change_page[j].actual_trigger_ce_value = 0;
3227 ei->change_page[j].actual_trigger_ce_score = 0;
3231 // ---------- initialize trigger events -------------------------------------
3233 // initialize trigger events information
3234 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3235 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3236 trigger_events[i][j] = FALSE;
3238 // add trigger events from element change event properties
3239 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3241 struct ElementInfo *ei = &element_info[i];
3243 for (j = 0; j < ei->num_change_pages; j++)
3245 if (!ei->change_page[j].can_change_or_has_action)
3248 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3250 int trigger_element = ei->change_page[j].trigger_element;
3252 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3254 if (ei->change_page[j].has_event[k])
3256 if (IS_GROUP_ELEMENT(trigger_element))
3258 struct ElementGroupInfo *group =
3259 element_info[trigger_element].group;
3261 for (l = 0; l < group->num_elements_resolved; l++)
3262 trigger_events[group->element_resolved[l]][k] = TRUE;
3264 else if (trigger_element == EL_ANY_ELEMENT)
3265 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3266 trigger_events[l][k] = TRUE;
3268 trigger_events[trigger_element][k] = TRUE;
3275 // ---------- initialize push delay -----------------------------------------
3277 // initialize push delay values to default
3278 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3280 if (!IS_CUSTOM_ELEMENT(i))
3282 // set default push delay values (corrected since version 3.0.7-1)
3283 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3285 element_info[i].push_delay_fixed = 2;
3286 element_info[i].push_delay_random = 8;
3290 element_info[i].push_delay_fixed = 8;
3291 element_info[i].push_delay_random = 8;
3296 // set push delay value for certain elements from pre-defined list
3297 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3299 int e = push_delay_list[i].element;
3301 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3302 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3305 // set push delay value for Supaplex elements for newer engine versions
3306 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3308 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3310 if (IS_SP_ELEMENT(i))
3312 // set SP push delay to just enough to push under a falling zonk
3313 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3315 element_info[i].push_delay_fixed = delay;
3316 element_info[i].push_delay_random = 0;
3321 // ---------- initialize move stepsize --------------------------------------
3323 // initialize move stepsize values to default
3324 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3325 if (!IS_CUSTOM_ELEMENT(i))
3326 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3328 // set move stepsize value for certain elements from pre-defined list
3329 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3331 int e = move_stepsize_list[i].element;
3333 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3335 // set move stepsize value for certain elements for older engine versions
3336 if (use_old_move_stepsize_for_magic_wall)
3338 if (e == EL_MAGIC_WALL_FILLING ||
3339 e == EL_MAGIC_WALL_EMPTYING ||
3340 e == EL_BD_MAGIC_WALL_FILLING ||
3341 e == EL_BD_MAGIC_WALL_EMPTYING)
3342 element_info[e].move_stepsize *= 2;
3346 // ---------- initialize collect score --------------------------------------
3348 // initialize collect score values for custom elements from initial value
3349 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3350 if (IS_CUSTOM_ELEMENT(i))
3351 element_info[i].collect_score = element_info[i].collect_score_initial;
3353 // ---------- initialize collect count --------------------------------------
3355 // initialize collect count values for non-custom elements
3356 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3357 if (!IS_CUSTOM_ELEMENT(i))
3358 element_info[i].collect_count_initial = 0;
3360 // add collect count values for all elements from pre-defined list
3361 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3362 element_info[collect_count_list[i].element].collect_count_initial =
3363 collect_count_list[i].count;
3365 // ---------- initialize access direction -----------------------------------
3367 // initialize access direction values to default (access from every side)
3368 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3369 if (!IS_CUSTOM_ELEMENT(i))
3370 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3372 // set access direction value for certain elements from pre-defined list
3373 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3374 element_info[access_direction_list[i].element].access_direction =
3375 access_direction_list[i].direction;
3377 // ---------- initialize explosion content ----------------------------------
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (IS_CUSTOM_ELEMENT(i))
3383 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3385 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3387 element_info[i].content.e[x][y] =
3388 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3389 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3390 i == EL_PLAYER_3 ? EL_EMERALD :
3391 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3392 i == EL_MOLE ? EL_EMERALD_RED :
3393 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3394 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3395 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3396 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3397 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3398 i == EL_WALL_EMERALD ? EL_EMERALD :
3399 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3400 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3401 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3402 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3403 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3404 i == EL_WALL_PEARL ? EL_PEARL :
3405 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3410 // ---------- initialize recursion detection --------------------------------
3411 recursion_loop_depth = 0;
3412 recursion_loop_detected = FALSE;
3413 recursion_loop_element = EL_UNDEFINED;
3415 // ---------- initialize graphics engine ------------------------------------
3416 game.scroll_delay_value =
3417 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3418 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3419 !setup.forced_scroll_delay ? 0 :
3420 setup.scroll_delay ? setup.scroll_delay_value : 0);
3421 game.scroll_delay_value =
3422 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3424 // ---------- initialize game engine snapshots ------------------------------
3425 for (i = 0; i < MAX_PLAYERS; i++)
3426 game.snapshot.last_action[i] = 0;
3427 game.snapshot.changed_action = FALSE;
3428 game.snapshot.collected_item = FALSE;
3429 game.snapshot.mode =
3430 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3431 SNAPSHOT_MODE_EVERY_STEP :
3432 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3433 SNAPSHOT_MODE_EVERY_MOVE :
3434 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3435 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3436 game.snapshot.save_snapshot = FALSE;
3438 // ---------- initialize level time for Supaplex engine ---------------------
3439 // Supaplex levels with time limit currently unsupported -- should be added
3440 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3443 // ---------- initialize flags for handling game actions --------------------
3445 // set flags for game actions to default values
3446 game.use_key_actions = TRUE;
3447 game.use_mouse_actions = FALSE;
3449 // when using Mirror Magic game engine, handle mouse events only
3450 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3452 game.use_key_actions = FALSE;
3453 game.use_mouse_actions = TRUE;
3456 // check for custom elements with mouse click events
3457 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3459 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3461 int element = EL_CUSTOM_START + i;
3463 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3464 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3465 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3466 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3467 game.use_mouse_actions = TRUE;
3472 static int get_num_special_action(int element, int action_first,
3475 int num_special_action = 0;
3478 for (i = action_first; i <= action_last; i++)
3480 boolean found = FALSE;
3482 for (j = 0; j < NUM_DIRECTIONS; j++)
3483 if (el_act_dir2img(element, i, j) !=
3484 el_act_dir2img(element, ACTION_DEFAULT, j))
3488 num_special_action++;
3493 return num_special_action;
3497 // ============================================================================
3499 // ----------------------------------------------------------------------------
3500 // initialize and start new game
3501 // ============================================================================
3503 #if DEBUG_INIT_PLAYER
3504 static void DebugPrintPlayerStatus(char *message)
3511 Debug("game:init:player", "%s:", message);
3513 for (i = 0; i < MAX_PLAYERS; i++)
3515 struct PlayerInfo *player = &stored_player[i];
3517 Debug("game:init:player",
3518 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3522 player->connected_locally,
3523 player->connected_network,
3525 (local_player == player ? " (local player)" : ""));
3532 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3533 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3534 int fade_mask = REDRAW_FIELD;
3536 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3537 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3538 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3539 int initial_move_dir = MV_DOWN;
3542 // required here to update video display before fading (FIX THIS)
3543 DrawMaskedBorder(REDRAW_DOOR_2);
3545 if (!game.restart_level)
3546 CloseDoor(DOOR_CLOSE_1);
3548 SetGameStatus(GAME_MODE_PLAYING);
3550 if (level_editor_test_game)
3551 FadeSkipNextFadeOut();
3553 FadeSetEnterScreen();
3556 fade_mask = REDRAW_ALL;
3558 FadeLevelSoundsAndMusic();
3560 ExpireSoundLoops(TRUE);
3564 if (level_editor_test_game)
3565 FadeSkipNextFadeIn();
3567 // needed if different viewport properties defined for playing
3568 ChangeViewportPropertiesIfNeeded();
3572 DrawCompleteVideoDisplay();
3574 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3577 InitGameControlValues();
3581 // initialize tape actions from game when recording tape
3582 tape.use_key_actions = game.use_key_actions;
3583 tape.use_mouse_actions = game.use_mouse_actions;
3585 // initialize visible playfield size when recording tape (for team mode)
3586 tape.scr_fieldx = SCR_FIELDX;
3587 tape.scr_fieldy = SCR_FIELDY;
3590 // don't play tapes over network
3591 network_playing = (network.enabled && !tape.playing);
3593 for (i = 0; i < MAX_PLAYERS; i++)
3595 struct PlayerInfo *player = &stored_player[i];
3597 player->index_nr = i;
3598 player->index_bit = (1 << i);
3599 player->element_nr = EL_PLAYER_1 + i;
3601 player->present = FALSE;
3602 player->active = FALSE;
3603 player->mapped = FALSE;
3605 player->killed = FALSE;
3606 player->reanimated = FALSE;
3607 player->buried = FALSE;
3610 player->effective_action = 0;
3611 player->programmed_action = 0;
3612 player->snap_action = 0;
3614 player->mouse_action.lx = 0;
3615 player->mouse_action.ly = 0;
3616 player->mouse_action.button = 0;
3617 player->mouse_action.button_hint = 0;
3619 player->effective_mouse_action.lx = 0;
3620 player->effective_mouse_action.ly = 0;
3621 player->effective_mouse_action.button = 0;
3622 player->effective_mouse_action.button_hint = 0;
3624 for (j = 0; j < MAX_NUM_KEYS; j++)
3625 player->key[j] = FALSE;
3627 player->num_white_keys = 0;
3629 player->dynabomb_count = 0;
3630 player->dynabomb_size = 1;
3631 player->dynabombs_left = 0;
3632 player->dynabomb_xl = FALSE;
3634 player->MovDir = initial_move_dir;
3637 player->GfxDir = initial_move_dir;
3638 player->GfxAction = ACTION_DEFAULT;
3640 player->StepFrame = 0;
3642 player->initial_element = player->element_nr;
3643 player->artwork_element =
3644 (level.use_artwork_element[i] ? level.artwork_element[i] :
3645 player->element_nr);
3646 player->use_murphy = FALSE;
3648 player->block_last_field = FALSE; // initialized in InitPlayerField()
3649 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3651 player->gravity = level.initial_player_gravity[i];
3653 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3655 player->actual_frame_counter = 0;
3657 player->step_counter = 0;
3659 player->last_move_dir = initial_move_dir;
3661 player->is_active = FALSE;
3663 player->is_waiting = FALSE;
3664 player->is_moving = FALSE;
3665 player->is_auto_moving = FALSE;
3666 player->is_digging = FALSE;
3667 player->is_snapping = FALSE;
3668 player->is_collecting = FALSE;
3669 player->is_pushing = FALSE;
3670 player->is_switching = FALSE;
3671 player->is_dropping = FALSE;
3672 player->is_dropping_pressed = FALSE;
3674 player->is_bored = FALSE;
3675 player->is_sleeping = FALSE;
3677 player->was_waiting = TRUE;
3678 player->was_moving = FALSE;
3679 player->was_snapping = FALSE;
3680 player->was_dropping = FALSE;
3682 player->force_dropping = FALSE;
3684 player->frame_counter_bored = -1;
3685 player->frame_counter_sleeping = -1;
3687 player->anim_delay_counter = 0;
3688 player->post_delay_counter = 0;
3690 player->dir_waiting = initial_move_dir;
3691 player->action_waiting = ACTION_DEFAULT;
3692 player->last_action_waiting = ACTION_DEFAULT;
3693 player->special_action_bored = ACTION_DEFAULT;
3694 player->special_action_sleeping = ACTION_DEFAULT;
3696 player->switch_x = -1;
3697 player->switch_y = -1;
3699 player->drop_x = -1;
3700 player->drop_y = -1;
3702 player->show_envelope = 0;
3704 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3706 player->push_delay = -1; // initialized when pushing starts
3707 player->push_delay_value = game.initial_push_delay_value;
3709 player->drop_delay = 0;
3710 player->drop_pressed_delay = 0;
3712 player->last_jx = -1;
3713 player->last_jy = -1;
3717 player->shield_normal_time_left = 0;
3718 player->shield_deadly_time_left = 0;
3720 player->last_removed_element = EL_UNDEFINED;
3722 player->inventory_infinite_element = EL_UNDEFINED;
3723 player->inventory_size = 0;
3725 if (level.use_initial_inventory[i])
3727 for (j = 0; j < level.initial_inventory_size[i]; j++)
3729 int element = level.initial_inventory_content[i][j];
3730 int collect_count = element_info[element].collect_count_initial;
3733 if (!IS_CUSTOM_ELEMENT(element))
3736 if (collect_count == 0)
3737 player->inventory_infinite_element = element;
3739 for (k = 0; k < collect_count; k++)
3740 if (player->inventory_size < MAX_INVENTORY_SIZE)
3741 player->inventory_element[player->inventory_size++] = element;
3745 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3746 SnapField(player, 0, 0);
3748 map_player_action[i] = i;
3751 network_player_action_received = FALSE;
3753 // initial null action
3754 if (network_playing)
3755 SendToServer_MovePlayer(MV_NONE);
3760 TimeLeft = level.time;
3763 ScreenMovDir = MV_NONE;
3767 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3769 game.robot_wheel_x = -1;
3770 game.robot_wheel_y = -1;
3775 game.all_players_gone = FALSE;
3777 game.LevelSolved = FALSE;
3778 game.GameOver = FALSE;
3780 game.GamePlayed = !tape.playing;
3782 game.LevelSolved_GameWon = FALSE;
3783 game.LevelSolved_GameEnd = FALSE;
3784 game.LevelSolved_SaveTape = FALSE;
3785 game.LevelSolved_SaveScore = FALSE;
3787 game.LevelSolved_CountingTime = 0;
3788 game.LevelSolved_CountingScore = 0;
3789 game.LevelSolved_CountingHealth = 0;
3791 game.panel.active = TRUE;
3793 game.no_time_limit = (level.time == 0);
3795 game.yamyam_content_nr = 0;
3796 game.robot_wheel_active = FALSE;
3797 game.magic_wall_active = FALSE;
3798 game.magic_wall_time_left = 0;
3799 game.light_time_left = 0;
3800 game.timegate_time_left = 0;
3801 game.switchgate_pos = 0;
3802 game.wind_direction = level.wind_direction_initial;
3805 game.score_final = 0;
3807 game.health = MAX_HEALTH;
3808 game.health_final = MAX_HEALTH;
3810 game.gems_still_needed = level.gems_needed;
3811 game.sokoban_fields_still_needed = 0;
3812 game.sokoban_objects_still_needed = 0;
3813 game.lights_still_needed = 0;
3814 game.players_still_needed = 0;
3815 game.friends_still_needed = 0;
3817 game.lenses_time_left = 0;
3818 game.magnify_time_left = 0;
3820 game.ball_active = level.ball_active_initial;
3821 game.ball_content_nr = 0;
3823 game.explosions_delayed = TRUE;
3825 game.envelope_active = FALSE;
3827 for (i = 0; i < NUM_BELTS; i++)
3829 game.belt_dir[i] = MV_NONE;
3830 game.belt_dir_nr[i] = 3; // not moving, next moving left
3833 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3834 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3836 #if DEBUG_INIT_PLAYER
3837 DebugPrintPlayerStatus("Player status at level initialization");
3840 SCAN_PLAYFIELD(x, y)
3842 Tile[x][y] = Last[x][y] = level.field[x][y];
3843 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3844 ChangeDelay[x][y] = 0;
3845 ChangePage[x][y] = -1;
3846 CustomValue[x][y] = 0; // initialized in InitField()
3847 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3849 WasJustMoving[x][y] = 0;
3850 WasJustFalling[x][y] = 0;
3851 CheckCollision[x][y] = 0;
3852 CheckImpact[x][y] = 0;
3854 Pushed[x][y] = FALSE;
3856 ChangeCount[x][y] = 0;
3857 ChangeEvent[x][y] = -1;
3859 ExplodePhase[x][y] = 0;
3860 ExplodeDelay[x][y] = 0;
3861 ExplodeField[x][y] = EX_TYPE_NONE;
3863 RunnerVisit[x][y] = 0;
3864 PlayerVisit[x][y] = 0;
3867 GfxRandom[x][y] = INIT_GFX_RANDOM();
3868 GfxElement[x][y] = EL_UNDEFINED;
3869 GfxAction[x][y] = ACTION_DEFAULT;
3870 GfxDir[x][y] = MV_NONE;
3871 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3874 SCAN_PLAYFIELD(x, y)
3876 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3878 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3880 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3883 InitField(x, y, TRUE);
3885 ResetGfxAnimation(x, y);
3890 for (i = 0; i < MAX_PLAYERS; i++)
3892 struct PlayerInfo *player = &stored_player[i];
3894 // set number of special actions for bored and sleeping animation
3895 player->num_special_action_bored =
3896 get_num_special_action(player->artwork_element,
3897 ACTION_BORING_1, ACTION_BORING_LAST);
3898 player->num_special_action_sleeping =
3899 get_num_special_action(player->artwork_element,
3900 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3903 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3904 emulate_sb ? EMU_SOKOBAN :
3905 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3907 // initialize type of slippery elements
3908 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3910 if (!IS_CUSTOM_ELEMENT(i))
3912 // default: elements slip down either to the left or right randomly
3913 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3915 // SP style elements prefer to slip down on the left side
3916 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3917 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3919 // BD style elements prefer to slip down on the left side
3920 if (game.emulation == EMU_BOULDERDASH)
3921 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3925 // initialize explosion and ignition delay
3926 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3928 if (!IS_CUSTOM_ELEMENT(i))
3931 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3932 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3933 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3934 int last_phase = (num_phase + 1) * delay;
3935 int half_phase = (num_phase / 2) * delay;
3937 element_info[i].explosion_delay = last_phase - 1;
3938 element_info[i].ignition_delay = half_phase;
3940 if (i == EL_BLACK_ORB)
3941 element_info[i].ignition_delay = 1;
3945 // correct non-moving belts to start moving left
3946 for (i = 0; i < NUM_BELTS; i++)
3947 if (game.belt_dir[i] == MV_NONE)
3948 game.belt_dir_nr[i] = 3; // not moving, next moving left
3950 #if USE_NEW_PLAYER_ASSIGNMENTS
3951 // use preferred player also in local single-player mode
3952 if (!network.enabled && !game.team_mode)
3954 int new_index_nr = setup.network_player_nr;
3956 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3958 for (i = 0; i < MAX_PLAYERS; i++)
3959 stored_player[i].connected_locally = FALSE;
3961 stored_player[new_index_nr].connected_locally = TRUE;
3965 for (i = 0; i < MAX_PLAYERS; i++)
3967 stored_player[i].connected = FALSE;
3969 // in network game mode, the local player might not be the first player
3970 if (stored_player[i].connected_locally)
3971 local_player = &stored_player[i];
3974 if (!network.enabled)
3975 local_player->connected = TRUE;
3979 for (i = 0; i < MAX_PLAYERS; i++)
3980 stored_player[i].connected = tape.player_participates[i];
3982 else if (network.enabled)
3984 // add team mode players connected over the network (needed for correct
3985 // assignment of player figures from level to locally playing players)
3987 for (i = 0; i < MAX_PLAYERS; i++)
3988 if (stored_player[i].connected_network)
3989 stored_player[i].connected = TRUE;
3991 else if (game.team_mode)
3993 // try to guess locally connected team mode players (needed for correct
3994 // assignment of player figures from level to locally playing players)
3996 for (i = 0; i < MAX_PLAYERS; i++)
3997 if (setup.input[i].use_joystick ||
3998 setup.input[i].key.left != KSYM_UNDEFINED)
3999 stored_player[i].connected = TRUE;
4002 #if DEBUG_INIT_PLAYER
4003 DebugPrintPlayerStatus("Player status after level initialization");
4006 #if DEBUG_INIT_PLAYER
4007 Debug("game:init:player", "Reassigning players ...");
4010 // check if any connected player was not found in playfield
4011 for (i = 0; i < MAX_PLAYERS; i++)
4013 struct PlayerInfo *player = &stored_player[i];
4015 if (player->connected && !player->present)
4017 struct PlayerInfo *field_player = NULL;
4019 #if DEBUG_INIT_PLAYER
4020 Debug("game:init:player",
4021 "- looking for field player for player %d ...", i + 1);
4024 // assign first free player found that is present in the playfield
4026 // first try: look for unmapped playfield player that is not connected
4027 for (j = 0; j < MAX_PLAYERS; j++)
4028 if (field_player == NULL &&
4029 stored_player[j].present &&
4030 !stored_player[j].mapped &&
4031 !stored_player[j].connected)
4032 field_player = &stored_player[j];
4034 // second try: look for *any* unmapped playfield player
4035 for (j = 0; j < MAX_PLAYERS; j++)
4036 if (field_player == NULL &&
4037 stored_player[j].present &&
4038 !stored_player[j].mapped)
4039 field_player = &stored_player[j];
4041 if (field_player != NULL)
4043 int jx = field_player->jx, jy = field_player->jy;
4045 #if DEBUG_INIT_PLAYER
4046 Debug("game:init:player", "- found player %d",
4047 field_player->index_nr + 1);
4050 player->present = FALSE;
4051 player->active = FALSE;
4053 field_player->present = TRUE;
4054 field_player->active = TRUE;
4057 player->initial_element = field_player->initial_element;
4058 player->artwork_element = field_player->artwork_element;
4060 player->block_last_field = field_player->block_last_field;
4061 player->block_delay_adjustment = field_player->block_delay_adjustment;
4064 StorePlayer[jx][jy] = field_player->element_nr;
4066 field_player->jx = field_player->last_jx = jx;
4067 field_player->jy = field_player->last_jy = jy;
4069 if (local_player == player)
4070 local_player = field_player;
4072 map_player_action[field_player->index_nr] = i;
4074 field_player->mapped = TRUE;
4076 #if DEBUG_INIT_PLAYER
4077 Debug("game:init:player", "- map_player_action[%d] == %d",
4078 field_player->index_nr + 1, i + 1);
4083 if (player->connected && player->present)
4084 player->mapped = TRUE;
4087 #if DEBUG_INIT_PLAYER
4088 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4093 // check if any connected player was not found in playfield
4094 for (i = 0; i < MAX_PLAYERS; i++)
4096 struct PlayerInfo *player = &stored_player[i];
4098 if (player->connected && !player->present)
4100 for (j = 0; j < MAX_PLAYERS; j++)
4102 struct PlayerInfo *field_player = &stored_player[j];
4103 int jx = field_player->jx, jy = field_player->jy;
4105 // assign first free player found that is present in the playfield
4106 if (field_player->present && !field_player->connected)
4108 player->present = TRUE;
4109 player->active = TRUE;
4111 field_player->present = FALSE;
4112 field_player->active = FALSE;
4114 player->initial_element = field_player->initial_element;
4115 player->artwork_element = field_player->artwork_element;
4117 player->block_last_field = field_player->block_last_field;
4118 player->block_delay_adjustment = field_player->block_delay_adjustment;
4120 StorePlayer[jx][jy] = player->element_nr;
4122 player->jx = player->last_jx = jx;
4123 player->jy = player->last_jy = jy;
4133 Debug("game:init:player", "local_player->present == %d",
4134 local_player->present);
4137 // set focus to local player for network games, else to all players
4138 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4139 game.centered_player_nr_next = game.centered_player_nr;
4140 game.set_centered_player = FALSE;
4141 game.set_centered_player_wrap = FALSE;
4143 if (network_playing && tape.recording)
4145 // store client dependent player focus when recording network games
4146 tape.centered_player_nr_next = game.centered_player_nr_next;
4147 tape.set_centered_player = TRUE;
4152 // when playing a tape, eliminate all players who do not participate
4154 #if USE_NEW_PLAYER_ASSIGNMENTS
4156 if (!game.team_mode)
4158 for (i = 0; i < MAX_PLAYERS; i++)
4160 if (stored_player[i].active &&
4161 !tape.player_participates[map_player_action[i]])
4163 struct PlayerInfo *player = &stored_player[i];
4164 int jx = player->jx, jy = player->jy;
4166 #if DEBUG_INIT_PLAYER
4167 Debug("game:init:player", "Removing player %d at (%d, %d)",
4171 player->active = FALSE;
4172 StorePlayer[jx][jy] = 0;
4173 Tile[jx][jy] = EL_EMPTY;
4180 for (i = 0; i < MAX_PLAYERS; i++)
4182 if (stored_player[i].active &&
4183 !tape.player_participates[i])
4185 struct PlayerInfo *player = &stored_player[i];
4186 int jx = player->jx, jy = player->jy;
4188 player->active = FALSE;
4189 StorePlayer[jx][jy] = 0;
4190 Tile[jx][jy] = EL_EMPTY;
4195 else if (!network.enabled && !game.team_mode) // && !tape.playing
4197 // when in single player mode, eliminate all but the local player
4199 for (i = 0; i < MAX_PLAYERS; i++)
4201 struct PlayerInfo *player = &stored_player[i];
4203 if (player->active && player != local_player)
4205 int jx = player->jx, jy = player->jy;
4207 player->active = FALSE;
4208 player->present = FALSE;
4210 StorePlayer[jx][jy] = 0;
4211 Tile[jx][jy] = EL_EMPTY;
4216 for (i = 0; i < MAX_PLAYERS; i++)
4217 if (stored_player[i].active)
4218 game.players_still_needed++;
4220 if (level.solved_by_one_player)
4221 game.players_still_needed = 1;
4223 // when recording the game, store which players take part in the game
4226 #if USE_NEW_PLAYER_ASSIGNMENTS
4227 for (i = 0; i < MAX_PLAYERS; i++)
4228 if (stored_player[i].connected)
4229 tape.player_participates[i] = TRUE;
4231 for (i = 0; i < MAX_PLAYERS; i++)
4232 if (stored_player[i].active)
4233 tape.player_participates[i] = TRUE;
4237 #if DEBUG_INIT_PLAYER
4238 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4241 if (BorderElement == EL_EMPTY)
4244 SBX_Right = lev_fieldx - SCR_FIELDX;
4246 SBY_Lower = lev_fieldy - SCR_FIELDY;
4251 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4253 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4256 if (full_lev_fieldx <= SCR_FIELDX)
4257 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4258 if (full_lev_fieldy <= SCR_FIELDY)
4259 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4261 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4263 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4266 // if local player not found, look for custom element that might create
4267 // the player (make some assumptions about the right custom element)
4268 if (!local_player->present)
4270 int start_x = 0, start_y = 0;
4271 int found_rating = 0;
4272 int found_element = EL_UNDEFINED;
4273 int player_nr = local_player->index_nr;
4275 SCAN_PLAYFIELD(x, y)
4277 int element = Tile[x][y];
4282 if (level.use_start_element[player_nr] &&
4283 level.start_element[player_nr] == element &&
4290 found_element = element;
4293 if (!IS_CUSTOM_ELEMENT(element))
4296 if (CAN_CHANGE(element))
4298 for (i = 0; i < element_info[element].num_change_pages; i++)
4300 // check for player created from custom element as single target
4301 content = element_info[element].change_page[i].target_element;
4302 is_player = ELEM_IS_PLAYER(content);
4304 if (is_player && (found_rating < 3 ||
4305 (found_rating == 3 && element < found_element)))
4311 found_element = element;
4316 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4318 // check for player created from custom element as explosion content
4319 content = element_info[element].content.e[xx][yy];
4320 is_player = ELEM_IS_PLAYER(content);
4322 if (is_player && (found_rating < 2 ||
4323 (found_rating == 2 && element < found_element)))
4325 start_x = x + xx - 1;
4326 start_y = y + yy - 1;
4329 found_element = element;
4332 if (!CAN_CHANGE(element))
4335 for (i = 0; i < element_info[element].num_change_pages; i++)
4337 // check for player created from custom element as extended target
4339 element_info[element].change_page[i].target_content.e[xx][yy];
4341 is_player = ELEM_IS_PLAYER(content);
4343 if (is_player && (found_rating < 1 ||
4344 (found_rating == 1 && element < found_element)))
4346 start_x = x + xx - 1;
4347 start_y = y + yy - 1;
4350 found_element = element;
4356 scroll_x = SCROLL_POSITION_X(start_x);
4357 scroll_y = SCROLL_POSITION_Y(start_y);
4361 scroll_x = SCROLL_POSITION_X(local_player->jx);
4362 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4365 // !!! FIX THIS (START) !!!
4366 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4368 InitGameEngine_EM();
4370 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4372 InitGameEngine_SP();
4374 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4376 InitGameEngine_MM();
4380 DrawLevel(REDRAW_FIELD);
4383 // after drawing the level, correct some elements
4384 if (game.timegate_time_left == 0)
4385 CloseAllOpenTimegates();
4388 // blit playfield from scroll buffer to normal back buffer for fading in
4389 BlitScreenToBitmap(backbuffer);
4390 // !!! FIX THIS (END) !!!
4392 DrawMaskedBorder(fade_mask);
4397 // full screen redraw is required at this point in the following cases:
4398 // - special editor door undrawn when game was started from level editor
4399 // - drawing area (playfield) was changed and has to be removed completely
4400 redraw_mask = REDRAW_ALL;
4404 if (!game.restart_level)
4406 // copy default game door content to main double buffer
4408 // !!! CHECK AGAIN !!!
4409 SetPanelBackground();
4410 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4411 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4414 SetPanelBackground();
4415 SetDrawBackgroundMask(REDRAW_DOOR_1);
4417 UpdateAndDisplayGameControlValues();
4419 if (!game.restart_level)
4425 CreateGameButtons();
4430 // copy actual game door content to door double buffer for OpenDoor()
4431 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4433 OpenDoor(DOOR_OPEN_ALL);
4435 KeyboardAutoRepeatOffUnlessAutoplay();
4437 #if DEBUG_INIT_PLAYER
4438 DebugPrintPlayerStatus("Player status (final)");
4447 if (!game.restart_level && !tape.playing)
4449 LevelStats_incPlayed(level_nr);
4451 SaveLevelSetup_SeriesInfo();
4454 game.restart_level = FALSE;
4455 game.restart_game_message = NULL;
4457 game.request_active = FALSE;
4458 game.request_active_or_moving = FALSE;
4460 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4461 InitGameActions_MM();
4463 SaveEngineSnapshotToListInitial();
4465 if (!game.restart_level)
4467 PlaySound(SND_GAME_STARTING);
4469 if (setup.sound_music)
4474 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4475 int actual_player_x, int actual_player_y)
4477 // this is used for non-R'n'D game engines to update certain engine values
4479 // needed to determine if sounds are played within the visible screen area
4480 scroll_x = actual_scroll_x;
4481 scroll_y = actual_scroll_y;
4483 // needed to get player position for "follow finger" playing input method
4484 local_player->jx = actual_player_x;
4485 local_player->jy = actual_player_y;
4488 void InitMovDir(int x, int y)
4490 int i, element = Tile[x][y];
4491 static int xy[4][2] =
4498 static int direction[3][4] =
4500 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4501 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4502 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4511 Tile[x][y] = EL_BUG;
4512 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4515 case EL_SPACESHIP_RIGHT:
4516 case EL_SPACESHIP_UP:
4517 case EL_SPACESHIP_LEFT:
4518 case EL_SPACESHIP_DOWN:
4519 Tile[x][y] = EL_SPACESHIP;
4520 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4523 case EL_BD_BUTTERFLY_RIGHT:
4524 case EL_BD_BUTTERFLY_UP:
4525 case EL_BD_BUTTERFLY_LEFT:
4526 case EL_BD_BUTTERFLY_DOWN:
4527 Tile[x][y] = EL_BD_BUTTERFLY;
4528 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4531 case EL_BD_FIREFLY_RIGHT:
4532 case EL_BD_FIREFLY_UP:
4533 case EL_BD_FIREFLY_LEFT:
4534 case EL_BD_FIREFLY_DOWN:
4535 Tile[x][y] = EL_BD_FIREFLY;
4536 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4539 case EL_PACMAN_RIGHT:
4541 case EL_PACMAN_LEFT:
4542 case EL_PACMAN_DOWN:
4543 Tile[x][y] = EL_PACMAN;
4544 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4547 case EL_YAMYAM_LEFT:
4548 case EL_YAMYAM_RIGHT:
4550 case EL_YAMYAM_DOWN:
4551 Tile[x][y] = EL_YAMYAM;
4552 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4555 case EL_SP_SNIKSNAK:
4556 MovDir[x][y] = MV_UP;
4559 case EL_SP_ELECTRON:
4560 MovDir[x][y] = MV_LEFT;
4567 Tile[x][y] = EL_MOLE;
4568 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4571 case EL_SPRING_LEFT:
4572 case EL_SPRING_RIGHT:
4573 Tile[x][y] = EL_SPRING;
4574 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4578 if (IS_CUSTOM_ELEMENT(element))
4580 struct ElementInfo *ei = &element_info[element];
4581 int move_direction_initial = ei->move_direction_initial;
4582 int move_pattern = ei->move_pattern;
4584 if (move_direction_initial == MV_START_PREVIOUS)
4586 if (MovDir[x][y] != MV_NONE)
4589 move_direction_initial = MV_START_AUTOMATIC;
4592 if (move_direction_initial == MV_START_RANDOM)
4593 MovDir[x][y] = 1 << RND(4);
4594 else if (move_direction_initial & MV_ANY_DIRECTION)
4595 MovDir[x][y] = move_direction_initial;
4596 else if (move_pattern == MV_ALL_DIRECTIONS ||
4597 move_pattern == MV_TURNING_LEFT ||
4598 move_pattern == MV_TURNING_RIGHT ||
4599 move_pattern == MV_TURNING_LEFT_RIGHT ||
4600 move_pattern == MV_TURNING_RIGHT_LEFT ||
4601 move_pattern == MV_TURNING_RANDOM)
4602 MovDir[x][y] = 1 << RND(4);
4603 else if (move_pattern == MV_HORIZONTAL)
4604 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4605 else if (move_pattern == MV_VERTICAL)
4606 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4607 else if (move_pattern & MV_ANY_DIRECTION)
4608 MovDir[x][y] = element_info[element].move_pattern;
4609 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4610 move_pattern == MV_ALONG_RIGHT_SIDE)
4612 // use random direction as default start direction
4613 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4614 MovDir[x][y] = 1 << RND(4);
4616 for (i = 0; i < NUM_DIRECTIONS; i++)
4618 int x1 = x + xy[i][0];
4619 int y1 = y + xy[i][1];
4621 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4623 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4624 MovDir[x][y] = direction[0][i];
4626 MovDir[x][y] = direction[1][i];
4635 MovDir[x][y] = 1 << RND(4);
4637 if (element != EL_BUG &&
4638 element != EL_SPACESHIP &&
4639 element != EL_BD_BUTTERFLY &&
4640 element != EL_BD_FIREFLY)
4643 for (i = 0; i < NUM_DIRECTIONS; i++)
4645 int x1 = x + xy[i][0];
4646 int y1 = y + xy[i][1];
4648 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4650 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4652 MovDir[x][y] = direction[0][i];
4655 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4656 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4658 MovDir[x][y] = direction[1][i];
4667 GfxDir[x][y] = MovDir[x][y];
4670 void InitAmoebaNr(int x, int y)
4673 int group_nr = AmoebaNeighbourNr(x, y);
4677 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4679 if (AmoebaCnt[i] == 0)
4687 AmoebaNr[x][y] = group_nr;
4688 AmoebaCnt[group_nr]++;
4689 AmoebaCnt2[group_nr]++;
4692 static void LevelSolved(void)
4694 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4695 game.players_still_needed > 0)
4698 game.LevelSolved = TRUE;
4699 game.GameOver = TRUE;
4701 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4702 game_em.lev->score :
4703 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4706 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4707 MM_HEALTH(game_mm.laser_overload_value) :
4710 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4711 game.LevelSolved_CountingScore = game.score_final;
4712 game.LevelSolved_CountingHealth = game.health_final;
4717 static int time_count_steps;
4718 static int time, time_final;
4719 static float score, score_final; // needed for time score < 10 for 10 seconds
4720 static int health, health_final;
4721 static int game_over_delay_1 = 0;
4722 static int game_over_delay_2 = 0;
4723 static int game_over_delay_3 = 0;
4724 int game_over_delay_value_1 = 50;
4725 int game_over_delay_value_2 = 25;
4726 int game_over_delay_value_3 = 50;
4727 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4728 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4730 if (!game.LevelSolved_GameWon)
4734 // do not start end game actions before the player stops moving (to exit)
4735 if (local_player->active && local_player->MovPos)
4738 game.LevelSolved_GameWon = TRUE;
4739 game.LevelSolved_SaveTape = tape.recording;
4740 game.LevelSolved_SaveScore = !tape.playing;
4744 LevelStats_incSolved(level_nr);
4746 SaveLevelSetup_SeriesInfo();
4749 if (tape.auto_play) // tape might already be stopped here
4750 tape.auto_play_level_solved = TRUE;
4754 game_over_delay_1 = game_over_delay_value_1;
4755 game_over_delay_2 = game_over_delay_value_2;
4756 game_over_delay_3 = game_over_delay_value_3;
4758 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4759 score = score_final = game.score_final;
4760 health = health_final = game.health_final;
4764 int time_frames = 0;
4769 time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4771 else if (game.no_time_limit && TimePlayed < 999)
4774 time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
4777 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4779 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4781 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4784 score_final += health * time_score;
4787 game.score_final = score_final;
4788 game.health_final = health_final;
4791 if (level_editor_test_game || !setup.count_score_after_game)
4794 score = score_final;
4796 game.LevelSolved_CountingTime = time;
4797 game.LevelSolved_CountingScore = score;
4799 game_panel_controls[GAME_PANEL_TIME].value = time;
4800 game_panel_controls[GAME_PANEL_SCORE].value = score;
4802 DisplayGameControlValues();
4805 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4807 // check if last player has left the level
4808 if (game.exit_x >= 0 &&
4811 int x = game.exit_x;
4812 int y = game.exit_y;
4813 int element = Tile[x][y];
4815 // close exit door after last player
4816 if ((game.all_players_gone &&
4817 (element == EL_EXIT_OPEN ||
4818 element == EL_SP_EXIT_OPEN ||
4819 element == EL_STEEL_EXIT_OPEN)) ||
4820 element == EL_EM_EXIT_OPEN ||
4821 element == EL_EM_STEEL_EXIT_OPEN)
4825 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4826 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4827 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4828 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4829 EL_EM_STEEL_EXIT_CLOSING);
4831 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4834 // player disappears
4835 DrawLevelField(x, y);
4838 for (i = 0; i < MAX_PLAYERS; i++)
4840 struct PlayerInfo *player = &stored_player[i];
4842 if (player->present)
4844 RemovePlayer(player);
4846 // player disappears
4847 DrawLevelField(player->jx, player->jy);
4852 PlaySound(SND_GAME_WINNING);
4855 if (setup.count_score_after_game)
4857 if (time != time_final)
4859 if (game_over_delay_1 > 0)
4861 game_over_delay_1--;
4866 int time_to_go = ABS(time_final - time);
4867 int time_count_dir = (time < time_final ? +1 : -1);
4869 if (time_to_go < time_count_steps)
4870 time_count_steps = 1;
4872 time += time_count_steps * time_count_dir;
4873 score += time_count_steps * time_score;
4875 // set final score to correct rounding differences after counting score
4876 if (time == time_final)
4877 score = score_final;
4879 game.LevelSolved_CountingTime = time;
4880 game.LevelSolved_CountingScore = score;
4882 game_panel_controls[GAME_PANEL_TIME].value = time;
4883 game_panel_controls[GAME_PANEL_SCORE].value = score;
4885 DisplayGameControlValues();
4887 if (time == time_final)
4888 StopSound(SND_GAME_LEVELTIME_BONUS);
4889 else if (setup.sound_loops)
4890 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4892 PlaySound(SND_GAME_LEVELTIME_BONUS);
4897 if (health != health_final)
4899 if (game_over_delay_2 > 0)
4901 game_over_delay_2--;
4906 int health_count_dir = (health < health_final ? +1 : -1);
4908 health += health_count_dir;
4909 score += time_score;
4911 game.LevelSolved_CountingHealth = health;
4912 game.LevelSolved_CountingScore = score;
4914 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4915 game_panel_controls[GAME_PANEL_SCORE].value = score;
4917 DisplayGameControlValues();
4919 if (health == health_final)
4920 StopSound(SND_GAME_LEVELTIME_BONUS);
4921 else if (setup.sound_loops)
4922 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4924 PlaySound(SND_GAME_LEVELTIME_BONUS);
4930 game.panel.active = FALSE;
4932 if (game_over_delay_3 > 0)
4934 game_over_delay_3--;
4944 // used instead of "level_nr" (needed for network games)
4945 int last_level_nr = levelset.level_nr;
4948 game.LevelSolved_GameEnd = TRUE;
4950 if (game.LevelSolved_SaveTape)
4952 // make sure that request dialog to save tape does not open door again
4953 if (!global.use_envelope_request)
4954 CloseDoor(DOOR_CLOSE_1);
4956 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4959 // if no tape is to be saved, close both doors simultaneously
4960 CloseDoor(DOOR_CLOSE_ALL);
4962 if (level_editor_test_game)
4964 SetGameStatus(GAME_MODE_MAIN);
4971 if (!game.LevelSolved_SaveScore)
4973 SetGameStatus(GAME_MODE_MAIN);
4980 if (level_nr == leveldir_current->handicap_level)
4982 leveldir_current->handicap_level++;
4984 SaveLevelSetup_SeriesInfo();
4987 if (setup.increment_levels &&
4988 level_nr < leveldir_current->last_level &&
4991 level_nr++; // advance to next level
4992 TapeErase(); // start with empty tape
4994 if (setup.auto_play_next_level)
4996 LoadLevel(level_nr);
4998 SaveLevelSetup_SeriesInfo();
5002 hi_pos = NewHiScore(last_level_nr);
5004 if (hi_pos >= 0 && setup.show_scores_after_game)
5006 SetGameStatus(GAME_MODE_SCORES);
5008 DrawHallOfFame(last_level_nr, hi_pos);
5010 else if (setup.auto_play_next_level && setup.increment_levels &&
5011 last_level_nr < leveldir_current->last_level &&
5014 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5018 SetGameStatus(GAME_MODE_MAIN);
5024 int NewHiScore(int level_nr)
5028 boolean one_score_entry_per_name = !program.many_scores_per_name;
5030 LoadScore(level_nr);
5032 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5033 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5036 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5038 if (game.score_final > highscore[k].Score)
5040 // player has made it to the hall of fame
5042 if (k < MAX_SCORE_ENTRIES - 1)
5044 int m = MAX_SCORE_ENTRIES - 1;
5046 if (one_score_entry_per_name)
5048 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5049 if (strEqual(setup.player_name, highscore[l].Name))
5052 if (m == k) // player's new highscore overwrites his old one
5056 for (l = m; l > k; l--)
5058 strcpy(highscore[l].Name, highscore[l - 1].Name);
5059 highscore[l].Score = highscore[l - 1].Score;
5065 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5066 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5067 highscore[k].Score = game.score_final;
5072 else if (one_score_entry_per_name &&
5073 !strncmp(setup.player_name, highscore[k].Name,
5074 MAX_PLAYER_NAME_LEN))
5075 break; // player already there with a higher score
5079 SaveScore(level_nr);
5084 static int getElementMoveStepsizeExt(int x, int y, int direction)
5086 int element = Tile[x][y];
5087 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5088 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5089 int horiz_move = (dx != 0);
5090 int sign = (horiz_move ? dx : dy);
5091 int step = sign * element_info[element].move_stepsize;
5093 // special values for move stepsize for spring and things on conveyor belt
5096 if (CAN_FALL(element) &&
5097 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5098 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5099 else if (element == EL_SPRING)
5100 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5106 static int getElementMoveStepsize(int x, int y)
5108 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5111 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5113 if (player->GfxAction != action || player->GfxDir != dir)
5115 player->GfxAction = action;
5116 player->GfxDir = dir;
5118 player->StepFrame = 0;
5122 static void ResetGfxFrame(int x, int y)
5124 // profiling showed that "autotest" spends 10~20% of its time in this function
5125 if (DrawingDeactivatedField())
5128 int element = Tile[x][y];
5129 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5131 if (graphic_info[graphic].anim_global_sync)
5132 GfxFrame[x][y] = FrameCounter;
5133 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5134 GfxFrame[x][y] = CustomValue[x][y];
5135 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5136 GfxFrame[x][y] = element_info[element].collect_score;
5137 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5138 GfxFrame[x][y] = ChangeDelay[x][y];
5141 static void ResetGfxAnimation(int x, int y)
5143 GfxAction[x][y] = ACTION_DEFAULT;
5144 GfxDir[x][y] = MovDir[x][y];
5147 ResetGfxFrame(x, y);
5150 static void ResetRandomAnimationValue(int x, int y)
5152 GfxRandom[x][y] = INIT_GFX_RANDOM();
5155 static void InitMovingField(int x, int y, int direction)
5157 int element = Tile[x][y];
5158 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5159 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5162 boolean is_moving_before, is_moving_after;
5164 // check if element was/is moving or being moved before/after mode change
5165 is_moving_before = (WasJustMoving[x][y] != 0);
5166 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5168 // reset animation only for moving elements which change direction of moving
5169 // or which just started or stopped moving
5170 // (else CEs with property "can move" / "not moving" are reset each frame)
5171 if (is_moving_before != is_moving_after ||
5172 direction != MovDir[x][y])
5173 ResetGfxAnimation(x, y);
5175 MovDir[x][y] = direction;
5176 GfxDir[x][y] = direction;
5178 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5179 direction == MV_DOWN && CAN_FALL(element) ?
5180 ACTION_FALLING : ACTION_MOVING);
5182 // this is needed for CEs with property "can move" / "not moving"
5184 if (is_moving_after)
5186 if (Tile[newx][newy] == EL_EMPTY)
5187 Tile[newx][newy] = EL_BLOCKED;
5189 MovDir[newx][newy] = MovDir[x][y];
5191 CustomValue[newx][newy] = CustomValue[x][y];
5193 GfxFrame[newx][newy] = GfxFrame[x][y];
5194 GfxRandom[newx][newy] = GfxRandom[x][y];
5195 GfxAction[newx][newy] = GfxAction[x][y];
5196 GfxDir[newx][newy] = GfxDir[x][y];
5200 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5202 int direction = MovDir[x][y];
5203 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5204 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5210 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5212 int oldx = x, oldy = y;
5213 int direction = MovDir[x][y];
5215 if (direction == MV_LEFT)
5217 else if (direction == MV_RIGHT)
5219 else if (direction == MV_UP)
5221 else if (direction == MV_DOWN)
5224 *comes_from_x = oldx;
5225 *comes_from_y = oldy;
5228 static int MovingOrBlocked2Element(int x, int y)
5230 int element = Tile[x][y];
5232 if (element == EL_BLOCKED)
5236 Blocked2Moving(x, y, &oldx, &oldy);
5237 return Tile[oldx][oldy];
5243 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5245 // like MovingOrBlocked2Element(), but if element is moving
5246 // and (x,y) is the field the moving element is just leaving,
5247 // return EL_BLOCKED instead of the element value
5248 int element = Tile[x][y];
5250 if (IS_MOVING(x, y))
5252 if (element == EL_BLOCKED)
5256 Blocked2Moving(x, y, &oldx, &oldy);
5257 return Tile[oldx][oldy];
5266 static void RemoveField(int x, int y)
5268 Tile[x][y] = EL_EMPTY;
5274 CustomValue[x][y] = 0;
5277 ChangeDelay[x][y] = 0;
5278 ChangePage[x][y] = -1;
5279 Pushed[x][y] = FALSE;
5281 GfxElement[x][y] = EL_UNDEFINED;
5282 GfxAction[x][y] = ACTION_DEFAULT;
5283 GfxDir[x][y] = MV_NONE;
5286 static void RemoveMovingField(int x, int y)
5288 int oldx = x, oldy = y, newx = x, newy = y;
5289 int element = Tile[x][y];
5290 int next_element = EL_UNDEFINED;
5292 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5295 if (IS_MOVING(x, y))
5297 Moving2Blocked(x, y, &newx, &newy);
5299 if (Tile[newx][newy] != EL_BLOCKED)
5301 // element is moving, but target field is not free (blocked), but
5302 // already occupied by something different (example: acid pool);
5303 // in this case, only remove the moving field, but not the target
5305 RemoveField(oldx, oldy);
5307 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5309 TEST_DrawLevelField(oldx, oldy);
5314 else if (element == EL_BLOCKED)
5316 Blocked2Moving(x, y, &oldx, &oldy);
5317 if (!IS_MOVING(oldx, oldy))
5321 if (element == EL_BLOCKED &&
5322 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5323 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5324 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5325 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5326 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5327 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5328 next_element = get_next_element(Tile[oldx][oldy]);
5330 RemoveField(oldx, oldy);
5331 RemoveField(newx, newy);
5333 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5335 if (next_element != EL_UNDEFINED)
5336 Tile[oldx][oldy] = next_element;
5338 TEST_DrawLevelField(oldx, oldy);
5339 TEST_DrawLevelField(newx, newy);
5342 void DrawDynamite(int x, int y)
5344 int sx = SCREENX(x), sy = SCREENY(y);
5345 int graphic = el2img(Tile[x][y]);
5348 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5351 if (IS_WALKABLE_INSIDE(Back[x][y]))
5355 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5356 else if (Store[x][y])
5357 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5359 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5361 if (Back[x][y] || Store[x][y])
5362 DrawGraphicThruMask(sx, sy, graphic, frame);
5364 DrawGraphic(sx, sy, graphic, frame);
5367 static void CheckDynamite(int x, int y)
5369 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5373 if (MovDelay[x][y] != 0)
5376 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5382 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5387 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5389 boolean num_checked_players = 0;
5392 for (i = 0; i < MAX_PLAYERS; i++)
5394 if (stored_player[i].active)
5396 int sx = stored_player[i].jx;
5397 int sy = stored_player[i].jy;
5399 if (num_checked_players == 0)
5406 *sx1 = MIN(*sx1, sx);
5407 *sy1 = MIN(*sy1, sy);
5408 *sx2 = MAX(*sx2, sx);
5409 *sy2 = MAX(*sy2, sy);
5412 num_checked_players++;
5417 static boolean checkIfAllPlayersFitToScreen_RND(void)
5419 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5421 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5423 return (sx2 - sx1 < SCR_FIELDX &&
5424 sy2 - sy1 < SCR_FIELDY);
5427 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5429 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5431 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5433 *sx = (sx1 + sx2) / 2;
5434 *sy = (sy1 + sy2) / 2;
5437 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5438 boolean center_screen, boolean quick_relocation)
5440 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5441 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5442 boolean no_delay = (tape.warp_forward);
5443 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5444 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5445 int new_scroll_x, new_scroll_y;
5447 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5449 // case 1: quick relocation inside visible screen (without scrolling)
5456 if (!level.shifted_relocation || center_screen)
5458 // relocation _with_ centering of screen
5460 new_scroll_x = SCROLL_POSITION_X(x);
5461 new_scroll_y = SCROLL_POSITION_Y(y);
5465 // relocation _without_ centering of screen
5467 int center_scroll_x = SCROLL_POSITION_X(old_x);
5468 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5469 int offset_x = x + (scroll_x - center_scroll_x);
5470 int offset_y = y + (scroll_y - center_scroll_y);
5472 // for new screen position, apply previous offset to center position
5473 new_scroll_x = SCROLL_POSITION_X(offset_x);
5474 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5477 if (quick_relocation)
5479 // case 2: quick relocation (redraw without visible scrolling)
5481 scroll_x = new_scroll_x;
5482 scroll_y = new_scroll_y;
5489 // case 3: visible relocation (with scrolling to new position)
5491 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5493 SetVideoFrameDelay(wait_delay_value);
5495 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5497 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5498 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5500 if (dx == 0 && dy == 0) // no scrolling needed at all
5506 // set values for horizontal/vertical screen scrolling (half tile size)
5507 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5508 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5509 int pos_x = dx * TILEX / 2;
5510 int pos_y = dy * TILEY / 2;
5511 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5512 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5514 ScrollLevel(dx, dy);
5517 // scroll in two steps of half tile size to make things smoother
5518 BlitScreenToBitmapExt_RND(window, fx, fy);
5520 // scroll second step to align at full tile size
5521 BlitScreenToBitmap(window);
5527 SetVideoFrameDelay(frame_delay_value_old);
5530 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5532 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5533 int player_nr = GET_PLAYER_NR(el_player);
5534 struct PlayerInfo *player = &stored_player[player_nr];
5535 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5536 boolean no_delay = (tape.warp_forward);
5537 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5538 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5539 int old_jx = player->jx;
5540 int old_jy = player->jy;
5541 int old_element = Tile[old_jx][old_jy];
5542 int element = Tile[jx][jy];
5543 boolean player_relocated = (old_jx != jx || old_jy != jy);
5545 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5546 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5547 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5548 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5549 int leave_side_horiz = move_dir_horiz;
5550 int leave_side_vert = move_dir_vert;
5551 int enter_side = enter_side_horiz | enter_side_vert;
5552 int leave_side = leave_side_horiz | leave_side_vert;
5554 if (player->buried) // do not reanimate dead player
5557 if (!player_relocated) // no need to relocate the player
5560 if (IS_PLAYER(jx, jy)) // player already placed at new position
5562 RemoveField(jx, jy); // temporarily remove newly placed player
5563 DrawLevelField(jx, jy);
5566 if (player->present)
5568 while (player->MovPos)
5570 ScrollPlayer(player, SCROLL_GO_ON);
5571 ScrollScreen(NULL, SCROLL_GO_ON);
5573 AdvanceFrameAndPlayerCounters(player->index_nr);
5577 BackToFront_WithFrameDelay(wait_delay_value);
5580 DrawPlayer(player); // needed here only to cleanup last field
5581 DrawLevelField(player->jx, player->jy); // remove player graphic
5583 player->is_moving = FALSE;
5586 if (IS_CUSTOM_ELEMENT(old_element))
5587 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5589 player->index_bit, leave_side);
5591 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5593 player->index_bit, leave_side);
5595 Tile[jx][jy] = el_player;
5596 InitPlayerField(jx, jy, el_player, TRUE);
5598 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5599 possible that the relocation target field did not contain a player element,
5600 but a walkable element, to which the new player was relocated -- in this
5601 case, restore that (already initialized!) element on the player field */
5602 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5604 Tile[jx][jy] = element; // restore previously existing element
5607 // only visually relocate centered player
5608 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5609 FALSE, level.instant_relocation);
5611 TestIfPlayerTouchesBadThing(jx, jy);
5612 TestIfPlayerTouchesCustomElement(jx, jy);
5614 if (IS_CUSTOM_ELEMENT(element))
5615 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5616 player->index_bit, enter_side);
5618 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5619 player->index_bit, enter_side);
5621 if (player->is_switching)
5623 /* ensure that relocation while still switching an element does not cause
5624 a new element to be treated as also switched directly after relocation
5625 (this is important for teleporter switches that teleport the player to
5626 a place where another teleporter switch is in the same direction, which
5627 would then incorrectly be treated as immediately switched before the
5628 direction key that caused the switch was released) */
5630 player->switch_x += jx - old_jx;
5631 player->switch_y += jy - old_jy;
5635 static void Explode(int ex, int ey, int phase, int mode)
5641 // !!! eliminate this variable !!!
5642 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5644 if (game.explosions_delayed)
5646 ExplodeField[ex][ey] = mode;
5650 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5652 int center_element = Tile[ex][ey];
5653 int artwork_element, explosion_element; // set these values later
5655 // remove things displayed in background while burning dynamite
5656 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5659 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5661 // put moving element to center field (and let it explode there)
5662 center_element = MovingOrBlocked2Element(ex, ey);
5663 RemoveMovingField(ex, ey);
5664 Tile[ex][ey] = center_element;
5667 // now "center_element" is finally determined -- set related values now
5668 artwork_element = center_element; // for custom player artwork
5669 explosion_element = center_element; // for custom player artwork
5671 if (IS_PLAYER(ex, ey))
5673 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5675 artwork_element = stored_player[player_nr].artwork_element;
5677 if (level.use_explosion_element[player_nr])
5679 explosion_element = level.explosion_element[player_nr];
5680 artwork_element = explosion_element;
5684 if (mode == EX_TYPE_NORMAL ||
5685 mode == EX_TYPE_CENTER ||
5686 mode == EX_TYPE_CROSS)
5687 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5689 last_phase = element_info[explosion_element].explosion_delay + 1;
5691 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5693 int xx = x - ex + 1;
5694 int yy = y - ey + 1;
5697 if (!IN_LEV_FIELD(x, y) ||
5698 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5699 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5702 element = Tile[x][y];
5704 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5706 element = MovingOrBlocked2Element(x, y);
5708 if (!IS_EXPLOSION_PROOF(element))
5709 RemoveMovingField(x, y);
5712 // indestructible elements can only explode in center (but not flames)
5713 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5714 mode == EX_TYPE_BORDER)) ||
5715 element == EL_FLAMES)
5718 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5719 behaviour, for example when touching a yamyam that explodes to rocks
5720 with active deadly shield, a rock is created under the player !!! */
5721 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5723 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5724 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5725 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5727 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5730 if (IS_ACTIVE_BOMB(element))
5732 // re-activate things under the bomb like gate or penguin
5733 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5740 // save walkable background elements while explosion on same tile
5741 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5742 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5743 Back[x][y] = element;
5745 // ignite explodable elements reached by other explosion
5746 if (element == EL_EXPLOSION)
5747 element = Store2[x][y];
5749 if (AmoebaNr[x][y] &&
5750 (element == EL_AMOEBA_FULL ||
5751 element == EL_BD_AMOEBA ||
5752 element == EL_AMOEBA_GROWING))
5754 AmoebaCnt[AmoebaNr[x][y]]--;
5755 AmoebaCnt2[AmoebaNr[x][y]]--;
5760 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5762 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5764 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5766 if (PLAYERINFO(ex, ey)->use_murphy)
5767 Store[x][y] = EL_EMPTY;
5770 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5771 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5772 else if (ELEM_IS_PLAYER(center_element))
5773 Store[x][y] = EL_EMPTY;
5774 else if (center_element == EL_YAMYAM)
5775 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5776 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5777 Store[x][y] = element_info[center_element].content.e[xx][yy];
5779 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5780 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5781 // otherwise) -- FIX THIS !!!
5782 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5783 Store[x][y] = element_info[element].content.e[1][1];
5785 else if (!CAN_EXPLODE(element))
5786 Store[x][y] = element_info[element].content.e[1][1];
5789 Store[x][y] = EL_EMPTY;
5791 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5792 center_element == EL_AMOEBA_TO_DIAMOND)
5793 Store2[x][y] = element;
5795 Tile[x][y] = EL_EXPLOSION;
5796 GfxElement[x][y] = artwork_element;
5798 ExplodePhase[x][y] = 1;
5799 ExplodeDelay[x][y] = last_phase;
5804 if (center_element == EL_YAMYAM)
5805 game.yamyam_content_nr =
5806 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5818 GfxFrame[x][y] = 0; // restart explosion animation
5820 last_phase = ExplodeDelay[x][y];
5822 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5824 // this can happen if the player leaves an explosion just in time
5825 if (GfxElement[x][y] == EL_UNDEFINED)
5826 GfxElement[x][y] = EL_EMPTY;
5828 border_element = Store2[x][y];
5829 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5830 border_element = StorePlayer[x][y];
5832 if (phase == element_info[border_element].ignition_delay ||
5833 phase == last_phase)
5835 boolean border_explosion = FALSE;
5837 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5838 !PLAYER_EXPLOSION_PROTECTED(x, y))
5840 KillPlayerUnlessExplosionProtected(x, y);
5841 border_explosion = TRUE;
5843 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5845 Tile[x][y] = Store2[x][y];
5848 border_explosion = TRUE;
5850 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5852 AmoebaToDiamond(x, y);
5854 border_explosion = TRUE;
5857 // if an element just explodes due to another explosion (chain-reaction),
5858 // do not immediately end the new explosion when it was the last frame of
5859 // the explosion (as it would be done in the following "if"-statement!)
5860 if (border_explosion && phase == last_phase)
5864 if (phase == last_phase)
5868 element = Tile[x][y] = Store[x][y];
5869 Store[x][y] = Store2[x][y] = 0;
5870 GfxElement[x][y] = EL_UNDEFINED;
5872 // player can escape from explosions and might therefore be still alive
5873 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5874 element <= EL_PLAYER_IS_EXPLODING_4)
5876 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5877 int explosion_element = EL_PLAYER_1 + player_nr;
5878 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5879 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5881 if (level.use_explosion_element[player_nr])
5882 explosion_element = level.explosion_element[player_nr];
5884 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5885 element_info[explosion_element].content.e[xx][yy]);
5888 // restore probably existing indestructible background element
5889 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5890 element = Tile[x][y] = Back[x][y];
5893 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5894 GfxDir[x][y] = MV_NONE;
5895 ChangeDelay[x][y] = 0;
5896 ChangePage[x][y] = -1;
5898 CustomValue[x][y] = 0;
5900 InitField_WithBug2(x, y, FALSE);
5902 TEST_DrawLevelField(x, y);
5904 TestIfElementTouchesCustomElement(x, y);
5906 if (GFX_CRUMBLED(element))
5907 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5909 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5910 StorePlayer[x][y] = 0;
5912 if (ELEM_IS_PLAYER(element))
5913 RelocatePlayer(x, y, element);
5915 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5917 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5918 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5921 TEST_DrawLevelFieldCrumbled(x, y);
5923 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5925 DrawLevelElement(x, y, Back[x][y]);
5926 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5928 else if (IS_WALKABLE_UNDER(Back[x][y]))
5930 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5931 DrawLevelElementThruMask(x, y, Back[x][y]);
5933 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5934 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5938 static void DynaExplode(int ex, int ey)
5941 int dynabomb_element = Tile[ex][ey];
5942 int dynabomb_size = 1;
5943 boolean dynabomb_xl = FALSE;
5944 struct PlayerInfo *player;
5945 static int xy[4][2] =
5953 if (IS_ACTIVE_BOMB(dynabomb_element))
5955 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5956 dynabomb_size = player->dynabomb_size;
5957 dynabomb_xl = player->dynabomb_xl;
5958 player->dynabombs_left++;
5961 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5963 for (i = 0; i < NUM_DIRECTIONS; i++)
5965 for (j = 1; j <= dynabomb_size; j++)
5967 int x = ex + j * xy[i][0];
5968 int y = ey + j * xy[i][1];
5971 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5974 element = Tile[x][y];
5976 // do not restart explosions of fields with active bombs
5977 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5980 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5982 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5983 !IS_DIGGABLE(element) && !dynabomb_xl)
5989 void Bang(int x, int y)
5991 int element = MovingOrBlocked2Element(x, y);
5992 int explosion_type = EX_TYPE_NORMAL;
5994 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5996 struct PlayerInfo *player = PLAYERINFO(x, y);
5998 element = Tile[x][y] = player->initial_element;
6000 if (level.use_explosion_element[player->index_nr])
6002 int explosion_element = level.explosion_element[player->index_nr];
6004 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6005 explosion_type = EX_TYPE_CROSS;
6006 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6007 explosion_type = EX_TYPE_CENTER;
6015 case EL_BD_BUTTERFLY:
6018 case EL_DARK_YAMYAM:
6022 RaiseScoreElement(element);
6025 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6026 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6027 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6028 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6029 case EL_DYNABOMB_INCREASE_NUMBER:
6030 case EL_DYNABOMB_INCREASE_SIZE:
6031 case EL_DYNABOMB_INCREASE_POWER:
6032 explosion_type = EX_TYPE_DYNA;
6035 case EL_DC_LANDMINE:
6036 explosion_type = EX_TYPE_CENTER;
6041 case EL_LAMP_ACTIVE:
6042 case EL_AMOEBA_TO_DIAMOND:
6043 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6044 explosion_type = EX_TYPE_CENTER;
6048 if (element_info[element].explosion_type == EXPLODES_CROSS)
6049 explosion_type = EX_TYPE_CROSS;
6050 else if (element_info[element].explosion_type == EXPLODES_1X1)
6051 explosion_type = EX_TYPE_CENTER;
6055 if (explosion_type == EX_TYPE_DYNA)
6058 Explode(x, y, EX_PHASE_START, explosion_type);
6060 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6063 static void SplashAcid(int x, int y)
6065 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6066 (!IN_LEV_FIELD(x - 1, y - 2) ||
6067 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6068 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6070 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6071 (!IN_LEV_FIELD(x + 1, y - 2) ||
6072 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6073 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6075 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6078 static void InitBeltMovement(void)
6080 static int belt_base_element[4] =
6082 EL_CONVEYOR_BELT_1_LEFT,
6083 EL_CONVEYOR_BELT_2_LEFT,
6084 EL_CONVEYOR_BELT_3_LEFT,
6085 EL_CONVEYOR_BELT_4_LEFT
6087 static int belt_base_active_element[4] =
6089 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6090 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6091 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6092 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6097 // set frame order for belt animation graphic according to belt direction
6098 for (i = 0; i < NUM_BELTS; i++)
6102 for (j = 0; j < NUM_BELT_PARTS; j++)
6104 int element = belt_base_active_element[belt_nr] + j;
6105 int graphic_1 = el2img(element);
6106 int graphic_2 = el2panelimg(element);
6108 if (game.belt_dir[i] == MV_LEFT)
6110 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6111 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6115 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6116 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6121 SCAN_PLAYFIELD(x, y)
6123 int element = Tile[x][y];
6125 for (i = 0; i < NUM_BELTS; i++)
6127 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6129 int e_belt_nr = getBeltNrFromBeltElement(element);
6132 if (e_belt_nr == belt_nr)
6134 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6136 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6143 static void ToggleBeltSwitch(int x, int y)
6145 static int belt_base_element[4] =
6147 EL_CONVEYOR_BELT_1_LEFT,
6148 EL_CONVEYOR_BELT_2_LEFT,
6149 EL_CONVEYOR_BELT_3_LEFT,
6150 EL_CONVEYOR_BELT_4_LEFT
6152 static int belt_base_active_element[4] =
6154 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6155 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6156 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6157 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6159 static int belt_base_switch_element[4] =
6161 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6162 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6163 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6164 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6166 static int belt_move_dir[4] =
6174 int element = Tile[x][y];
6175 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6176 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6177 int belt_dir = belt_move_dir[belt_dir_nr];
6180 if (!IS_BELT_SWITCH(element))
6183 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6184 game.belt_dir[belt_nr] = belt_dir;
6186 if (belt_dir_nr == 3)
6189 // set frame order for belt animation graphic according to belt direction
6190 for (i = 0; i < NUM_BELT_PARTS; i++)
6192 int element = belt_base_active_element[belt_nr] + i;
6193 int graphic_1 = el2img(element);
6194 int graphic_2 = el2panelimg(element);
6196 if (belt_dir == MV_LEFT)
6198 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6199 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6203 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6204 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6208 SCAN_PLAYFIELD(xx, yy)
6210 int element = Tile[xx][yy];
6212 if (IS_BELT_SWITCH(element))
6214 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6216 if (e_belt_nr == belt_nr)
6218 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6219 TEST_DrawLevelField(xx, yy);
6222 else if (IS_BELT(element) && belt_dir != MV_NONE)
6224 int e_belt_nr = getBeltNrFromBeltElement(element);
6226 if (e_belt_nr == belt_nr)
6228 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6230 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6231 TEST_DrawLevelField(xx, yy);
6234 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6236 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6238 if (e_belt_nr == belt_nr)
6240 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6242 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6243 TEST_DrawLevelField(xx, yy);
6249 static void ToggleSwitchgateSwitch(int x, int y)
6253 game.switchgate_pos = !game.switchgate_pos;
6255 SCAN_PLAYFIELD(xx, yy)
6257 int element = Tile[xx][yy];
6259 if (element == EL_SWITCHGATE_SWITCH_UP)
6261 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6262 TEST_DrawLevelField(xx, yy);
6264 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6266 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6267 TEST_DrawLevelField(xx, yy);
6269 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6271 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6272 TEST_DrawLevelField(xx, yy);
6274 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6276 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6277 TEST_DrawLevelField(xx, yy);
6279 else if (element == EL_SWITCHGATE_OPEN ||
6280 element == EL_SWITCHGATE_OPENING)
6282 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6284 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6286 else if (element == EL_SWITCHGATE_CLOSED ||
6287 element == EL_SWITCHGATE_CLOSING)
6289 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6291 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6296 static int getInvisibleActiveFromInvisibleElement(int element)
6298 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6299 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6300 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6304 static int getInvisibleFromInvisibleActiveElement(int element)
6306 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6307 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6308 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6312 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6316 SCAN_PLAYFIELD(x, y)
6318 int element = Tile[x][y];
6320 if (element == EL_LIGHT_SWITCH &&
6321 game.light_time_left > 0)
6323 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6324 TEST_DrawLevelField(x, y);
6326 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6327 game.light_time_left == 0)
6329 Tile[x][y] = EL_LIGHT_SWITCH;
6330 TEST_DrawLevelField(x, y);
6332 else if (element == EL_EMC_DRIPPER &&
6333 game.light_time_left > 0)
6335 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6336 TEST_DrawLevelField(x, y);
6338 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6339 game.light_time_left == 0)
6341 Tile[x][y] = EL_EMC_DRIPPER;
6342 TEST_DrawLevelField(x, y);
6344 else if (element == EL_INVISIBLE_STEELWALL ||
6345 element == EL_INVISIBLE_WALL ||
6346 element == EL_INVISIBLE_SAND)
6348 if (game.light_time_left > 0)
6349 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6351 TEST_DrawLevelField(x, y);
6353 // uncrumble neighbour fields, if needed
6354 if (element == EL_INVISIBLE_SAND)
6355 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6357 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6358 element == EL_INVISIBLE_WALL_ACTIVE ||
6359 element == EL_INVISIBLE_SAND_ACTIVE)
6361 if (game.light_time_left == 0)
6362 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6364 TEST_DrawLevelField(x, y);
6366 // re-crumble neighbour fields, if needed
6367 if (element == EL_INVISIBLE_SAND)
6368 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6373 static void RedrawAllInvisibleElementsForLenses(void)
6377 SCAN_PLAYFIELD(x, y)
6379 int element = Tile[x][y];
6381 if (element == EL_EMC_DRIPPER &&
6382 game.lenses_time_left > 0)
6384 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6385 TEST_DrawLevelField(x, y);
6387 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6388 game.lenses_time_left == 0)
6390 Tile[x][y] = EL_EMC_DRIPPER;
6391 TEST_DrawLevelField(x, y);
6393 else if (element == EL_INVISIBLE_STEELWALL ||
6394 element == EL_INVISIBLE_WALL ||
6395 element == EL_INVISIBLE_SAND)
6397 if (game.lenses_time_left > 0)
6398 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6400 TEST_DrawLevelField(x, y);
6402 // uncrumble neighbour fields, if needed
6403 if (element == EL_INVISIBLE_SAND)
6404 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6406 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6407 element == EL_INVISIBLE_WALL_ACTIVE ||
6408 element == EL_INVISIBLE_SAND_ACTIVE)
6410 if (game.lenses_time_left == 0)
6411 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6413 TEST_DrawLevelField(x, y);
6415 // re-crumble neighbour fields, if needed
6416 if (element == EL_INVISIBLE_SAND)
6417 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6422 static void RedrawAllInvisibleElementsForMagnifier(void)
6426 SCAN_PLAYFIELD(x, y)
6428 int element = Tile[x][y];
6430 if (element == EL_EMC_FAKE_GRASS &&
6431 game.magnify_time_left > 0)
6433 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6434 TEST_DrawLevelField(x, y);
6436 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6437 game.magnify_time_left == 0)
6439 Tile[x][y] = EL_EMC_FAKE_GRASS;
6440 TEST_DrawLevelField(x, y);
6442 else if (IS_GATE_GRAY(element) &&
6443 game.magnify_time_left > 0)
6445 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6446 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6447 IS_EM_GATE_GRAY(element) ?
6448 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6449 IS_EMC_GATE_GRAY(element) ?
6450 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6451 IS_DC_GATE_GRAY(element) ?
6452 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6454 TEST_DrawLevelField(x, y);
6456 else if (IS_GATE_GRAY_ACTIVE(element) &&
6457 game.magnify_time_left == 0)
6459 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6460 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6461 IS_EM_GATE_GRAY_ACTIVE(element) ?
6462 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6463 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6464 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6465 IS_DC_GATE_GRAY_ACTIVE(element) ?
6466 EL_DC_GATE_WHITE_GRAY :
6468 TEST_DrawLevelField(x, y);
6473 static void ToggleLightSwitch(int x, int y)
6475 int element = Tile[x][y];
6477 game.light_time_left =
6478 (element == EL_LIGHT_SWITCH ?
6479 level.time_light * FRAMES_PER_SECOND : 0);
6481 RedrawAllLightSwitchesAndInvisibleElements();
6484 static void ActivateTimegateSwitch(int x, int y)
6488 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6490 SCAN_PLAYFIELD(xx, yy)
6492 int element = Tile[xx][yy];
6494 if (element == EL_TIMEGATE_CLOSED ||
6495 element == EL_TIMEGATE_CLOSING)
6497 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6498 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6502 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6504 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6505 TEST_DrawLevelField(xx, yy);
6511 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6512 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6515 static void Impact(int x, int y)
6517 boolean last_line = (y == lev_fieldy - 1);
6518 boolean object_hit = FALSE;
6519 boolean impact = (last_line || object_hit);
6520 int element = Tile[x][y];
6521 int smashed = EL_STEELWALL;
6523 if (!last_line) // check if element below was hit
6525 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6528 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6529 MovDir[x][y + 1] != MV_DOWN ||
6530 MovPos[x][y + 1] <= TILEY / 2));
6532 // do not smash moving elements that left the smashed field in time
6533 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6534 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6537 #if USE_QUICKSAND_IMPACT_BUGFIX
6538 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6540 RemoveMovingField(x, y + 1);
6541 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6542 Tile[x][y + 2] = EL_ROCK;
6543 TEST_DrawLevelField(x, y + 2);
6548 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6550 RemoveMovingField(x, y + 1);
6551 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6552 Tile[x][y + 2] = EL_ROCK;
6553 TEST_DrawLevelField(x, y + 2);
6560 smashed = MovingOrBlocked2Element(x, y + 1);
6562 impact = (last_line || object_hit);
6565 if (!last_line && smashed == EL_ACID) // element falls into acid
6567 SplashAcid(x, y + 1);
6571 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6572 // only reset graphic animation if graphic really changes after impact
6574 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6576 ResetGfxAnimation(x, y);
6577 TEST_DrawLevelField(x, y);
6580 if (impact && CAN_EXPLODE_IMPACT(element))
6585 else if (impact && element == EL_PEARL &&
6586 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6588 ResetGfxAnimation(x, y);
6590 Tile[x][y] = EL_PEARL_BREAKING;
6591 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6594 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6596 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6601 if (impact && element == EL_AMOEBA_DROP)
6603 if (object_hit && IS_PLAYER(x, y + 1))
6604 KillPlayerUnlessEnemyProtected(x, y + 1);
6605 else if (object_hit && smashed == EL_PENGUIN)
6609 Tile[x][y] = EL_AMOEBA_GROWING;
6610 Store[x][y] = EL_AMOEBA_WET;
6612 ResetRandomAnimationValue(x, y);
6617 if (object_hit) // check which object was hit
6619 if ((CAN_PASS_MAGIC_WALL(element) &&
6620 (smashed == EL_MAGIC_WALL ||
6621 smashed == EL_BD_MAGIC_WALL)) ||
6622 (CAN_PASS_DC_MAGIC_WALL(element) &&
6623 smashed == EL_DC_MAGIC_WALL))
6626 int activated_magic_wall =
6627 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6628 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6629 EL_DC_MAGIC_WALL_ACTIVE);
6631 // activate magic wall / mill
6632 SCAN_PLAYFIELD(xx, yy)
6634 if (Tile[xx][yy] == smashed)
6635 Tile[xx][yy] = activated_magic_wall;
6638 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6639 game.magic_wall_active = TRUE;
6641 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6642 SND_MAGIC_WALL_ACTIVATING :
6643 smashed == EL_BD_MAGIC_WALL ?
6644 SND_BD_MAGIC_WALL_ACTIVATING :
6645 SND_DC_MAGIC_WALL_ACTIVATING));
6648 if (IS_PLAYER(x, y + 1))
6650 if (CAN_SMASH_PLAYER(element))
6652 KillPlayerUnlessEnemyProtected(x, y + 1);
6656 else if (smashed == EL_PENGUIN)
6658 if (CAN_SMASH_PLAYER(element))
6664 else if (element == EL_BD_DIAMOND)
6666 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6672 else if (((element == EL_SP_INFOTRON ||
6673 element == EL_SP_ZONK) &&
6674 (smashed == EL_SP_SNIKSNAK ||
6675 smashed == EL_SP_ELECTRON ||
6676 smashed == EL_SP_DISK_ORANGE)) ||
6677 (element == EL_SP_INFOTRON &&
6678 smashed == EL_SP_DISK_YELLOW))
6683 else if (CAN_SMASH_EVERYTHING(element))
6685 if (IS_CLASSIC_ENEMY(smashed) ||
6686 CAN_EXPLODE_SMASHED(smashed))
6691 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6693 if (smashed == EL_LAMP ||
6694 smashed == EL_LAMP_ACTIVE)
6699 else if (smashed == EL_NUT)
6701 Tile[x][y + 1] = EL_NUT_BREAKING;
6702 PlayLevelSound(x, y, SND_NUT_BREAKING);
6703 RaiseScoreElement(EL_NUT);
6706 else if (smashed == EL_PEARL)
6708 ResetGfxAnimation(x, y);
6710 Tile[x][y + 1] = EL_PEARL_BREAKING;
6711 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6714 else if (smashed == EL_DIAMOND)
6716 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6717 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6720 else if (IS_BELT_SWITCH(smashed))
6722 ToggleBeltSwitch(x, y + 1);
6724 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6725 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6726 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6727 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6729 ToggleSwitchgateSwitch(x, y + 1);
6731 else if (smashed == EL_LIGHT_SWITCH ||
6732 smashed == EL_LIGHT_SWITCH_ACTIVE)
6734 ToggleLightSwitch(x, y + 1);
6738 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6740 CheckElementChangeBySide(x, y + 1, smashed, element,
6741 CE_SWITCHED, CH_SIDE_TOP);
6742 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6748 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6753 // play sound of magic wall / mill
6755 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6756 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6757 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6759 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6760 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6761 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6762 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6763 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6764 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6769 // play sound of object that hits the ground
6770 if (last_line || object_hit)
6771 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6774 static void TurnRoundExt(int x, int y)
6786 { 0, 0 }, { 0, 0 }, { 0, 0 },
6791 int left, right, back;
6795 { MV_DOWN, MV_UP, MV_RIGHT },
6796 { MV_UP, MV_DOWN, MV_LEFT },
6798 { MV_LEFT, MV_RIGHT, MV_DOWN },
6802 { MV_RIGHT, MV_LEFT, MV_UP }
6805 int element = Tile[x][y];
6806 int move_pattern = element_info[element].move_pattern;
6808 int old_move_dir = MovDir[x][y];
6809 int left_dir = turn[old_move_dir].left;
6810 int right_dir = turn[old_move_dir].right;
6811 int back_dir = turn[old_move_dir].back;
6813 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6814 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6815 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6816 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6818 int left_x = x + left_dx, left_y = y + left_dy;
6819 int right_x = x + right_dx, right_y = y + right_dy;
6820 int move_x = x + move_dx, move_y = y + move_dy;
6824 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6826 TestIfBadThingTouchesOtherBadThing(x, y);
6828 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6829 MovDir[x][y] = right_dir;
6830 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6831 MovDir[x][y] = left_dir;
6833 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6835 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6838 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6840 TestIfBadThingTouchesOtherBadThing(x, y);
6842 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6843 MovDir[x][y] = left_dir;
6844 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6845 MovDir[x][y] = right_dir;
6847 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6849 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6852 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6854 TestIfBadThingTouchesOtherBadThing(x, y);
6856 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6857 MovDir[x][y] = left_dir;
6858 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6859 MovDir[x][y] = right_dir;
6861 if (MovDir[x][y] != old_move_dir)
6864 else if (element == EL_YAMYAM)
6866 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6867 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6869 if (can_turn_left && can_turn_right)
6870 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6871 else if (can_turn_left)
6872 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6873 else if (can_turn_right)
6874 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6876 MovDir[x][y] = back_dir;
6878 MovDelay[x][y] = 16 + 16 * RND(3);
6880 else if (element == EL_DARK_YAMYAM)
6882 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6884 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6887 if (can_turn_left && can_turn_right)
6888 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6889 else if (can_turn_left)
6890 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6891 else if (can_turn_right)
6892 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6894 MovDir[x][y] = back_dir;
6896 MovDelay[x][y] = 16 + 16 * RND(3);
6898 else if (element == EL_PACMAN)
6900 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6901 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6903 if (can_turn_left && can_turn_right)
6904 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6905 else if (can_turn_left)
6906 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6907 else if (can_turn_right)
6908 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6910 MovDir[x][y] = back_dir;
6912 MovDelay[x][y] = 6 + RND(40);
6914 else if (element == EL_PIG)
6916 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6917 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6918 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6919 boolean should_turn_left, should_turn_right, should_move_on;
6921 int rnd = RND(rnd_value);
6923 should_turn_left = (can_turn_left &&
6925 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6926 y + back_dy + left_dy)));
6927 should_turn_right = (can_turn_right &&
6929 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6930 y + back_dy + right_dy)));
6931 should_move_on = (can_move_on &&
6934 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6935 y + move_dy + left_dy) ||
6936 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6937 y + move_dy + right_dy)));
6939 if (should_turn_left || should_turn_right || should_move_on)
6941 if (should_turn_left && should_turn_right && should_move_on)
6942 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6943 rnd < 2 * rnd_value / 3 ? right_dir :
6945 else if (should_turn_left && should_turn_right)
6946 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6947 else if (should_turn_left && should_move_on)
6948 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6949 else if (should_turn_right && should_move_on)
6950 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6951 else if (should_turn_left)
6952 MovDir[x][y] = left_dir;
6953 else if (should_turn_right)
6954 MovDir[x][y] = right_dir;
6955 else if (should_move_on)
6956 MovDir[x][y] = old_move_dir;
6958 else if (can_move_on && rnd > rnd_value / 8)
6959 MovDir[x][y] = old_move_dir;
6960 else if (can_turn_left && can_turn_right)
6961 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6962 else if (can_turn_left && rnd > rnd_value / 8)
6963 MovDir[x][y] = left_dir;
6964 else if (can_turn_right && rnd > rnd_value/8)
6965 MovDir[x][y] = right_dir;
6967 MovDir[x][y] = back_dir;
6969 xx = x + move_xy[MovDir[x][y]].dx;
6970 yy = y + move_xy[MovDir[x][y]].dy;
6972 if (!IN_LEV_FIELD(xx, yy) ||
6973 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6974 MovDir[x][y] = old_move_dir;
6978 else if (element == EL_DRAGON)
6980 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6981 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6982 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6984 int rnd = RND(rnd_value);
6986 if (can_move_on && rnd > rnd_value / 8)
6987 MovDir[x][y] = old_move_dir;
6988 else if (can_turn_left && can_turn_right)
6989 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6990 else if (can_turn_left && rnd > rnd_value / 8)
6991 MovDir[x][y] = left_dir;
6992 else if (can_turn_right && rnd > rnd_value / 8)
6993 MovDir[x][y] = right_dir;
6995 MovDir[x][y] = back_dir;
6997 xx = x + move_xy[MovDir[x][y]].dx;
6998 yy = y + move_xy[MovDir[x][y]].dy;
7000 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7001 MovDir[x][y] = old_move_dir;
7005 else if (element == EL_MOLE)
7007 boolean can_move_on =
7008 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7009 IS_AMOEBOID(Tile[move_x][move_y]) ||
7010 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7013 boolean can_turn_left =
7014 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7015 IS_AMOEBOID(Tile[left_x][left_y])));
7017 boolean can_turn_right =
7018 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7019 IS_AMOEBOID(Tile[right_x][right_y])));
7021 if (can_turn_left && can_turn_right)
7022 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7023 else if (can_turn_left)
7024 MovDir[x][y] = left_dir;
7026 MovDir[x][y] = right_dir;
7029 if (MovDir[x][y] != old_move_dir)
7032 else if (element == EL_BALLOON)
7034 MovDir[x][y] = game.wind_direction;
7037 else if (element == EL_SPRING)
7039 if (MovDir[x][y] & MV_HORIZONTAL)
7041 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7042 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7044 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7045 ResetGfxAnimation(move_x, move_y);
7046 TEST_DrawLevelField(move_x, move_y);
7048 MovDir[x][y] = back_dir;
7050 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7051 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7052 MovDir[x][y] = MV_NONE;
7057 else if (element == EL_ROBOT ||
7058 element == EL_SATELLITE ||
7059 element == EL_PENGUIN ||
7060 element == EL_EMC_ANDROID)
7062 int attr_x = -1, attr_y = -1;
7064 if (game.all_players_gone)
7066 attr_x = game.exit_x;
7067 attr_y = game.exit_y;
7073 for (i = 0; i < MAX_PLAYERS; i++)
7075 struct PlayerInfo *player = &stored_player[i];
7076 int jx = player->jx, jy = player->jy;
7078 if (!player->active)
7082 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7090 if (element == EL_ROBOT &&
7091 game.robot_wheel_x >= 0 &&
7092 game.robot_wheel_y >= 0 &&
7093 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7094 game.engine_version < VERSION_IDENT(3,1,0,0)))
7096 attr_x = game.robot_wheel_x;
7097 attr_y = game.robot_wheel_y;
7100 if (element == EL_PENGUIN)
7103 static int xy[4][2] =
7111 for (i = 0; i < NUM_DIRECTIONS; i++)
7113 int ex = x + xy[i][0];
7114 int ey = y + xy[i][1];
7116 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7117 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7118 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7119 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7128 MovDir[x][y] = MV_NONE;
7130 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7131 else if (attr_x > x)
7132 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7134 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7135 else if (attr_y > y)
7136 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7138 if (element == EL_ROBOT)
7142 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7143 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7144 Moving2Blocked(x, y, &newx, &newy);
7146 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7147 MovDelay[x][y] = 8 + 8 * !RND(3);
7149 MovDelay[x][y] = 16;
7151 else if (element == EL_PENGUIN)
7157 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7159 boolean first_horiz = RND(2);
7160 int new_move_dir = MovDir[x][y];
7163 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7164 Moving2Blocked(x, y, &newx, &newy);
7166 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7170 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7171 Moving2Blocked(x, y, &newx, &newy);
7173 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7176 MovDir[x][y] = old_move_dir;
7180 else if (element == EL_SATELLITE)
7186 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7188 boolean first_horiz = RND(2);
7189 int new_move_dir = MovDir[x][y];
7192 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7193 Moving2Blocked(x, y, &newx, &newy);
7195 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7199 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7200 Moving2Blocked(x, y, &newx, &newy);
7202 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7205 MovDir[x][y] = old_move_dir;
7209 else if (element == EL_EMC_ANDROID)
7211 static int check_pos[16] =
7213 -1, // 0 => (invalid)
7216 -1, // 3 => (invalid)
7218 0, // 5 => MV_LEFT | MV_UP
7219 2, // 6 => MV_RIGHT | MV_UP
7220 -1, // 7 => (invalid)
7222 6, // 9 => MV_LEFT | MV_DOWN
7223 4, // 10 => MV_RIGHT | MV_DOWN
7224 -1, // 11 => (invalid)
7225 -1, // 12 => (invalid)
7226 -1, // 13 => (invalid)
7227 -1, // 14 => (invalid)
7228 -1, // 15 => (invalid)
7236 { -1, -1, MV_LEFT | MV_UP },
7238 { +1, -1, MV_RIGHT | MV_UP },
7239 { +1, 0, MV_RIGHT },
7240 { +1, +1, MV_RIGHT | MV_DOWN },
7242 { -1, +1, MV_LEFT | MV_DOWN },
7245 int start_pos, check_order;
7246 boolean can_clone = FALSE;
7249 // check if there is any free field around current position
7250 for (i = 0; i < 8; i++)
7252 int newx = x + check_xy[i].dx;
7253 int newy = y + check_xy[i].dy;
7255 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7263 if (can_clone) // randomly find an element to clone
7267 start_pos = check_pos[RND(8)];
7268 check_order = (RND(2) ? -1 : +1);
7270 for (i = 0; i < 8; i++)
7272 int pos_raw = start_pos + i * check_order;
7273 int pos = (pos_raw + 8) % 8;
7274 int newx = x + check_xy[pos].dx;
7275 int newy = y + check_xy[pos].dy;
7277 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7279 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7280 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7282 Store[x][y] = Tile[newx][newy];
7291 if (can_clone) // randomly find a direction to move
7295 start_pos = check_pos[RND(8)];
7296 check_order = (RND(2) ? -1 : +1);
7298 for (i = 0; i < 8; i++)
7300 int pos_raw = start_pos + i * check_order;
7301 int pos = (pos_raw + 8) % 8;
7302 int newx = x + check_xy[pos].dx;
7303 int newy = y + check_xy[pos].dy;
7304 int new_move_dir = check_xy[pos].dir;
7306 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7308 MovDir[x][y] = new_move_dir;
7309 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7318 if (can_clone) // cloning and moving successful
7321 // cannot clone -- try to move towards player
7323 start_pos = check_pos[MovDir[x][y] & 0x0f];
7324 check_order = (RND(2) ? -1 : +1);
7326 for (i = 0; i < 3; i++)
7328 // first check start_pos, then previous/next or (next/previous) pos
7329 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7330 int pos = (pos_raw + 8) % 8;
7331 int newx = x + check_xy[pos].dx;
7332 int newy = y + check_xy[pos].dy;
7333 int new_move_dir = check_xy[pos].dir;
7335 if (IS_PLAYER(newx, newy))
7338 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7340 MovDir[x][y] = new_move_dir;
7341 MovDelay[x][y] = level.android_move_time * 8 + 1;
7348 else if (move_pattern == MV_TURNING_LEFT ||
7349 move_pattern == MV_TURNING_RIGHT ||
7350 move_pattern == MV_TURNING_LEFT_RIGHT ||
7351 move_pattern == MV_TURNING_RIGHT_LEFT ||
7352 move_pattern == MV_TURNING_RANDOM ||
7353 move_pattern == MV_ALL_DIRECTIONS)
7355 boolean can_turn_left =
7356 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7357 boolean can_turn_right =
7358 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7360 if (element_info[element].move_stepsize == 0) // "not moving"
7363 if (move_pattern == MV_TURNING_LEFT)
7364 MovDir[x][y] = left_dir;
7365 else if (move_pattern == MV_TURNING_RIGHT)
7366 MovDir[x][y] = right_dir;
7367 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7368 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7369 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7370 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7371 else if (move_pattern == MV_TURNING_RANDOM)
7372 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7373 can_turn_right && !can_turn_left ? right_dir :
7374 RND(2) ? left_dir : right_dir);
7375 else if (can_turn_left && can_turn_right)
7376 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7377 else if (can_turn_left)
7378 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7379 else if (can_turn_right)
7380 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7382 MovDir[x][y] = back_dir;
7384 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7386 else if (move_pattern == MV_HORIZONTAL ||
7387 move_pattern == MV_VERTICAL)
7389 if (move_pattern & old_move_dir)
7390 MovDir[x][y] = back_dir;
7391 else if (move_pattern == MV_HORIZONTAL)
7392 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7393 else if (move_pattern == MV_VERTICAL)
7394 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7396 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7398 else if (move_pattern & MV_ANY_DIRECTION)
7400 MovDir[x][y] = move_pattern;
7401 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7403 else if (move_pattern & MV_WIND_DIRECTION)
7405 MovDir[x][y] = game.wind_direction;
7406 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7408 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7410 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7411 MovDir[x][y] = left_dir;
7412 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7413 MovDir[x][y] = right_dir;
7415 if (MovDir[x][y] != old_move_dir)
7416 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7418 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7420 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7421 MovDir[x][y] = right_dir;
7422 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7423 MovDir[x][y] = left_dir;
7425 if (MovDir[x][y] != old_move_dir)
7426 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7428 else if (move_pattern == MV_TOWARDS_PLAYER ||
7429 move_pattern == MV_AWAY_FROM_PLAYER)
7431 int attr_x = -1, attr_y = -1;
7433 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7435 if (game.all_players_gone)
7437 attr_x = game.exit_x;
7438 attr_y = game.exit_y;
7444 for (i = 0; i < MAX_PLAYERS; i++)
7446 struct PlayerInfo *player = &stored_player[i];
7447 int jx = player->jx, jy = player->jy;
7449 if (!player->active)
7453 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7461 MovDir[x][y] = MV_NONE;
7463 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7464 else if (attr_x > x)
7465 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7467 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7468 else if (attr_y > y)
7469 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7471 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7473 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7475 boolean first_horiz = RND(2);
7476 int new_move_dir = MovDir[x][y];
7478 if (element_info[element].move_stepsize == 0) // "not moving"
7480 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7481 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7487 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7488 Moving2Blocked(x, y, &newx, &newy);
7490 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7494 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7495 Moving2Blocked(x, y, &newx, &newy);
7497 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7500 MovDir[x][y] = old_move_dir;
7503 else if (move_pattern == MV_WHEN_PUSHED ||
7504 move_pattern == MV_WHEN_DROPPED)
7506 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7507 MovDir[x][y] = MV_NONE;
7511 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7513 static int test_xy[7][2] =
7523 static int test_dir[7] =
7533 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7534 int move_preference = -1000000; // start with very low preference
7535 int new_move_dir = MV_NONE;
7536 int start_test = RND(4);
7539 for (i = 0; i < NUM_DIRECTIONS; i++)
7541 int move_dir = test_dir[start_test + i];
7542 int move_dir_preference;
7544 xx = x + test_xy[start_test + i][0];
7545 yy = y + test_xy[start_test + i][1];
7547 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7548 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7550 new_move_dir = move_dir;
7555 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7558 move_dir_preference = -1 * RunnerVisit[xx][yy];
7559 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7560 move_dir_preference = PlayerVisit[xx][yy];
7562 if (move_dir_preference > move_preference)
7564 // prefer field that has not been visited for the longest time
7565 move_preference = move_dir_preference;
7566 new_move_dir = move_dir;
7568 else if (move_dir_preference == move_preference &&
7569 move_dir == old_move_dir)
7571 // prefer last direction when all directions are preferred equally
7572 move_preference = move_dir_preference;
7573 new_move_dir = move_dir;
7577 MovDir[x][y] = new_move_dir;
7578 if (old_move_dir != new_move_dir)
7579 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7583 static void TurnRound(int x, int y)
7585 int direction = MovDir[x][y];
7589 GfxDir[x][y] = MovDir[x][y];
7591 if (direction != MovDir[x][y])
7595 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7597 ResetGfxFrame(x, y);
7600 static boolean JustBeingPushed(int x, int y)
7604 for (i = 0; i < MAX_PLAYERS; i++)
7606 struct PlayerInfo *player = &stored_player[i];
7608 if (player->active && player->is_pushing && player->MovPos)
7610 int next_jx = player->jx + (player->jx - player->last_jx);
7611 int next_jy = player->jy + (player->jy - player->last_jy);
7613 if (x == next_jx && y == next_jy)
7621 static void StartMoving(int x, int y)
7623 boolean started_moving = FALSE; // some elements can fall _and_ move
7624 int element = Tile[x][y];
7629 if (MovDelay[x][y] == 0)
7630 GfxAction[x][y] = ACTION_DEFAULT;
7632 if (CAN_FALL(element) && y < lev_fieldy - 1)
7634 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7635 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7636 if (JustBeingPushed(x, y))
7639 if (element == EL_QUICKSAND_FULL)
7641 if (IS_FREE(x, y + 1))
7643 InitMovingField(x, y, MV_DOWN);
7644 started_moving = TRUE;
7646 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7647 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7648 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7649 Store[x][y] = EL_ROCK;
7651 Store[x][y] = EL_ROCK;
7654 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7656 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7658 if (!MovDelay[x][y])
7660 MovDelay[x][y] = TILEY + 1;
7662 ResetGfxAnimation(x, y);
7663 ResetGfxAnimation(x, y + 1);
7668 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7669 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7676 Tile[x][y] = EL_QUICKSAND_EMPTY;
7677 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7678 Store[x][y + 1] = Store[x][y];
7681 PlayLevelSoundAction(x, y, ACTION_FILLING);
7683 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7685 if (!MovDelay[x][y])
7687 MovDelay[x][y] = TILEY + 1;
7689 ResetGfxAnimation(x, y);
7690 ResetGfxAnimation(x, y + 1);
7695 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7696 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7703 Tile[x][y] = EL_QUICKSAND_EMPTY;
7704 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7705 Store[x][y + 1] = Store[x][y];
7708 PlayLevelSoundAction(x, y, ACTION_FILLING);
7711 else if (element == EL_QUICKSAND_FAST_FULL)
7713 if (IS_FREE(x, y + 1))
7715 InitMovingField(x, y, MV_DOWN);
7716 started_moving = TRUE;
7718 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7719 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7720 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7721 Store[x][y] = EL_ROCK;
7723 Store[x][y] = EL_ROCK;
7726 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7728 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7730 if (!MovDelay[x][y])
7732 MovDelay[x][y] = TILEY + 1;
7734 ResetGfxAnimation(x, y);
7735 ResetGfxAnimation(x, y + 1);
7740 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7741 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7748 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7749 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7750 Store[x][y + 1] = Store[x][y];
7753 PlayLevelSoundAction(x, y, ACTION_FILLING);
7755 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7757 if (!MovDelay[x][y])
7759 MovDelay[x][y] = TILEY + 1;
7761 ResetGfxAnimation(x, y);
7762 ResetGfxAnimation(x, y + 1);
7767 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7768 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7775 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7776 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7777 Store[x][y + 1] = Store[x][y];
7780 PlayLevelSoundAction(x, y, ACTION_FILLING);
7783 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7784 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7786 InitMovingField(x, y, MV_DOWN);
7787 started_moving = TRUE;
7789 Tile[x][y] = EL_QUICKSAND_FILLING;
7790 Store[x][y] = element;
7792 PlayLevelSoundAction(x, y, ACTION_FILLING);
7794 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7795 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7797 InitMovingField(x, y, MV_DOWN);
7798 started_moving = TRUE;
7800 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7801 Store[x][y] = element;
7803 PlayLevelSoundAction(x, y, ACTION_FILLING);
7805 else if (element == EL_MAGIC_WALL_FULL)
7807 if (IS_FREE(x, y + 1))
7809 InitMovingField(x, y, MV_DOWN);
7810 started_moving = TRUE;
7812 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7813 Store[x][y] = EL_CHANGED(Store[x][y]);
7815 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7817 if (!MovDelay[x][y])
7818 MovDelay[x][y] = TILEY / 4 + 1;
7827 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7828 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7829 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7833 else if (element == EL_BD_MAGIC_WALL_FULL)
7835 if (IS_FREE(x, y + 1))
7837 InitMovingField(x, y, MV_DOWN);
7838 started_moving = TRUE;
7840 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7841 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7843 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7845 if (!MovDelay[x][y])
7846 MovDelay[x][y] = TILEY / 4 + 1;
7855 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7856 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7857 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7861 else if (element == EL_DC_MAGIC_WALL_FULL)
7863 if (IS_FREE(x, y + 1))
7865 InitMovingField(x, y, MV_DOWN);
7866 started_moving = TRUE;
7868 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7869 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7871 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7873 if (!MovDelay[x][y])
7874 MovDelay[x][y] = TILEY / 4 + 1;
7883 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7884 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7885 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7889 else if ((CAN_PASS_MAGIC_WALL(element) &&
7890 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7891 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7892 (CAN_PASS_DC_MAGIC_WALL(element) &&
7893 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7896 InitMovingField(x, y, MV_DOWN);
7897 started_moving = TRUE;
7900 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7901 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7902 EL_DC_MAGIC_WALL_FILLING);
7903 Store[x][y] = element;
7905 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7907 SplashAcid(x, y + 1);
7909 InitMovingField(x, y, MV_DOWN);
7910 started_moving = TRUE;
7912 Store[x][y] = EL_ACID;
7915 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7916 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7917 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7918 CAN_FALL(element) && WasJustFalling[x][y] &&
7919 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7921 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7922 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7923 (Tile[x][y + 1] == EL_BLOCKED)))
7925 /* this is needed for a special case not covered by calling "Impact()"
7926 from "ContinueMoving()": if an element moves to a tile directly below
7927 another element which was just falling on that tile (which was empty
7928 in the previous frame), the falling element above would just stop
7929 instead of smashing the element below (in previous version, the above
7930 element was just checked for "moving" instead of "falling", resulting
7931 in incorrect smashes caused by horizontal movement of the above
7932 element; also, the case of the player being the element to smash was
7933 simply not covered here... :-/ ) */
7935 CheckCollision[x][y] = 0;
7936 CheckImpact[x][y] = 0;
7940 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7942 if (MovDir[x][y] == MV_NONE)
7944 InitMovingField(x, y, MV_DOWN);
7945 started_moving = TRUE;
7948 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7950 if (WasJustFalling[x][y]) // prevent animation from being restarted
7951 MovDir[x][y] = MV_DOWN;
7953 InitMovingField(x, y, MV_DOWN);
7954 started_moving = TRUE;
7956 else if (element == EL_AMOEBA_DROP)
7958 Tile[x][y] = EL_AMOEBA_GROWING;
7959 Store[x][y] = EL_AMOEBA_WET;
7961 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7962 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7963 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7964 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7966 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7967 (IS_FREE(x - 1, y + 1) ||
7968 Tile[x - 1][y + 1] == EL_ACID));
7969 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7970 (IS_FREE(x + 1, y + 1) ||
7971 Tile[x + 1][y + 1] == EL_ACID));
7972 boolean can_fall_any = (can_fall_left || can_fall_right);
7973 boolean can_fall_both = (can_fall_left && can_fall_right);
7974 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7976 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7978 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7979 can_fall_right = FALSE;
7980 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7981 can_fall_left = FALSE;
7982 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7983 can_fall_right = FALSE;
7984 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7985 can_fall_left = FALSE;
7987 can_fall_any = (can_fall_left || can_fall_right);
7988 can_fall_both = FALSE;
7993 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7994 can_fall_right = FALSE; // slip down on left side
7996 can_fall_left = !(can_fall_right = RND(2));
7998 can_fall_both = FALSE;
8003 // if not determined otherwise, prefer left side for slipping down
8004 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8005 started_moving = TRUE;
8008 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8010 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8011 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8012 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8013 int belt_dir = game.belt_dir[belt_nr];
8015 if ((belt_dir == MV_LEFT && left_is_free) ||
8016 (belt_dir == MV_RIGHT && right_is_free))
8018 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8020 InitMovingField(x, y, belt_dir);
8021 started_moving = TRUE;
8023 Pushed[x][y] = TRUE;
8024 Pushed[nextx][y] = TRUE;
8026 GfxAction[x][y] = ACTION_DEFAULT;
8030 MovDir[x][y] = 0; // if element was moving, stop it
8035 // not "else if" because of elements that can fall and move (EL_SPRING)
8036 if (CAN_MOVE(element) && !started_moving)
8038 int move_pattern = element_info[element].move_pattern;
8041 Moving2Blocked(x, y, &newx, &newy);
8043 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8046 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8047 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8049 WasJustMoving[x][y] = 0;
8050 CheckCollision[x][y] = 0;
8052 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8054 if (Tile[x][y] != element) // element has changed
8058 if (!MovDelay[x][y]) // start new movement phase
8060 // all objects that can change their move direction after each step
8061 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8063 if (element != EL_YAMYAM &&
8064 element != EL_DARK_YAMYAM &&
8065 element != EL_PACMAN &&
8066 !(move_pattern & MV_ANY_DIRECTION) &&
8067 move_pattern != MV_TURNING_LEFT &&
8068 move_pattern != MV_TURNING_RIGHT &&
8069 move_pattern != MV_TURNING_LEFT_RIGHT &&
8070 move_pattern != MV_TURNING_RIGHT_LEFT &&
8071 move_pattern != MV_TURNING_RANDOM)
8075 if (MovDelay[x][y] && (element == EL_BUG ||
8076 element == EL_SPACESHIP ||
8077 element == EL_SP_SNIKSNAK ||
8078 element == EL_SP_ELECTRON ||
8079 element == EL_MOLE))
8080 TEST_DrawLevelField(x, y);
8084 if (MovDelay[x][y]) // wait some time before next movement
8088 if (element == EL_ROBOT ||
8089 element == EL_YAMYAM ||
8090 element == EL_DARK_YAMYAM)
8092 DrawLevelElementAnimationIfNeeded(x, y, element);
8093 PlayLevelSoundAction(x, y, ACTION_WAITING);
8095 else if (element == EL_SP_ELECTRON)
8096 DrawLevelElementAnimationIfNeeded(x, y, element);
8097 else if (element == EL_DRAGON)
8100 int dir = MovDir[x][y];
8101 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8102 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8103 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8104 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8105 dir == MV_UP ? IMG_FLAMES_1_UP :
8106 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8107 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8109 GfxAction[x][y] = ACTION_ATTACKING;
8111 if (IS_PLAYER(x, y))
8112 DrawPlayerField(x, y);
8114 TEST_DrawLevelField(x, y);
8116 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8118 for (i = 1; i <= 3; i++)
8120 int xx = x + i * dx;
8121 int yy = y + i * dy;
8122 int sx = SCREENX(xx);
8123 int sy = SCREENY(yy);
8124 int flame_graphic = graphic + (i - 1);
8126 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8131 int flamed = MovingOrBlocked2Element(xx, yy);
8133 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8136 RemoveMovingField(xx, yy);
8138 ChangeDelay[xx][yy] = 0;
8140 Tile[xx][yy] = EL_FLAMES;
8142 if (IN_SCR_FIELD(sx, sy))
8144 TEST_DrawLevelFieldCrumbled(xx, yy);
8145 DrawGraphic(sx, sy, flame_graphic, frame);
8150 if (Tile[xx][yy] == EL_FLAMES)
8151 Tile[xx][yy] = EL_EMPTY;
8152 TEST_DrawLevelField(xx, yy);
8157 if (MovDelay[x][y]) // element still has to wait some time
8159 PlayLevelSoundAction(x, y, ACTION_WAITING);
8165 // now make next step
8167 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8169 if (DONT_COLLIDE_WITH(element) &&
8170 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8171 !PLAYER_ENEMY_PROTECTED(newx, newy))
8173 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8178 else if (CAN_MOVE_INTO_ACID(element) &&
8179 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8180 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8181 (MovDir[x][y] == MV_DOWN ||
8182 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8184 SplashAcid(newx, newy);
8185 Store[x][y] = EL_ACID;
8187 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8189 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8190 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8191 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8192 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8195 TEST_DrawLevelField(x, y);
8197 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8198 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8199 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8201 game.friends_still_needed--;
8202 if (!game.friends_still_needed &&
8204 game.all_players_gone)
8209 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8211 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8212 TEST_DrawLevelField(newx, newy);
8214 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8216 else if (!IS_FREE(newx, newy))
8218 GfxAction[x][y] = ACTION_WAITING;
8220 if (IS_PLAYER(x, y))
8221 DrawPlayerField(x, y);
8223 TEST_DrawLevelField(x, y);
8228 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8230 if (IS_FOOD_PIG(Tile[newx][newy]))
8232 if (IS_MOVING(newx, newy))
8233 RemoveMovingField(newx, newy);
8236 Tile[newx][newy] = EL_EMPTY;
8237 TEST_DrawLevelField(newx, newy);
8240 PlayLevelSound(x, y, SND_PIG_DIGGING);
8242 else if (!IS_FREE(newx, newy))
8244 if (IS_PLAYER(x, y))
8245 DrawPlayerField(x, y);
8247 TEST_DrawLevelField(x, y);
8252 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8254 if (Store[x][y] != EL_EMPTY)
8256 boolean can_clone = FALSE;
8259 // check if element to clone is still there
8260 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8262 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8270 // cannot clone or target field not free anymore -- do not clone
8271 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8272 Store[x][y] = EL_EMPTY;
8275 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8277 if (IS_MV_DIAGONAL(MovDir[x][y]))
8279 int diagonal_move_dir = MovDir[x][y];
8280 int stored = Store[x][y];
8281 int change_delay = 8;
8284 // android is moving diagonally
8286 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8288 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8289 GfxElement[x][y] = EL_EMC_ANDROID;
8290 GfxAction[x][y] = ACTION_SHRINKING;
8291 GfxDir[x][y] = diagonal_move_dir;
8292 ChangeDelay[x][y] = change_delay;
8294 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8297 DrawLevelGraphicAnimation(x, y, graphic);
8298 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8300 if (Tile[newx][newy] == EL_ACID)
8302 SplashAcid(newx, newy);
8307 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8309 Store[newx][newy] = EL_EMC_ANDROID;
8310 GfxElement[newx][newy] = EL_EMC_ANDROID;
8311 GfxAction[newx][newy] = ACTION_GROWING;
8312 GfxDir[newx][newy] = diagonal_move_dir;
8313 ChangeDelay[newx][newy] = change_delay;
8315 graphic = el_act_dir2img(GfxElement[newx][newy],
8316 GfxAction[newx][newy], GfxDir[newx][newy]);
8318 DrawLevelGraphicAnimation(newx, newy, graphic);
8319 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8325 Tile[newx][newy] = EL_EMPTY;
8326 TEST_DrawLevelField(newx, newy);
8328 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8331 else if (!IS_FREE(newx, newy))
8336 else if (IS_CUSTOM_ELEMENT(element) &&
8337 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8339 if (!DigFieldByCE(newx, newy, element))
8342 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8344 RunnerVisit[x][y] = FrameCounter;
8345 PlayerVisit[x][y] /= 8; // expire player visit path
8348 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8350 if (!IS_FREE(newx, newy))
8352 if (IS_PLAYER(x, y))
8353 DrawPlayerField(x, y);
8355 TEST_DrawLevelField(x, y);
8361 boolean wanna_flame = !RND(10);
8362 int dx = newx - x, dy = newy - y;
8363 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8364 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8365 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8366 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8367 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8368 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8371 IS_CLASSIC_ENEMY(element1) ||
8372 IS_CLASSIC_ENEMY(element2)) &&
8373 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8374 element1 != EL_FLAMES && element2 != EL_FLAMES)
8376 ResetGfxAnimation(x, y);
8377 GfxAction[x][y] = ACTION_ATTACKING;
8379 if (IS_PLAYER(x, y))
8380 DrawPlayerField(x, y);
8382 TEST_DrawLevelField(x, y);
8384 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8386 MovDelay[x][y] = 50;
8388 Tile[newx][newy] = EL_FLAMES;
8389 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8390 Tile[newx1][newy1] = EL_FLAMES;
8391 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8392 Tile[newx2][newy2] = EL_FLAMES;
8398 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8399 Tile[newx][newy] == EL_DIAMOND)
8401 if (IS_MOVING(newx, newy))
8402 RemoveMovingField(newx, newy);
8405 Tile[newx][newy] = EL_EMPTY;
8406 TEST_DrawLevelField(newx, newy);
8409 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8411 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8412 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8414 if (AmoebaNr[newx][newy])
8416 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8417 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8418 Tile[newx][newy] == EL_BD_AMOEBA)
8419 AmoebaCnt[AmoebaNr[newx][newy]]--;
8422 if (IS_MOVING(newx, newy))
8424 RemoveMovingField(newx, newy);
8428 Tile[newx][newy] = EL_EMPTY;
8429 TEST_DrawLevelField(newx, newy);
8432 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8434 else if ((element == EL_PACMAN || element == EL_MOLE)
8435 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8437 if (AmoebaNr[newx][newy])
8439 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8440 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8441 Tile[newx][newy] == EL_BD_AMOEBA)
8442 AmoebaCnt[AmoebaNr[newx][newy]]--;
8445 if (element == EL_MOLE)
8447 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8448 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8450 ResetGfxAnimation(x, y);
8451 GfxAction[x][y] = ACTION_DIGGING;
8452 TEST_DrawLevelField(x, y);
8454 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8456 return; // wait for shrinking amoeba
8458 else // element == EL_PACMAN
8460 Tile[newx][newy] = EL_EMPTY;
8461 TEST_DrawLevelField(newx, newy);
8462 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8465 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8466 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8467 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8469 // wait for shrinking amoeba to completely disappear
8472 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8474 // object was running against a wall
8478 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8479 DrawLevelElementAnimation(x, y, element);
8481 if (DONT_TOUCH(element))
8482 TestIfBadThingTouchesPlayer(x, y);
8487 InitMovingField(x, y, MovDir[x][y]);
8489 PlayLevelSoundAction(x, y, ACTION_MOVING);
8493 ContinueMoving(x, y);
8496 void ContinueMoving(int x, int y)
8498 int element = Tile[x][y];
8499 struct ElementInfo *ei = &element_info[element];
8500 int direction = MovDir[x][y];
8501 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8502 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8503 int newx = x + dx, newy = y + dy;
8504 int stored = Store[x][y];
8505 int stored_new = Store[newx][newy];
8506 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8507 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8508 boolean last_line = (newy == lev_fieldy - 1);
8510 MovPos[x][y] += getElementMoveStepsize(x, y);
8512 if (pushed_by_player) // special case: moving object pushed by player
8513 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8515 if (ABS(MovPos[x][y]) < TILEX)
8517 TEST_DrawLevelField(x, y);
8519 return; // element is still moving
8522 // element reached destination field
8524 Tile[x][y] = EL_EMPTY;
8525 Tile[newx][newy] = element;
8526 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8528 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8530 element = Tile[newx][newy] = EL_ACID;
8532 else if (element == EL_MOLE)
8534 Tile[x][y] = EL_SAND;
8536 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8538 else if (element == EL_QUICKSAND_FILLING)
8540 element = Tile[newx][newy] = get_next_element(element);
8541 Store[newx][newy] = Store[x][y];
8543 else if (element == EL_QUICKSAND_EMPTYING)
8545 Tile[x][y] = get_next_element(element);
8546 element = Tile[newx][newy] = Store[x][y];
8548 else if (element == EL_QUICKSAND_FAST_FILLING)
8550 element = Tile[newx][newy] = get_next_element(element);
8551 Store[newx][newy] = Store[x][y];
8553 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8555 Tile[x][y] = get_next_element(element);
8556 element = Tile[newx][newy] = Store[x][y];
8558 else if (element == EL_MAGIC_WALL_FILLING)
8560 element = Tile[newx][newy] = get_next_element(element);
8561 if (!game.magic_wall_active)
8562 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8563 Store[newx][newy] = Store[x][y];
8565 else if (element == EL_MAGIC_WALL_EMPTYING)
8567 Tile[x][y] = get_next_element(element);
8568 if (!game.magic_wall_active)
8569 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8570 element = Tile[newx][newy] = Store[x][y];
8572 InitField(newx, newy, FALSE);
8574 else if (element == EL_BD_MAGIC_WALL_FILLING)
8576 element = Tile[newx][newy] = get_next_element(element);
8577 if (!game.magic_wall_active)
8578 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8579 Store[newx][newy] = Store[x][y];
8581 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8583 Tile[x][y] = get_next_element(element);
8584 if (!game.magic_wall_active)
8585 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8586 element = Tile[newx][newy] = Store[x][y];
8588 InitField(newx, newy, FALSE);
8590 else if (element == EL_DC_MAGIC_WALL_FILLING)
8592 element = Tile[newx][newy] = get_next_element(element);
8593 if (!game.magic_wall_active)
8594 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8595 Store[newx][newy] = Store[x][y];
8597 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8599 Tile[x][y] = get_next_element(element);
8600 if (!game.magic_wall_active)
8601 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8602 element = Tile[newx][newy] = Store[x][y];
8604 InitField(newx, newy, FALSE);
8606 else if (element == EL_AMOEBA_DROPPING)
8608 Tile[x][y] = get_next_element(element);
8609 element = Tile[newx][newy] = Store[x][y];
8611 else if (element == EL_SOKOBAN_OBJECT)
8614 Tile[x][y] = Back[x][y];
8616 if (Back[newx][newy])
8617 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8619 Back[x][y] = Back[newx][newy] = 0;
8622 Store[x][y] = EL_EMPTY;
8627 MovDelay[newx][newy] = 0;
8629 if (CAN_CHANGE_OR_HAS_ACTION(element))
8631 // copy element change control values to new field
8632 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8633 ChangePage[newx][newy] = ChangePage[x][y];
8634 ChangeCount[newx][newy] = ChangeCount[x][y];
8635 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8638 CustomValue[newx][newy] = CustomValue[x][y];
8640 ChangeDelay[x][y] = 0;
8641 ChangePage[x][y] = -1;
8642 ChangeCount[x][y] = 0;
8643 ChangeEvent[x][y] = -1;
8645 CustomValue[x][y] = 0;
8647 // copy animation control values to new field
8648 GfxFrame[newx][newy] = GfxFrame[x][y];
8649 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8650 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8651 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8653 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8655 // some elements can leave other elements behind after moving
8656 if (ei->move_leave_element != EL_EMPTY &&
8657 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8658 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8660 int move_leave_element = ei->move_leave_element;
8662 // this makes it possible to leave the removed element again
8663 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8664 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8666 Tile[x][y] = move_leave_element;
8668 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8669 MovDir[x][y] = direction;
8671 InitField(x, y, FALSE);
8673 if (GFX_CRUMBLED(Tile[x][y]))
8674 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8676 if (ELEM_IS_PLAYER(move_leave_element))
8677 RelocatePlayer(x, y, move_leave_element);
8680 // do this after checking for left-behind element
8681 ResetGfxAnimation(x, y); // reset animation values for old field
8683 if (!CAN_MOVE(element) ||
8684 (CAN_FALL(element) && direction == MV_DOWN &&
8685 (element == EL_SPRING ||
8686 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8687 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8688 GfxDir[x][y] = MovDir[newx][newy] = 0;
8690 TEST_DrawLevelField(x, y);
8691 TEST_DrawLevelField(newx, newy);
8693 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8695 // prevent pushed element from moving on in pushed direction
8696 if (pushed_by_player && CAN_MOVE(element) &&
8697 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8698 !(element_info[element].move_pattern & direction))
8699 TurnRound(newx, newy);
8701 // prevent elements on conveyor belt from moving on in last direction
8702 if (pushed_by_conveyor && CAN_FALL(element) &&
8703 direction & MV_HORIZONTAL)
8704 MovDir[newx][newy] = 0;
8706 if (!pushed_by_player)
8708 int nextx = newx + dx, nexty = newy + dy;
8709 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8711 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8713 if (CAN_FALL(element) && direction == MV_DOWN)
8714 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8716 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8717 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8719 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8720 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8723 if (DONT_TOUCH(element)) // object may be nasty to player or others
8725 TestIfBadThingTouchesPlayer(newx, newy);
8726 TestIfBadThingTouchesFriend(newx, newy);
8728 if (!IS_CUSTOM_ELEMENT(element))
8729 TestIfBadThingTouchesOtherBadThing(newx, newy);
8731 else if (element == EL_PENGUIN)
8732 TestIfFriendTouchesBadThing(newx, newy);
8734 if (DONT_GET_HIT_BY(element))
8736 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8739 // give the player one last chance (one more frame) to move away
8740 if (CAN_FALL(element) && direction == MV_DOWN &&
8741 (last_line || (!IS_FREE(x, newy + 1) &&
8742 (!IS_PLAYER(x, newy + 1) ||
8743 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8746 if (pushed_by_player && !game.use_change_when_pushing_bug)
8748 int push_side = MV_DIR_OPPOSITE(direction);
8749 struct PlayerInfo *player = PLAYERINFO(x, y);
8751 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8752 player->index_bit, push_side);
8753 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8754 player->index_bit, push_side);
8757 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8758 MovDelay[newx][newy] = 1;
8760 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8762 TestIfElementTouchesCustomElement(x, y); // empty or new element
8763 TestIfElementHitsCustomElement(newx, newy, direction);
8764 TestIfPlayerTouchesCustomElement(newx, newy);
8765 TestIfElementTouchesCustomElement(newx, newy);
8767 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8768 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8769 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8770 MV_DIR_OPPOSITE(direction));
8773 int AmoebaNeighbourNr(int ax, int ay)
8776 int element = Tile[ax][ay];
8778 static int xy[4][2] =
8786 for (i = 0; i < NUM_DIRECTIONS; i++)
8788 int x = ax + xy[i][0];
8789 int y = ay + xy[i][1];
8791 if (!IN_LEV_FIELD(x, y))
8794 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8795 group_nr = AmoebaNr[x][y];
8801 static void AmoebaMerge(int ax, int ay)
8803 int i, x, y, xx, yy;
8804 int new_group_nr = AmoebaNr[ax][ay];
8805 static int xy[4][2] =
8813 if (new_group_nr == 0)
8816 for (i = 0; i < NUM_DIRECTIONS; i++)
8821 if (!IN_LEV_FIELD(x, y))
8824 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8825 Tile[x][y] == EL_BD_AMOEBA ||
8826 Tile[x][y] == EL_AMOEBA_DEAD) &&
8827 AmoebaNr[x][y] != new_group_nr)
8829 int old_group_nr = AmoebaNr[x][y];
8831 if (old_group_nr == 0)
8834 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8835 AmoebaCnt[old_group_nr] = 0;
8836 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8837 AmoebaCnt2[old_group_nr] = 0;
8839 SCAN_PLAYFIELD(xx, yy)
8841 if (AmoebaNr[xx][yy] == old_group_nr)
8842 AmoebaNr[xx][yy] = new_group_nr;
8848 void AmoebaToDiamond(int ax, int ay)
8852 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8854 int group_nr = AmoebaNr[ax][ay];
8859 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8860 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8866 SCAN_PLAYFIELD(x, y)
8868 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8871 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8875 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8876 SND_AMOEBA_TURNING_TO_GEM :
8877 SND_AMOEBA_TURNING_TO_ROCK));
8882 static int xy[4][2] =
8890 for (i = 0; i < NUM_DIRECTIONS; i++)
8895 if (!IN_LEV_FIELD(x, y))
8898 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8900 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8901 SND_AMOEBA_TURNING_TO_GEM :
8902 SND_AMOEBA_TURNING_TO_ROCK));
8909 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8912 int group_nr = AmoebaNr[ax][ay];
8913 boolean done = FALSE;
8918 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8919 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8925 SCAN_PLAYFIELD(x, y)
8927 if (AmoebaNr[x][y] == group_nr &&
8928 (Tile[x][y] == EL_AMOEBA_DEAD ||
8929 Tile[x][y] == EL_BD_AMOEBA ||
8930 Tile[x][y] == EL_AMOEBA_GROWING))
8933 Tile[x][y] = new_element;
8934 InitField(x, y, FALSE);
8935 TEST_DrawLevelField(x, y);
8941 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8942 SND_BD_AMOEBA_TURNING_TO_ROCK :
8943 SND_BD_AMOEBA_TURNING_TO_GEM));
8946 static void AmoebaGrowing(int x, int y)
8948 static unsigned int sound_delay = 0;
8949 static unsigned int sound_delay_value = 0;
8951 if (!MovDelay[x][y]) // start new growing cycle
8955 if (DelayReached(&sound_delay, sound_delay_value))
8957 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8958 sound_delay_value = 30;
8962 if (MovDelay[x][y]) // wait some time before growing bigger
8965 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8967 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8968 6 - MovDelay[x][y]);
8970 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8973 if (!MovDelay[x][y])
8975 Tile[x][y] = Store[x][y];
8977 TEST_DrawLevelField(x, y);
8982 static void AmoebaShrinking(int x, int y)
8984 static unsigned int sound_delay = 0;
8985 static unsigned int sound_delay_value = 0;
8987 if (!MovDelay[x][y]) // start new shrinking cycle
8991 if (DelayReached(&sound_delay, sound_delay_value))
8992 sound_delay_value = 30;
8995 if (MovDelay[x][y]) // wait some time before shrinking
8998 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9000 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9001 6 - MovDelay[x][y]);
9003 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9006 if (!MovDelay[x][y])
9008 Tile[x][y] = EL_EMPTY;
9009 TEST_DrawLevelField(x, y);
9011 // don't let mole enter this field in this cycle;
9012 // (give priority to objects falling to this field from above)
9018 static void AmoebaReproduce(int ax, int ay)
9021 int element = Tile[ax][ay];
9022 int graphic = el2img(element);
9023 int newax = ax, neway = ay;
9024 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9025 static int xy[4][2] =
9033 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9035 Tile[ax][ay] = EL_AMOEBA_DEAD;
9036 TEST_DrawLevelField(ax, ay);
9040 if (IS_ANIMATED(graphic))
9041 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9043 if (!MovDelay[ax][ay]) // start making new amoeba field
9044 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9046 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9049 if (MovDelay[ax][ay])
9053 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9056 int x = ax + xy[start][0];
9057 int y = ay + xy[start][1];
9059 if (!IN_LEV_FIELD(x, y))
9062 if (IS_FREE(x, y) ||
9063 CAN_GROW_INTO(Tile[x][y]) ||
9064 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9065 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9071 if (newax == ax && neway == ay)
9074 else // normal or "filled" (BD style) amoeba
9077 boolean waiting_for_player = FALSE;
9079 for (i = 0; i < NUM_DIRECTIONS; i++)
9081 int j = (start + i) % 4;
9082 int x = ax + xy[j][0];
9083 int y = ay + xy[j][1];
9085 if (!IN_LEV_FIELD(x, y))
9088 if (IS_FREE(x, y) ||
9089 CAN_GROW_INTO(Tile[x][y]) ||
9090 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9091 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9097 else if (IS_PLAYER(x, y))
9098 waiting_for_player = TRUE;
9101 if (newax == ax && neway == ay) // amoeba cannot grow
9103 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9105 Tile[ax][ay] = EL_AMOEBA_DEAD;
9106 TEST_DrawLevelField(ax, ay);
9107 AmoebaCnt[AmoebaNr[ax][ay]]--;
9109 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9111 if (element == EL_AMOEBA_FULL)
9112 AmoebaToDiamond(ax, ay);
9113 else if (element == EL_BD_AMOEBA)
9114 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9119 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9121 // amoeba gets larger by growing in some direction
9123 int new_group_nr = AmoebaNr[ax][ay];
9126 if (new_group_nr == 0)
9128 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9130 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9136 AmoebaNr[newax][neway] = new_group_nr;
9137 AmoebaCnt[new_group_nr]++;
9138 AmoebaCnt2[new_group_nr]++;
9140 // if amoeba touches other amoeba(s) after growing, unify them
9141 AmoebaMerge(newax, neway);
9143 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9145 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9151 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9152 (neway == lev_fieldy - 1 && newax != ax))
9154 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9155 Store[newax][neway] = element;
9157 else if (neway == ay || element == EL_EMC_DRIPPER)
9159 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9161 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9165 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9166 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9167 Store[ax][ay] = EL_AMOEBA_DROP;
9168 ContinueMoving(ax, ay);
9172 TEST_DrawLevelField(newax, neway);
9175 static void Life(int ax, int ay)
9179 int element = Tile[ax][ay];
9180 int graphic = el2img(element);
9181 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9183 boolean changed = FALSE;
9185 if (IS_ANIMATED(graphic))
9186 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9191 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9192 MovDelay[ax][ay] = life_time;
9194 if (MovDelay[ax][ay]) // wait some time before next cycle
9197 if (MovDelay[ax][ay])
9201 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9203 int xx = ax+x1, yy = ay+y1;
9204 int old_element = Tile[xx][yy];
9205 int num_neighbours = 0;
9207 if (!IN_LEV_FIELD(xx, yy))
9210 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9212 int x = xx+x2, y = yy+y2;
9214 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9217 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9218 boolean is_neighbour = FALSE;
9220 if (level.use_life_bugs)
9222 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9223 (IS_FREE(x, y) && Stop[x][y]));
9226 (Last[x][y] == element || is_player_cell);
9232 boolean is_free = FALSE;
9234 if (level.use_life_bugs)
9235 is_free = (IS_FREE(xx, yy));
9237 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9239 if (xx == ax && yy == ay) // field in the middle
9241 if (num_neighbours < life_parameter[0] ||
9242 num_neighbours > life_parameter[1])
9244 Tile[xx][yy] = EL_EMPTY;
9245 if (Tile[xx][yy] != old_element)
9246 TEST_DrawLevelField(xx, yy);
9247 Stop[xx][yy] = TRUE;
9251 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9252 { // free border field
9253 if (num_neighbours >= life_parameter[2] &&
9254 num_neighbours <= life_parameter[3])
9256 Tile[xx][yy] = element;
9257 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9258 if (Tile[xx][yy] != old_element)
9259 TEST_DrawLevelField(xx, yy);
9260 Stop[xx][yy] = TRUE;
9267 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9268 SND_GAME_OF_LIFE_GROWING);
9271 static void InitRobotWheel(int x, int y)
9273 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9276 static void RunRobotWheel(int x, int y)
9278 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9281 static void StopRobotWheel(int x, int y)
9283 if (game.robot_wheel_x == x &&
9284 game.robot_wheel_y == y)
9286 game.robot_wheel_x = -1;
9287 game.robot_wheel_y = -1;
9288 game.robot_wheel_active = FALSE;
9292 static void InitTimegateWheel(int x, int y)
9294 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9297 static void RunTimegateWheel(int x, int y)
9299 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9302 static void InitMagicBallDelay(int x, int y)
9304 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9307 static void ActivateMagicBall(int bx, int by)
9311 if (level.ball_random)
9313 int pos_border = RND(8); // select one of the eight border elements
9314 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9315 int xx = pos_content % 3;
9316 int yy = pos_content / 3;
9321 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9322 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9326 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9328 int xx = x - bx + 1;
9329 int yy = y - by + 1;
9331 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9332 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9336 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9339 static void CheckExit(int x, int y)
9341 if (game.gems_still_needed > 0 ||
9342 game.sokoban_fields_still_needed > 0 ||
9343 game.sokoban_objects_still_needed > 0 ||
9344 game.lights_still_needed > 0)
9346 int element = Tile[x][y];
9347 int graphic = el2img(element);
9349 if (IS_ANIMATED(graphic))
9350 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9355 // do not re-open exit door closed after last player
9356 if (game.all_players_gone)
9359 Tile[x][y] = EL_EXIT_OPENING;
9361 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9364 static void CheckExitEM(int x, int y)
9366 if (game.gems_still_needed > 0 ||
9367 game.sokoban_fields_still_needed > 0 ||
9368 game.sokoban_objects_still_needed > 0 ||
9369 game.lights_still_needed > 0)
9371 int element = Tile[x][y];
9372 int graphic = el2img(element);
9374 if (IS_ANIMATED(graphic))
9375 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9380 // do not re-open exit door closed after last player
9381 if (game.all_players_gone)
9384 Tile[x][y] = EL_EM_EXIT_OPENING;
9386 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9389 static void CheckExitSteel(int x, int y)
9391 if (game.gems_still_needed > 0 ||
9392 game.sokoban_fields_still_needed > 0 ||
9393 game.sokoban_objects_still_needed > 0 ||
9394 game.lights_still_needed > 0)
9396 int element = Tile[x][y];
9397 int graphic = el2img(element);
9399 if (IS_ANIMATED(graphic))
9400 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9405 // do not re-open exit door closed after last player
9406 if (game.all_players_gone)
9409 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9411 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9414 static void CheckExitSteelEM(int x, int y)
9416 if (game.gems_still_needed > 0 ||
9417 game.sokoban_fields_still_needed > 0 ||
9418 game.sokoban_objects_still_needed > 0 ||
9419 game.lights_still_needed > 0)
9421 int element = Tile[x][y];
9422 int graphic = el2img(element);
9424 if (IS_ANIMATED(graphic))
9425 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9430 // do not re-open exit door closed after last player
9431 if (game.all_players_gone)
9434 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9436 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9439 static void CheckExitSP(int x, int y)
9441 if (game.gems_still_needed > 0)
9443 int element = Tile[x][y];
9444 int graphic = el2img(element);
9446 if (IS_ANIMATED(graphic))
9447 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9452 // do not re-open exit door closed after last player
9453 if (game.all_players_gone)
9456 Tile[x][y] = EL_SP_EXIT_OPENING;
9458 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9461 static void CloseAllOpenTimegates(void)
9465 SCAN_PLAYFIELD(x, y)
9467 int element = Tile[x][y];
9469 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9471 Tile[x][y] = EL_TIMEGATE_CLOSING;
9473 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9478 static void DrawTwinkleOnField(int x, int y)
9480 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9483 if (Tile[x][y] == EL_BD_DIAMOND)
9486 if (MovDelay[x][y] == 0) // next animation frame
9487 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9489 if (MovDelay[x][y] != 0) // wait some time before next frame
9493 DrawLevelElementAnimation(x, y, Tile[x][y]);
9495 if (MovDelay[x][y] != 0)
9497 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9498 10 - MovDelay[x][y]);
9500 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9505 static void MauerWaechst(int x, int y)
9509 if (!MovDelay[x][y]) // next animation frame
9510 MovDelay[x][y] = 3 * delay;
9512 if (MovDelay[x][y]) // wait some time before next frame
9516 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9518 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9519 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9521 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9524 if (!MovDelay[x][y])
9526 if (MovDir[x][y] == MV_LEFT)
9528 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9529 TEST_DrawLevelField(x - 1, y);
9531 else if (MovDir[x][y] == MV_RIGHT)
9533 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9534 TEST_DrawLevelField(x + 1, y);
9536 else if (MovDir[x][y] == MV_UP)
9538 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9539 TEST_DrawLevelField(x, y - 1);
9543 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9544 TEST_DrawLevelField(x, y + 1);
9547 Tile[x][y] = Store[x][y];
9549 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9550 TEST_DrawLevelField(x, y);
9555 static void MauerAbleger(int ax, int ay)
9557 int element = Tile[ax][ay];
9558 int graphic = el2img(element);
9559 boolean oben_frei = FALSE, unten_frei = FALSE;
9560 boolean links_frei = FALSE, rechts_frei = FALSE;
9561 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9562 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9563 boolean new_wall = FALSE;
9565 if (IS_ANIMATED(graphic))
9566 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9568 if (!MovDelay[ax][ay]) // start building new wall
9569 MovDelay[ax][ay] = 6;
9571 if (MovDelay[ax][ay]) // wait some time before building new wall
9574 if (MovDelay[ax][ay])
9578 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9580 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9582 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9584 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9587 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9588 element == EL_EXPANDABLE_WALL_ANY)
9592 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9593 Store[ax][ay-1] = element;
9594 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9595 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9596 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9597 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9602 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9603 Store[ax][ay+1] = element;
9604 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9605 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9606 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9607 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9612 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9613 element == EL_EXPANDABLE_WALL_ANY ||
9614 element == EL_EXPANDABLE_WALL ||
9615 element == EL_BD_EXPANDABLE_WALL)
9619 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9620 Store[ax-1][ay] = element;
9621 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9622 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9623 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9624 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9630 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9631 Store[ax+1][ay] = element;
9632 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9633 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9634 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9635 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9640 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9641 TEST_DrawLevelField(ax, ay);
9643 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9645 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9646 unten_massiv = TRUE;
9647 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9648 links_massiv = TRUE;
9649 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9650 rechts_massiv = TRUE;
9652 if (((oben_massiv && unten_massiv) ||
9653 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9654 element == EL_EXPANDABLE_WALL) &&
9655 ((links_massiv && rechts_massiv) ||
9656 element == EL_EXPANDABLE_WALL_VERTICAL))
9657 Tile[ax][ay] = EL_WALL;
9660 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9663 static void MauerAblegerStahl(int ax, int ay)
9665 int element = Tile[ax][ay];
9666 int graphic = el2img(element);
9667 boolean oben_frei = FALSE, unten_frei = FALSE;
9668 boolean links_frei = FALSE, rechts_frei = FALSE;
9669 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9670 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9671 boolean new_wall = FALSE;
9673 if (IS_ANIMATED(graphic))
9674 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9676 if (!MovDelay[ax][ay]) // start building new wall
9677 MovDelay[ax][ay] = 6;
9679 if (MovDelay[ax][ay]) // wait some time before building new wall
9682 if (MovDelay[ax][ay])
9686 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9688 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9690 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9692 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9695 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9696 element == EL_EXPANDABLE_STEELWALL_ANY)
9700 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9701 Store[ax][ay-1] = element;
9702 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9703 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9704 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9705 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9710 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9711 Store[ax][ay+1] = element;
9712 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9713 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9714 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9715 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9720 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9721 element == EL_EXPANDABLE_STEELWALL_ANY)
9725 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9726 Store[ax-1][ay] = element;
9727 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9728 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9729 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9730 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9736 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9737 Store[ax+1][ay] = element;
9738 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9739 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9740 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9741 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9746 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9748 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9749 unten_massiv = TRUE;
9750 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9751 links_massiv = TRUE;
9752 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9753 rechts_massiv = TRUE;
9755 if (((oben_massiv && unten_massiv) ||
9756 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9757 ((links_massiv && rechts_massiv) ||
9758 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9759 Tile[ax][ay] = EL_STEELWALL;
9762 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9765 static void CheckForDragon(int x, int y)
9768 boolean dragon_found = FALSE;
9769 static int xy[4][2] =
9777 for (i = 0; i < NUM_DIRECTIONS; i++)
9779 for (j = 0; j < 4; j++)
9781 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9783 if (IN_LEV_FIELD(xx, yy) &&
9784 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9786 if (Tile[xx][yy] == EL_DRAGON)
9787 dragon_found = TRUE;
9796 for (i = 0; i < NUM_DIRECTIONS; i++)
9798 for (j = 0; j < 3; j++)
9800 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9802 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9804 Tile[xx][yy] = EL_EMPTY;
9805 TEST_DrawLevelField(xx, yy);
9814 static void InitBuggyBase(int x, int y)
9816 int element = Tile[x][y];
9817 int activating_delay = FRAMES_PER_SECOND / 4;
9820 (element == EL_SP_BUGGY_BASE ?
9821 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9822 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9824 element == EL_SP_BUGGY_BASE_ACTIVE ?
9825 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9828 static void WarnBuggyBase(int x, int y)
9831 static int xy[4][2] =
9839 for (i = 0; i < NUM_DIRECTIONS; i++)
9841 int xx = x + xy[i][0];
9842 int yy = y + xy[i][1];
9844 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9846 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9853 static void InitTrap(int x, int y)
9855 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9858 static void ActivateTrap(int x, int y)
9860 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9863 static void ChangeActiveTrap(int x, int y)
9865 int graphic = IMG_TRAP_ACTIVE;
9867 // if new animation frame was drawn, correct crumbled sand border
9868 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9869 TEST_DrawLevelFieldCrumbled(x, y);
9872 static int getSpecialActionElement(int element, int number, int base_element)
9874 return (element != EL_EMPTY ? element :
9875 number != -1 ? base_element + number - 1 :
9879 static int getModifiedActionNumber(int value_old, int operator, int operand,
9880 int value_min, int value_max)
9882 int value_new = (operator == CA_MODE_SET ? operand :
9883 operator == CA_MODE_ADD ? value_old + operand :
9884 operator == CA_MODE_SUBTRACT ? value_old - operand :
9885 operator == CA_MODE_MULTIPLY ? value_old * operand :
9886 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9887 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9890 return (value_new < value_min ? value_min :
9891 value_new > value_max ? value_max :
9895 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9897 struct ElementInfo *ei = &element_info[element];
9898 struct ElementChangeInfo *change = &ei->change_page[page];
9899 int target_element = change->target_element;
9900 int action_type = change->action_type;
9901 int action_mode = change->action_mode;
9902 int action_arg = change->action_arg;
9903 int action_element = change->action_element;
9906 if (!change->has_action)
9909 // ---------- determine action paramater values -----------------------------
9911 int level_time_value =
9912 (level.time > 0 ? TimeLeft :
9915 int action_arg_element_raw =
9916 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9917 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9918 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9919 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9920 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9921 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9922 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9924 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9926 int action_arg_direction =
9927 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9928 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9929 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9930 change->actual_trigger_side :
9931 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9932 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9935 int action_arg_number_min =
9936 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9939 int action_arg_number_max =
9940 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9941 action_type == CA_SET_LEVEL_GEMS ? 999 :
9942 action_type == CA_SET_LEVEL_TIME ? 9999 :
9943 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9944 action_type == CA_SET_CE_VALUE ? 9999 :
9945 action_type == CA_SET_CE_SCORE ? 9999 :
9948 int action_arg_number_reset =
9949 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9950 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9951 action_type == CA_SET_LEVEL_TIME ? level.time :
9952 action_type == CA_SET_LEVEL_SCORE ? 0 :
9953 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9954 action_type == CA_SET_CE_SCORE ? 0 :
9957 int action_arg_number =
9958 (action_arg <= CA_ARG_MAX ? action_arg :
9959 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9960 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9961 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9962 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9963 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9964 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9965 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9966 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9967 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9968 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9969 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9970 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9971 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9972 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9973 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9974 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9975 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9976 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9977 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9978 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9979 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9982 int action_arg_number_old =
9983 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9984 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9985 action_type == CA_SET_LEVEL_SCORE ? game.score :
9986 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9987 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9990 int action_arg_number_new =
9991 getModifiedActionNumber(action_arg_number_old,
9992 action_mode, action_arg_number,
9993 action_arg_number_min, action_arg_number_max);
9995 int trigger_player_bits =
9996 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9997 change->actual_trigger_player_bits : change->trigger_player);
9999 int action_arg_player_bits =
10000 (action_arg >= CA_ARG_PLAYER_1 &&
10001 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10002 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10003 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10006 // ---------- execute action -----------------------------------------------
10008 switch (action_type)
10015 // ---------- level actions ----------------------------------------------
10017 case CA_RESTART_LEVEL:
10019 game.restart_level = TRUE;
10024 case CA_SHOW_ENVELOPE:
10026 int element = getSpecialActionElement(action_arg_element,
10027 action_arg_number, EL_ENVELOPE_1);
10029 if (IS_ENVELOPE(element))
10030 local_player->show_envelope = element;
10035 case CA_SET_LEVEL_TIME:
10037 if (level.time > 0) // only modify limited time value
10039 TimeLeft = action_arg_number_new;
10041 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10043 DisplayGameControlValues();
10045 if (!TimeLeft && setup.time_limit)
10046 for (i = 0; i < MAX_PLAYERS; i++)
10047 KillPlayer(&stored_player[i]);
10053 case CA_SET_LEVEL_SCORE:
10055 game.score = action_arg_number_new;
10057 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10059 DisplayGameControlValues();
10064 case CA_SET_LEVEL_GEMS:
10066 game.gems_still_needed = action_arg_number_new;
10068 game.snapshot.collected_item = TRUE;
10070 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10072 DisplayGameControlValues();
10077 case CA_SET_LEVEL_WIND:
10079 game.wind_direction = action_arg_direction;
10084 case CA_SET_LEVEL_RANDOM_SEED:
10086 // ensure that setting a new random seed while playing is predictable
10087 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10092 // ---------- player actions ---------------------------------------------
10094 case CA_MOVE_PLAYER:
10095 case CA_MOVE_PLAYER_NEW:
10097 // automatically move to the next field in specified direction
10098 for (i = 0; i < MAX_PLAYERS; i++)
10099 if (trigger_player_bits & (1 << i))
10100 if (action_type == CA_MOVE_PLAYER ||
10101 stored_player[i].MovPos == 0)
10102 stored_player[i].programmed_action = action_arg_direction;
10107 case CA_EXIT_PLAYER:
10109 for (i = 0; i < MAX_PLAYERS; i++)
10110 if (action_arg_player_bits & (1 << i))
10111 ExitPlayer(&stored_player[i]);
10113 if (game.players_still_needed == 0)
10119 case CA_KILL_PLAYER:
10121 for (i = 0; i < MAX_PLAYERS; i++)
10122 if (action_arg_player_bits & (1 << i))
10123 KillPlayer(&stored_player[i]);
10128 case CA_SET_PLAYER_KEYS:
10130 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10131 int element = getSpecialActionElement(action_arg_element,
10132 action_arg_number, EL_KEY_1);
10134 if (IS_KEY(element))
10136 for (i = 0; i < MAX_PLAYERS; i++)
10138 if (trigger_player_bits & (1 << i))
10140 stored_player[i].key[KEY_NR(element)] = key_state;
10142 DrawGameDoorValues();
10150 case CA_SET_PLAYER_SPEED:
10152 for (i = 0; i < MAX_PLAYERS; i++)
10154 if (trigger_player_bits & (1 << i))
10156 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10158 if (action_arg == CA_ARG_SPEED_FASTER &&
10159 stored_player[i].cannot_move)
10161 action_arg_number = STEPSIZE_VERY_SLOW;
10163 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10164 action_arg == CA_ARG_SPEED_FASTER)
10166 action_arg_number = 2;
10167 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10170 else if (action_arg == CA_ARG_NUMBER_RESET)
10172 action_arg_number = level.initial_player_stepsize[i];
10176 getModifiedActionNumber(move_stepsize,
10179 action_arg_number_min,
10180 action_arg_number_max);
10182 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10189 case CA_SET_PLAYER_SHIELD:
10191 for (i = 0; i < MAX_PLAYERS; i++)
10193 if (trigger_player_bits & (1 << i))
10195 if (action_arg == CA_ARG_SHIELD_OFF)
10197 stored_player[i].shield_normal_time_left = 0;
10198 stored_player[i].shield_deadly_time_left = 0;
10200 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10202 stored_player[i].shield_normal_time_left = 999999;
10204 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10206 stored_player[i].shield_normal_time_left = 999999;
10207 stored_player[i].shield_deadly_time_left = 999999;
10215 case CA_SET_PLAYER_GRAVITY:
10217 for (i = 0; i < MAX_PLAYERS; i++)
10219 if (trigger_player_bits & (1 << i))
10221 stored_player[i].gravity =
10222 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10223 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10224 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10225 stored_player[i].gravity);
10232 case CA_SET_PLAYER_ARTWORK:
10234 for (i = 0; i < MAX_PLAYERS; i++)
10236 if (trigger_player_bits & (1 << i))
10238 int artwork_element = action_arg_element;
10240 if (action_arg == CA_ARG_ELEMENT_RESET)
10242 (level.use_artwork_element[i] ? level.artwork_element[i] :
10243 stored_player[i].element_nr);
10245 if (stored_player[i].artwork_element != artwork_element)
10246 stored_player[i].Frame = 0;
10248 stored_player[i].artwork_element = artwork_element;
10250 SetPlayerWaiting(&stored_player[i], FALSE);
10252 // set number of special actions for bored and sleeping animation
10253 stored_player[i].num_special_action_bored =
10254 get_num_special_action(artwork_element,
10255 ACTION_BORING_1, ACTION_BORING_LAST);
10256 stored_player[i].num_special_action_sleeping =
10257 get_num_special_action(artwork_element,
10258 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10265 case CA_SET_PLAYER_INVENTORY:
10267 for (i = 0; i < MAX_PLAYERS; i++)
10269 struct PlayerInfo *player = &stored_player[i];
10272 if (trigger_player_bits & (1 << i))
10274 int inventory_element = action_arg_element;
10276 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10277 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10278 action_arg == CA_ARG_ELEMENT_ACTION)
10280 int element = inventory_element;
10281 int collect_count = element_info[element].collect_count_initial;
10283 if (!IS_CUSTOM_ELEMENT(element))
10286 if (collect_count == 0)
10287 player->inventory_infinite_element = element;
10289 for (k = 0; k < collect_count; k++)
10290 if (player->inventory_size < MAX_INVENTORY_SIZE)
10291 player->inventory_element[player->inventory_size++] =
10294 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10295 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10296 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10298 if (player->inventory_infinite_element != EL_UNDEFINED &&
10299 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10300 action_arg_element_raw))
10301 player->inventory_infinite_element = EL_UNDEFINED;
10303 for (k = 0, j = 0; j < player->inventory_size; j++)
10305 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10306 action_arg_element_raw))
10307 player->inventory_element[k++] = player->inventory_element[j];
10310 player->inventory_size = k;
10312 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10314 if (player->inventory_size > 0)
10316 for (j = 0; j < player->inventory_size - 1; j++)
10317 player->inventory_element[j] = player->inventory_element[j + 1];
10319 player->inventory_size--;
10322 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10324 if (player->inventory_size > 0)
10325 player->inventory_size--;
10327 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10329 player->inventory_infinite_element = EL_UNDEFINED;
10330 player->inventory_size = 0;
10332 else if (action_arg == CA_ARG_INVENTORY_RESET)
10334 player->inventory_infinite_element = EL_UNDEFINED;
10335 player->inventory_size = 0;
10337 if (level.use_initial_inventory[i])
10339 for (j = 0; j < level.initial_inventory_size[i]; j++)
10341 int element = level.initial_inventory_content[i][j];
10342 int collect_count = element_info[element].collect_count_initial;
10344 if (!IS_CUSTOM_ELEMENT(element))
10347 if (collect_count == 0)
10348 player->inventory_infinite_element = element;
10350 for (k = 0; k < collect_count; k++)
10351 if (player->inventory_size < MAX_INVENTORY_SIZE)
10352 player->inventory_element[player->inventory_size++] =
10363 // ---------- CE actions -------------------------------------------------
10365 case CA_SET_CE_VALUE:
10367 int last_ce_value = CustomValue[x][y];
10369 CustomValue[x][y] = action_arg_number_new;
10371 if (CustomValue[x][y] != last_ce_value)
10373 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10374 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10376 if (CustomValue[x][y] == 0)
10378 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10379 ChangeCount[x][y] = 0; // allow at least one more change
10381 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10382 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10389 case CA_SET_CE_SCORE:
10391 int last_ce_score = ei->collect_score;
10393 ei->collect_score = action_arg_number_new;
10395 if (ei->collect_score != last_ce_score)
10397 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10398 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10400 if (ei->collect_score == 0)
10404 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10405 ChangeCount[x][y] = 0; // allow at least one more change
10407 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10408 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10411 This is a very special case that seems to be a mixture between
10412 CheckElementChange() and CheckTriggeredElementChange(): while
10413 the first one only affects single elements that are triggered
10414 directly, the second one affects multiple elements in the playfield
10415 that are triggered indirectly by another element. This is a third
10416 case: Changing the CE score always affects multiple identical CEs,
10417 so every affected CE must be checked, not only the single CE for
10418 which the CE score was changed in the first place (as every instance
10419 of that CE shares the same CE score, and therefore also can change)!
10421 SCAN_PLAYFIELD(xx, yy)
10423 if (Tile[xx][yy] == element)
10424 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10425 CE_SCORE_GETS_ZERO);
10433 case CA_SET_CE_ARTWORK:
10435 int artwork_element = action_arg_element;
10436 boolean reset_frame = FALSE;
10439 if (action_arg == CA_ARG_ELEMENT_RESET)
10440 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10443 if (ei->gfx_element != artwork_element)
10444 reset_frame = TRUE;
10446 ei->gfx_element = artwork_element;
10448 SCAN_PLAYFIELD(xx, yy)
10450 if (Tile[xx][yy] == element)
10454 ResetGfxAnimation(xx, yy);
10455 ResetRandomAnimationValue(xx, yy);
10458 TEST_DrawLevelField(xx, yy);
10465 // ---------- engine actions ---------------------------------------------
10467 case CA_SET_ENGINE_SCAN_MODE:
10469 InitPlayfieldScanMode(action_arg);
10479 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10481 int old_element = Tile[x][y];
10482 int new_element = GetElementFromGroupElement(element);
10483 int previous_move_direction = MovDir[x][y];
10484 int last_ce_value = CustomValue[x][y];
10485 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10486 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10487 boolean add_player_onto_element = (new_element_is_player &&
10488 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10489 IS_WALKABLE(old_element));
10491 if (!add_player_onto_element)
10493 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10494 RemoveMovingField(x, y);
10498 Tile[x][y] = new_element;
10500 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10501 MovDir[x][y] = previous_move_direction;
10503 if (element_info[new_element].use_last_ce_value)
10504 CustomValue[x][y] = last_ce_value;
10506 InitField_WithBug1(x, y, FALSE);
10508 new_element = Tile[x][y]; // element may have changed
10510 ResetGfxAnimation(x, y);
10511 ResetRandomAnimationValue(x, y);
10513 TEST_DrawLevelField(x, y);
10515 if (GFX_CRUMBLED(new_element))
10516 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10519 // check if element under the player changes from accessible to unaccessible
10520 // (needed for special case of dropping element which then changes)
10521 // (must be checked after creating new element for walkable group elements)
10522 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10523 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10530 // "ChangeCount" not set yet to allow "entered by player" change one time
10531 if (new_element_is_player)
10532 RelocatePlayer(x, y, new_element);
10535 ChangeCount[x][y]++; // count number of changes in the same frame
10537 TestIfBadThingTouchesPlayer(x, y);
10538 TestIfPlayerTouchesCustomElement(x, y);
10539 TestIfElementTouchesCustomElement(x, y);
10542 static void CreateField(int x, int y, int element)
10544 CreateFieldExt(x, y, element, FALSE);
10547 static void CreateElementFromChange(int x, int y, int element)
10549 element = GET_VALID_RUNTIME_ELEMENT(element);
10551 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10553 int old_element = Tile[x][y];
10555 // prevent changed element from moving in same engine frame
10556 // unless both old and new element can either fall or move
10557 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10558 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10562 CreateFieldExt(x, y, element, TRUE);
10565 static boolean ChangeElement(int x, int y, int element, int page)
10567 struct ElementInfo *ei = &element_info[element];
10568 struct ElementChangeInfo *change = &ei->change_page[page];
10569 int ce_value = CustomValue[x][y];
10570 int ce_score = ei->collect_score;
10571 int target_element;
10572 int old_element = Tile[x][y];
10574 // always use default change event to prevent running into a loop
10575 if (ChangeEvent[x][y] == -1)
10576 ChangeEvent[x][y] = CE_DELAY;
10578 if (ChangeEvent[x][y] == CE_DELAY)
10580 // reset actual trigger element, trigger player and action element
10581 change->actual_trigger_element = EL_EMPTY;
10582 change->actual_trigger_player = EL_EMPTY;
10583 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10584 change->actual_trigger_side = CH_SIDE_NONE;
10585 change->actual_trigger_ce_value = 0;
10586 change->actual_trigger_ce_score = 0;
10589 // do not change elements more than a specified maximum number of changes
10590 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10593 ChangeCount[x][y]++; // count number of changes in the same frame
10595 if (change->explode)
10602 if (change->use_target_content)
10604 boolean complete_replace = TRUE;
10605 boolean can_replace[3][3];
10608 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10611 boolean is_walkable;
10612 boolean is_diggable;
10613 boolean is_collectible;
10614 boolean is_removable;
10615 boolean is_destructible;
10616 int ex = x + xx - 1;
10617 int ey = y + yy - 1;
10618 int content_element = change->target_content.e[xx][yy];
10621 can_replace[xx][yy] = TRUE;
10623 if (ex == x && ey == y) // do not check changing element itself
10626 if (content_element == EL_EMPTY_SPACE)
10628 can_replace[xx][yy] = FALSE; // do not replace border with space
10633 if (!IN_LEV_FIELD(ex, ey))
10635 can_replace[xx][yy] = FALSE;
10636 complete_replace = FALSE;
10643 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10644 e = MovingOrBlocked2Element(ex, ey);
10646 is_empty = (IS_FREE(ex, ey) ||
10647 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10649 is_walkable = (is_empty || IS_WALKABLE(e));
10650 is_diggable = (is_empty || IS_DIGGABLE(e));
10651 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10652 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10653 is_removable = (is_diggable || is_collectible);
10655 can_replace[xx][yy] =
10656 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10657 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10658 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10659 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10660 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10661 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10662 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10664 if (!can_replace[xx][yy])
10665 complete_replace = FALSE;
10668 if (!change->only_if_complete || complete_replace)
10670 boolean something_has_changed = FALSE;
10672 if (change->only_if_complete && change->use_random_replace &&
10673 RND(100) < change->random_percentage)
10676 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10678 int ex = x + xx - 1;
10679 int ey = y + yy - 1;
10680 int content_element;
10682 if (can_replace[xx][yy] && (!change->use_random_replace ||
10683 RND(100) < change->random_percentage))
10685 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10686 RemoveMovingField(ex, ey);
10688 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10690 content_element = change->target_content.e[xx][yy];
10691 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10692 ce_value, ce_score);
10694 CreateElementFromChange(ex, ey, target_element);
10696 something_has_changed = TRUE;
10698 // for symmetry reasons, freeze newly created border elements
10699 if (ex != x || ey != y)
10700 Stop[ex][ey] = TRUE; // no more moving in this frame
10704 if (something_has_changed)
10706 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10707 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10713 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10714 ce_value, ce_score);
10716 if (element == EL_DIAGONAL_GROWING ||
10717 element == EL_DIAGONAL_SHRINKING)
10719 target_element = Store[x][y];
10721 Store[x][y] = EL_EMPTY;
10724 CreateElementFromChange(x, y, target_element);
10726 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10727 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10730 // this uses direct change before indirect change
10731 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10736 static void HandleElementChange(int x, int y, int page)
10738 int element = MovingOrBlocked2Element(x, y);
10739 struct ElementInfo *ei = &element_info[element];
10740 struct ElementChangeInfo *change = &ei->change_page[page];
10741 boolean handle_action_before_change = FALSE;
10744 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10745 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10747 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10748 x, y, element, element_info[element].token_name);
10749 Debug("game:playing:HandleElementChange", "This should never happen!");
10753 // this can happen with classic bombs on walkable, changing elements
10754 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10759 if (ChangeDelay[x][y] == 0) // initialize element change
10761 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10763 if (change->can_change)
10765 // !!! not clear why graphic animation should be reset at all here !!!
10766 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10767 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10770 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10772 When using an animation frame delay of 1 (this only happens with
10773 "sp_zonk.moving.left/right" in the classic graphics), the default
10774 (non-moving) animation shows wrong animation frames (while the
10775 moving animation, like "sp_zonk.moving.left/right", is correct,
10776 so this graphical bug never shows up with the classic graphics).
10777 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10778 be drawn instead of the correct frames 0,1,2,3. This is caused by
10779 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10780 an element change: First when the change delay ("ChangeDelay[][]")
10781 counter has reached zero after decrementing, then a second time in
10782 the next frame (after "GfxFrame[][]" was already incremented) when
10783 "ChangeDelay[][]" is reset to the initial delay value again.
10785 This causes frame 0 to be drawn twice, while the last frame won't
10786 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10788 As some animations may already be cleverly designed around this bug
10789 (at least the "Snake Bite" snake tail animation does this), it cannot
10790 simply be fixed here without breaking such existing animations.
10791 Unfortunately, it cannot easily be detected if a graphics set was
10792 designed "before" or "after" the bug was fixed. As a workaround,
10793 a new graphics set option "game.graphics_engine_version" was added
10794 to be able to specify the game's major release version for which the
10795 graphics set was designed, which can then be used to decide if the
10796 bugfix should be used (version 4 and above) or not (version 3 or
10797 below, or if no version was specified at all, as with old sets).
10799 (The wrong/fixed animation frames can be tested with the test level set
10800 "test_gfxframe" and level "000", which contains a specially prepared
10801 custom element at level position (x/y) == (11/9) which uses the zonk
10802 animation mentioned above. Using "game.graphics_engine_version: 4"
10803 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10804 This can also be seen from the debug output for this test element.)
10807 // when a custom element is about to change (for example by change delay),
10808 // do not reset graphic animation when the custom element is moving
10809 if (game.graphics_engine_version < 4 &&
10812 ResetGfxAnimation(x, y);
10813 ResetRandomAnimationValue(x, y);
10816 if (change->pre_change_function)
10817 change->pre_change_function(x, y);
10821 ChangeDelay[x][y]--;
10823 if (ChangeDelay[x][y] != 0) // continue element change
10825 if (change->can_change)
10827 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10829 if (IS_ANIMATED(graphic))
10830 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10832 if (change->change_function)
10833 change->change_function(x, y);
10836 else // finish element change
10838 if (ChangePage[x][y] != -1) // remember page from delayed change
10840 page = ChangePage[x][y];
10841 ChangePage[x][y] = -1;
10843 change = &ei->change_page[page];
10846 if (IS_MOVING(x, y)) // never change a running system ;-)
10848 ChangeDelay[x][y] = 1; // try change after next move step
10849 ChangePage[x][y] = page; // remember page to use for change
10854 // special case: set new level random seed before changing element
10855 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10856 handle_action_before_change = TRUE;
10858 if (change->has_action && handle_action_before_change)
10859 ExecuteCustomElementAction(x, y, element, page);
10861 if (change->can_change)
10863 if (ChangeElement(x, y, element, page))
10865 if (change->post_change_function)
10866 change->post_change_function(x, y);
10870 if (change->has_action && !handle_action_before_change)
10871 ExecuteCustomElementAction(x, y, element, page);
10875 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10876 int trigger_element,
10878 int trigger_player,
10882 boolean change_done_any = FALSE;
10883 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10886 if (!(trigger_events[trigger_element][trigger_event]))
10889 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10891 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10893 int element = EL_CUSTOM_START + i;
10894 boolean change_done = FALSE;
10897 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10898 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10901 for (p = 0; p < element_info[element].num_change_pages; p++)
10903 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10905 if (change->can_change_or_has_action &&
10906 change->has_event[trigger_event] &&
10907 change->trigger_side & trigger_side &&
10908 change->trigger_player & trigger_player &&
10909 change->trigger_page & trigger_page_bits &&
10910 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10912 change->actual_trigger_element = trigger_element;
10913 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10914 change->actual_trigger_player_bits = trigger_player;
10915 change->actual_trigger_side = trigger_side;
10916 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10917 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10919 if ((change->can_change && !change_done) || change->has_action)
10923 SCAN_PLAYFIELD(x, y)
10925 if (Tile[x][y] == element)
10927 if (change->can_change && !change_done)
10929 // if element already changed in this frame, not only prevent
10930 // another element change (checked in ChangeElement()), but
10931 // also prevent additional element actions for this element
10933 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10934 !level.use_action_after_change_bug)
10937 ChangeDelay[x][y] = 1;
10938 ChangeEvent[x][y] = trigger_event;
10940 HandleElementChange(x, y, p);
10942 else if (change->has_action)
10944 // if element already changed in this frame, not only prevent
10945 // another element change (checked in ChangeElement()), but
10946 // also prevent additional element actions for this element
10948 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10949 !level.use_action_after_change_bug)
10952 ExecuteCustomElementAction(x, y, element, p);
10953 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10958 if (change->can_change)
10960 change_done = TRUE;
10961 change_done_any = TRUE;
10968 RECURSION_LOOP_DETECTION_END();
10970 return change_done_any;
10973 static boolean CheckElementChangeExt(int x, int y,
10975 int trigger_element,
10977 int trigger_player,
10980 boolean change_done = FALSE;
10983 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10984 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10987 if (Tile[x][y] == EL_BLOCKED)
10989 Blocked2Moving(x, y, &x, &y);
10990 element = Tile[x][y];
10993 // check if element has already changed or is about to change after moving
10994 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10995 Tile[x][y] != element) ||
10997 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10998 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10999 ChangePage[x][y] != -1)))
11002 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11004 for (p = 0; p < element_info[element].num_change_pages; p++)
11006 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11008 /* check trigger element for all events where the element that is checked
11009 for changing interacts with a directly adjacent element -- this is
11010 different to element changes that affect other elements to change on the
11011 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11012 boolean check_trigger_element =
11013 (trigger_event == CE_TOUCHING_X ||
11014 trigger_event == CE_HITTING_X ||
11015 trigger_event == CE_HIT_BY_X ||
11016 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11018 if (change->can_change_or_has_action &&
11019 change->has_event[trigger_event] &&
11020 change->trigger_side & trigger_side &&
11021 change->trigger_player & trigger_player &&
11022 (!check_trigger_element ||
11023 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11025 change->actual_trigger_element = trigger_element;
11026 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11027 change->actual_trigger_player_bits = trigger_player;
11028 change->actual_trigger_side = trigger_side;
11029 change->actual_trigger_ce_value = CustomValue[x][y];
11030 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11032 // special case: trigger element not at (x,y) position for some events
11033 if (check_trigger_element)
11045 { 0, 0 }, { 0, 0 }, { 0, 0 },
11049 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11050 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11052 change->actual_trigger_ce_value = CustomValue[xx][yy];
11053 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11056 if (change->can_change && !change_done)
11058 ChangeDelay[x][y] = 1;
11059 ChangeEvent[x][y] = trigger_event;
11061 HandleElementChange(x, y, p);
11063 change_done = TRUE;
11065 else if (change->has_action)
11067 ExecuteCustomElementAction(x, y, element, p);
11068 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11073 RECURSION_LOOP_DETECTION_END();
11075 return change_done;
11078 static void PlayPlayerSound(struct PlayerInfo *player)
11080 int jx = player->jx, jy = player->jy;
11081 int sound_element = player->artwork_element;
11082 int last_action = player->last_action_waiting;
11083 int action = player->action_waiting;
11085 if (player->is_waiting)
11087 if (action != last_action)
11088 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11090 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11094 if (action != last_action)
11095 StopSound(element_info[sound_element].sound[last_action]);
11097 if (last_action == ACTION_SLEEPING)
11098 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11102 static void PlayAllPlayersSound(void)
11106 for (i = 0; i < MAX_PLAYERS; i++)
11107 if (stored_player[i].active)
11108 PlayPlayerSound(&stored_player[i]);
11111 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11113 boolean last_waiting = player->is_waiting;
11114 int move_dir = player->MovDir;
11116 player->dir_waiting = move_dir;
11117 player->last_action_waiting = player->action_waiting;
11121 if (!last_waiting) // not waiting -> waiting
11123 player->is_waiting = TRUE;
11125 player->frame_counter_bored =
11127 game.player_boring_delay_fixed +
11128 GetSimpleRandom(game.player_boring_delay_random);
11129 player->frame_counter_sleeping =
11131 game.player_sleeping_delay_fixed +
11132 GetSimpleRandom(game.player_sleeping_delay_random);
11134 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11137 if (game.player_sleeping_delay_fixed +
11138 game.player_sleeping_delay_random > 0 &&
11139 player->anim_delay_counter == 0 &&
11140 player->post_delay_counter == 0 &&
11141 FrameCounter >= player->frame_counter_sleeping)
11142 player->is_sleeping = TRUE;
11143 else if (game.player_boring_delay_fixed +
11144 game.player_boring_delay_random > 0 &&
11145 FrameCounter >= player->frame_counter_bored)
11146 player->is_bored = TRUE;
11148 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11149 player->is_bored ? ACTION_BORING :
11152 if (player->is_sleeping && player->use_murphy)
11154 // special case for sleeping Murphy when leaning against non-free tile
11156 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11157 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11158 !IS_MOVING(player->jx - 1, player->jy)))
11159 move_dir = MV_LEFT;
11160 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11161 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11162 !IS_MOVING(player->jx + 1, player->jy)))
11163 move_dir = MV_RIGHT;
11165 player->is_sleeping = FALSE;
11167 player->dir_waiting = move_dir;
11170 if (player->is_sleeping)
11172 if (player->num_special_action_sleeping > 0)
11174 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11176 int last_special_action = player->special_action_sleeping;
11177 int num_special_action = player->num_special_action_sleeping;
11178 int special_action =
11179 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11180 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11181 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11182 last_special_action + 1 : ACTION_SLEEPING);
11183 int special_graphic =
11184 el_act_dir2img(player->artwork_element, special_action, move_dir);
11186 player->anim_delay_counter =
11187 graphic_info[special_graphic].anim_delay_fixed +
11188 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11189 player->post_delay_counter =
11190 graphic_info[special_graphic].post_delay_fixed +
11191 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11193 player->special_action_sleeping = special_action;
11196 if (player->anim_delay_counter > 0)
11198 player->action_waiting = player->special_action_sleeping;
11199 player->anim_delay_counter--;
11201 else if (player->post_delay_counter > 0)
11203 player->post_delay_counter--;
11207 else if (player->is_bored)
11209 if (player->num_special_action_bored > 0)
11211 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11213 int special_action =
11214 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11215 int special_graphic =
11216 el_act_dir2img(player->artwork_element, special_action, move_dir);
11218 player->anim_delay_counter =
11219 graphic_info[special_graphic].anim_delay_fixed +
11220 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11221 player->post_delay_counter =
11222 graphic_info[special_graphic].post_delay_fixed +
11223 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11225 player->special_action_bored = special_action;
11228 if (player->anim_delay_counter > 0)
11230 player->action_waiting = player->special_action_bored;
11231 player->anim_delay_counter--;
11233 else if (player->post_delay_counter > 0)
11235 player->post_delay_counter--;
11240 else if (last_waiting) // waiting -> not waiting
11242 player->is_waiting = FALSE;
11243 player->is_bored = FALSE;
11244 player->is_sleeping = FALSE;
11246 player->frame_counter_bored = -1;
11247 player->frame_counter_sleeping = -1;
11249 player->anim_delay_counter = 0;
11250 player->post_delay_counter = 0;
11252 player->dir_waiting = player->MovDir;
11253 player->action_waiting = ACTION_DEFAULT;
11255 player->special_action_bored = ACTION_DEFAULT;
11256 player->special_action_sleeping = ACTION_DEFAULT;
11260 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11262 if ((!player->is_moving && player->was_moving) ||
11263 (player->MovPos == 0 && player->was_moving) ||
11264 (player->is_snapping && !player->was_snapping) ||
11265 (player->is_dropping && !player->was_dropping))
11267 if (!CheckSaveEngineSnapshotToList())
11270 player->was_moving = FALSE;
11271 player->was_snapping = TRUE;
11272 player->was_dropping = TRUE;
11276 if (player->is_moving)
11277 player->was_moving = TRUE;
11279 if (!player->is_snapping)
11280 player->was_snapping = FALSE;
11282 if (!player->is_dropping)
11283 player->was_dropping = FALSE;
11286 static struct MouseActionInfo mouse_action_last = { 0 };
11287 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11288 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11291 CheckSaveEngineSnapshotToList();
11293 mouse_action_last = mouse_action;
11296 static void CheckSingleStepMode(struct PlayerInfo *player)
11298 if (tape.single_step && tape.recording && !tape.pausing)
11300 // as it is called "single step mode", just return to pause mode when the
11301 // player stopped moving after one tile (or never starts moving at all)
11302 // (reverse logic needed here in case single step mode used in team mode)
11303 if (player->is_moving ||
11304 player->is_pushing ||
11305 player->is_dropping_pressed ||
11306 player->effective_mouse_action.button)
11307 game.enter_single_step_mode = FALSE;
11310 CheckSaveEngineSnapshot(player);
11313 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11315 int left = player_action & JOY_LEFT;
11316 int right = player_action & JOY_RIGHT;
11317 int up = player_action & JOY_UP;
11318 int down = player_action & JOY_DOWN;
11319 int button1 = player_action & JOY_BUTTON_1;
11320 int button2 = player_action & JOY_BUTTON_2;
11321 int dx = (left ? -1 : right ? 1 : 0);
11322 int dy = (up ? -1 : down ? 1 : 0);
11324 if (!player->active || tape.pausing)
11330 SnapField(player, dx, dy);
11334 DropElement(player);
11336 MovePlayer(player, dx, dy);
11339 CheckSingleStepMode(player);
11341 SetPlayerWaiting(player, FALSE);
11343 return player_action;
11347 // no actions for this player (no input at player's configured device)
11349 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11350 SnapField(player, 0, 0);
11351 CheckGravityMovementWhenNotMoving(player);
11353 if (player->MovPos == 0)
11354 SetPlayerWaiting(player, TRUE);
11356 if (player->MovPos == 0) // needed for tape.playing
11357 player->is_moving = FALSE;
11359 player->is_dropping = FALSE;
11360 player->is_dropping_pressed = FALSE;
11361 player->drop_pressed_delay = 0;
11363 CheckSingleStepMode(player);
11369 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11372 if (!tape.use_mouse_actions)
11375 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11376 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11377 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11380 static void SetTapeActionFromMouseAction(byte *tape_action,
11381 struct MouseActionInfo *mouse_action)
11383 if (!tape.use_mouse_actions)
11386 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11387 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11388 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11391 static void CheckLevelSolved(void)
11393 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11395 if (game_em.level_solved &&
11396 !game_em.game_over) // game won
11400 game_em.game_over = TRUE;
11402 game.all_players_gone = TRUE;
11405 if (game_em.game_over) // game lost
11406 game.all_players_gone = TRUE;
11408 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11410 if (game_sp.level_solved &&
11411 !game_sp.game_over) // game won
11415 game_sp.game_over = TRUE;
11417 game.all_players_gone = TRUE;
11420 if (game_sp.game_over) // game lost
11421 game.all_players_gone = TRUE;
11423 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11425 if (game_mm.level_solved &&
11426 !game_mm.game_over) // game won
11430 game_mm.game_over = TRUE;
11432 game.all_players_gone = TRUE;
11435 if (game_mm.game_over) // game lost
11436 game.all_players_gone = TRUE;
11440 static void CheckLevelTime(void)
11444 if (TimeFrames >= FRAMES_PER_SECOND)
11449 for (i = 0; i < MAX_PLAYERS; i++)
11451 struct PlayerInfo *player = &stored_player[i];
11453 if (SHIELD_ON(player))
11455 player->shield_normal_time_left--;
11457 if (player->shield_deadly_time_left > 0)
11458 player->shield_deadly_time_left--;
11462 if (!game.LevelSolved && !level.use_step_counter)
11470 if (TimeLeft <= 10 && setup.time_limit)
11471 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11473 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11474 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11476 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11478 if (!TimeLeft && setup.time_limit)
11480 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11481 game_em.lev->killed_out_of_time = TRUE;
11483 for (i = 0; i < MAX_PLAYERS; i++)
11484 KillPlayer(&stored_player[i]);
11487 else if (game.no_time_limit && !game.all_players_gone)
11489 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11492 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11495 if (tape.recording || tape.playing)
11496 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11499 if (tape.recording || tape.playing)
11500 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11502 UpdateAndDisplayGameControlValues();
11505 void AdvanceFrameAndPlayerCounters(int player_nr)
11509 // advance frame counters (global frame counter and time frame counter)
11513 // advance player counters (counters for move delay, move animation etc.)
11514 for (i = 0; i < MAX_PLAYERS; i++)
11516 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11517 int move_delay_value = stored_player[i].move_delay_value;
11518 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11520 if (!advance_player_counters) // not all players may be affected
11523 if (move_frames == 0) // less than one move per game frame
11525 int stepsize = TILEX / move_delay_value;
11526 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11527 int count = (stored_player[i].is_moving ?
11528 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11530 if (count % delay == 0)
11534 stored_player[i].Frame += move_frames;
11536 if (stored_player[i].MovPos != 0)
11537 stored_player[i].StepFrame += move_frames;
11539 if (stored_player[i].move_delay > 0)
11540 stored_player[i].move_delay--;
11542 // due to bugs in previous versions, counter must count up, not down
11543 if (stored_player[i].push_delay != -1)
11544 stored_player[i].push_delay++;
11546 if (stored_player[i].drop_delay > 0)
11547 stored_player[i].drop_delay--;
11549 if (stored_player[i].is_dropping_pressed)
11550 stored_player[i].drop_pressed_delay++;
11554 void StartGameActions(boolean init_network_game, boolean record_tape,
11557 unsigned int new_random_seed = InitRND(random_seed);
11560 TapeStartRecording(new_random_seed);
11562 if (init_network_game)
11564 SendToServer_LevelFile();
11565 SendToServer_StartPlaying();
11573 static void GameActionsExt(void)
11576 static unsigned int game_frame_delay = 0;
11578 unsigned int game_frame_delay_value;
11579 byte *recorded_player_action;
11580 byte summarized_player_action = 0;
11581 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11584 // detect endless loops, caused by custom element programming
11585 if (recursion_loop_detected && recursion_loop_depth == 0)
11587 char *message = getStringCat3("Internal Error! Element ",
11588 EL_NAME(recursion_loop_element),
11589 " caused endless loop! Quit the game?");
11591 Warn("element '%s' caused endless loop in game engine",
11592 EL_NAME(recursion_loop_element));
11594 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11596 recursion_loop_detected = FALSE; // if game should be continued
11603 if (game.restart_level)
11604 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11606 CheckLevelSolved();
11608 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11611 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11614 if (game_status != GAME_MODE_PLAYING) // status might have changed
11617 game_frame_delay_value =
11618 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11620 if (tape.playing && tape.warp_forward && !tape.pausing)
11621 game_frame_delay_value = 0;
11623 SetVideoFrameDelay(game_frame_delay_value);
11625 // (de)activate virtual buttons depending on current game status
11626 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11628 if (game.all_players_gone) // if no players there to be controlled anymore
11629 SetOverlayActive(FALSE);
11630 else if (!tape.playing) // if game continues after tape stopped playing
11631 SetOverlayActive(TRUE);
11636 // ---------- main game synchronization point ----------
11638 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11640 Debug("game:playing:skip", "skip == %d", skip);
11643 // ---------- main game synchronization point ----------
11645 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11649 if (network_playing && !network_player_action_received)
11651 // try to get network player actions in time
11653 // last chance to get network player actions without main loop delay
11654 HandleNetworking();
11656 // game was quit by network peer
11657 if (game_status != GAME_MODE_PLAYING)
11660 // check if network player actions still missing and game still running
11661 if (!network_player_action_received && !checkGameEnded())
11662 return; // failed to get network player actions in time
11664 // do not yet reset "network_player_action_received" (for tape.pausing)
11670 // at this point we know that we really continue executing the game
11672 network_player_action_received = FALSE;
11674 // when playing tape, read previously recorded player input from tape data
11675 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11677 local_player->effective_mouse_action = local_player->mouse_action;
11679 if (recorded_player_action != NULL)
11680 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11681 recorded_player_action);
11683 // TapePlayAction() may return NULL when toggling to "pause before death"
11687 if (tape.set_centered_player)
11689 game.centered_player_nr_next = tape.centered_player_nr_next;
11690 game.set_centered_player = TRUE;
11693 for (i = 0; i < MAX_PLAYERS; i++)
11695 summarized_player_action |= stored_player[i].action;
11697 if (!network_playing && (game.team_mode || tape.playing))
11698 stored_player[i].effective_action = stored_player[i].action;
11701 if (network_playing && !checkGameEnded())
11702 SendToServer_MovePlayer(summarized_player_action);
11704 // summarize all actions at local players mapped input device position
11705 // (this allows using different input devices in single player mode)
11706 if (!network.enabled && !game.team_mode)
11707 stored_player[map_player_action[local_player->index_nr]].effective_action =
11708 summarized_player_action;
11710 // summarize all actions at centered player in local team mode
11711 if (tape.recording &&
11712 setup.team_mode && !network.enabled &&
11713 setup.input_on_focus &&
11714 game.centered_player_nr != -1)
11716 for (i = 0; i < MAX_PLAYERS; i++)
11717 stored_player[map_player_action[i]].effective_action =
11718 (i == game.centered_player_nr ? summarized_player_action : 0);
11721 if (recorded_player_action != NULL)
11722 for (i = 0; i < MAX_PLAYERS; i++)
11723 stored_player[i].effective_action = recorded_player_action[i];
11725 for (i = 0; i < MAX_PLAYERS; i++)
11727 tape_action[i] = stored_player[i].effective_action;
11729 /* (this may happen in the RND game engine if a player was not present on
11730 the playfield on level start, but appeared later from a custom element */
11731 if (setup.team_mode &&
11734 !tape.player_participates[i])
11735 tape.player_participates[i] = TRUE;
11738 SetTapeActionFromMouseAction(tape_action,
11739 &local_player->effective_mouse_action);
11741 // only record actions from input devices, but not programmed actions
11742 if (tape.recording)
11743 TapeRecordAction(tape_action);
11745 // remember if game was played (especially after tape stopped playing)
11746 if (!tape.playing && summarized_player_action)
11747 game.GamePlayed = TRUE;
11749 #if USE_NEW_PLAYER_ASSIGNMENTS
11750 // !!! also map player actions in single player mode !!!
11751 // if (game.team_mode)
11754 byte mapped_action[MAX_PLAYERS];
11756 #if DEBUG_PLAYER_ACTIONS
11757 for (i = 0; i < MAX_PLAYERS; i++)
11758 DebugContinued("", "%d, ", stored_player[i].effective_action);
11761 for (i = 0; i < MAX_PLAYERS; i++)
11762 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11764 for (i = 0; i < MAX_PLAYERS; i++)
11765 stored_player[i].effective_action = mapped_action[i];
11767 #if DEBUG_PLAYER_ACTIONS
11768 DebugContinued("", "=> ");
11769 for (i = 0; i < MAX_PLAYERS; i++)
11770 DebugContinued("", "%d, ", stored_player[i].effective_action);
11771 DebugContinued("game:playing:player", "\n");
11774 #if DEBUG_PLAYER_ACTIONS
11777 for (i = 0; i < MAX_PLAYERS; i++)
11778 DebugContinued("", "%d, ", stored_player[i].effective_action);
11779 DebugContinued("game:playing:player", "\n");
11784 for (i = 0; i < MAX_PLAYERS; i++)
11786 // allow engine snapshot in case of changed movement attempt
11787 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11788 (stored_player[i].effective_action & KEY_MOTION))
11789 game.snapshot.changed_action = TRUE;
11791 // allow engine snapshot in case of snapping/dropping attempt
11792 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11793 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11794 game.snapshot.changed_action = TRUE;
11796 game.snapshot.last_action[i] = stored_player[i].effective_action;
11799 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11801 GameActions_EM_Main();
11803 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11805 GameActions_SP_Main();
11807 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11809 GameActions_MM_Main();
11813 GameActions_RND_Main();
11816 BlitScreenToBitmap(backbuffer);
11818 CheckLevelSolved();
11821 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11823 if (global.show_frames_per_second)
11825 static unsigned int fps_counter = 0;
11826 static int fps_frames = 0;
11827 unsigned int fps_delay_ms = Counter() - fps_counter;
11831 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11833 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11836 fps_counter = Counter();
11838 // always draw FPS to screen after FPS value was updated
11839 redraw_mask |= REDRAW_FPS;
11842 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11843 if (GetDrawDeactivationMask() == REDRAW_NONE)
11844 redraw_mask |= REDRAW_FPS;
11848 static void GameActions_CheckSaveEngineSnapshot(void)
11850 if (!game.snapshot.save_snapshot)
11853 // clear flag for saving snapshot _before_ saving snapshot
11854 game.snapshot.save_snapshot = FALSE;
11856 SaveEngineSnapshotToList();
11859 void GameActions(void)
11863 GameActions_CheckSaveEngineSnapshot();
11866 void GameActions_EM_Main(void)
11868 byte effective_action[MAX_PLAYERS];
11869 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11872 for (i = 0; i < MAX_PLAYERS; i++)
11873 effective_action[i] = stored_player[i].effective_action;
11875 GameActions_EM(effective_action, warp_mode);
11878 void GameActions_SP_Main(void)
11880 byte effective_action[MAX_PLAYERS];
11881 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11884 for (i = 0; i < MAX_PLAYERS; i++)
11885 effective_action[i] = stored_player[i].effective_action;
11887 GameActions_SP(effective_action, warp_mode);
11889 for (i = 0; i < MAX_PLAYERS; i++)
11891 if (stored_player[i].force_dropping)
11892 stored_player[i].action |= KEY_BUTTON_DROP;
11894 stored_player[i].force_dropping = FALSE;
11898 void GameActions_MM_Main(void)
11900 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11902 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11905 void GameActions_RND_Main(void)
11910 void GameActions_RND(void)
11912 static struct MouseActionInfo mouse_action_last = { 0 };
11913 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11914 int magic_wall_x = 0, magic_wall_y = 0;
11915 int i, x, y, element, graphic, last_gfx_frame;
11917 InitPlayfieldScanModeVars();
11919 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11921 SCAN_PLAYFIELD(x, y)
11923 ChangeCount[x][y] = 0;
11924 ChangeEvent[x][y] = -1;
11928 if (game.set_centered_player)
11930 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11932 // switching to "all players" only possible if all players fit to screen
11933 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11935 game.centered_player_nr_next = game.centered_player_nr;
11936 game.set_centered_player = FALSE;
11939 // do not switch focus to non-existing (or non-active) player
11940 if (game.centered_player_nr_next >= 0 &&
11941 !stored_player[game.centered_player_nr_next].active)
11943 game.centered_player_nr_next = game.centered_player_nr;
11944 game.set_centered_player = FALSE;
11948 if (game.set_centered_player &&
11949 ScreenMovPos == 0) // screen currently aligned at tile position
11953 if (game.centered_player_nr_next == -1)
11955 setScreenCenteredToAllPlayers(&sx, &sy);
11959 sx = stored_player[game.centered_player_nr_next].jx;
11960 sy = stored_player[game.centered_player_nr_next].jy;
11963 game.centered_player_nr = game.centered_player_nr_next;
11964 game.set_centered_player = FALSE;
11966 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11967 DrawGameDoorValues();
11970 // check single step mode (set flag and clear again if any player is active)
11971 game.enter_single_step_mode =
11972 (tape.single_step && tape.recording && !tape.pausing);
11974 for (i = 0; i < MAX_PLAYERS; i++)
11976 int actual_player_action = stored_player[i].effective_action;
11979 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11980 - rnd_equinox_tetrachloride 048
11981 - rnd_equinox_tetrachloride_ii 096
11982 - rnd_emanuel_schmieg 002
11983 - doctor_sloan_ww 001, 020
11985 if (stored_player[i].MovPos == 0)
11986 CheckGravityMovement(&stored_player[i]);
11989 // overwrite programmed action with tape action
11990 if (stored_player[i].programmed_action)
11991 actual_player_action = stored_player[i].programmed_action;
11993 PlayerActions(&stored_player[i], actual_player_action);
11995 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11998 // single step pause mode may already have been toggled by "ScrollPlayer()"
11999 if (game.enter_single_step_mode && !tape.pausing)
12000 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12002 ScrollScreen(NULL, SCROLL_GO_ON);
12004 /* for backwards compatibility, the following code emulates a fixed bug that
12005 occured when pushing elements (causing elements that just made their last
12006 pushing step to already (if possible) make their first falling step in the
12007 same game frame, which is bad); this code is also needed to use the famous
12008 "spring push bug" which is used in older levels and might be wanted to be
12009 used also in newer levels, but in this case the buggy pushing code is only
12010 affecting the "spring" element and no other elements */
12012 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12014 for (i = 0; i < MAX_PLAYERS; i++)
12016 struct PlayerInfo *player = &stored_player[i];
12017 int x = player->jx;
12018 int y = player->jy;
12020 if (player->active && player->is_pushing && player->is_moving &&
12022 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12023 Tile[x][y] == EL_SPRING))
12025 ContinueMoving(x, y);
12027 // continue moving after pushing (this is actually a bug)
12028 if (!IS_MOVING(x, y))
12029 Stop[x][y] = FALSE;
12034 SCAN_PLAYFIELD(x, y)
12036 Last[x][y] = Tile[x][y];
12038 ChangeCount[x][y] = 0;
12039 ChangeEvent[x][y] = -1;
12041 // this must be handled before main playfield loop
12042 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12045 if (MovDelay[x][y] <= 0)
12049 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12052 if (MovDelay[x][y] <= 0)
12054 int element = Store[x][y];
12055 int move_direction = MovDir[x][y];
12056 int player_index_bit = Store2[x][y];
12062 TEST_DrawLevelField(x, y);
12064 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12069 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12071 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12073 Debug("game:playing:GameActions_RND", "This should never happen!");
12075 ChangePage[x][y] = -1;
12079 Stop[x][y] = FALSE;
12080 if (WasJustMoving[x][y] > 0)
12081 WasJustMoving[x][y]--;
12082 if (WasJustFalling[x][y] > 0)
12083 WasJustFalling[x][y]--;
12084 if (CheckCollision[x][y] > 0)
12085 CheckCollision[x][y]--;
12086 if (CheckImpact[x][y] > 0)
12087 CheckImpact[x][y]--;
12091 /* reset finished pushing action (not done in ContinueMoving() to allow
12092 continuous pushing animation for elements with zero push delay) */
12093 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12095 ResetGfxAnimation(x, y);
12096 TEST_DrawLevelField(x, y);
12100 if (IS_BLOCKED(x, y))
12104 Blocked2Moving(x, y, &oldx, &oldy);
12105 if (!IS_MOVING(oldx, oldy))
12107 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12108 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12109 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12110 Debug("game:playing:GameActions_RND", "This should never happen!");
12116 if (mouse_action.button)
12118 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12120 x = mouse_action.lx;
12121 y = mouse_action.ly;
12122 element = Tile[x][y];
12126 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12127 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12130 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12131 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12134 SCAN_PLAYFIELD(x, y)
12136 element = Tile[x][y];
12137 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12138 last_gfx_frame = GfxFrame[x][y];
12140 ResetGfxFrame(x, y);
12142 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12143 DrawLevelGraphicAnimation(x, y, graphic);
12145 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12146 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12147 ResetRandomAnimationValue(x, y);
12149 SetRandomAnimationValue(x, y);
12151 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12153 if (IS_INACTIVE(element))
12155 if (IS_ANIMATED(graphic))
12156 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12161 // this may take place after moving, so 'element' may have changed
12162 if (IS_CHANGING(x, y) &&
12163 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12165 int page = element_info[element].event_page_nr[CE_DELAY];
12167 HandleElementChange(x, y, page);
12169 element = Tile[x][y];
12170 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12173 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12177 element = Tile[x][y];
12178 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12180 if (IS_ANIMATED(graphic) &&
12181 !IS_MOVING(x, y) &&
12183 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12185 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12186 TEST_DrawTwinkleOnField(x, y);
12188 else if (element == EL_ACID)
12191 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12193 else if ((element == EL_EXIT_OPEN ||
12194 element == EL_EM_EXIT_OPEN ||
12195 element == EL_SP_EXIT_OPEN ||
12196 element == EL_STEEL_EXIT_OPEN ||
12197 element == EL_EM_STEEL_EXIT_OPEN ||
12198 element == EL_SP_TERMINAL ||
12199 element == EL_SP_TERMINAL_ACTIVE ||
12200 element == EL_EXTRA_TIME ||
12201 element == EL_SHIELD_NORMAL ||
12202 element == EL_SHIELD_DEADLY) &&
12203 IS_ANIMATED(graphic))
12204 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12205 else if (IS_MOVING(x, y))
12206 ContinueMoving(x, y);
12207 else if (IS_ACTIVE_BOMB(element))
12208 CheckDynamite(x, y);
12209 else if (element == EL_AMOEBA_GROWING)
12210 AmoebaGrowing(x, y);
12211 else if (element == EL_AMOEBA_SHRINKING)
12212 AmoebaShrinking(x, y);
12214 #if !USE_NEW_AMOEBA_CODE
12215 else if (IS_AMOEBALIVE(element))
12216 AmoebaReproduce(x, y);
12219 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12221 else if (element == EL_EXIT_CLOSED)
12223 else if (element == EL_EM_EXIT_CLOSED)
12225 else if (element == EL_STEEL_EXIT_CLOSED)
12226 CheckExitSteel(x, y);
12227 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12228 CheckExitSteelEM(x, y);
12229 else if (element == EL_SP_EXIT_CLOSED)
12231 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12232 element == EL_EXPANDABLE_STEELWALL_GROWING)
12233 MauerWaechst(x, y);
12234 else if (element == EL_EXPANDABLE_WALL ||
12235 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12236 element == EL_EXPANDABLE_WALL_VERTICAL ||
12237 element == EL_EXPANDABLE_WALL_ANY ||
12238 element == EL_BD_EXPANDABLE_WALL)
12239 MauerAbleger(x, y);
12240 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12241 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12242 element == EL_EXPANDABLE_STEELWALL_ANY)
12243 MauerAblegerStahl(x, y);
12244 else if (element == EL_FLAMES)
12245 CheckForDragon(x, y);
12246 else if (element == EL_EXPLOSION)
12247 ; // drawing of correct explosion animation is handled separately
12248 else if (element == EL_ELEMENT_SNAPPING ||
12249 element == EL_DIAGONAL_SHRINKING ||
12250 element == EL_DIAGONAL_GROWING)
12252 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12254 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12256 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12257 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12259 if (IS_BELT_ACTIVE(element))
12260 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12262 if (game.magic_wall_active)
12264 int jx = local_player->jx, jy = local_player->jy;
12266 // play the element sound at the position nearest to the player
12267 if ((element == EL_MAGIC_WALL_FULL ||
12268 element == EL_MAGIC_WALL_ACTIVE ||
12269 element == EL_MAGIC_WALL_EMPTYING ||
12270 element == EL_BD_MAGIC_WALL_FULL ||
12271 element == EL_BD_MAGIC_WALL_ACTIVE ||
12272 element == EL_BD_MAGIC_WALL_EMPTYING ||
12273 element == EL_DC_MAGIC_WALL_FULL ||
12274 element == EL_DC_MAGIC_WALL_ACTIVE ||
12275 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12276 ABS(x - jx) + ABS(y - jy) <
12277 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12285 #if USE_NEW_AMOEBA_CODE
12286 // new experimental amoeba growth stuff
12287 if (!(FrameCounter % 8))
12289 static unsigned int random = 1684108901;
12291 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12293 x = RND(lev_fieldx);
12294 y = RND(lev_fieldy);
12295 element = Tile[x][y];
12297 if (!IS_PLAYER(x,y) &&
12298 (element == EL_EMPTY ||
12299 CAN_GROW_INTO(element) ||
12300 element == EL_QUICKSAND_EMPTY ||
12301 element == EL_QUICKSAND_FAST_EMPTY ||
12302 element == EL_ACID_SPLASH_LEFT ||
12303 element == EL_ACID_SPLASH_RIGHT))
12305 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12306 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12307 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12308 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12309 Tile[x][y] = EL_AMOEBA_DROP;
12312 random = random * 129 + 1;
12317 game.explosions_delayed = FALSE;
12319 SCAN_PLAYFIELD(x, y)
12321 element = Tile[x][y];
12323 if (ExplodeField[x][y])
12324 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12325 else if (element == EL_EXPLOSION)
12326 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12328 ExplodeField[x][y] = EX_TYPE_NONE;
12331 game.explosions_delayed = TRUE;
12333 if (game.magic_wall_active)
12335 if (!(game.magic_wall_time_left % 4))
12337 int element = Tile[magic_wall_x][magic_wall_y];
12339 if (element == EL_BD_MAGIC_WALL_FULL ||
12340 element == EL_BD_MAGIC_WALL_ACTIVE ||
12341 element == EL_BD_MAGIC_WALL_EMPTYING)
12342 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12343 else if (element == EL_DC_MAGIC_WALL_FULL ||
12344 element == EL_DC_MAGIC_WALL_ACTIVE ||
12345 element == EL_DC_MAGIC_WALL_EMPTYING)
12346 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12348 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12351 if (game.magic_wall_time_left > 0)
12353 game.magic_wall_time_left--;
12355 if (!game.magic_wall_time_left)
12357 SCAN_PLAYFIELD(x, y)
12359 element = Tile[x][y];
12361 if (element == EL_MAGIC_WALL_ACTIVE ||
12362 element == EL_MAGIC_WALL_FULL)
12364 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12365 TEST_DrawLevelField(x, y);
12367 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12368 element == EL_BD_MAGIC_WALL_FULL)
12370 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12371 TEST_DrawLevelField(x, y);
12373 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12374 element == EL_DC_MAGIC_WALL_FULL)
12376 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12377 TEST_DrawLevelField(x, y);
12381 game.magic_wall_active = FALSE;
12386 if (game.light_time_left > 0)
12388 game.light_time_left--;
12390 if (game.light_time_left == 0)
12391 RedrawAllLightSwitchesAndInvisibleElements();
12394 if (game.timegate_time_left > 0)
12396 game.timegate_time_left--;
12398 if (game.timegate_time_left == 0)
12399 CloseAllOpenTimegates();
12402 if (game.lenses_time_left > 0)
12404 game.lenses_time_left--;
12406 if (game.lenses_time_left == 0)
12407 RedrawAllInvisibleElementsForLenses();
12410 if (game.magnify_time_left > 0)
12412 game.magnify_time_left--;
12414 if (game.magnify_time_left == 0)
12415 RedrawAllInvisibleElementsForMagnifier();
12418 for (i = 0; i < MAX_PLAYERS; i++)
12420 struct PlayerInfo *player = &stored_player[i];
12422 if (SHIELD_ON(player))
12424 if (player->shield_deadly_time_left)
12425 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12426 else if (player->shield_normal_time_left)
12427 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12431 #if USE_DELAYED_GFX_REDRAW
12432 SCAN_PLAYFIELD(x, y)
12434 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12436 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12437 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12439 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12440 DrawLevelField(x, y);
12442 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12443 DrawLevelFieldCrumbled(x, y);
12445 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12446 DrawLevelFieldCrumbledNeighbours(x, y);
12448 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12449 DrawTwinkleOnField(x, y);
12452 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12457 PlayAllPlayersSound();
12459 for (i = 0; i < MAX_PLAYERS; i++)
12461 struct PlayerInfo *player = &stored_player[i];
12463 if (player->show_envelope != 0 && (!player->active ||
12464 player->MovPos == 0))
12466 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12468 player->show_envelope = 0;
12472 // use random number generator in every frame to make it less predictable
12473 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12476 mouse_action_last = mouse_action;
12479 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12481 int min_x = x, min_y = y, max_x = x, max_y = y;
12482 int scr_fieldx = getScreenFieldSizeX();
12483 int scr_fieldy = getScreenFieldSizeY();
12486 for (i = 0; i < MAX_PLAYERS; i++)
12488 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12490 if (!stored_player[i].active || &stored_player[i] == player)
12493 min_x = MIN(min_x, jx);
12494 min_y = MIN(min_y, jy);
12495 max_x = MAX(max_x, jx);
12496 max_y = MAX(max_y, jy);
12499 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12502 static boolean AllPlayersInVisibleScreen(void)
12506 for (i = 0; i < MAX_PLAYERS; i++)
12508 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12510 if (!stored_player[i].active)
12513 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12520 void ScrollLevel(int dx, int dy)
12522 int scroll_offset = 2 * TILEX_VAR;
12525 BlitBitmap(drawto_field, drawto_field,
12526 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12527 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12528 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12529 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12530 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12531 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12535 x = (dx == 1 ? BX1 : BX2);
12536 for (y = BY1; y <= BY2; y++)
12537 DrawScreenField(x, y);
12542 y = (dy == 1 ? BY1 : BY2);
12543 for (x = BX1; x <= BX2; x++)
12544 DrawScreenField(x, y);
12547 redraw_mask |= REDRAW_FIELD;
12550 static boolean canFallDown(struct PlayerInfo *player)
12552 int jx = player->jx, jy = player->jy;
12554 return (IN_LEV_FIELD(jx, jy + 1) &&
12555 (IS_FREE(jx, jy + 1) ||
12556 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12557 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12558 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12561 static boolean canPassField(int x, int y, int move_dir)
12563 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12564 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12565 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12566 int nextx = x + dx;
12567 int nexty = y + dy;
12568 int element = Tile[x][y];
12570 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12571 !CAN_MOVE(element) &&
12572 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12573 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12574 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12577 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12579 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12580 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12581 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12585 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12586 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12587 (IS_DIGGABLE(Tile[newx][newy]) ||
12588 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12589 canPassField(newx, newy, move_dir)));
12592 static void CheckGravityMovement(struct PlayerInfo *player)
12594 if (player->gravity && !player->programmed_action)
12596 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12597 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12598 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12599 int jx = player->jx, jy = player->jy;
12600 boolean player_is_moving_to_valid_field =
12601 (!player_is_snapping &&
12602 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12603 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12604 boolean player_can_fall_down = canFallDown(player);
12606 if (player_can_fall_down &&
12607 !player_is_moving_to_valid_field)
12608 player->programmed_action = MV_DOWN;
12612 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12614 return CheckGravityMovement(player);
12616 if (player->gravity && !player->programmed_action)
12618 int jx = player->jx, jy = player->jy;
12619 boolean field_under_player_is_free =
12620 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12621 boolean player_is_standing_on_valid_field =
12622 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12623 (IS_WALKABLE(Tile[jx][jy]) &&
12624 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12626 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12627 player->programmed_action = MV_DOWN;
12632 MovePlayerOneStep()
12633 -----------------------------------------------------------------------------
12634 dx, dy: direction (non-diagonal) to try to move the player to
12635 real_dx, real_dy: direction as read from input device (can be diagonal)
12638 boolean MovePlayerOneStep(struct PlayerInfo *player,
12639 int dx, int dy, int real_dx, int real_dy)
12641 int jx = player->jx, jy = player->jy;
12642 int new_jx = jx + dx, new_jy = jy + dy;
12644 boolean player_can_move = !player->cannot_move;
12646 if (!player->active || (!dx && !dy))
12647 return MP_NO_ACTION;
12649 player->MovDir = (dx < 0 ? MV_LEFT :
12650 dx > 0 ? MV_RIGHT :
12652 dy > 0 ? MV_DOWN : MV_NONE);
12654 if (!IN_LEV_FIELD(new_jx, new_jy))
12655 return MP_NO_ACTION;
12657 if (!player_can_move)
12659 if (player->MovPos == 0)
12661 player->is_moving = FALSE;
12662 player->is_digging = FALSE;
12663 player->is_collecting = FALSE;
12664 player->is_snapping = FALSE;
12665 player->is_pushing = FALSE;
12669 if (!network.enabled && game.centered_player_nr == -1 &&
12670 !AllPlayersInSight(player, new_jx, new_jy))
12671 return MP_NO_ACTION;
12673 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12674 if (can_move != MP_MOVING)
12677 // check if DigField() has caused relocation of the player
12678 if (player->jx != jx || player->jy != jy)
12679 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12681 StorePlayer[jx][jy] = 0;
12682 player->last_jx = jx;
12683 player->last_jy = jy;
12684 player->jx = new_jx;
12685 player->jy = new_jy;
12686 StorePlayer[new_jx][new_jy] = player->element_nr;
12688 if (player->move_delay_value_next != -1)
12690 player->move_delay_value = player->move_delay_value_next;
12691 player->move_delay_value_next = -1;
12695 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12697 player->step_counter++;
12699 PlayerVisit[jx][jy] = FrameCounter;
12701 player->is_moving = TRUE;
12704 // should better be called in MovePlayer(), but this breaks some tapes
12705 ScrollPlayer(player, SCROLL_INIT);
12711 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12713 int jx = player->jx, jy = player->jy;
12714 int old_jx = jx, old_jy = jy;
12715 int moved = MP_NO_ACTION;
12717 if (!player->active)
12722 if (player->MovPos == 0)
12724 player->is_moving = FALSE;
12725 player->is_digging = FALSE;
12726 player->is_collecting = FALSE;
12727 player->is_snapping = FALSE;
12728 player->is_pushing = FALSE;
12734 if (player->move_delay > 0)
12737 player->move_delay = -1; // set to "uninitialized" value
12739 // store if player is automatically moved to next field
12740 player->is_auto_moving = (player->programmed_action != MV_NONE);
12742 // remove the last programmed player action
12743 player->programmed_action = 0;
12745 if (player->MovPos)
12747 // should only happen if pre-1.2 tape recordings are played
12748 // this is only for backward compatibility
12750 int original_move_delay_value = player->move_delay_value;
12753 Debug("game:playing:MovePlayer",
12754 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12758 // scroll remaining steps with finest movement resolution
12759 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12761 while (player->MovPos)
12763 ScrollPlayer(player, SCROLL_GO_ON);
12764 ScrollScreen(NULL, SCROLL_GO_ON);
12766 AdvanceFrameAndPlayerCounters(player->index_nr);
12769 BackToFront_WithFrameDelay(0);
12772 player->move_delay_value = original_move_delay_value;
12775 player->is_active = FALSE;
12777 if (player->last_move_dir & MV_HORIZONTAL)
12779 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12780 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12784 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12785 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12788 if (!moved && !player->is_active)
12790 player->is_moving = FALSE;
12791 player->is_digging = FALSE;
12792 player->is_collecting = FALSE;
12793 player->is_snapping = FALSE;
12794 player->is_pushing = FALSE;
12800 if (moved & MP_MOVING && !ScreenMovPos &&
12801 (player->index_nr == game.centered_player_nr ||
12802 game.centered_player_nr == -1))
12804 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12806 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12808 // actual player has left the screen -- scroll in that direction
12809 if (jx != old_jx) // player has moved horizontally
12810 scroll_x += (jx - old_jx);
12811 else // player has moved vertically
12812 scroll_y += (jy - old_jy);
12816 int offset_raw = game.scroll_delay_value;
12818 if (jx != old_jx) // player has moved horizontally
12820 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12821 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12822 int new_scroll_x = jx - MIDPOSX + offset_x;
12824 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12825 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12826 scroll_x = new_scroll_x;
12828 // don't scroll over playfield boundaries
12829 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12831 // don't scroll more than one field at a time
12832 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12834 // don't scroll against the player's moving direction
12835 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12836 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12837 scroll_x = old_scroll_x;
12839 else // player has moved vertically
12841 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12842 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12843 int new_scroll_y = jy - MIDPOSY + offset_y;
12845 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12846 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12847 scroll_y = new_scroll_y;
12849 // don't scroll over playfield boundaries
12850 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12852 // don't scroll more than one field at a time
12853 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12855 // don't scroll against the player's moving direction
12856 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12857 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12858 scroll_y = old_scroll_y;
12862 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12864 if (!network.enabled && game.centered_player_nr == -1 &&
12865 !AllPlayersInVisibleScreen())
12867 scroll_x = old_scroll_x;
12868 scroll_y = old_scroll_y;
12872 ScrollScreen(player, SCROLL_INIT);
12873 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12878 player->StepFrame = 0;
12880 if (moved & MP_MOVING)
12882 if (old_jx != jx && old_jy == jy)
12883 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12884 else if (old_jx == jx && old_jy != jy)
12885 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12887 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12889 player->last_move_dir = player->MovDir;
12890 player->is_moving = TRUE;
12891 player->is_snapping = FALSE;
12892 player->is_switching = FALSE;
12893 player->is_dropping = FALSE;
12894 player->is_dropping_pressed = FALSE;
12895 player->drop_pressed_delay = 0;
12898 // should better be called here than above, but this breaks some tapes
12899 ScrollPlayer(player, SCROLL_INIT);
12904 CheckGravityMovementWhenNotMoving(player);
12906 player->is_moving = FALSE;
12908 /* at this point, the player is allowed to move, but cannot move right now
12909 (e.g. because of something blocking the way) -- ensure that the player
12910 is also allowed to move in the next frame (in old versions before 3.1.1,
12911 the player was forced to wait again for eight frames before next try) */
12913 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12914 player->move_delay = 0; // allow direct movement in the next frame
12917 if (player->move_delay == -1) // not yet initialized by DigField()
12918 player->move_delay = player->move_delay_value;
12920 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12922 TestIfPlayerTouchesBadThing(jx, jy);
12923 TestIfPlayerTouchesCustomElement(jx, jy);
12926 if (!player->active)
12927 RemovePlayer(player);
12932 void ScrollPlayer(struct PlayerInfo *player, int mode)
12934 int jx = player->jx, jy = player->jy;
12935 int last_jx = player->last_jx, last_jy = player->last_jy;
12936 int move_stepsize = TILEX / player->move_delay_value;
12938 if (!player->active)
12941 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12944 if (mode == SCROLL_INIT)
12946 player->actual_frame_counter = FrameCounter;
12947 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12949 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12950 Tile[last_jx][last_jy] == EL_EMPTY)
12952 int last_field_block_delay = 0; // start with no blocking at all
12953 int block_delay_adjustment = player->block_delay_adjustment;
12955 // if player blocks last field, add delay for exactly one move
12956 if (player->block_last_field)
12958 last_field_block_delay += player->move_delay_value;
12960 // when blocking enabled, prevent moving up despite gravity
12961 if (player->gravity && player->MovDir == MV_UP)
12962 block_delay_adjustment = -1;
12965 // add block delay adjustment (also possible when not blocking)
12966 last_field_block_delay += block_delay_adjustment;
12968 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12969 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12972 if (player->MovPos != 0) // player has not yet reached destination
12975 else if (!FrameReached(&player->actual_frame_counter, 1))
12978 if (player->MovPos != 0)
12980 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12981 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12983 // before DrawPlayer() to draw correct player graphic for this case
12984 if (player->MovPos == 0)
12985 CheckGravityMovement(player);
12988 if (player->MovPos == 0) // player reached destination field
12990 if (player->move_delay_reset_counter > 0)
12992 player->move_delay_reset_counter--;
12994 if (player->move_delay_reset_counter == 0)
12996 // continue with normal speed after quickly moving through gate
12997 HALVE_PLAYER_SPEED(player);
12999 // be able to make the next move without delay
13000 player->move_delay = 0;
13004 player->last_jx = jx;
13005 player->last_jy = jy;
13007 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13008 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13009 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13010 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13011 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13012 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13013 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13014 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13016 ExitPlayer(player);
13018 if (game.players_still_needed == 0 &&
13019 (game.friends_still_needed == 0 ||
13020 IS_SP_ELEMENT(Tile[jx][jy])))
13024 // this breaks one level: "machine", level 000
13026 int move_direction = player->MovDir;
13027 int enter_side = MV_DIR_OPPOSITE(move_direction);
13028 int leave_side = move_direction;
13029 int old_jx = last_jx;
13030 int old_jy = last_jy;
13031 int old_element = Tile[old_jx][old_jy];
13032 int new_element = Tile[jx][jy];
13034 if (IS_CUSTOM_ELEMENT(old_element))
13035 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13037 player->index_bit, leave_side);
13039 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13040 CE_PLAYER_LEAVES_X,
13041 player->index_bit, leave_side);
13043 if (IS_CUSTOM_ELEMENT(new_element))
13044 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13045 player->index_bit, enter_side);
13047 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13048 CE_PLAYER_ENTERS_X,
13049 player->index_bit, enter_side);
13051 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13052 CE_MOVE_OF_X, move_direction);
13055 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13057 TestIfPlayerTouchesBadThing(jx, jy);
13058 TestIfPlayerTouchesCustomElement(jx, jy);
13060 /* needed because pushed element has not yet reached its destination,
13061 so it would trigger a change event at its previous field location */
13062 if (!player->is_pushing)
13063 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13065 if (level.finish_dig_collect &&
13066 (player->is_digging || player->is_collecting))
13068 int last_element = player->last_removed_element;
13069 int move_direction = player->MovDir;
13070 int enter_side = MV_DIR_OPPOSITE(move_direction);
13071 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13072 CE_PLAYER_COLLECTS_X);
13074 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13075 player->index_bit, enter_side);
13077 player->last_removed_element = EL_UNDEFINED;
13080 if (!player->active)
13081 RemovePlayer(player);
13084 if (!game.LevelSolved && level.use_step_counter)
13094 if (TimeLeft <= 10 && setup.time_limit)
13095 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13097 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13099 DisplayGameControlValues();
13101 if (!TimeLeft && setup.time_limit)
13102 for (i = 0; i < MAX_PLAYERS; i++)
13103 KillPlayer(&stored_player[i]);
13105 else if (game.no_time_limit && !game.all_players_gone)
13107 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13109 DisplayGameControlValues();
13113 if (tape.single_step && tape.recording && !tape.pausing &&
13114 !player->programmed_action)
13115 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13117 if (!player->programmed_action)
13118 CheckSaveEngineSnapshot(player);
13122 void ScrollScreen(struct PlayerInfo *player, int mode)
13124 static unsigned int screen_frame_counter = 0;
13126 if (mode == SCROLL_INIT)
13128 // set scrolling step size according to actual player's moving speed
13129 ScrollStepSize = TILEX / player->move_delay_value;
13131 screen_frame_counter = FrameCounter;
13132 ScreenMovDir = player->MovDir;
13133 ScreenMovPos = player->MovPos;
13134 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13137 else if (!FrameReached(&screen_frame_counter, 1))
13142 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13143 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13144 redraw_mask |= REDRAW_FIELD;
13147 ScreenMovDir = MV_NONE;
13150 void TestIfPlayerTouchesCustomElement(int x, int y)
13152 static int xy[4][2] =
13159 static int trigger_sides[4][2] =
13161 // center side border side
13162 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13163 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13164 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13165 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13167 static int touch_dir[4] =
13169 MV_LEFT | MV_RIGHT,
13174 int center_element = Tile[x][y]; // should always be non-moving!
13177 for (i = 0; i < NUM_DIRECTIONS; i++)
13179 int xx = x + xy[i][0];
13180 int yy = y + xy[i][1];
13181 int center_side = trigger_sides[i][0];
13182 int border_side = trigger_sides[i][1];
13183 int border_element;
13185 if (!IN_LEV_FIELD(xx, yy))
13188 if (IS_PLAYER(x, y)) // player found at center element
13190 struct PlayerInfo *player = PLAYERINFO(x, y);
13192 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13193 border_element = Tile[xx][yy]; // may be moving!
13194 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13195 border_element = Tile[xx][yy];
13196 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13197 border_element = MovingOrBlocked2Element(xx, yy);
13199 continue; // center and border element do not touch
13201 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13202 player->index_bit, border_side);
13203 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13204 CE_PLAYER_TOUCHES_X,
13205 player->index_bit, border_side);
13208 /* use player element that is initially defined in the level playfield,
13209 not the player element that corresponds to the runtime player number
13210 (example: a level that contains EL_PLAYER_3 as the only player would
13211 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13212 int player_element = PLAYERINFO(x, y)->initial_element;
13214 CheckElementChangeBySide(xx, yy, border_element, player_element,
13215 CE_TOUCHING_X, border_side);
13218 else if (IS_PLAYER(xx, yy)) // player found at border element
13220 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13222 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13224 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13225 continue; // center and border element do not touch
13228 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13229 player->index_bit, center_side);
13230 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13231 CE_PLAYER_TOUCHES_X,
13232 player->index_bit, center_side);
13235 /* use player element that is initially defined in the level playfield,
13236 not the player element that corresponds to the runtime player number
13237 (example: a level that contains EL_PLAYER_3 as the only player would
13238 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13239 int player_element = PLAYERINFO(xx, yy)->initial_element;
13241 CheckElementChangeBySide(x, y, center_element, player_element,
13242 CE_TOUCHING_X, center_side);
13250 void TestIfElementTouchesCustomElement(int x, int y)
13252 static int xy[4][2] =
13259 static int trigger_sides[4][2] =
13261 // center side border side
13262 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13263 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13264 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13265 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13267 static int touch_dir[4] =
13269 MV_LEFT | MV_RIGHT,
13274 boolean change_center_element = FALSE;
13275 int center_element = Tile[x][y]; // should always be non-moving!
13276 int border_element_old[NUM_DIRECTIONS];
13279 for (i = 0; i < NUM_DIRECTIONS; i++)
13281 int xx = x + xy[i][0];
13282 int yy = y + xy[i][1];
13283 int border_element;
13285 border_element_old[i] = -1;
13287 if (!IN_LEV_FIELD(xx, yy))
13290 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13291 border_element = Tile[xx][yy]; // may be moving!
13292 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13293 border_element = Tile[xx][yy];
13294 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13295 border_element = MovingOrBlocked2Element(xx, yy);
13297 continue; // center and border element do not touch
13299 border_element_old[i] = border_element;
13302 for (i = 0; i < NUM_DIRECTIONS; i++)
13304 int xx = x + xy[i][0];
13305 int yy = y + xy[i][1];
13306 int center_side = trigger_sides[i][0];
13307 int border_element = border_element_old[i];
13309 if (border_element == -1)
13312 // check for change of border element
13313 CheckElementChangeBySide(xx, yy, border_element, center_element,
13314 CE_TOUCHING_X, center_side);
13316 // (center element cannot be player, so we dont have to check this here)
13319 for (i = 0; i < NUM_DIRECTIONS; i++)
13321 int xx = x + xy[i][0];
13322 int yy = y + xy[i][1];
13323 int border_side = trigger_sides[i][1];
13324 int border_element = border_element_old[i];
13326 if (border_element == -1)
13329 // check for change of center element (but change it only once)
13330 if (!change_center_element)
13331 change_center_element =
13332 CheckElementChangeBySide(x, y, center_element, border_element,
13333 CE_TOUCHING_X, border_side);
13335 if (IS_PLAYER(xx, yy))
13337 /* use player element that is initially defined in the level playfield,
13338 not the player element that corresponds to the runtime player number
13339 (example: a level that contains EL_PLAYER_3 as the only player would
13340 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13341 int player_element = PLAYERINFO(xx, yy)->initial_element;
13343 CheckElementChangeBySide(x, y, center_element, player_element,
13344 CE_TOUCHING_X, border_side);
13349 void TestIfElementHitsCustomElement(int x, int y, int direction)
13351 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13352 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13353 int hitx = x + dx, hity = y + dy;
13354 int hitting_element = Tile[x][y];
13355 int touched_element;
13357 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13360 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13361 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13363 if (IN_LEV_FIELD(hitx, hity))
13365 int opposite_direction = MV_DIR_OPPOSITE(direction);
13366 int hitting_side = direction;
13367 int touched_side = opposite_direction;
13368 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13369 MovDir[hitx][hity] != direction ||
13370 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13376 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13377 CE_HITTING_X, touched_side);
13379 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13380 CE_HIT_BY_X, hitting_side);
13382 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13383 CE_HIT_BY_SOMETHING, opposite_direction);
13385 if (IS_PLAYER(hitx, hity))
13387 /* use player element that is initially defined in the level playfield,
13388 not the player element that corresponds to the runtime player number
13389 (example: a level that contains EL_PLAYER_3 as the only player would
13390 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13391 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13393 CheckElementChangeBySide(x, y, hitting_element, player_element,
13394 CE_HITTING_X, touched_side);
13399 // "hitting something" is also true when hitting the playfield border
13400 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13401 CE_HITTING_SOMETHING, direction);
13404 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13406 int i, kill_x = -1, kill_y = -1;
13408 int bad_element = -1;
13409 static int test_xy[4][2] =
13416 static int test_dir[4] =
13424 for (i = 0; i < NUM_DIRECTIONS; i++)
13426 int test_x, test_y, test_move_dir, test_element;
13428 test_x = good_x + test_xy[i][0];
13429 test_y = good_y + test_xy[i][1];
13431 if (!IN_LEV_FIELD(test_x, test_y))
13435 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13437 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13439 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13440 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13442 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13443 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13447 bad_element = test_element;
13453 if (kill_x != -1 || kill_y != -1)
13455 if (IS_PLAYER(good_x, good_y))
13457 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13459 if (player->shield_deadly_time_left > 0 &&
13460 !IS_INDESTRUCTIBLE(bad_element))
13461 Bang(kill_x, kill_y);
13462 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13463 KillPlayer(player);
13466 Bang(good_x, good_y);
13470 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13472 int i, kill_x = -1, kill_y = -1;
13473 int bad_element = Tile[bad_x][bad_y];
13474 static int test_xy[4][2] =
13481 static int touch_dir[4] =
13483 MV_LEFT | MV_RIGHT,
13488 static int test_dir[4] =
13496 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13499 for (i = 0; i < NUM_DIRECTIONS; i++)
13501 int test_x, test_y, test_move_dir, test_element;
13503 test_x = bad_x + test_xy[i][0];
13504 test_y = bad_y + test_xy[i][1];
13506 if (!IN_LEV_FIELD(test_x, test_y))
13510 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13512 test_element = Tile[test_x][test_y];
13514 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13515 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13517 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13518 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13520 // good thing is player or penguin that does not move away
13521 if (IS_PLAYER(test_x, test_y))
13523 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13525 if (bad_element == EL_ROBOT && player->is_moving)
13526 continue; // robot does not kill player if he is moving
13528 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13530 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13531 continue; // center and border element do not touch
13539 else if (test_element == EL_PENGUIN)
13549 if (kill_x != -1 || kill_y != -1)
13551 if (IS_PLAYER(kill_x, kill_y))
13553 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13555 if (player->shield_deadly_time_left > 0 &&
13556 !IS_INDESTRUCTIBLE(bad_element))
13557 Bang(bad_x, bad_y);
13558 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13559 KillPlayer(player);
13562 Bang(kill_x, kill_y);
13566 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13568 int bad_element = Tile[bad_x][bad_y];
13569 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13570 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13571 int test_x = bad_x + dx, test_y = bad_y + dy;
13572 int test_move_dir, test_element;
13573 int kill_x = -1, kill_y = -1;
13575 if (!IN_LEV_FIELD(test_x, test_y))
13579 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13581 test_element = Tile[test_x][test_y];
13583 if (test_move_dir != bad_move_dir)
13585 // good thing can be player or penguin that does not move away
13586 if (IS_PLAYER(test_x, test_y))
13588 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13590 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13591 player as being hit when he is moving towards the bad thing, because
13592 the "get hit by" condition would be lost after the player stops) */
13593 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13594 return; // player moves away from bad thing
13599 else if (test_element == EL_PENGUIN)
13606 if (kill_x != -1 || kill_y != -1)
13608 if (IS_PLAYER(kill_x, kill_y))
13610 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13612 if (player->shield_deadly_time_left > 0 &&
13613 !IS_INDESTRUCTIBLE(bad_element))
13614 Bang(bad_x, bad_y);
13615 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13616 KillPlayer(player);
13619 Bang(kill_x, kill_y);
13623 void TestIfPlayerTouchesBadThing(int x, int y)
13625 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13628 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13630 TestIfGoodThingHitsBadThing(x, y, move_dir);
13633 void TestIfBadThingTouchesPlayer(int x, int y)
13635 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13638 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13640 TestIfBadThingHitsGoodThing(x, y, move_dir);
13643 void TestIfFriendTouchesBadThing(int x, int y)
13645 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13648 void TestIfBadThingTouchesFriend(int x, int y)
13650 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13653 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13655 int i, kill_x = bad_x, kill_y = bad_y;
13656 static int xy[4][2] =
13664 for (i = 0; i < NUM_DIRECTIONS; i++)
13668 x = bad_x + xy[i][0];
13669 y = bad_y + xy[i][1];
13670 if (!IN_LEV_FIELD(x, y))
13673 element = Tile[x][y];
13674 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13675 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13683 if (kill_x != bad_x || kill_y != bad_y)
13684 Bang(bad_x, bad_y);
13687 void KillPlayer(struct PlayerInfo *player)
13689 int jx = player->jx, jy = player->jy;
13691 if (!player->active)
13695 Debug("game:playing:KillPlayer",
13696 "0: killed == %d, active == %d, reanimated == %d",
13697 player->killed, player->active, player->reanimated);
13700 /* the following code was introduced to prevent an infinite loop when calling
13702 -> CheckTriggeredElementChangeExt()
13703 -> ExecuteCustomElementAction()
13705 -> (infinitely repeating the above sequence of function calls)
13706 which occurs when killing the player while having a CE with the setting
13707 "kill player X when explosion of <player X>"; the solution using a new
13708 field "player->killed" was chosen for backwards compatibility, although
13709 clever use of the fields "player->active" etc. would probably also work */
13711 if (player->killed)
13715 player->killed = TRUE;
13717 // remove accessible field at the player's position
13718 Tile[jx][jy] = EL_EMPTY;
13720 // deactivate shield (else Bang()/Explode() would not work right)
13721 player->shield_normal_time_left = 0;
13722 player->shield_deadly_time_left = 0;
13725 Debug("game:playing:KillPlayer",
13726 "1: killed == %d, active == %d, reanimated == %d",
13727 player->killed, player->active, player->reanimated);
13733 Debug("game:playing:KillPlayer",
13734 "2: killed == %d, active == %d, reanimated == %d",
13735 player->killed, player->active, player->reanimated);
13738 if (player->reanimated) // killed player may have been reanimated
13739 player->killed = player->reanimated = FALSE;
13741 BuryPlayer(player);
13744 static void KillPlayerUnlessEnemyProtected(int x, int y)
13746 if (!PLAYER_ENEMY_PROTECTED(x, y))
13747 KillPlayer(PLAYERINFO(x, y));
13750 static void KillPlayerUnlessExplosionProtected(int x, int y)
13752 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13753 KillPlayer(PLAYERINFO(x, y));
13756 void BuryPlayer(struct PlayerInfo *player)
13758 int jx = player->jx, jy = player->jy;
13760 if (!player->active)
13763 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13764 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13766 RemovePlayer(player);
13768 player->buried = TRUE;
13770 if (game.all_players_gone)
13771 game.GameOver = TRUE;
13774 void RemovePlayer(struct PlayerInfo *player)
13776 int jx = player->jx, jy = player->jy;
13777 int i, found = FALSE;
13779 player->present = FALSE;
13780 player->active = FALSE;
13782 // required for some CE actions (even if the player is not active anymore)
13783 player->MovPos = 0;
13785 if (!ExplodeField[jx][jy])
13786 StorePlayer[jx][jy] = 0;
13788 if (player->is_moving)
13789 TEST_DrawLevelField(player->last_jx, player->last_jy);
13791 for (i = 0; i < MAX_PLAYERS; i++)
13792 if (stored_player[i].active)
13797 game.all_players_gone = TRUE;
13798 game.GameOver = TRUE;
13801 game.exit_x = game.robot_wheel_x = jx;
13802 game.exit_y = game.robot_wheel_y = jy;
13805 void ExitPlayer(struct PlayerInfo *player)
13807 DrawPlayer(player); // needed here only to cleanup last field
13808 RemovePlayer(player);
13810 if (game.players_still_needed > 0)
13811 game.players_still_needed--;
13814 static void SetFieldForSnapping(int x, int y, int element, int direction,
13815 int player_index_bit)
13817 struct ElementInfo *ei = &element_info[element];
13818 int direction_bit = MV_DIR_TO_BIT(direction);
13819 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13820 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13821 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13823 Tile[x][y] = EL_ELEMENT_SNAPPING;
13824 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13825 MovDir[x][y] = direction;
13826 Store[x][y] = element;
13827 Store2[x][y] = player_index_bit;
13829 ResetGfxAnimation(x, y);
13831 GfxElement[x][y] = element;
13832 GfxAction[x][y] = action;
13833 GfxDir[x][y] = direction;
13834 GfxFrame[x][y] = -1;
13837 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13838 int player_index_bit)
13840 TestIfElementTouchesCustomElement(x, y); // for empty space
13842 if (level.finish_dig_collect)
13844 int dig_side = MV_DIR_OPPOSITE(direction);
13846 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13847 player_index_bit, dig_side);
13852 =============================================================================
13853 checkDiagonalPushing()
13854 -----------------------------------------------------------------------------
13855 check if diagonal input device direction results in pushing of object
13856 (by checking if the alternative direction is walkable, diggable, ...)
13857 =============================================================================
13860 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13861 int x, int y, int real_dx, int real_dy)
13863 int jx, jy, dx, dy, xx, yy;
13865 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13868 // diagonal direction: check alternative direction
13873 xx = jx + (dx == 0 ? real_dx : 0);
13874 yy = jy + (dy == 0 ? real_dy : 0);
13876 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13880 =============================================================================
13882 -----------------------------------------------------------------------------
13883 x, y: field next to player (non-diagonal) to try to dig to
13884 real_dx, real_dy: direction as read from input device (can be diagonal)
13885 =============================================================================
13888 static int DigField(struct PlayerInfo *player,
13889 int oldx, int oldy, int x, int y,
13890 int real_dx, int real_dy, int mode)
13892 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13893 boolean player_was_pushing = player->is_pushing;
13894 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13895 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13896 int jx = oldx, jy = oldy;
13897 int dx = x - jx, dy = y - jy;
13898 int nextx = x + dx, nexty = y + dy;
13899 int move_direction = (dx == -1 ? MV_LEFT :
13900 dx == +1 ? MV_RIGHT :
13902 dy == +1 ? MV_DOWN : MV_NONE);
13903 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13904 int dig_side = MV_DIR_OPPOSITE(move_direction);
13905 int old_element = Tile[jx][jy];
13906 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13909 if (is_player) // function can also be called by EL_PENGUIN
13911 if (player->MovPos == 0)
13913 player->is_digging = FALSE;
13914 player->is_collecting = FALSE;
13917 if (player->MovPos == 0) // last pushing move finished
13918 player->is_pushing = FALSE;
13920 if (mode == DF_NO_PUSH) // player just stopped pushing
13922 player->is_switching = FALSE;
13923 player->push_delay = -1;
13925 return MP_NO_ACTION;
13929 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13930 old_element = Back[jx][jy];
13932 // in case of element dropped at player position, check background
13933 else if (Back[jx][jy] != EL_EMPTY &&
13934 game.engine_version >= VERSION_IDENT(2,2,0,0))
13935 old_element = Back[jx][jy];
13937 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13938 return MP_NO_ACTION; // field has no opening in this direction
13940 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13941 return MP_NO_ACTION; // field has no opening in this direction
13943 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13947 Tile[jx][jy] = player->artwork_element;
13948 InitMovingField(jx, jy, MV_DOWN);
13949 Store[jx][jy] = EL_ACID;
13950 ContinueMoving(jx, jy);
13951 BuryPlayer(player);
13953 return MP_DONT_RUN_INTO;
13956 if (player_can_move && DONT_RUN_INTO(element))
13958 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13960 return MP_DONT_RUN_INTO;
13963 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13964 return MP_NO_ACTION;
13966 collect_count = element_info[element].collect_count_initial;
13968 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13969 return MP_NO_ACTION;
13971 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13972 player_can_move = player_can_move_or_snap;
13974 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13975 game.engine_version >= VERSION_IDENT(2,2,0,0))
13977 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13978 player->index_bit, dig_side);
13979 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13980 player->index_bit, dig_side);
13982 if (element == EL_DC_LANDMINE)
13985 if (Tile[x][y] != element) // field changed by snapping
13988 return MP_NO_ACTION;
13991 if (player->gravity && is_player && !player->is_auto_moving &&
13992 canFallDown(player) && move_direction != MV_DOWN &&
13993 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13994 return MP_NO_ACTION; // player cannot walk here due to gravity
13996 if (player_can_move &&
13997 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13999 int sound_element = SND_ELEMENT(element);
14000 int sound_action = ACTION_WALKING;
14002 if (IS_RND_GATE(element))
14004 if (!player->key[RND_GATE_NR(element)])
14005 return MP_NO_ACTION;
14007 else if (IS_RND_GATE_GRAY(element))
14009 if (!player->key[RND_GATE_GRAY_NR(element)])
14010 return MP_NO_ACTION;
14012 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14014 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14015 return MP_NO_ACTION;
14017 else if (element == EL_EXIT_OPEN ||
14018 element == EL_EM_EXIT_OPEN ||
14019 element == EL_EM_EXIT_OPENING ||
14020 element == EL_STEEL_EXIT_OPEN ||
14021 element == EL_EM_STEEL_EXIT_OPEN ||
14022 element == EL_EM_STEEL_EXIT_OPENING ||
14023 element == EL_SP_EXIT_OPEN ||
14024 element == EL_SP_EXIT_OPENING)
14026 sound_action = ACTION_PASSING; // player is passing exit
14028 else if (element == EL_EMPTY)
14030 sound_action = ACTION_MOVING; // nothing to walk on
14033 // play sound from background or player, whatever is available
14034 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14035 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14037 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14039 else if (player_can_move &&
14040 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14042 if (!ACCESS_FROM(element, opposite_direction))
14043 return MP_NO_ACTION; // field not accessible from this direction
14045 if (CAN_MOVE(element)) // only fixed elements can be passed!
14046 return MP_NO_ACTION;
14048 if (IS_EM_GATE(element))
14050 if (!player->key[EM_GATE_NR(element)])
14051 return MP_NO_ACTION;
14053 else if (IS_EM_GATE_GRAY(element))
14055 if (!player->key[EM_GATE_GRAY_NR(element)])
14056 return MP_NO_ACTION;
14058 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14060 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14061 return MP_NO_ACTION;
14063 else if (IS_EMC_GATE(element))
14065 if (!player->key[EMC_GATE_NR(element)])
14066 return MP_NO_ACTION;
14068 else if (IS_EMC_GATE_GRAY(element))
14070 if (!player->key[EMC_GATE_GRAY_NR(element)])
14071 return MP_NO_ACTION;
14073 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14075 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14076 return MP_NO_ACTION;
14078 else if (element == EL_DC_GATE_WHITE ||
14079 element == EL_DC_GATE_WHITE_GRAY ||
14080 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14082 if (player->num_white_keys == 0)
14083 return MP_NO_ACTION;
14085 player->num_white_keys--;
14087 else if (IS_SP_PORT(element))
14089 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14090 element == EL_SP_GRAVITY_PORT_RIGHT ||
14091 element == EL_SP_GRAVITY_PORT_UP ||
14092 element == EL_SP_GRAVITY_PORT_DOWN)
14093 player->gravity = !player->gravity;
14094 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14095 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14096 element == EL_SP_GRAVITY_ON_PORT_UP ||
14097 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14098 player->gravity = TRUE;
14099 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14100 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14101 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14102 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14103 player->gravity = FALSE;
14106 // automatically move to the next field with double speed
14107 player->programmed_action = move_direction;
14109 if (player->move_delay_reset_counter == 0)
14111 player->move_delay_reset_counter = 2; // two double speed steps
14113 DOUBLE_PLAYER_SPEED(player);
14116 PlayLevelSoundAction(x, y, ACTION_PASSING);
14118 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14122 if (mode != DF_SNAP)
14124 GfxElement[x][y] = GFX_ELEMENT(element);
14125 player->is_digging = TRUE;
14128 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14130 // use old behaviour for old levels (digging)
14131 if (!level.finish_dig_collect)
14133 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14134 player->index_bit, dig_side);
14136 // if digging triggered player relocation, finish digging tile
14137 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14138 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14141 if (mode == DF_SNAP)
14143 if (level.block_snap_field)
14144 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14146 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14148 // use old behaviour for old levels (snapping)
14149 if (!level.finish_dig_collect)
14150 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14151 player->index_bit, dig_side);
14154 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14158 if (is_player && mode != DF_SNAP)
14160 GfxElement[x][y] = element;
14161 player->is_collecting = TRUE;
14164 if (element == EL_SPEED_PILL)
14166 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14168 else if (element == EL_EXTRA_TIME && level.time > 0)
14170 TimeLeft += level.extra_time;
14172 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14174 DisplayGameControlValues();
14176 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14178 player->shield_normal_time_left += level.shield_normal_time;
14179 if (element == EL_SHIELD_DEADLY)
14180 player->shield_deadly_time_left += level.shield_deadly_time;
14182 else if (element == EL_DYNAMITE ||
14183 element == EL_EM_DYNAMITE ||
14184 element == EL_SP_DISK_RED)
14186 if (player->inventory_size < MAX_INVENTORY_SIZE)
14187 player->inventory_element[player->inventory_size++] = element;
14189 DrawGameDoorValues();
14191 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14193 player->dynabomb_count++;
14194 player->dynabombs_left++;
14196 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14198 player->dynabomb_size++;
14200 else if (element == EL_DYNABOMB_INCREASE_POWER)
14202 player->dynabomb_xl = TRUE;
14204 else if (IS_KEY(element))
14206 player->key[KEY_NR(element)] = TRUE;
14208 DrawGameDoorValues();
14210 else if (element == EL_DC_KEY_WHITE)
14212 player->num_white_keys++;
14214 // display white keys?
14215 // DrawGameDoorValues();
14217 else if (IS_ENVELOPE(element))
14219 player->show_envelope = element;
14221 else if (element == EL_EMC_LENSES)
14223 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14225 RedrawAllInvisibleElementsForLenses();
14227 else if (element == EL_EMC_MAGNIFIER)
14229 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14231 RedrawAllInvisibleElementsForMagnifier();
14233 else if (IS_DROPPABLE(element) ||
14234 IS_THROWABLE(element)) // can be collected and dropped
14238 if (collect_count == 0)
14239 player->inventory_infinite_element = element;
14241 for (i = 0; i < collect_count; i++)
14242 if (player->inventory_size < MAX_INVENTORY_SIZE)
14243 player->inventory_element[player->inventory_size++] = element;
14245 DrawGameDoorValues();
14247 else if (collect_count > 0)
14249 game.gems_still_needed -= collect_count;
14250 if (game.gems_still_needed < 0)
14251 game.gems_still_needed = 0;
14253 game.snapshot.collected_item = TRUE;
14255 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14257 DisplayGameControlValues();
14260 RaiseScoreElement(element);
14261 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14263 // use old behaviour for old levels (collecting)
14264 if (!level.finish_dig_collect && is_player)
14266 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14267 player->index_bit, dig_side);
14269 // if collecting triggered player relocation, finish collecting tile
14270 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14271 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14274 if (mode == DF_SNAP)
14276 if (level.block_snap_field)
14277 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14279 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14281 // use old behaviour for old levels (snapping)
14282 if (!level.finish_dig_collect)
14283 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14284 player->index_bit, dig_side);
14287 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14289 if (mode == DF_SNAP && element != EL_BD_ROCK)
14290 return MP_NO_ACTION;
14292 if (CAN_FALL(element) && dy)
14293 return MP_NO_ACTION;
14295 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14296 !(element == EL_SPRING && level.use_spring_bug))
14297 return MP_NO_ACTION;
14299 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14300 ((move_direction & MV_VERTICAL &&
14301 ((element_info[element].move_pattern & MV_LEFT &&
14302 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14303 (element_info[element].move_pattern & MV_RIGHT &&
14304 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14305 (move_direction & MV_HORIZONTAL &&
14306 ((element_info[element].move_pattern & MV_UP &&
14307 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14308 (element_info[element].move_pattern & MV_DOWN &&
14309 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14310 return MP_NO_ACTION;
14312 // do not push elements already moving away faster than player
14313 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14314 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14315 return MP_NO_ACTION;
14317 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14319 if (player->push_delay_value == -1 || !player_was_pushing)
14320 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14322 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14324 if (player->push_delay_value == -1)
14325 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14327 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14329 if (!player->is_pushing)
14330 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14333 player->is_pushing = TRUE;
14334 player->is_active = TRUE;
14336 if (!(IN_LEV_FIELD(nextx, nexty) &&
14337 (IS_FREE(nextx, nexty) ||
14338 (IS_SB_ELEMENT(element) &&
14339 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14340 (IS_CUSTOM_ELEMENT(element) &&
14341 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14342 return MP_NO_ACTION;
14344 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14345 return MP_NO_ACTION;
14347 if (player->push_delay == -1) // new pushing; restart delay
14348 player->push_delay = 0;
14350 if (player->push_delay < player->push_delay_value &&
14351 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14352 element != EL_SPRING && element != EL_BALLOON)
14354 // make sure that there is no move delay before next try to push
14355 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14356 player->move_delay = 0;
14358 return MP_NO_ACTION;
14361 if (IS_CUSTOM_ELEMENT(element) &&
14362 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14364 if (!DigFieldByCE(nextx, nexty, element))
14365 return MP_NO_ACTION;
14368 if (IS_SB_ELEMENT(element))
14370 boolean sokoban_task_solved = FALSE;
14372 if (element == EL_SOKOBAN_FIELD_FULL)
14374 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14376 IncrementSokobanFieldsNeeded();
14377 IncrementSokobanObjectsNeeded();
14380 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14382 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14384 DecrementSokobanFieldsNeeded();
14385 DecrementSokobanObjectsNeeded();
14387 // sokoban object was pushed from empty field to sokoban field
14388 if (Back[x][y] == EL_EMPTY)
14389 sokoban_task_solved = TRUE;
14392 Tile[x][y] = EL_SOKOBAN_OBJECT;
14394 if (Back[x][y] == Back[nextx][nexty])
14395 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14396 else if (Back[x][y] != 0)
14397 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14400 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14403 if (sokoban_task_solved &&
14404 game.sokoban_fields_still_needed == 0 &&
14405 game.sokoban_objects_still_needed == 0 &&
14406 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14408 game.players_still_needed = 0;
14412 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14416 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14418 InitMovingField(x, y, move_direction);
14419 GfxAction[x][y] = ACTION_PUSHING;
14421 if (mode == DF_SNAP)
14422 ContinueMoving(x, y);
14424 MovPos[x][y] = (dx != 0 ? dx : dy);
14426 Pushed[x][y] = TRUE;
14427 Pushed[nextx][nexty] = TRUE;
14429 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14430 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14432 player->push_delay_value = -1; // get new value later
14434 // check for element change _after_ element has been pushed
14435 if (game.use_change_when_pushing_bug)
14437 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14438 player->index_bit, dig_side);
14439 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14440 player->index_bit, dig_side);
14443 else if (IS_SWITCHABLE(element))
14445 if (PLAYER_SWITCHING(player, x, y))
14447 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14448 player->index_bit, dig_side);
14453 player->is_switching = TRUE;
14454 player->switch_x = x;
14455 player->switch_y = y;
14457 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14459 if (element == EL_ROBOT_WHEEL)
14461 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14463 game.robot_wheel_x = x;
14464 game.robot_wheel_y = y;
14465 game.robot_wheel_active = TRUE;
14467 TEST_DrawLevelField(x, y);
14469 else if (element == EL_SP_TERMINAL)
14473 SCAN_PLAYFIELD(xx, yy)
14475 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14479 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14481 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14483 ResetGfxAnimation(xx, yy);
14484 TEST_DrawLevelField(xx, yy);
14488 else if (IS_BELT_SWITCH(element))
14490 ToggleBeltSwitch(x, y);
14492 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14493 element == EL_SWITCHGATE_SWITCH_DOWN ||
14494 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14495 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14497 ToggleSwitchgateSwitch(x, y);
14499 else if (element == EL_LIGHT_SWITCH ||
14500 element == EL_LIGHT_SWITCH_ACTIVE)
14502 ToggleLightSwitch(x, y);
14504 else if (element == EL_TIMEGATE_SWITCH ||
14505 element == EL_DC_TIMEGATE_SWITCH)
14507 ActivateTimegateSwitch(x, y);
14509 else if (element == EL_BALLOON_SWITCH_LEFT ||
14510 element == EL_BALLOON_SWITCH_RIGHT ||
14511 element == EL_BALLOON_SWITCH_UP ||
14512 element == EL_BALLOON_SWITCH_DOWN ||
14513 element == EL_BALLOON_SWITCH_NONE ||
14514 element == EL_BALLOON_SWITCH_ANY)
14516 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14517 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14518 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14519 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14520 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14523 else if (element == EL_LAMP)
14525 Tile[x][y] = EL_LAMP_ACTIVE;
14526 game.lights_still_needed--;
14528 ResetGfxAnimation(x, y);
14529 TEST_DrawLevelField(x, y);
14531 else if (element == EL_TIME_ORB_FULL)
14533 Tile[x][y] = EL_TIME_ORB_EMPTY;
14535 if (level.time > 0 || level.use_time_orb_bug)
14537 TimeLeft += level.time_orb_time;
14538 game.no_time_limit = FALSE;
14540 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14542 DisplayGameControlValues();
14545 ResetGfxAnimation(x, y);
14546 TEST_DrawLevelField(x, y);
14548 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14549 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14553 game.ball_active = !game.ball_active;
14555 SCAN_PLAYFIELD(xx, yy)
14557 int e = Tile[xx][yy];
14559 if (game.ball_active)
14561 if (e == EL_EMC_MAGIC_BALL)
14562 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14563 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14564 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14568 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14569 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14570 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14571 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14576 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14577 player->index_bit, dig_side);
14579 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14580 player->index_bit, dig_side);
14582 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14583 player->index_bit, dig_side);
14589 if (!PLAYER_SWITCHING(player, x, y))
14591 player->is_switching = TRUE;
14592 player->switch_x = x;
14593 player->switch_y = y;
14595 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14596 player->index_bit, dig_side);
14597 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14598 player->index_bit, dig_side);
14600 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14601 player->index_bit, dig_side);
14602 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14603 player->index_bit, dig_side);
14606 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14607 player->index_bit, dig_side);
14608 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14609 player->index_bit, dig_side);
14611 return MP_NO_ACTION;
14614 player->push_delay = -1;
14616 if (is_player) // function can also be called by EL_PENGUIN
14618 if (Tile[x][y] != element) // really digged/collected something
14620 player->is_collecting = !player->is_digging;
14621 player->is_active = TRUE;
14623 player->last_removed_element = element;
14630 static boolean DigFieldByCE(int x, int y, int digging_element)
14632 int element = Tile[x][y];
14634 if (!IS_FREE(x, y))
14636 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14637 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14640 // no element can dig solid indestructible elements
14641 if (IS_INDESTRUCTIBLE(element) &&
14642 !IS_DIGGABLE(element) &&
14643 !IS_COLLECTIBLE(element))
14646 if (AmoebaNr[x][y] &&
14647 (element == EL_AMOEBA_FULL ||
14648 element == EL_BD_AMOEBA ||
14649 element == EL_AMOEBA_GROWING))
14651 AmoebaCnt[AmoebaNr[x][y]]--;
14652 AmoebaCnt2[AmoebaNr[x][y]]--;
14655 if (IS_MOVING(x, y))
14656 RemoveMovingField(x, y);
14660 TEST_DrawLevelField(x, y);
14663 // if digged element was about to explode, prevent the explosion
14664 ExplodeField[x][y] = EX_TYPE_NONE;
14666 PlayLevelSoundAction(x, y, action);
14669 Store[x][y] = EL_EMPTY;
14671 // this makes it possible to leave the removed element again
14672 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14673 Store[x][y] = element;
14678 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14680 int jx = player->jx, jy = player->jy;
14681 int x = jx + dx, y = jy + dy;
14682 int snap_direction = (dx == -1 ? MV_LEFT :
14683 dx == +1 ? MV_RIGHT :
14685 dy == +1 ? MV_DOWN : MV_NONE);
14686 boolean can_continue_snapping = (level.continuous_snapping &&
14687 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14689 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14692 if (!player->active || !IN_LEV_FIELD(x, y))
14700 if (player->MovPos == 0)
14701 player->is_pushing = FALSE;
14703 player->is_snapping = FALSE;
14705 if (player->MovPos == 0)
14707 player->is_moving = FALSE;
14708 player->is_digging = FALSE;
14709 player->is_collecting = FALSE;
14715 // prevent snapping with already pressed snap key when not allowed
14716 if (player->is_snapping && !can_continue_snapping)
14719 player->MovDir = snap_direction;
14721 if (player->MovPos == 0)
14723 player->is_moving = FALSE;
14724 player->is_digging = FALSE;
14725 player->is_collecting = FALSE;
14728 player->is_dropping = FALSE;
14729 player->is_dropping_pressed = FALSE;
14730 player->drop_pressed_delay = 0;
14732 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14735 player->is_snapping = TRUE;
14736 player->is_active = TRUE;
14738 if (player->MovPos == 0)
14740 player->is_moving = FALSE;
14741 player->is_digging = FALSE;
14742 player->is_collecting = FALSE;
14745 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14746 TEST_DrawLevelField(player->last_jx, player->last_jy);
14748 TEST_DrawLevelField(x, y);
14753 static boolean DropElement(struct PlayerInfo *player)
14755 int old_element, new_element;
14756 int dropx = player->jx, dropy = player->jy;
14757 int drop_direction = player->MovDir;
14758 int drop_side = drop_direction;
14759 int drop_element = get_next_dropped_element(player);
14761 /* do not drop an element on top of another element; when holding drop key
14762 pressed without moving, dropped element must move away before the next
14763 element can be dropped (this is especially important if the next element
14764 is dynamite, which can be placed on background for historical reasons) */
14765 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14768 if (IS_THROWABLE(drop_element))
14770 dropx += GET_DX_FROM_DIR(drop_direction);
14771 dropy += GET_DY_FROM_DIR(drop_direction);
14773 if (!IN_LEV_FIELD(dropx, dropy))
14777 old_element = Tile[dropx][dropy]; // old element at dropping position
14778 new_element = drop_element; // default: no change when dropping
14780 // check if player is active, not moving and ready to drop
14781 if (!player->active || player->MovPos || player->drop_delay > 0)
14784 // check if player has anything that can be dropped
14785 if (new_element == EL_UNDEFINED)
14788 // only set if player has anything that can be dropped
14789 player->is_dropping_pressed = TRUE;
14791 // check if drop key was pressed long enough for EM style dynamite
14792 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14795 // check if anything can be dropped at the current position
14796 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14799 // collected custom elements can only be dropped on empty fields
14800 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14803 if (old_element != EL_EMPTY)
14804 Back[dropx][dropy] = old_element; // store old element on this field
14806 ResetGfxAnimation(dropx, dropy);
14807 ResetRandomAnimationValue(dropx, dropy);
14809 if (player->inventory_size > 0 ||
14810 player->inventory_infinite_element != EL_UNDEFINED)
14812 if (player->inventory_size > 0)
14814 player->inventory_size--;
14816 DrawGameDoorValues();
14818 if (new_element == EL_DYNAMITE)
14819 new_element = EL_DYNAMITE_ACTIVE;
14820 else if (new_element == EL_EM_DYNAMITE)
14821 new_element = EL_EM_DYNAMITE_ACTIVE;
14822 else if (new_element == EL_SP_DISK_RED)
14823 new_element = EL_SP_DISK_RED_ACTIVE;
14826 Tile[dropx][dropy] = new_element;
14828 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14829 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14830 el2img(Tile[dropx][dropy]), 0);
14832 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14834 // needed if previous element just changed to "empty" in the last frame
14835 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14837 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14838 player->index_bit, drop_side);
14839 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14841 player->index_bit, drop_side);
14843 TestIfElementTouchesCustomElement(dropx, dropy);
14845 else // player is dropping a dyna bomb
14847 player->dynabombs_left--;
14849 Tile[dropx][dropy] = new_element;
14851 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14852 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14853 el2img(Tile[dropx][dropy]), 0);
14855 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14858 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14859 InitField_WithBug1(dropx, dropy, FALSE);
14861 new_element = Tile[dropx][dropy]; // element might have changed
14863 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14864 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14866 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14867 MovDir[dropx][dropy] = drop_direction;
14869 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14871 // do not cause impact style collision by dropping elements that can fall
14872 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14875 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14876 player->is_dropping = TRUE;
14878 player->drop_pressed_delay = 0;
14879 player->is_dropping_pressed = FALSE;
14881 player->drop_x = dropx;
14882 player->drop_y = dropy;
14887 // ----------------------------------------------------------------------------
14888 // game sound playing functions
14889 // ----------------------------------------------------------------------------
14891 static int *loop_sound_frame = NULL;
14892 static int *loop_sound_volume = NULL;
14894 void InitPlayLevelSound(void)
14896 int num_sounds = getSoundListSize();
14898 checked_free(loop_sound_frame);
14899 checked_free(loop_sound_volume);
14901 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14902 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14905 static void PlayLevelSound(int x, int y, int nr)
14907 int sx = SCREENX(x), sy = SCREENY(y);
14908 int volume, stereo_position;
14909 int max_distance = 8;
14910 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14912 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14913 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14916 if (!IN_LEV_FIELD(x, y) ||
14917 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14918 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14921 volume = SOUND_MAX_VOLUME;
14923 if (!IN_SCR_FIELD(sx, sy))
14925 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14926 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14928 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14931 stereo_position = (SOUND_MAX_LEFT +
14932 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14933 (SCR_FIELDX + 2 * max_distance));
14935 if (IS_LOOP_SOUND(nr))
14937 /* This assures that quieter loop sounds do not overwrite louder ones,
14938 while restarting sound volume comparison with each new game frame. */
14940 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14943 loop_sound_volume[nr] = volume;
14944 loop_sound_frame[nr] = FrameCounter;
14947 PlaySoundExt(nr, volume, stereo_position, type);
14950 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14952 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14953 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14954 y < LEVELY(BY1) ? LEVELY(BY1) :
14955 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14959 static void PlayLevelSoundAction(int x, int y, int action)
14961 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14964 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14966 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14968 if (sound_effect != SND_UNDEFINED)
14969 PlayLevelSound(x, y, sound_effect);
14972 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14975 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14977 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14978 PlayLevelSound(x, y, sound_effect);
14981 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14983 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14985 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14986 PlayLevelSound(x, y, sound_effect);
14989 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14991 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14993 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14994 StopSound(sound_effect);
14997 static int getLevelMusicNr(void)
14999 if (levelset.music[level_nr] != MUS_UNDEFINED)
15000 return levelset.music[level_nr]; // from config file
15002 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15005 static void FadeLevelSounds(void)
15010 static void FadeLevelMusic(void)
15012 int music_nr = getLevelMusicNr();
15013 char *curr_music = getCurrentlyPlayingMusicFilename();
15014 char *next_music = getMusicInfoEntryFilename(music_nr);
15016 if (!strEqual(curr_music, next_music))
15020 void FadeLevelSoundsAndMusic(void)
15026 static void PlayLevelMusic(void)
15028 int music_nr = getLevelMusicNr();
15029 char *curr_music = getCurrentlyPlayingMusicFilename();
15030 char *next_music = getMusicInfoEntryFilename(music_nr);
15032 if (!strEqual(curr_music, next_music))
15033 PlayMusicLoop(music_nr);
15036 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15038 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15040 int x = xx - offset;
15041 int y = yy - offset;
15046 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15050 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15054 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15058 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15062 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15066 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15070 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15073 case SOUND_android_clone:
15074 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15077 case SOUND_android_move:
15078 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15082 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15086 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15090 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15093 case SOUND_eater_eat:
15094 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15098 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15101 case SOUND_collect:
15102 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15105 case SOUND_diamond:
15106 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15110 // !!! CHECK THIS !!!
15112 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15114 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15118 case SOUND_wonderfall:
15119 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15123 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15127 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15131 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15135 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15139 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15143 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15147 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15151 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15154 case SOUND_exit_open:
15155 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15158 case SOUND_exit_leave:
15159 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15162 case SOUND_dynamite:
15163 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15167 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15171 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15175 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15179 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15183 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15187 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15191 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15196 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15198 int element = map_element_SP_to_RND(element_sp);
15199 int action = map_action_SP_to_RND(action_sp);
15200 int offset = (setup.sp_show_border_elements ? 0 : 1);
15201 int x = xx - offset;
15202 int y = yy - offset;
15204 PlayLevelSoundElementAction(x, y, element, action);
15207 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15209 int element = map_element_MM_to_RND(element_mm);
15210 int action = map_action_MM_to_RND(action_mm);
15212 int x = xx - offset;
15213 int y = yy - offset;
15215 if (!IS_MM_ELEMENT(element))
15216 element = EL_MM_DEFAULT;
15218 PlayLevelSoundElementAction(x, y, element, action);
15221 void PlaySound_MM(int sound_mm)
15223 int sound = map_sound_MM_to_RND(sound_mm);
15225 if (sound == SND_UNDEFINED)
15231 void PlaySoundLoop_MM(int sound_mm)
15233 int sound = map_sound_MM_to_RND(sound_mm);
15235 if (sound == SND_UNDEFINED)
15238 PlaySoundLoop(sound);
15241 void StopSound_MM(int sound_mm)
15243 int sound = map_sound_MM_to_RND(sound_mm);
15245 if (sound == SND_UNDEFINED)
15251 void RaiseScore(int value)
15253 game.score += value;
15255 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15257 DisplayGameControlValues();
15260 void RaiseScoreElement(int element)
15265 case EL_BD_DIAMOND:
15266 case EL_EMERALD_YELLOW:
15267 case EL_EMERALD_RED:
15268 case EL_EMERALD_PURPLE:
15269 case EL_SP_INFOTRON:
15270 RaiseScore(level.score[SC_EMERALD]);
15273 RaiseScore(level.score[SC_DIAMOND]);
15276 RaiseScore(level.score[SC_CRYSTAL]);
15279 RaiseScore(level.score[SC_PEARL]);
15282 case EL_BD_BUTTERFLY:
15283 case EL_SP_ELECTRON:
15284 RaiseScore(level.score[SC_BUG]);
15287 case EL_BD_FIREFLY:
15288 case EL_SP_SNIKSNAK:
15289 RaiseScore(level.score[SC_SPACESHIP]);
15292 case EL_DARK_YAMYAM:
15293 RaiseScore(level.score[SC_YAMYAM]);
15296 RaiseScore(level.score[SC_ROBOT]);
15299 RaiseScore(level.score[SC_PACMAN]);
15302 RaiseScore(level.score[SC_NUT]);
15305 case EL_EM_DYNAMITE:
15306 case EL_SP_DISK_RED:
15307 case EL_DYNABOMB_INCREASE_NUMBER:
15308 case EL_DYNABOMB_INCREASE_SIZE:
15309 case EL_DYNABOMB_INCREASE_POWER:
15310 RaiseScore(level.score[SC_DYNAMITE]);
15312 case EL_SHIELD_NORMAL:
15313 case EL_SHIELD_DEADLY:
15314 RaiseScore(level.score[SC_SHIELD]);
15316 case EL_EXTRA_TIME:
15317 RaiseScore(level.extra_time_score);
15331 case EL_DC_KEY_WHITE:
15332 RaiseScore(level.score[SC_KEY]);
15335 RaiseScore(element_info[element].collect_score);
15340 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15342 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15344 // closing door required in case of envelope style request dialogs
15347 // prevent short reactivation of overlay buttons while closing door
15348 SetOverlayActive(FALSE);
15350 CloseDoor(DOOR_CLOSE_1);
15353 if (network.enabled)
15354 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15358 FadeSkipNextFadeIn();
15360 SetGameStatus(GAME_MODE_MAIN);
15365 else // continue playing the game
15367 if (tape.playing && tape.deactivate_display)
15368 TapeDeactivateDisplayOff(TRUE);
15370 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15372 if (tape.playing && tape.deactivate_display)
15373 TapeDeactivateDisplayOn();
15377 void RequestQuitGame(boolean ask_if_really_quit)
15379 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15380 boolean skip_request = game.all_players_gone || quick_quit;
15382 RequestQuitGameExt(skip_request, quick_quit,
15383 "Do you really want to quit the game?");
15386 void RequestRestartGame(char *message)
15388 game.restart_game_message = NULL;
15390 boolean has_started_game = hasStartedNetworkGame();
15391 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15393 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15395 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15399 // needed in case of envelope request to close game panel
15400 CloseDoor(DOOR_CLOSE_1);
15402 SetGameStatus(GAME_MODE_MAIN);
15408 void CheckGameOver(void)
15410 static boolean last_game_over = FALSE;
15411 static int game_over_delay = 0;
15412 int game_over_delay_value = 50;
15413 boolean game_over = checkGameFailed();
15415 // do not handle game over if request dialog is already active
15416 if (game.request_active)
15419 // do not ask to play again if game was never actually played
15420 if (!game.GamePlayed)
15425 last_game_over = FALSE;
15426 game_over_delay = game_over_delay_value;
15431 if (game_over_delay > 0)
15438 if (last_game_over != game_over)
15439 game.restart_game_message = (hasStartedNetworkGame() ?
15440 "Game over! Play it again?" :
15443 last_game_over = game_over;
15446 boolean checkGameSolved(void)
15448 // set for all game engines if level was solved
15449 return game.LevelSolved_GameEnd;
15452 boolean checkGameFailed(void)
15454 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15455 return (game_em.game_over && !game_em.level_solved);
15456 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15457 return (game_sp.game_over && !game_sp.level_solved);
15458 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15459 return (game_mm.game_over && !game_mm.level_solved);
15460 else // GAME_ENGINE_TYPE_RND
15461 return (game.GameOver && !game.LevelSolved);
15464 boolean checkGameEnded(void)
15466 return (checkGameSolved() || checkGameFailed());
15470 // ----------------------------------------------------------------------------
15471 // random generator functions
15472 // ----------------------------------------------------------------------------
15474 unsigned int InitEngineRandom_RND(int seed)
15476 game.num_random_calls = 0;
15478 return InitEngineRandom(seed);
15481 unsigned int RND(int max)
15485 game.num_random_calls++;
15487 return GetEngineRandom(max);
15494 // ----------------------------------------------------------------------------
15495 // game engine snapshot handling functions
15496 // ----------------------------------------------------------------------------
15498 struct EngineSnapshotInfo
15500 // runtime values for custom element collect score
15501 int collect_score[NUM_CUSTOM_ELEMENTS];
15503 // runtime values for group element choice position
15504 int choice_pos[NUM_GROUP_ELEMENTS];
15506 // runtime values for belt position animations
15507 int belt_graphic[4][NUM_BELT_PARTS];
15508 int belt_anim_mode[4][NUM_BELT_PARTS];
15511 static struct EngineSnapshotInfo engine_snapshot_rnd;
15512 static char *snapshot_level_identifier = NULL;
15513 static int snapshot_level_nr = -1;
15515 static void SaveEngineSnapshotValues_RND(void)
15517 static int belt_base_active_element[4] =
15519 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15520 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15521 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15522 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15526 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15528 int element = EL_CUSTOM_START + i;
15530 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15533 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15535 int element = EL_GROUP_START + i;
15537 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15540 for (i = 0; i < 4; i++)
15542 for (j = 0; j < NUM_BELT_PARTS; j++)
15544 int element = belt_base_active_element[i] + j;
15545 int graphic = el2img(element);
15546 int anim_mode = graphic_info[graphic].anim_mode;
15548 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15549 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15554 static void LoadEngineSnapshotValues_RND(void)
15556 unsigned int num_random_calls = game.num_random_calls;
15559 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15561 int element = EL_CUSTOM_START + i;
15563 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15566 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15568 int element = EL_GROUP_START + i;
15570 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15573 for (i = 0; i < 4; i++)
15575 for (j = 0; j < NUM_BELT_PARTS; j++)
15577 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15578 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15580 graphic_info[graphic].anim_mode = anim_mode;
15584 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15586 InitRND(tape.random_seed);
15587 for (i = 0; i < num_random_calls; i++)
15591 if (game.num_random_calls != num_random_calls)
15593 Error("number of random calls out of sync");
15594 Error("number of random calls should be %d", num_random_calls);
15595 Error("number of random calls is %d", game.num_random_calls);
15597 Fail("this should not happen -- please debug");
15601 void FreeEngineSnapshotSingle(void)
15603 FreeSnapshotSingle();
15605 setString(&snapshot_level_identifier, NULL);
15606 snapshot_level_nr = -1;
15609 void FreeEngineSnapshotList(void)
15611 FreeSnapshotList();
15614 static ListNode *SaveEngineSnapshotBuffers(void)
15616 ListNode *buffers = NULL;
15618 // copy some special values to a structure better suited for the snapshot
15620 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15621 SaveEngineSnapshotValues_RND();
15622 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15623 SaveEngineSnapshotValues_EM();
15624 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15625 SaveEngineSnapshotValues_SP(&buffers);
15626 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15627 SaveEngineSnapshotValues_MM(&buffers);
15629 // save values stored in special snapshot structure
15631 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15632 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15633 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15635 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15637 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15640 // save further RND engine values
15642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15676 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15677 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15680 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15683 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15685 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15687 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15690 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15692 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15693 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15694 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15696 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15697 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15700 ListNode *node = engine_snapshot_list_rnd;
15703 while (node != NULL)
15705 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15710 Debug("game:playing:SaveEngineSnapshotBuffers",
15711 "size of engine snapshot: %d bytes", num_bytes);
15717 void SaveEngineSnapshotSingle(void)
15719 ListNode *buffers = SaveEngineSnapshotBuffers();
15721 // finally save all snapshot buffers to single snapshot
15722 SaveSnapshotSingle(buffers);
15724 // save level identification information
15725 setString(&snapshot_level_identifier, leveldir_current->identifier);
15726 snapshot_level_nr = level_nr;
15729 boolean CheckSaveEngineSnapshotToList(void)
15731 boolean save_snapshot =
15732 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15733 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15734 game.snapshot.changed_action) ||
15735 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15736 game.snapshot.collected_item));
15738 game.snapshot.changed_action = FALSE;
15739 game.snapshot.collected_item = FALSE;
15740 game.snapshot.save_snapshot = save_snapshot;
15742 return save_snapshot;
15745 void SaveEngineSnapshotToList(void)
15747 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15751 ListNode *buffers = SaveEngineSnapshotBuffers();
15753 // finally save all snapshot buffers to snapshot list
15754 SaveSnapshotToList(buffers);
15757 void SaveEngineSnapshotToListInitial(void)
15759 FreeEngineSnapshotList();
15761 SaveEngineSnapshotToList();
15764 static void LoadEngineSnapshotValues(void)
15766 // restore special values from snapshot structure
15768 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15769 LoadEngineSnapshotValues_RND();
15770 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15771 LoadEngineSnapshotValues_EM();
15772 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15773 LoadEngineSnapshotValues_SP();
15774 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15775 LoadEngineSnapshotValues_MM();
15778 void LoadEngineSnapshotSingle(void)
15780 LoadSnapshotSingle();
15782 LoadEngineSnapshotValues();
15785 static void LoadEngineSnapshot_Undo(int steps)
15787 LoadSnapshotFromList_Older(steps);
15789 LoadEngineSnapshotValues();
15792 static void LoadEngineSnapshot_Redo(int steps)
15794 LoadSnapshotFromList_Newer(steps);
15796 LoadEngineSnapshotValues();
15799 boolean CheckEngineSnapshotSingle(void)
15801 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15802 snapshot_level_nr == level_nr);
15805 boolean CheckEngineSnapshotList(void)
15807 return CheckSnapshotList();
15811 // ---------- new game button stuff -------------------------------------------
15818 boolean *setup_value;
15819 boolean allowed_on_tape;
15820 boolean is_touch_button;
15822 } gamebutton_info[NUM_GAME_BUTTONS] =
15825 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15826 GAME_CTRL_ID_STOP, NULL,
15827 TRUE, FALSE, "stop game"
15830 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15831 GAME_CTRL_ID_PAUSE, NULL,
15832 TRUE, FALSE, "pause game"
15835 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15836 GAME_CTRL_ID_PLAY, NULL,
15837 TRUE, FALSE, "play game"
15840 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15841 GAME_CTRL_ID_UNDO, NULL,
15842 TRUE, FALSE, "undo step"
15845 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15846 GAME_CTRL_ID_REDO, NULL,
15847 TRUE, FALSE, "redo step"
15850 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15851 GAME_CTRL_ID_SAVE, NULL,
15852 TRUE, FALSE, "save game"
15855 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15856 GAME_CTRL_ID_PAUSE2, NULL,
15857 TRUE, FALSE, "pause game"
15860 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15861 GAME_CTRL_ID_LOAD, NULL,
15862 TRUE, FALSE, "load game"
15865 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15866 GAME_CTRL_ID_PANEL_STOP, NULL,
15867 FALSE, FALSE, "stop game"
15870 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15871 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15872 FALSE, FALSE, "pause game"
15875 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15876 GAME_CTRL_ID_PANEL_PLAY, NULL,
15877 FALSE, FALSE, "play game"
15880 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15881 GAME_CTRL_ID_TOUCH_STOP, NULL,
15882 FALSE, TRUE, "stop game"
15885 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15886 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15887 FALSE, TRUE, "pause game"
15890 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15891 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15892 TRUE, FALSE, "background music on/off"
15895 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15896 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15897 TRUE, FALSE, "sound loops on/off"
15900 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15901 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15902 TRUE, FALSE, "normal sounds on/off"
15905 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15906 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15907 FALSE, FALSE, "background music on/off"
15910 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15911 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15912 FALSE, FALSE, "sound loops on/off"
15915 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15916 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15917 FALSE, FALSE, "normal sounds on/off"
15921 void CreateGameButtons(void)
15925 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15927 int graphic = gamebutton_info[i].graphic;
15928 struct GraphicInfo *gfx = &graphic_info[graphic];
15929 struct XY *pos = gamebutton_info[i].pos;
15930 struct GadgetInfo *gi;
15933 unsigned int event_mask;
15934 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15935 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15936 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15937 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15938 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15939 int gd_x = gfx->src_x;
15940 int gd_y = gfx->src_y;
15941 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15942 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15943 int gd_xa = gfx->src_x + gfx->active_xoffset;
15944 int gd_ya = gfx->src_y + gfx->active_yoffset;
15945 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15946 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15947 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15948 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15951 if (gfx->bitmap == NULL)
15953 game_gadget[id] = NULL;
15958 if (id == GAME_CTRL_ID_STOP ||
15959 id == GAME_CTRL_ID_PANEL_STOP ||
15960 id == GAME_CTRL_ID_TOUCH_STOP ||
15961 id == GAME_CTRL_ID_PLAY ||
15962 id == GAME_CTRL_ID_PANEL_PLAY ||
15963 id == GAME_CTRL_ID_SAVE ||
15964 id == GAME_CTRL_ID_LOAD)
15966 button_type = GD_TYPE_NORMAL_BUTTON;
15968 event_mask = GD_EVENT_RELEASED;
15970 else if (id == GAME_CTRL_ID_UNDO ||
15971 id == GAME_CTRL_ID_REDO)
15973 button_type = GD_TYPE_NORMAL_BUTTON;
15975 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15979 button_type = GD_TYPE_CHECK_BUTTON;
15980 checked = (gamebutton_info[i].setup_value != NULL ?
15981 *gamebutton_info[i].setup_value : FALSE);
15982 event_mask = GD_EVENT_PRESSED;
15985 gi = CreateGadget(GDI_CUSTOM_ID, id,
15986 GDI_IMAGE_ID, graphic,
15987 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15990 GDI_WIDTH, gfx->width,
15991 GDI_HEIGHT, gfx->height,
15992 GDI_TYPE, button_type,
15993 GDI_STATE, GD_BUTTON_UNPRESSED,
15994 GDI_CHECKED, checked,
15995 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15996 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15997 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15998 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15999 GDI_DIRECT_DRAW, FALSE,
16000 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16001 GDI_EVENT_MASK, event_mask,
16002 GDI_CALLBACK_ACTION, HandleGameButtons,
16006 Fail("cannot create gadget");
16008 game_gadget[id] = gi;
16012 void FreeGameButtons(void)
16016 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16017 FreeGadget(game_gadget[i]);
16020 static void UnmapGameButtonsAtSamePosition(int id)
16024 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16026 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16027 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16028 UnmapGadget(game_gadget[i]);
16031 static void UnmapGameButtonsAtSamePosition_All(void)
16033 if (setup.show_snapshot_buttons)
16035 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16036 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16037 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16041 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16042 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16043 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16045 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16046 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16047 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16051 static void MapGameButtonsAtSamePosition(int id)
16055 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16057 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16058 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16059 MapGadget(game_gadget[i]);
16061 UnmapGameButtonsAtSamePosition_All();
16064 void MapUndoRedoButtons(void)
16066 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16067 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16069 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16070 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16073 void UnmapUndoRedoButtons(void)
16075 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16076 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16078 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16079 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16082 void ModifyPauseButtons(void)
16086 GAME_CTRL_ID_PAUSE,
16087 GAME_CTRL_ID_PAUSE2,
16088 GAME_CTRL_ID_PANEL_PAUSE,
16089 GAME_CTRL_ID_TOUCH_PAUSE,
16094 for (i = 0; ids[i] > -1; i++)
16095 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16098 static void MapGameButtonsExt(boolean on_tape)
16102 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16103 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16104 i != GAME_CTRL_ID_UNDO &&
16105 i != GAME_CTRL_ID_REDO)
16106 MapGadget(game_gadget[i]);
16108 UnmapGameButtonsAtSamePosition_All();
16110 RedrawGameButtons();
16113 static void UnmapGameButtonsExt(boolean on_tape)
16117 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16118 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16119 UnmapGadget(game_gadget[i]);
16122 static void RedrawGameButtonsExt(boolean on_tape)
16126 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16127 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16128 RedrawGadget(game_gadget[i]);
16131 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16136 gi->checked = state;
16139 static void RedrawSoundButtonGadget(int id)
16141 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16142 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16143 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16144 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16145 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16146 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16149 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16150 RedrawGadget(game_gadget[id2]);
16153 void MapGameButtons(void)
16155 MapGameButtonsExt(FALSE);
16158 void UnmapGameButtons(void)
16160 UnmapGameButtonsExt(FALSE);
16163 void RedrawGameButtons(void)
16165 RedrawGameButtonsExt(FALSE);
16168 void MapGameButtonsOnTape(void)
16170 MapGameButtonsExt(TRUE);
16173 void UnmapGameButtonsOnTape(void)
16175 UnmapGameButtonsExt(TRUE);
16178 void RedrawGameButtonsOnTape(void)
16180 RedrawGameButtonsExt(TRUE);
16183 static void GameUndoRedoExt(void)
16185 ClearPlayerAction();
16187 tape.pausing = TRUE;
16190 UpdateAndDisplayGameControlValues();
16192 DrawCompleteVideoDisplay();
16193 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16194 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16195 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16200 static void GameUndo(int steps)
16202 if (!CheckEngineSnapshotList())
16205 LoadEngineSnapshot_Undo(steps);
16210 static void GameRedo(int steps)
16212 if (!CheckEngineSnapshotList())
16215 LoadEngineSnapshot_Redo(steps);
16220 static void HandleGameButtonsExt(int id, int button)
16222 static boolean game_undo_executed = FALSE;
16223 int steps = BUTTON_STEPSIZE(button);
16224 boolean handle_game_buttons =
16225 (game_status == GAME_MODE_PLAYING ||
16226 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16228 if (!handle_game_buttons)
16233 case GAME_CTRL_ID_STOP:
16234 case GAME_CTRL_ID_PANEL_STOP:
16235 case GAME_CTRL_ID_TOUCH_STOP:
16236 if (game_status == GAME_MODE_MAIN)
16242 RequestQuitGame(TRUE);
16246 case GAME_CTRL_ID_PAUSE:
16247 case GAME_CTRL_ID_PAUSE2:
16248 case GAME_CTRL_ID_PANEL_PAUSE:
16249 case GAME_CTRL_ID_TOUCH_PAUSE:
16250 if (network.enabled && game_status == GAME_MODE_PLAYING)
16253 SendToServer_ContinuePlaying();
16255 SendToServer_PausePlaying();
16258 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16260 game_undo_executed = FALSE;
16264 case GAME_CTRL_ID_PLAY:
16265 case GAME_CTRL_ID_PANEL_PLAY:
16266 if (game_status == GAME_MODE_MAIN)
16268 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16270 else if (tape.pausing)
16272 if (network.enabled)
16273 SendToServer_ContinuePlaying();
16275 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16279 case GAME_CTRL_ID_UNDO:
16280 // Important: When using "save snapshot when collecting an item" mode,
16281 // load last (current) snapshot for first "undo" after pressing "pause"
16282 // (else the last-but-one snapshot would be loaded, because the snapshot
16283 // pointer already points to the last snapshot when pressing "pause",
16284 // which is fine for "every step/move" mode, but not for "every collect")
16285 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16286 !game_undo_executed)
16289 game_undo_executed = TRUE;
16294 case GAME_CTRL_ID_REDO:
16298 case GAME_CTRL_ID_SAVE:
16302 case GAME_CTRL_ID_LOAD:
16306 case SOUND_CTRL_ID_MUSIC:
16307 case SOUND_CTRL_ID_PANEL_MUSIC:
16308 if (setup.sound_music)
16310 setup.sound_music = FALSE;
16314 else if (audio.music_available)
16316 setup.sound = setup.sound_music = TRUE;
16318 SetAudioMode(setup.sound);
16320 if (game_status == GAME_MODE_PLAYING)
16324 RedrawSoundButtonGadget(id);
16328 case SOUND_CTRL_ID_LOOPS:
16329 case SOUND_CTRL_ID_PANEL_LOOPS:
16330 if (setup.sound_loops)
16331 setup.sound_loops = FALSE;
16332 else if (audio.loops_available)
16334 setup.sound = setup.sound_loops = TRUE;
16336 SetAudioMode(setup.sound);
16339 RedrawSoundButtonGadget(id);
16343 case SOUND_CTRL_ID_SIMPLE:
16344 case SOUND_CTRL_ID_PANEL_SIMPLE:
16345 if (setup.sound_simple)
16346 setup.sound_simple = FALSE;
16347 else if (audio.sound_available)
16349 setup.sound = setup.sound_simple = TRUE;
16351 SetAudioMode(setup.sound);
16354 RedrawSoundButtonGadget(id);
16363 static void HandleGameButtons(struct GadgetInfo *gi)
16365 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16368 void HandleSoundButtonKeys(Key key)
16370 if (key == setup.shortcut.sound_simple)
16371 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16372 else if (key == setup.shortcut.sound_loops)
16373 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16374 else if (key == setup.shortcut.sound_music)
16375 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);