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 struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Tile[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Tile[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Tile[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Tile[x][y];
1771 #if DEBUG_INIT_PLAYER
1772 Debug("game:init:player", "- player element %d activated",
1773 player->element_nr);
1774 Debug("game:init:player", " (local player is %d and currently %s)",
1775 local_player->element_nr,
1776 local_player->active ? "active" : "not active");
1780 Tile[x][y] = EL_EMPTY;
1782 player->jx = player->last_jx = x;
1783 player->jy = player->last_jy = y;
1786 // always check if player was just killed and should be reanimated
1788 int player_nr = GET_PLAYER_NR(element);
1789 struct PlayerInfo *player = &stored_player[player_nr];
1791 if (player->active && player->killed)
1792 player->reanimated = TRUE; // if player was just killed, reanimate him
1796 static void InitField(int x, int y, boolean init_game)
1798 int element = Tile[x][y];
1807 InitPlayerField(x, y, element, init_game);
1810 case EL_SOKOBAN_FIELD_PLAYER:
1811 element = Tile[x][y] = EL_PLAYER_1;
1812 InitField(x, y, init_game);
1814 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1815 InitField(x, y, init_game);
1818 case EL_SOKOBAN_FIELD_EMPTY:
1819 IncrementSokobanFieldsNeeded();
1822 case EL_SOKOBAN_OBJECT:
1823 IncrementSokobanObjectsNeeded();
1827 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1828 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1829 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1830 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1831 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1832 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1833 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1834 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1835 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1836 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1845 case EL_SPACESHIP_RIGHT:
1846 case EL_SPACESHIP_UP:
1847 case EL_SPACESHIP_LEFT:
1848 case EL_SPACESHIP_DOWN:
1849 case EL_BD_BUTTERFLY:
1850 case EL_BD_BUTTERFLY_RIGHT:
1851 case EL_BD_BUTTERFLY_UP:
1852 case EL_BD_BUTTERFLY_LEFT:
1853 case EL_BD_BUTTERFLY_DOWN:
1855 case EL_BD_FIREFLY_RIGHT:
1856 case EL_BD_FIREFLY_UP:
1857 case EL_BD_FIREFLY_LEFT:
1858 case EL_BD_FIREFLY_DOWN:
1859 case EL_PACMAN_RIGHT:
1861 case EL_PACMAN_LEFT:
1862 case EL_PACMAN_DOWN:
1864 case EL_YAMYAM_LEFT:
1865 case EL_YAMYAM_RIGHT:
1867 case EL_YAMYAM_DOWN:
1868 case EL_DARK_YAMYAM:
1871 case EL_SP_SNIKSNAK:
1872 case EL_SP_ELECTRON:
1878 case EL_SPRING_LEFT:
1879 case EL_SPRING_RIGHT:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Tile[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Tile[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Tile[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Tile[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Tile[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error("'game_panel_controls' structure corrupted at %d", i);
2152 Fail("this should not happen -- please debug");
2155 // force update of game controls after initialization
2156 gpc->value = gpc->last_value = -1;
2157 gpc->frame = gpc->last_frame = -1;
2158 gpc->gfx_frame = -1;
2160 // determine panel value width for later calculation of alignment
2161 if (type == TYPE_INTEGER || type == TYPE_STRING)
2163 pos->width = pos->size * getFontWidth(pos->font);
2164 pos->height = getFontHeight(pos->font);
2166 else if (type == TYPE_ELEMENT)
2168 pos->width = pos->size;
2169 pos->height = pos->size;
2172 // fill structure for game panel draw order
2174 gpo->sort_priority = pos->sort_priority;
2177 // sort game panel controls according to sort_priority and control number
2178 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2179 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2182 static void UpdatePlayfieldElementCount(void)
2184 boolean use_element_count = FALSE;
2187 // first check if it is needed at all to calculate playfield element count
2188 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2189 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2190 use_element_count = TRUE;
2192 if (!use_element_count)
2195 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2196 element_info[i].element_count = 0;
2198 SCAN_PLAYFIELD(x, y)
2200 element_info[Tile[x][y]].element_count++;
2203 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2204 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2205 if (IS_IN_GROUP(j, i))
2206 element_info[EL_GROUP_START + i].element_count +=
2207 element_info[j].element_count;
2210 static void UpdateGameControlValues(void)
2213 int time = (game.LevelSolved ?
2214 game.LevelSolved_CountingTime :
2215 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2217 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2218 game_sp.time_played :
2219 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2220 game_mm.energy_left :
2221 game.no_time_limit ? TimePlayed : TimeLeft);
2222 int score = (game.LevelSolved ?
2223 game.LevelSolved_CountingScore :
2224 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2225 game_em.lev->score :
2226 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2231 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2232 game_em.lev->gems_needed :
2233 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2234 game_sp.infotrons_still_needed :
2235 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2236 game_mm.kettles_still_needed :
2237 game.gems_still_needed);
2238 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2239 game_em.lev->gems_needed > 0 :
2240 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2241 game_sp.infotrons_still_needed > 0 :
2242 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2243 game_mm.kettles_still_needed > 0 ||
2244 game_mm.lights_still_needed > 0 :
2245 game.gems_still_needed > 0 ||
2246 game.sokoban_fields_still_needed > 0 ||
2247 game.sokoban_objects_still_needed > 0 ||
2248 game.lights_still_needed > 0);
2249 int health = (game.LevelSolved ?
2250 game.LevelSolved_CountingHealth :
2251 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 MM_HEALTH(game_mm.laser_overload_value) :
2255 UpdatePlayfieldElementCount();
2257 // update game panel control values
2259 // used instead of "level_nr" (for network games)
2260 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2261 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2263 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2264 for (i = 0; i < MAX_NUM_KEYS; i++)
2265 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2267 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2269 if (game.centered_player_nr == -1)
2271 for (i = 0; i < MAX_PLAYERS; i++)
2273 // only one player in Supaplex game engine
2274 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2277 for (k = 0; k < MAX_NUM_KEYS; k++)
2279 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2281 if (game_em.ply[i]->keys & (1 << k))
2282 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2283 get_key_element_from_nr(k);
2285 else if (stored_player[i].key[k])
2286 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2287 get_key_element_from_nr(k);
2290 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2291 getPlayerInventorySize(i);
2293 if (stored_player[i].num_white_keys > 0)
2294 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2297 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2298 stored_player[i].num_white_keys;
2303 int player_nr = game.centered_player_nr;
2305 for (k = 0; k < MAX_NUM_KEYS; k++)
2307 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2309 if (game_em.ply[player_nr]->keys & (1 << k))
2310 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2311 get_key_element_from_nr(k);
2313 else if (stored_player[player_nr].key[k])
2314 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2315 get_key_element_from_nr(k);
2318 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2319 getPlayerInventorySize(player_nr);
2321 if (stored_player[player_nr].num_white_keys > 0)
2322 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2324 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2325 stored_player[player_nr].num_white_keys;
2328 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2330 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2331 get_inventory_element_from_pos(local_player, i);
2332 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2333 get_inventory_element_from_pos(local_player, -i - 1);
2336 game_panel_controls[GAME_PANEL_SCORE].value = score;
2337 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2339 game_panel_controls[GAME_PANEL_TIME].value = time;
2341 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2342 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2343 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2345 if (level.time == 0)
2346 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2348 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2350 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2351 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2353 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2355 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2356 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2358 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2359 local_player->shield_normal_time_left;
2360 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2361 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2363 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2364 local_player->shield_deadly_time_left;
2366 game_panel_controls[GAME_PANEL_EXIT].value =
2367 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2369 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2370 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2371 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2372 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2373 EL_EMC_MAGIC_BALL_SWITCH);
2375 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2376 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2377 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2378 game.light_time_left;
2380 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2381 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2382 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2383 game.timegate_time_left;
2385 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2386 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2388 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2389 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2390 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2391 game.lenses_time_left;
2393 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2394 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2395 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2396 game.magnify_time_left;
2398 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2399 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2400 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2401 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2402 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2403 EL_BALLOON_SWITCH_NONE);
2405 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2406 local_player->dynabomb_count;
2407 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2408 local_player->dynabomb_size;
2409 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2410 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2412 game_panel_controls[GAME_PANEL_PENGUINS].value =
2413 game.friends_still_needed;
2415 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2416 game.sokoban_objects_still_needed;
2417 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2418 game.sokoban_fields_still_needed;
2420 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2421 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2423 for (i = 0; i < NUM_BELTS; i++)
2425 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2426 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2427 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2428 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2429 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2432 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2433 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2434 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2435 game.magic_wall_time_left;
2437 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2438 local_player->gravity;
2440 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2441 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2443 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2444 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2445 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2446 game.panel.element[i].id : EL_UNDEFINED);
2448 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2449 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2450 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2451 element_info[game.panel.element_count[i].id].element_count : 0);
2453 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2454 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2455 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2456 element_info[game.panel.ce_score[i].id].collect_score : 0);
2458 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2459 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2460 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2461 element_info[game.panel.ce_score_element[i].id].collect_score :
2464 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2466 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2468 // update game panel control frames
2470 for (i = 0; game_panel_controls[i].nr != -1; i++)
2472 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2474 if (gpc->type == TYPE_ELEMENT)
2476 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2478 int last_anim_random_frame = gfx.anim_random_frame;
2479 int element = gpc->value;
2480 int graphic = el2panelimg(element);
2482 if (gpc->value != gpc->last_value)
2485 gpc->gfx_random = INIT_GFX_RANDOM();
2491 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2492 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2493 gpc->gfx_random = INIT_GFX_RANDOM();
2496 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2497 gfx.anim_random_frame = gpc->gfx_random;
2499 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2500 gpc->gfx_frame = element_info[element].collect_score;
2502 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2505 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2506 gfx.anim_random_frame = last_anim_random_frame;
2509 else if (gpc->type == TYPE_GRAPHIC)
2511 if (gpc->graphic != IMG_UNDEFINED)
2513 int last_anim_random_frame = gfx.anim_random_frame;
2514 int graphic = gpc->graphic;
2516 if (gpc->value != gpc->last_value)
2519 gpc->gfx_random = INIT_GFX_RANDOM();
2525 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2526 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2527 gpc->gfx_random = INIT_GFX_RANDOM();
2530 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2531 gfx.anim_random_frame = gpc->gfx_random;
2533 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2535 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2536 gfx.anim_random_frame = last_anim_random_frame;
2542 static void DisplayGameControlValues(void)
2544 boolean redraw_panel = FALSE;
2547 for (i = 0; game_panel_controls[i].nr != -1; i++)
2549 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2551 if (PANEL_DEACTIVATED(gpc->pos))
2554 if (gpc->value == gpc->last_value &&
2555 gpc->frame == gpc->last_frame)
2558 redraw_panel = TRUE;
2564 // copy default game door content to main double buffer
2566 // !!! CHECK AGAIN !!!
2567 SetPanelBackground();
2568 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2569 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2571 // redraw game control buttons
2572 RedrawGameButtons();
2574 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2576 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2578 int nr = game_panel_order[i].nr;
2579 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2580 struct TextPosInfo *pos = gpc->pos;
2581 int type = gpc->type;
2582 int value = gpc->value;
2583 int frame = gpc->frame;
2584 int size = pos->size;
2585 int font = pos->font;
2586 boolean draw_masked = pos->draw_masked;
2587 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2589 if (PANEL_DEACTIVATED(pos))
2592 gpc->last_value = value;
2593 gpc->last_frame = frame;
2595 if (type == TYPE_INTEGER)
2597 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2598 nr == GAME_PANEL_TIME)
2600 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2602 if (use_dynamic_size) // use dynamic number of digits
2604 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2605 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2606 int size2 = size1 + 1;
2607 int font1 = pos->font;
2608 int font2 = pos->font_alt;
2610 size = (value < value_change ? size1 : size2);
2611 font = (value < value_change ? font1 : font2);
2615 // correct text size if "digits" is zero or less
2617 size = strlen(int2str(value, size));
2619 // dynamically correct text alignment
2620 pos->width = size * getFontWidth(font);
2622 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2623 int2str(value, size), font, mask_mode);
2625 else if (type == TYPE_ELEMENT)
2627 int element, graphic;
2631 int dst_x = PANEL_XPOS(pos);
2632 int dst_y = PANEL_YPOS(pos);
2634 if (value != EL_UNDEFINED && value != EL_EMPTY)
2637 graphic = el2panelimg(value);
2640 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2641 element, EL_NAME(element), size);
2644 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2647 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2650 width = graphic_info[graphic].width * size / TILESIZE;
2651 height = graphic_info[graphic].height * size / TILESIZE;
2654 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2657 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2661 else if (type == TYPE_GRAPHIC)
2663 int graphic = gpc->graphic;
2664 int graphic_active = gpc->graphic_active;
2668 int dst_x = PANEL_XPOS(pos);
2669 int dst_y = PANEL_YPOS(pos);
2670 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2671 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2673 if (graphic != IMG_UNDEFINED && !skip)
2675 if (pos->style == STYLE_REVERSE)
2676 value = 100 - value;
2678 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2680 if (pos->direction & MV_HORIZONTAL)
2682 width = graphic_info[graphic_active].width * value / 100;
2683 height = graphic_info[graphic_active].height;
2685 if (pos->direction == MV_LEFT)
2687 src_x += graphic_info[graphic_active].width - width;
2688 dst_x += graphic_info[graphic_active].width - width;
2693 width = graphic_info[graphic_active].width;
2694 height = graphic_info[graphic_active].height * value / 100;
2696 if (pos->direction == MV_UP)
2698 src_y += graphic_info[graphic_active].height - height;
2699 dst_y += graphic_info[graphic_active].height - height;
2704 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2707 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2710 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2712 if (pos->direction & MV_HORIZONTAL)
2714 if (pos->direction == MV_RIGHT)
2721 dst_x = PANEL_XPOS(pos);
2724 width = graphic_info[graphic].width - width;
2728 if (pos->direction == MV_DOWN)
2735 dst_y = PANEL_YPOS(pos);
2738 height = graphic_info[graphic].height - height;
2742 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2745 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2749 else if (type == TYPE_STRING)
2751 boolean active = (value != 0);
2752 char *state_normal = "off";
2753 char *state_active = "on";
2754 char *state = (active ? state_active : state_normal);
2755 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2756 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2757 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2758 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2760 if (nr == GAME_PANEL_GRAVITY_STATE)
2762 int font1 = pos->font; // (used for normal state)
2763 int font2 = pos->font_alt; // (used for active state)
2765 font = (active ? font2 : font1);
2774 // don't truncate output if "chars" is zero or less
2777 // dynamically correct text alignment
2778 pos->width = size * getFontWidth(font);
2781 s_cut = getStringCopyN(s, size);
2783 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2784 s_cut, font, mask_mode);
2790 redraw_mask |= REDRAW_DOOR_1;
2793 SetGameStatus(GAME_MODE_PLAYING);
2796 void UpdateAndDisplayGameControlValues(void)
2798 if (tape.deactivate_display)
2801 UpdateGameControlValues();
2802 DisplayGameControlValues();
2806 static void UpdateGameDoorValues(void)
2808 UpdateGameControlValues();
2812 void DrawGameDoorValues(void)
2814 DisplayGameControlValues();
2818 // ============================================================================
2820 // ----------------------------------------------------------------------------
2821 // initialize game engine due to level / tape version number
2822 // ============================================================================
2824 static void InitGameEngine(void)
2826 int i, j, k, l, x, y;
2828 // set game engine from tape file when re-playing, else from level file
2829 game.engine_version = (tape.playing ? tape.engine_version :
2830 level.game_version);
2832 // set single or multi-player game mode (needed for re-playing tapes)
2833 game.team_mode = setup.team_mode;
2837 int num_players = 0;
2839 for (i = 0; i < MAX_PLAYERS; i++)
2840 if (tape.player_participates[i])
2843 // multi-player tapes contain input data for more than one player
2844 game.team_mode = (num_players > 1);
2848 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2849 level.game_version);
2850 Debug("game:init:level", " tape.file_version == %06d",
2852 Debug("game:init:level", " tape.game_version == %06d",
2854 Debug("game:init:level", " tape.engine_version == %06d",
2855 tape.engine_version);
2856 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2857 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2860 // --------------------------------------------------------------------------
2861 // set flags for bugs and changes according to active game engine version
2862 // --------------------------------------------------------------------------
2866 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2868 Bug was introduced in version:
2871 Bug was fixed in version:
2875 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2876 but the property "can fall" was missing, which caused some levels to be
2877 unsolvable. This was fixed in version 4.2.0.0.
2879 Affected levels/tapes:
2880 An example for a tape that was fixed by this bugfix is tape 029 from the
2881 level set "rnd_sam_bateman".
2882 The wrong behaviour will still be used for all levels or tapes that were
2883 created/recorded with it. An example for this is tape 023 from the level
2884 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2887 boolean use_amoeba_dropping_cannot_fall_bug =
2888 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2889 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2891 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2892 tape.game_version < VERSION_IDENT(4,2,0,0)));
2895 Summary of bugfix/change:
2896 Fixed move speed of elements entering or leaving magic wall.
2898 Fixed/changed in version:
2902 Before 2.0.1, move speed of elements entering or leaving magic wall was
2903 twice as fast as it is now.
2904 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2906 Affected levels/tapes:
2907 The first condition is generally needed for all levels/tapes before version
2908 2.0.1, which might use the old behaviour before it was changed; known tapes
2909 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2910 The second condition is an exception from the above case and is needed for
2911 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2912 above, but before it was known that this change would break tapes like the
2913 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2914 although the engine version while recording maybe was before 2.0.1. There
2915 are a lot of tapes that are affected by this exception, like tape 006 from
2916 the level set "rnd_conor_mancone".
2919 boolean use_old_move_stepsize_for_magic_wall =
2920 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2922 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2923 tape.game_version < VERSION_IDENT(4,2,0,0)));
2926 Summary of bugfix/change:
2927 Fixed handling for custom elements that change when pushed by the player.
2929 Fixed/changed in version:
2933 Before 3.1.0, custom elements that "change when pushing" changed directly
2934 after the player started pushing them (until then handled in "DigField()").
2935 Since 3.1.0, these custom elements are not changed until the "pushing"
2936 move of the element is finished (now handled in "ContinueMoving()").
2938 Affected levels/tapes:
2939 The first condition is generally needed for all levels/tapes before version
2940 3.1.0, which might use the old behaviour before it was changed; known tapes
2941 that are affected are some tapes from the level set "Walpurgis Gardens" by
2943 The second condition is an exception from the above case and is needed for
2944 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2945 above (including some development versions of 3.1.0), but before it was
2946 known that this change would break tapes like the above and was fixed in
2947 3.1.1, so that the changed behaviour was active although the engine version
2948 while recording maybe was before 3.1.0. There is at least one tape that is
2949 affected by this exception, which is the tape for the one-level set "Bug
2950 Machine" by Juergen Bonhagen.
2953 game.use_change_when_pushing_bug =
2954 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2956 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2957 tape.game_version < VERSION_IDENT(3,1,1,0)));
2960 Summary of bugfix/change:
2961 Fixed handling for blocking the field the player leaves when moving.
2963 Fixed/changed in version:
2967 Before 3.1.1, when "block last field when moving" was enabled, the field
2968 the player is leaving when moving was blocked for the time of the move,
2969 and was directly unblocked afterwards. This resulted in the last field
2970 being blocked for exactly one less than the number of frames of one player
2971 move. Additionally, even when blocking was disabled, the last field was
2972 blocked for exactly one frame.
2973 Since 3.1.1, due to changes in player movement handling, the last field
2974 is not blocked at all when blocking is disabled. When blocking is enabled,
2975 the last field is blocked for exactly the number of frames of one player
2976 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2977 last field is blocked for exactly one more than the number of frames of
2980 Affected levels/tapes:
2981 (!!! yet to be determined -- probably many !!!)
2984 game.use_block_last_field_bug =
2985 (game.engine_version < VERSION_IDENT(3,1,1,0));
2987 /* various special flags and settings for native Emerald Mine game engine */
2989 game_em.use_single_button =
2990 (game.engine_version > VERSION_IDENT(4,0,0,2));
2992 game_em.use_snap_key_bug =
2993 (game.engine_version < VERSION_IDENT(4,0,1,0));
2995 game_em.use_random_bug =
2996 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
2998 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3000 game_em.use_old_explosions = use_old_em_engine;
3001 game_em.use_old_android = use_old_em_engine;
3002 game_em.use_old_push_elements = use_old_em_engine;
3003 game_em.use_old_push_into_acid = use_old_em_engine;
3005 game_em.use_wrap_around = !use_old_em_engine;
3007 // --------------------------------------------------------------------------
3009 // set maximal allowed number of custom element changes per game frame
3010 game.max_num_changes_per_frame = 1;
3012 // default scan direction: scan playfield from top/left to bottom/right
3013 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3015 // dynamically adjust element properties according to game engine version
3016 InitElementPropertiesEngine(game.engine_version);
3018 // ---------- initialize special element properties -------------------------
3020 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3021 if (use_amoeba_dropping_cannot_fall_bug)
3022 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3024 // ---------- initialize player's initial move delay ------------------------
3026 // dynamically adjust player properties according to level information
3027 for (i = 0; i < MAX_PLAYERS; i++)
3028 game.initial_move_delay_value[i] =
3029 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3031 // dynamically adjust player properties according to game engine version
3032 for (i = 0; i < MAX_PLAYERS; i++)
3033 game.initial_move_delay[i] =
3034 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3035 game.initial_move_delay_value[i] : 0);
3037 // ---------- initialize player's initial push delay ------------------------
3039 // dynamically adjust player properties according to game engine version
3040 game.initial_push_delay_value =
3041 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3043 // ---------- initialize changing elements ----------------------------------
3045 // initialize changing elements information
3046 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3048 struct ElementInfo *ei = &element_info[i];
3050 // this pointer might have been changed in the level editor
3051 ei->change = &ei->change_page[0];
3053 if (!IS_CUSTOM_ELEMENT(i))
3055 ei->change->target_element = EL_EMPTY_SPACE;
3056 ei->change->delay_fixed = 0;
3057 ei->change->delay_random = 0;
3058 ei->change->delay_frames = 1;
3061 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3063 ei->has_change_event[j] = FALSE;
3065 ei->event_page_nr[j] = 0;
3066 ei->event_page[j] = &ei->change_page[0];
3070 // add changing elements from pre-defined list
3071 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3073 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3074 struct ElementInfo *ei = &element_info[ch_delay->element];
3076 ei->change->target_element = ch_delay->target_element;
3077 ei->change->delay_fixed = ch_delay->change_delay;
3079 ei->change->pre_change_function = ch_delay->pre_change_function;
3080 ei->change->change_function = ch_delay->change_function;
3081 ei->change->post_change_function = ch_delay->post_change_function;
3083 ei->change->can_change = TRUE;
3084 ei->change->can_change_or_has_action = TRUE;
3086 ei->has_change_event[CE_DELAY] = TRUE;
3088 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3089 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3092 // ---------- initialize internal run-time variables ------------------------
3094 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3096 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3098 for (j = 0; j < ei->num_change_pages; j++)
3100 ei->change_page[j].can_change_or_has_action =
3101 (ei->change_page[j].can_change |
3102 ei->change_page[j].has_action);
3106 // add change events from custom element configuration
3107 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3109 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3111 for (j = 0; j < ei->num_change_pages; j++)
3113 if (!ei->change_page[j].can_change_or_has_action)
3116 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3118 // only add event page for the first page found with this event
3119 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3121 ei->has_change_event[k] = TRUE;
3123 ei->event_page_nr[k] = j;
3124 ei->event_page[k] = &ei->change_page[j];
3130 // ---------- initialize reference elements in change conditions ------------
3132 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3134 int element = EL_CUSTOM_START + i;
3135 struct ElementInfo *ei = &element_info[element];
3137 for (j = 0; j < ei->num_change_pages; j++)
3139 int trigger_element = ei->change_page[j].initial_trigger_element;
3141 if (trigger_element >= EL_PREV_CE_8 &&
3142 trigger_element <= EL_NEXT_CE_8)
3143 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3145 ei->change_page[j].trigger_element = trigger_element;
3149 // ---------- initialize run-time trigger player and element ----------------
3151 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3153 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3155 for (j = 0; j < ei->num_change_pages; j++)
3157 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3158 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3159 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3160 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3161 ei->change_page[j].actual_trigger_ce_value = 0;
3162 ei->change_page[j].actual_trigger_ce_score = 0;
3166 // ---------- initialize trigger events -------------------------------------
3168 // initialize trigger events information
3169 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3170 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3171 trigger_events[i][j] = FALSE;
3173 // add trigger events from element change event properties
3174 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3176 struct ElementInfo *ei = &element_info[i];
3178 for (j = 0; j < ei->num_change_pages; j++)
3180 if (!ei->change_page[j].can_change_or_has_action)
3183 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3185 int trigger_element = ei->change_page[j].trigger_element;
3187 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3189 if (ei->change_page[j].has_event[k])
3191 if (IS_GROUP_ELEMENT(trigger_element))
3193 struct ElementGroupInfo *group =
3194 element_info[trigger_element].group;
3196 for (l = 0; l < group->num_elements_resolved; l++)
3197 trigger_events[group->element_resolved[l]][k] = TRUE;
3199 else if (trigger_element == EL_ANY_ELEMENT)
3200 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3201 trigger_events[l][k] = TRUE;
3203 trigger_events[trigger_element][k] = TRUE;
3210 // ---------- initialize push delay -----------------------------------------
3212 // initialize push delay values to default
3213 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3215 if (!IS_CUSTOM_ELEMENT(i))
3217 // set default push delay values (corrected since version 3.0.7-1)
3218 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3220 element_info[i].push_delay_fixed = 2;
3221 element_info[i].push_delay_random = 8;
3225 element_info[i].push_delay_fixed = 8;
3226 element_info[i].push_delay_random = 8;
3231 // set push delay value for certain elements from pre-defined list
3232 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3234 int e = push_delay_list[i].element;
3236 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3237 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3240 // set push delay value for Supaplex elements for newer engine versions
3241 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3243 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3245 if (IS_SP_ELEMENT(i))
3247 // set SP push delay to just enough to push under a falling zonk
3248 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3250 element_info[i].push_delay_fixed = delay;
3251 element_info[i].push_delay_random = 0;
3256 // ---------- initialize move stepsize --------------------------------------
3258 // initialize move stepsize values to default
3259 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3260 if (!IS_CUSTOM_ELEMENT(i))
3261 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3263 // set move stepsize value for certain elements from pre-defined list
3264 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3266 int e = move_stepsize_list[i].element;
3268 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3270 // set move stepsize value for certain elements for older engine versions
3271 if (use_old_move_stepsize_for_magic_wall)
3273 if (e == EL_MAGIC_WALL_FILLING ||
3274 e == EL_MAGIC_WALL_EMPTYING ||
3275 e == EL_BD_MAGIC_WALL_FILLING ||
3276 e == EL_BD_MAGIC_WALL_EMPTYING)
3277 element_info[e].move_stepsize *= 2;
3281 // ---------- initialize collect score --------------------------------------
3283 // initialize collect score values for custom elements from initial value
3284 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3285 if (IS_CUSTOM_ELEMENT(i))
3286 element_info[i].collect_score = element_info[i].collect_score_initial;
3288 // ---------- initialize collect count --------------------------------------
3290 // initialize collect count values for non-custom elements
3291 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3292 if (!IS_CUSTOM_ELEMENT(i))
3293 element_info[i].collect_count_initial = 0;
3295 // add collect count values for all elements from pre-defined list
3296 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3297 element_info[collect_count_list[i].element].collect_count_initial =
3298 collect_count_list[i].count;
3300 // ---------- initialize access direction -----------------------------------
3302 // initialize access direction values to default (access from every side)
3303 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3304 if (!IS_CUSTOM_ELEMENT(i))
3305 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3307 // set access direction value for certain elements from pre-defined list
3308 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3309 element_info[access_direction_list[i].element].access_direction =
3310 access_direction_list[i].direction;
3312 // ---------- initialize explosion content ----------------------------------
3313 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3315 if (IS_CUSTOM_ELEMENT(i))
3318 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3320 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3322 element_info[i].content.e[x][y] =
3323 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3324 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3325 i == EL_PLAYER_3 ? EL_EMERALD :
3326 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3327 i == EL_MOLE ? EL_EMERALD_RED :
3328 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3329 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3330 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3331 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3332 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3333 i == EL_WALL_EMERALD ? EL_EMERALD :
3334 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3335 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3336 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3337 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3338 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3339 i == EL_WALL_PEARL ? EL_PEARL :
3340 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3345 // ---------- initialize recursion detection --------------------------------
3346 recursion_loop_depth = 0;
3347 recursion_loop_detected = FALSE;
3348 recursion_loop_element = EL_UNDEFINED;
3350 // ---------- initialize graphics engine ------------------------------------
3351 game.scroll_delay_value =
3352 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3353 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3354 !setup.forced_scroll_delay ? 0 :
3355 setup.scroll_delay ? setup.scroll_delay_value : 0);
3356 game.scroll_delay_value =
3357 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3359 // ---------- initialize game engine snapshots ------------------------------
3360 for (i = 0; i < MAX_PLAYERS; i++)
3361 game.snapshot.last_action[i] = 0;
3362 game.snapshot.changed_action = FALSE;
3363 game.snapshot.collected_item = FALSE;
3364 game.snapshot.mode =
3365 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3366 SNAPSHOT_MODE_EVERY_STEP :
3367 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3368 SNAPSHOT_MODE_EVERY_MOVE :
3369 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3370 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3371 game.snapshot.save_snapshot = FALSE;
3373 // ---------- initialize level time for Supaplex engine ---------------------
3374 // Supaplex levels with time limit currently unsupported -- should be added
3375 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3378 // ---------- initialize flags for handling game actions --------------------
3380 // set flags for game actions to default values
3381 game.use_key_actions = TRUE;
3382 game.use_mouse_actions = FALSE;
3384 // when using Mirror Magic game engine, handle mouse events only
3385 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3387 game.use_key_actions = FALSE;
3388 game.use_mouse_actions = TRUE;
3391 // check for custom elements with mouse click events
3392 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3394 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3396 int element = EL_CUSTOM_START + i;
3398 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3399 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3400 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3401 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3402 game.use_mouse_actions = TRUE;
3407 static int get_num_special_action(int element, int action_first,
3410 int num_special_action = 0;
3413 for (i = action_first; i <= action_last; i++)
3415 boolean found = FALSE;
3417 for (j = 0; j < NUM_DIRECTIONS; j++)
3418 if (el_act_dir2img(element, i, j) !=
3419 el_act_dir2img(element, ACTION_DEFAULT, j))
3423 num_special_action++;
3428 return num_special_action;
3432 // ============================================================================
3434 // ----------------------------------------------------------------------------
3435 // initialize and start new game
3436 // ============================================================================
3438 #if DEBUG_INIT_PLAYER
3439 static void DebugPrintPlayerStatus(char *message)
3446 Debug("game:init:player", "%s:", message);
3448 for (i = 0; i < MAX_PLAYERS; i++)
3450 struct PlayerInfo *player = &stored_player[i];
3452 Debug("game:init:player",
3453 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3457 player->connected_locally,
3458 player->connected_network,
3460 (local_player == player ? " (local player)" : ""));
3467 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3468 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3469 int fade_mask = REDRAW_FIELD;
3471 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3472 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3473 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3474 int initial_move_dir = MV_DOWN;
3477 // required here to update video display before fading (FIX THIS)
3478 DrawMaskedBorder(REDRAW_DOOR_2);
3480 if (!game.restart_level)
3481 CloseDoor(DOOR_CLOSE_1);
3483 SetGameStatus(GAME_MODE_PLAYING);
3485 if (level_editor_test_game)
3486 FadeSkipNextFadeOut();
3488 FadeSetEnterScreen();
3491 fade_mask = REDRAW_ALL;
3493 FadeLevelSoundsAndMusic();
3495 ExpireSoundLoops(TRUE);
3499 if (level_editor_test_game)
3500 FadeSkipNextFadeIn();
3502 // needed if different viewport properties defined for playing
3503 ChangeViewportPropertiesIfNeeded();
3507 DrawCompleteVideoDisplay();
3509 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3512 InitGameControlValues();
3514 // initialize tape actions from game when recording tape
3517 tape.use_key_actions = game.use_key_actions;
3518 tape.use_mouse_actions = game.use_mouse_actions;
3521 // don't play tapes over network
3522 network_playing = (network.enabled && !tape.playing);
3524 for (i = 0; i < MAX_PLAYERS; i++)
3526 struct PlayerInfo *player = &stored_player[i];
3528 player->index_nr = i;
3529 player->index_bit = (1 << i);
3530 player->element_nr = EL_PLAYER_1 + i;
3532 player->present = FALSE;
3533 player->active = FALSE;
3534 player->mapped = FALSE;
3536 player->killed = FALSE;
3537 player->reanimated = FALSE;
3538 player->buried = FALSE;
3541 player->effective_action = 0;
3542 player->programmed_action = 0;
3543 player->snap_action = 0;
3545 player->mouse_action.lx = 0;
3546 player->mouse_action.ly = 0;
3547 player->mouse_action.button = 0;
3548 player->mouse_action.button_hint = 0;
3550 player->effective_mouse_action.lx = 0;
3551 player->effective_mouse_action.ly = 0;
3552 player->effective_mouse_action.button = 0;
3553 player->effective_mouse_action.button_hint = 0;
3555 for (j = 0; j < MAX_NUM_KEYS; j++)
3556 player->key[j] = FALSE;
3558 player->num_white_keys = 0;
3560 player->dynabomb_count = 0;
3561 player->dynabomb_size = 1;
3562 player->dynabombs_left = 0;
3563 player->dynabomb_xl = FALSE;
3565 player->MovDir = initial_move_dir;
3568 player->GfxDir = initial_move_dir;
3569 player->GfxAction = ACTION_DEFAULT;
3571 player->StepFrame = 0;
3573 player->initial_element = player->element_nr;
3574 player->artwork_element =
3575 (level.use_artwork_element[i] ? level.artwork_element[i] :
3576 player->element_nr);
3577 player->use_murphy = FALSE;
3579 player->block_last_field = FALSE; // initialized in InitPlayerField()
3580 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3582 player->gravity = level.initial_player_gravity[i];
3584 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3586 player->actual_frame_counter = 0;
3588 player->step_counter = 0;
3590 player->last_move_dir = initial_move_dir;
3592 player->is_active = FALSE;
3594 player->is_waiting = FALSE;
3595 player->is_moving = FALSE;
3596 player->is_auto_moving = FALSE;
3597 player->is_digging = FALSE;
3598 player->is_snapping = FALSE;
3599 player->is_collecting = FALSE;
3600 player->is_pushing = FALSE;
3601 player->is_switching = FALSE;
3602 player->is_dropping = FALSE;
3603 player->is_dropping_pressed = FALSE;
3605 player->is_bored = FALSE;
3606 player->is_sleeping = FALSE;
3608 player->was_waiting = TRUE;
3609 player->was_moving = FALSE;
3610 player->was_snapping = FALSE;
3611 player->was_dropping = FALSE;
3613 player->force_dropping = FALSE;
3615 player->frame_counter_bored = -1;
3616 player->frame_counter_sleeping = -1;
3618 player->anim_delay_counter = 0;
3619 player->post_delay_counter = 0;
3621 player->dir_waiting = initial_move_dir;
3622 player->action_waiting = ACTION_DEFAULT;
3623 player->last_action_waiting = ACTION_DEFAULT;
3624 player->special_action_bored = ACTION_DEFAULT;
3625 player->special_action_sleeping = ACTION_DEFAULT;
3627 player->switch_x = -1;
3628 player->switch_y = -1;
3630 player->drop_x = -1;
3631 player->drop_y = -1;
3633 player->show_envelope = 0;
3635 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3637 player->push_delay = -1; // initialized when pushing starts
3638 player->push_delay_value = game.initial_push_delay_value;
3640 player->drop_delay = 0;
3641 player->drop_pressed_delay = 0;
3643 player->last_jx = -1;
3644 player->last_jy = -1;
3648 player->shield_normal_time_left = 0;
3649 player->shield_deadly_time_left = 0;
3651 player->inventory_infinite_element = EL_UNDEFINED;
3652 player->inventory_size = 0;
3654 if (level.use_initial_inventory[i])
3656 for (j = 0; j < level.initial_inventory_size[i]; j++)
3658 int element = level.initial_inventory_content[i][j];
3659 int collect_count = element_info[element].collect_count_initial;
3662 if (!IS_CUSTOM_ELEMENT(element))
3665 if (collect_count == 0)
3666 player->inventory_infinite_element = element;
3668 for (k = 0; k < collect_count; k++)
3669 if (player->inventory_size < MAX_INVENTORY_SIZE)
3670 player->inventory_element[player->inventory_size++] = element;
3674 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3675 SnapField(player, 0, 0);
3677 map_player_action[i] = i;
3680 network_player_action_received = FALSE;
3682 // initial null action
3683 if (network_playing)
3684 SendToServer_MovePlayer(MV_NONE);
3689 TimeLeft = level.time;
3692 ScreenMovDir = MV_NONE;
3696 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3698 game.robot_wheel_x = -1;
3699 game.robot_wheel_y = -1;
3704 game.all_players_gone = FALSE;
3706 game.LevelSolved = FALSE;
3707 game.GameOver = FALSE;
3709 game.GamePlayed = !tape.playing;
3711 game.LevelSolved_GameWon = FALSE;
3712 game.LevelSolved_GameEnd = FALSE;
3713 game.LevelSolved_SaveTape = FALSE;
3714 game.LevelSolved_SaveScore = FALSE;
3716 game.LevelSolved_CountingTime = 0;
3717 game.LevelSolved_CountingScore = 0;
3718 game.LevelSolved_CountingHealth = 0;
3720 game.panel.active = TRUE;
3722 game.no_time_limit = (level.time == 0);
3724 game.yamyam_content_nr = 0;
3725 game.robot_wheel_active = FALSE;
3726 game.magic_wall_active = FALSE;
3727 game.magic_wall_time_left = 0;
3728 game.light_time_left = 0;
3729 game.timegate_time_left = 0;
3730 game.switchgate_pos = 0;
3731 game.wind_direction = level.wind_direction_initial;
3734 game.score_final = 0;
3736 game.health = MAX_HEALTH;
3737 game.health_final = MAX_HEALTH;
3739 game.gems_still_needed = level.gems_needed;
3740 game.sokoban_fields_still_needed = 0;
3741 game.sokoban_objects_still_needed = 0;
3742 game.lights_still_needed = 0;
3743 game.players_still_needed = 0;
3744 game.friends_still_needed = 0;
3746 game.lenses_time_left = 0;
3747 game.magnify_time_left = 0;
3749 game.ball_active = level.ball_active_initial;
3750 game.ball_content_nr = 0;
3752 game.explosions_delayed = TRUE;
3754 game.envelope_active = FALSE;
3756 for (i = 0; i < NUM_BELTS; i++)
3758 game.belt_dir[i] = MV_NONE;
3759 game.belt_dir_nr[i] = 3; // not moving, next moving left
3762 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3763 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3765 #if DEBUG_INIT_PLAYER
3766 DebugPrintPlayerStatus("Player status at level initialization");
3769 SCAN_PLAYFIELD(x, y)
3771 Tile[x][y] = Last[x][y] = level.field[x][y];
3772 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3773 ChangeDelay[x][y] = 0;
3774 ChangePage[x][y] = -1;
3775 CustomValue[x][y] = 0; // initialized in InitField()
3776 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3778 WasJustMoving[x][y] = 0;
3779 WasJustFalling[x][y] = 0;
3780 CheckCollision[x][y] = 0;
3781 CheckImpact[x][y] = 0;
3783 Pushed[x][y] = FALSE;
3785 ChangeCount[x][y] = 0;
3786 ChangeEvent[x][y] = -1;
3788 ExplodePhase[x][y] = 0;
3789 ExplodeDelay[x][y] = 0;
3790 ExplodeField[x][y] = EX_TYPE_NONE;
3792 RunnerVisit[x][y] = 0;
3793 PlayerVisit[x][y] = 0;
3796 GfxRandom[x][y] = INIT_GFX_RANDOM();
3797 GfxElement[x][y] = EL_UNDEFINED;
3798 GfxAction[x][y] = ACTION_DEFAULT;
3799 GfxDir[x][y] = MV_NONE;
3800 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3803 SCAN_PLAYFIELD(x, y)
3805 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3807 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3809 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3812 InitField(x, y, TRUE);
3814 ResetGfxAnimation(x, y);
3819 for (i = 0; i < MAX_PLAYERS; i++)
3821 struct PlayerInfo *player = &stored_player[i];
3823 // set number of special actions for bored and sleeping animation
3824 player->num_special_action_bored =
3825 get_num_special_action(player->artwork_element,
3826 ACTION_BORING_1, ACTION_BORING_LAST);
3827 player->num_special_action_sleeping =
3828 get_num_special_action(player->artwork_element,
3829 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3832 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3833 emulate_sb ? EMU_SOKOBAN :
3834 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3836 // initialize type of slippery elements
3837 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3839 if (!IS_CUSTOM_ELEMENT(i))
3841 // default: elements slip down either to the left or right randomly
3842 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3844 // SP style elements prefer to slip down on the left side
3845 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3846 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3848 // BD style elements prefer to slip down on the left side
3849 if (game.emulation == EMU_BOULDERDASH)
3850 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3854 // initialize explosion and ignition delay
3855 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3857 if (!IS_CUSTOM_ELEMENT(i))
3860 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3861 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3862 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3863 int last_phase = (num_phase + 1) * delay;
3864 int half_phase = (num_phase / 2) * delay;
3866 element_info[i].explosion_delay = last_phase - 1;
3867 element_info[i].ignition_delay = half_phase;
3869 if (i == EL_BLACK_ORB)
3870 element_info[i].ignition_delay = 1;
3874 // correct non-moving belts to start moving left
3875 for (i = 0; i < NUM_BELTS; i++)
3876 if (game.belt_dir[i] == MV_NONE)
3877 game.belt_dir_nr[i] = 3; // not moving, next moving left
3879 #if USE_NEW_PLAYER_ASSIGNMENTS
3880 // use preferred player also in local single-player mode
3881 if (!network.enabled && !game.team_mode)
3883 int new_index_nr = setup.network_player_nr;
3885 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3887 for (i = 0; i < MAX_PLAYERS; i++)
3888 stored_player[i].connected_locally = FALSE;
3890 stored_player[new_index_nr].connected_locally = TRUE;
3894 for (i = 0; i < MAX_PLAYERS; i++)
3896 stored_player[i].connected = FALSE;
3898 // in network game mode, the local player might not be the first player
3899 if (stored_player[i].connected_locally)
3900 local_player = &stored_player[i];
3903 if (!network.enabled)
3904 local_player->connected = TRUE;
3908 for (i = 0; i < MAX_PLAYERS; i++)
3909 stored_player[i].connected = tape.player_participates[i];
3911 else if (network.enabled)
3913 // add team mode players connected over the network (needed for correct
3914 // assignment of player figures from level to locally playing players)
3916 for (i = 0; i < MAX_PLAYERS; i++)
3917 if (stored_player[i].connected_network)
3918 stored_player[i].connected = TRUE;
3920 else if (game.team_mode)
3922 // try to guess locally connected team mode players (needed for correct
3923 // assignment of player figures from level to locally playing players)
3925 for (i = 0; i < MAX_PLAYERS; i++)
3926 if (setup.input[i].use_joystick ||
3927 setup.input[i].key.left != KSYM_UNDEFINED)
3928 stored_player[i].connected = TRUE;
3931 #if DEBUG_INIT_PLAYER
3932 DebugPrintPlayerStatus("Player status after level initialization");
3935 #if DEBUG_INIT_PLAYER
3936 Debug("game:init:player", "Reassigning players ...");
3939 // check if any connected player was not found in playfield
3940 for (i = 0; i < MAX_PLAYERS; i++)
3942 struct PlayerInfo *player = &stored_player[i];
3944 if (player->connected && !player->present)
3946 struct PlayerInfo *field_player = NULL;
3948 #if DEBUG_INIT_PLAYER
3949 Debug("game:init:player",
3950 "- looking for field player for player %d ...", i + 1);
3953 // assign first free player found that is present in the playfield
3955 // first try: look for unmapped playfield player that is not connected
3956 for (j = 0; j < MAX_PLAYERS; j++)
3957 if (field_player == NULL &&
3958 stored_player[j].present &&
3959 !stored_player[j].mapped &&
3960 !stored_player[j].connected)
3961 field_player = &stored_player[j];
3963 // second try: look for *any* unmapped playfield player
3964 for (j = 0; j < MAX_PLAYERS; j++)
3965 if (field_player == NULL &&
3966 stored_player[j].present &&
3967 !stored_player[j].mapped)
3968 field_player = &stored_player[j];
3970 if (field_player != NULL)
3972 int jx = field_player->jx, jy = field_player->jy;
3974 #if DEBUG_INIT_PLAYER
3975 Debug("game:init:player", "- found player %d",
3976 field_player->index_nr + 1);
3979 player->present = FALSE;
3980 player->active = FALSE;
3982 field_player->present = TRUE;
3983 field_player->active = TRUE;
3986 player->initial_element = field_player->initial_element;
3987 player->artwork_element = field_player->artwork_element;
3989 player->block_last_field = field_player->block_last_field;
3990 player->block_delay_adjustment = field_player->block_delay_adjustment;
3993 StorePlayer[jx][jy] = field_player->element_nr;
3995 field_player->jx = field_player->last_jx = jx;
3996 field_player->jy = field_player->last_jy = jy;
3998 if (local_player == player)
3999 local_player = field_player;
4001 map_player_action[field_player->index_nr] = i;
4003 field_player->mapped = TRUE;
4005 #if DEBUG_INIT_PLAYER
4006 Debug("game:init:player", "- map_player_action[%d] == %d",
4007 field_player->index_nr + 1, i + 1);
4012 if (player->connected && player->present)
4013 player->mapped = TRUE;
4016 #if DEBUG_INIT_PLAYER
4017 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4022 // check if any connected player was not found in playfield
4023 for (i = 0; i < MAX_PLAYERS; i++)
4025 struct PlayerInfo *player = &stored_player[i];
4027 if (player->connected && !player->present)
4029 for (j = 0; j < MAX_PLAYERS; j++)
4031 struct PlayerInfo *field_player = &stored_player[j];
4032 int jx = field_player->jx, jy = field_player->jy;
4034 // assign first free player found that is present in the playfield
4035 if (field_player->present && !field_player->connected)
4037 player->present = TRUE;
4038 player->active = TRUE;
4040 field_player->present = FALSE;
4041 field_player->active = FALSE;
4043 player->initial_element = field_player->initial_element;
4044 player->artwork_element = field_player->artwork_element;
4046 player->block_last_field = field_player->block_last_field;
4047 player->block_delay_adjustment = field_player->block_delay_adjustment;
4049 StorePlayer[jx][jy] = player->element_nr;
4051 player->jx = player->last_jx = jx;
4052 player->jy = player->last_jy = jy;
4062 Debug("game:init:player", "local_player->present == %d",
4063 local_player->present);
4066 // set focus to local player for network games, else to all players
4067 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4068 game.centered_player_nr_next = game.centered_player_nr;
4069 game.set_centered_player = FALSE;
4070 game.set_centered_player_wrap = FALSE;
4072 if (network_playing && tape.recording)
4074 // store client dependent player focus when recording network games
4075 tape.centered_player_nr_next = game.centered_player_nr_next;
4076 tape.set_centered_player = TRUE;
4081 // when playing a tape, eliminate all players who do not participate
4083 #if USE_NEW_PLAYER_ASSIGNMENTS
4085 if (!game.team_mode)
4087 for (i = 0; i < MAX_PLAYERS; i++)
4089 if (stored_player[i].active &&
4090 !tape.player_participates[map_player_action[i]])
4092 struct PlayerInfo *player = &stored_player[i];
4093 int jx = player->jx, jy = player->jy;
4095 #if DEBUG_INIT_PLAYER
4096 Debug("game:init:player", "Removing player %d at (%d, %d)",
4100 player->active = FALSE;
4101 StorePlayer[jx][jy] = 0;
4102 Tile[jx][jy] = EL_EMPTY;
4109 for (i = 0; i < MAX_PLAYERS; i++)
4111 if (stored_player[i].active &&
4112 !tape.player_participates[i])
4114 struct PlayerInfo *player = &stored_player[i];
4115 int jx = player->jx, jy = player->jy;
4117 player->active = FALSE;
4118 StorePlayer[jx][jy] = 0;
4119 Tile[jx][jy] = EL_EMPTY;
4124 else if (!network.enabled && !game.team_mode) // && !tape.playing
4126 // when in single player mode, eliminate all but the local player
4128 for (i = 0; i < MAX_PLAYERS; i++)
4130 struct PlayerInfo *player = &stored_player[i];
4132 if (player->active && player != local_player)
4134 int jx = player->jx, jy = player->jy;
4136 player->active = FALSE;
4137 player->present = FALSE;
4139 StorePlayer[jx][jy] = 0;
4140 Tile[jx][jy] = EL_EMPTY;
4145 for (i = 0; i < MAX_PLAYERS; i++)
4146 if (stored_player[i].active)
4147 game.players_still_needed++;
4149 if (level.solved_by_one_player)
4150 game.players_still_needed = 1;
4152 // when recording the game, store which players take part in the game
4155 #if USE_NEW_PLAYER_ASSIGNMENTS
4156 for (i = 0; i < MAX_PLAYERS; i++)
4157 if (stored_player[i].connected)
4158 tape.player_participates[i] = TRUE;
4160 for (i = 0; i < MAX_PLAYERS; i++)
4161 if (stored_player[i].active)
4162 tape.player_participates[i] = TRUE;
4166 #if DEBUG_INIT_PLAYER
4167 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4170 if (BorderElement == EL_EMPTY)
4173 SBX_Right = lev_fieldx - SCR_FIELDX;
4175 SBY_Lower = lev_fieldy - SCR_FIELDY;
4180 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4182 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4185 if (full_lev_fieldx <= SCR_FIELDX)
4186 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4187 if (full_lev_fieldy <= SCR_FIELDY)
4188 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4190 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4192 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4195 // if local player not found, look for custom element that might create
4196 // the player (make some assumptions about the right custom element)
4197 if (!local_player->present)
4199 int start_x = 0, start_y = 0;
4200 int found_rating = 0;
4201 int found_element = EL_UNDEFINED;
4202 int player_nr = local_player->index_nr;
4204 SCAN_PLAYFIELD(x, y)
4206 int element = Tile[x][y];
4211 if (level.use_start_element[player_nr] &&
4212 level.start_element[player_nr] == element &&
4219 found_element = element;
4222 if (!IS_CUSTOM_ELEMENT(element))
4225 if (CAN_CHANGE(element))
4227 for (i = 0; i < element_info[element].num_change_pages; i++)
4229 // check for player created from custom element as single target
4230 content = element_info[element].change_page[i].target_element;
4231 is_player = ELEM_IS_PLAYER(content);
4233 if (is_player && (found_rating < 3 ||
4234 (found_rating == 3 && element < found_element)))
4240 found_element = element;
4245 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4247 // check for player created from custom element as explosion content
4248 content = element_info[element].content.e[xx][yy];
4249 is_player = ELEM_IS_PLAYER(content);
4251 if (is_player && (found_rating < 2 ||
4252 (found_rating == 2 && element < found_element)))
4254 start_x = x + xx - 1;
4255 start_y = y + yy - 1;
4258 found_element = element;
4261 if (!CAN_CHANGE(element))
4264 for (i = 0; i < element_info[element].num_change_pages; i++)
4266 // check for player created from custom element as extended target
4268 element_info[element].change_page[i].target_content.e[xx][yy];
4270 is_player = ELEM_IS_PLAYER(content);
4272 if (is_player && (found_rating < 1 ||
4273 (found_rating == 1 && element < found_element)))
4275 start_x = x + xx - 1;
4276 start_y = y + yy - 1;
4279 found_element = element;
4285 scroll_x = SCROLL_POSITION_X(start_x);
4286 scroll_y = SCROLL_POSITION_Y(start_y);
4290 scroll_x = SCROLL_POSITION_X(local_player->jx);
4291 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4294 // !!! FIX THIS (START) !!!
4295 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4297 InitGameEngine_EM();
4299 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4301 InitGameEngine_SP();
4303 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4305 InitGameEngine_MM();
4309 DrawLevel(REDRAW_FIELD);
4312 // after drawing the level, correct some elements
4313 if (game.timegate_time_left == 0)
4314 CloseAllOpenTimegates();
4317 // blit playfield from scroll buffer to normal back buffer for fading in
4318 BlitScreenToBitmap(backbuffer);
4319 // !!! FIX THIS (END) !!!
4321 DrawMaskedBorder(fade_mask);
4326 // full screen redraw is required at this point in the following cases:
4327 // - special editor door undrawn when game was started from level editor
4328 // - drawing area (playfield) was changed and has to be removed completely
4329 redraw_mask = REDRAW_ALL;
4333 if (!game.restart_level)
4335 // copy default game door content to main double buffer
4337 // !!! CHECK AGAIN !!!
4338 SetPanelBackground();
4339 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4340 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4343 SetPanelBackground();
4344 SetDrawBackgroundMask(REDRAW_DOOR_1);
4346 UpdateAndDisplayGameControlValues();
4348 if (!game.restart_level)
4354 CreateGameButtons();
4359 // copy actual game door content to door double buffer for OpenDoor()
4360 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4362 OpenDoor(DOOR_OPEN_ALL);
4364 KeyboardAutoRepeatOffUnlessAutoplay();
4366 #if DEBUG_INIT_PLAYER
4367 DebugPrintPlayerStatus("Player status (final)");
4376 if (!game.restart_level && !tape.playing)
4378 LevelStats_incPlayed(level_nr);
4380 SaveLevelSetup_SeriesInfo();
4383 game.restart_level = FALSE;
4384 game.restart_game_message = NULL;
4385 game.request_active = FALSE;
4387 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4388 InitGameActions_MM();
4390 SaveEngineSnapshotToListInitial();
4392 if (!game.restart_level)
4394 PlaySound(SND_GAME_STARTING);
4396 if (setup.sound_music)
4401 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4402 int actual_player_x, int actual_player_y)
4404 // this is used for non-R'n'D game engines to update certain engine values
4406 // needed to determine if sounds are played within the visible screen area
4407 scroll_x = actual_scroll_x;
4408 scroll_y = actual_scroll_y;
4410 // needed to get player position for "follow finger" playing input method
4411 local_player->jx = actual_player_x;
4412 local_player->jy = actual_player_y;
4415 void InitMovDir(int x, int y)
4417 int i, element = Tile[x][y];
4418 static int xy[4][2] =
4425 static int direction[3][4] =
4427 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4428 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4429 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4438 Tile[x][y] = EL_BUG;
4439 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4442 case EL_SPACESHIP_RIGHT:
4443 case EL_SPACESHIP_UP:
4444 case EL_SPACESHIP_LEFT:
4445 case EL_SPACESHIP_DOWN:
4446 Tile[x][y] = EL_SPACESHIP;
4447 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4450 case EL_BD_BUTTERFLY_RIGHT:
4451 case EL_BD_BUTTERFLY_UP:
4452 case EL_BD_BUTTERFLY_LEFT:
4453 case EL_BD_BUTTERFLY_DOWN:
4454 Tile[x][y] = EL_BD_BUTTERFLY;
4455 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4458 case EL_BD_FIREFLY_RIGHT:
4459 case EL_BD_FIREFLY_UP:
4460 case EL_BD_FIREFLY_LEFT:
4461 case EL_BD_FIREFLY_DOWN:
4462 Tile[x][y] = EL_BD_FIREFLY;
4463 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4466 case EL_PACMAN_RIGHT:
4468 case EL_PACMAN_LEFT:
4469 case EL_PACMAN_DOWN:
4470 Tile[x][y] = EL_PACMAN;
4471 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4474 case EL_YAMYAM_LEFT:
4475 case EL_YAMYAM_RIGHT:
4477 case EL_YAMYAM_DOWN:
4478 Tile[x][y] = EL_YAMYAM;
4479 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4482 case EL_SP_SNIKSNAK:
4483 MovDir[x][y] = MV_UP;
4486 case EL_SP_ELECTRON:
4487 MovDir[x][y] = MV_LEFT;
4494 Tile[x][y] = EL_MOLE;
4495 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4498 case EL_SPRING_LEFT:
4499 case EL_SPRING_RIGHT:
4500 Tile[x][y] = EL_SPRING;
4501 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4505 if (IS_CUSTOM_ELEMENT(element))
4507 struct ElementInfo *ei = &element_info[element];
4508 int move_direction_initial = ei->move_direction_initial;
4509 int move_pattern = ei->move_pattern;
4511 if (move_direction_initial == MV_START_PREVIOUS)
4513 if (MovDir[x][y] != MV_NONE)
4516 move_direction_initial = MV_START_AUTOMATIC;
4519 if (move_direction_initial == MV_START_RANDOM)
4520 MovDir[x][y] = 1 << RND(4);
4521 else if (move_direction_initial & MV_ANY_DIRECTION)
4522 MovDir[x][y] = move_direction_initial;
4523 else if (move_pattern == MV_ALL_DIRECTIONS ||
4524 move_pattern == MV_TURNING_LEFT ||
4525 move_pattern == MV_TURNING_RIGHT ||
4526 move_pattern == MV_TURNING_LEFT_RIGHT ||
4527 move_pattern == MV_TURNING_RIGHT_LEFT ||
4528 move_pattern == MV_TURNING_RANDOM)
4529 MovDir[x][y] = 1 << RND(4);
4530 else if (move_pattern == MV_HORIZONTAL)
4531 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4532 else if (move_pattern == MV_VERTICAL)
4533 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4534 else if (move_pattern & MV_ANY_DIRECTION)
4535 MovDir[x][y] = element_info[element].move_pattern;
4536 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4537 move_pattern == MV_ALONG_RIGHT_SIDE)
4539 // use random direction as default start direction
4540 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4541 MovDir[x][y] = 1 << RND(4);
4543 for (i = 0; i < NUM_DIRECTIONS; i++)
4545 int x1 = x + xy[i][0];
4546 int y1 = y + xy[i][1];
4548 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4550 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4551 MovDir[x][y] = direction[0][i];
4553 MovDir[x][y] = direction[1][i];
4562 MovDir[x][y] = 1 << RND(4);
4564 if (element != EL_BUG &&
4565 element != EL_SPACESHIP &&
4566 element != EL_BD_BUTTERFLY &&
4567 element != EL_BD_FIREFLY)
4570 for (i = 0; i < NUM_DIRECTIONS; i++)
4572 int x1 = x + xy[i][0];
4573 int y1 = y + xy[i][1];
4575 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4577 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4579 MovDir[x][y] = direction[0][i];
4582 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4583 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4585 MovDir[x][y] = direction[1][i];
4594 GfxDir[x][y] = MovDir[x][y];
4597 void InitAmoebaNr(int x, int y)
4600 int group_nr = AmoebaNeighbourNr(x, y);
4604 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4606 if (AmoebaCnt[i] == 0)
4614 AmoebaNr[x][y] = group_nr;
4615 AmoebaCnt[group_nr]++;
4616 AmoebaCnt2[group_nr]++;
4619 static void LevelSolved(void)
4621 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4622 game.players_still_needed > 0)
4625 game.LevelSolved = TRUE;
4626 game.GameOver = TRUE;
4628 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4629 game_em.lev->score :
4630 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4633 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4634 MM_HEALTH(game_mm.laser_overload_value) :
4637 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4638 game.LevelSolved_CountingScore = game.score_final;
4639 game.LevelSolved_CountingHealth = game.health_final;
4644 static int time_count_steps;
4645 static int time, time_final;
4646 static int score, score_final;
4647 static int health, health_final;
4648 static int game_over_delay_1 = 0;
4649 static int game_over_delay_2 = 0;
4650 static int game_over_delay_3 = 0;
4651 int game_over_delay_value_1 = 50;
4652 int game_over_delay_value_2 = 25;
4653 int game_over_delay_value_3 = 50;
4655 if (!game.LevelSolved_GameWon)
4659 // do not start end game actions before the player stops moving (to exit)
4660 if (local_player->active && local_player->MovPos)
4663 game.LevelSolved_GameWon = TRUE;
4664 game.LevelSolved_SaveTape = tape.recording;
4665 game.LevelSolved_SaveScore = !tape.playing;
4669 LevelStats_incSolved(level_nr);
4671 SaveLevelSetup_SeriesInfo();
4674 if (tape.auto_play) // tape might already be stopped here
4675 tape.auto_play_level_solved = TRUE;
4679 game_over_delay_1 = 0;
4680 game_over_delay_2 = 0;
4681 game_over_delay_3 = game_over_delay_value_3;
4683 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4684 score = score_final = game.score_final;
4685 health = health_final = game.health_final;
4687 if (level.score[SC_TIME_BONUS] > 0)
4692 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4694 else if (game.no_time_limit && TimePlayed < 999)
4697 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4700 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4702 game_over_delay_1 = game_over_delay_value_1;
4704 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4707 score_final += health * level.score[SC_TIME_BONUS];
4709 game_over_delay_2 = game_over_delay_value_2;
4712 game.score_final = score_final;
4713 game.health_final = health_final;
4716 if (level_editor_test_game)
4719 score = score_final;
4721 game.LevelSolved_CountingTime = time;
4722 game.LevelSolved_CountingScore = score;
4724 game_panel_controls[GAME_PANEL_TIME].value = time;
4725 game_panel_controls[GAME_PANEL_SCORE].value = score;
4727 DisplayGameControlValues();
4730 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4732 // check if last player has left the level
4733 if (game.exit_x >= 0 &&
4736 int x = game.exit_x;
4737 int y = game.exit_y;
4738 int element = Tile[x][y];
4740 // close exit door after last player
4741 if ((game.all_players_gone &&
4742 (element == EL_EXIT_OPEN ||
4743 element == EL_SP_EXIT_OPEN ||
4744 element == EL_STEEL_EXIT_OPEN)) ||
4745 element == EL_EM_EXIT_OPEN ||
4746 element == EL_EM_STEEL_EXIT_OPEN)
4750 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4751 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4752 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4753 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4754 EL_EM_STEEL_EXIT_CLOSING);
4756 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4759 // player disappears
4760 DrawLevelField(x, y);
4763 for (i = 0; i < MAX_PLAYERS; i++)
4765 struct PlayerInfo *player = &stored_player[i];
4767 if (player->present)
4769 RemovePlayer(player);
4771 // player disappears
4772 DrawLevelField(player->jx, player->jy);
4777 PlaySound(SND_GAME_WINNING);
4780 if (game_over_delay_1 > 0)
4782 game_over_delay_1--;
4787 if (time != time_final)
4789 int time_to_go = ABS(time_final - time);
4790 int time_count_dir = (time < time_final ? +1 : -1);
4792 if (time_to_go < time_count_steps)
4793 time_count_steps = 1;
4795 time += time_count_steps * time_count_dir;
4796 score += time_count_steps * level.score[SC_TIME_BONUS];
4798 game.LevelSolved_CountingTime = time;
4799 game.LevelSolved_CountingScore = score;
4801 game_panel_controls[GAME_PANEL_TIME].value = time;
4802 game_panel_controls[GAME_PANEL_SCORE].value = score;
4804 DisplayGameControlValues();
4806 if (time == time_final)
4807 StopSound(SND_GAME_LEVELTIME_BONUS);
4808 else if (setup.sound_loops)
4809 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4811 PlaySound(SND_GAME_LEVELTIME_BONUS);
4816 if (game_over_delay_2 > 0)
4818 game_over_delay_2--;
4823 if (health != health_final)
4825 int health_count_dir = (health < health_final ? +1 : -1);
4827 health += health_count_dir;
4828 score += level.score[SC_TIME_BONUS];
4830 game.LevelSolved_CountingHealth = health;
4831 game.LevelSolved_CountingScore = score;
4833 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4834 game_panel_controls[GAME_PANEL_SCORE].value = score;
4836 DisplayGameControlValues();
4838 if (health == health_final)
4839 StopSound(SND_GAME_LEVELTIME_BONUS);
4840 else if (setup.sound_loops)
4841 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4843 PlaySound(SND_GAME_LEVELTIME_BONUS);
4848 game.panel.active = FALSE;
4850 if (game_over_delay_3 > 0)
4852 game_over_delay_3--;
4862 // used instead of "level_nr" (needed for network games)
4863 int last_level_nr = levelset.level_nr;
4866 game.LevelSolved_GameEnd = TRUE;
4868 if (game.LevelSolved_SaveTape)
4870 // make sure that request dialog to save tape does not open door again
4871 if (!global.use_envelope_request)
4872 CloseDoor(DOOR_CLOSE_1);
4874 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4877 // if no tape is to be saved, close both doors simultaneously
4878 CloseDoor(DOOR_CLOSE_ALL);
4880 if (level_editor_test_game)
4882 SetGameStatus(GAME_MODE_MAIN);
4889 if (!game.LevelSolved_SaveScore)
4891 SetGameStatus(GAME_MODE_MAIN);
4898 if (level_nr == leveldir_current->handicap_level)
4900 leveldir_current->handicap_level++;
4902 SaveLevelSetup_SeriesInfo();
4905 if (setup.increment_levels &&
4906 level_nr < leveldir_current->last_level &&
4909 level_nr++; // advance to next level
4910 TapeErase(); // start with empty tape
4912 if (setup.auto_play_next_level)
4914 LoadLevel(level_nr);
4916 SaveLevelSetup_SeriesInfo();
4920 hi_pos = NewHiScore(last_level_nr);
4922 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4924 SetGameStatus(GAME_MODE_SCORES);
4926 DrawHallOfFame(last_level_nr, hi_pos);
4928 else if (setup.auto_play_next_level && setup.increment_levels &&
4929 last_level_nr < leveldir_current->last_level &&
4932 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4936 SetGameStatus(GAME_MODE_MAIN);
4942 int NewHiScore(int level_nr)
4946 boolean one_score_entry_per_name = !program.many_scores_per_name;
4948 LoadScore(level_nr);
4950 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4951 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4954 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4956 if (game.score_final > highscore[k].Score)
4958 // player has made it to the hall of fame
4960 if (k < MAX_SCORE_ENTRIES - 1)
4962 int m = MAX_SCORE_ENTRIES - 1;
4964 if (one_score_entry_per_name)
4966 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4967 if (strEqual(setup.player_name, highscore[l].Name))
4970 if (m == k) // player's new highscore overwrites his old one
4974 for (l = m; l > k; l--)
4976 strcpy(highscore[l].Name, highscore[l - 1].Name);
4977 highscore[l].Score = highscore[l - 1].Score;
4983 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4984 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4985 highscore[k].Score = game.score_final;
4990 else if (one_score_entry_per_name &&
4991 !strncmp(setup.player_name, highscore[k].Name,
4992 MAX_PLAYER_NAME_LEN))
4993 break; // player already there with a higher score
4997 SaveScore(level_nr);
5002 static int getElementMoveStepsizeExt(int x, int y, int direction)
5004 int element = Tile[x][y];
5005 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5006 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5007 int horiz_move = (dx != 0);
5008 int sign = (horiz_move ? dx : dy);
5009 int step = sign * element_info[element].move_stepsize;
5011 // special values for move stepsize for spring and things on conveyor belt
5014 if (CAN_FALL(element) &&
5015 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5016 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5017 else if (element == EL_SPRING)
5018 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5024 static int getElementMoveStepsize(int x, int y)
5026 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5029 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5031 if (player->GfxAction != action || player->GfxDir != dir)
5033 player->GfxAction = action;
5034 player->GfxDir = dir;
5036 player->StepFrame = 0;
5040 static void ResetGfxFrame(int x, int y)
5042 // profiling showed that "autotest" spends 10~20% of its time in this function
5043 if (DrawingDeactivatedField())
5046 int element = Tile[x][y];
5047 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5049 if (graphic_info[graphic].anim_global_sync)
5050 GfxFrame[x][y] = FrameCounter;
5051 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5052 GfxFrame[x][y] = CustomValue[x][y];
5053 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5054 GfxFrame[x][y] = element_info[element].collect_score;
5055 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5056 GfxFrame[x][y] = ChangeDelay[x][y];
5059 static void ResetGfxAnimation(int x, int y)
5061 GfxAction[x][y] = ACTION_DEFAULT;
5062 GfxDir[x][y] = MovDir[x][y];
5065 ResetGfxFrame(x, y);
5068 static void ResetRandomAnimationValue(int x, int y)
5070 GfxRandom[x][y] = INIT_GFX_RANDOM();
5073 static void InitMovingField(int x, int y, int direction)
5075 int element = Tile[x][y];
5076 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5077 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5080 boolean is_moving_before, is_moving_after;
5082 // check if element was/is moving or being moved before/after mode change
5083 is_moving_before = (WasJustMoving[x][y] != 0);
5084 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5086 // reset animation only for moving elements which change direction of moving
5087 // or which just started or stopped moving
5088 // (else CEs with property "can move" / "not moving" are reset each frame)
5089 if (is_moving_before != is_moving_after ||
5090 direction != MovDir[x][y])
5091 ResetGfxAnimation(x, y);
5093 MovDir[x][y] = direction;
5094 GfxDir[x][y] = direction;
5096 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5097 direction == MV_DOWN && CAN_FALL(element) ?
5098 ACTION_FALLING : ACTION_MOVING);
5100 // this is needed for CEs with property "can move" / "not moving"
5102 if (is_moving_after)
5104 if (Tile[newx][newy] == EL_EMPTY)
5105 Tile[newx][newy] = EL_BLOCKED;
5107 MovDir[newx][newy] = MovDir[x][y];
5109 CustomValue[newx][newy] = CustomValue[x][y];
5111 GfxFrame[newx][newy] = GfxFrame[x][y];
5112 GfxRandom[newx][newy] = GfxRandom[x][y];
5113 GfxAction[newx][newy] = GfxAction[x][y];
5114 GfxDir[newx][newy] = GfxDir[x][y];
5118 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5120 int direction = MovDir[x][y];
5121 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5122 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5128 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5130 int oldx = x, oldy = y;
5131 int direction = MovDir[x][y];
5133 if (direction == MV_LEFT)
5135 else if (direction == MV_RIGHT)
5137 else if (direction == MV_UP)
5139 else if (direction == MV_DOWN)
5142 *comes_from_x = oldx;
5143 *comes_from_y = oldy;
5146 static int MovingOrBlocked2Element(int x, int y)
5148 int element = Tile[x][y];
5150 if (element == EL_BLOCKED)
5154 Blocked2Moving(x, y, &oldx, &oldy);
5155 return Tile[oldx][oldy];
5161 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5163 // like MovingOrBlocked2Element(), but if element is moving
5164 // and (x,y) is the field the moving element is just leaving,
5165 // return EL_BLOCKED instead of the element value
5166 int element = Tile[x][y];
5168 if (IS_MOVING(x, y))
5170 if (element == EL_BLOCKED)
5174 Blocked2Moving(x, y, &oldx, &oldy);
5175 return Tile[oldx][oldy];
5184 static void RemoveField(int x, int y)
5186 Tile[x][y] = EL_EMPTY;
5192 CustomValue[x][y] = 0;
5195 ChangeDelay[x][y] = 0;
5196 ChangePage[x][y] = -1;
5197 Pushed[x][y] = FALSE;
5199 GfxElement[x][y] = EL_UNDEFINED;
5200 GfxAction[x][y] = ACTION_DEFAULT;
5201 GfxDir[x][y] = MV_NONE;
5204 static void RemoveMovingField(int x, int y)
5206 int oldx = x, oldy = y, newx = x, newy = y;
5207 int element = Tile[x][y];
5208 int next_element = EL_UNDEFINED;
5210 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5213 if (IS_MOVING(x, y))
5215 Moving2Blocked(x, y, &newx, &newy);
5217 if (Tile[newx][newy] != EL_BLOCKED)
5219 // element is moving, but target field is not free (blocked), but
5220 // already occupied by something different (example: acid pool);
5221 // in this case, only remove the moving field, but not the target
5223 RemoveField(oldx, oldy);
5225 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5227 TEST_DrawLevelField(oldx, oldy);
5232 else if (element == EL_BLOCKED)
5234 Blocked2Moving(x, y, &oldx, &oldy);
5235 if (!IS_MOVING(oldx, oldy))
5239 if (element == EL_BLOCKED &&
5240 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5241 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5242 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5243 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5244 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5245 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5246 next_element = get_next_element(Tile[oldx][oldy]);
5248 RemoveField(oldx, oldy);
5249 RemoveField(newx, newy);
5251 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5253 if (next_element != EL_UNDEFINED)
5254 Tile[oldx][oldy] = next_element;
5256 TEST_DrawLevelField(oldx, oldy);
5257 TEST_DrawLevelField(newx, newy);
5260 void DrawDynamite(int x, int y)
5262 int sx = SCREENX(x), sy = SCREENY(y);
5263 int graphic = el2img(Tile[x][y]);
5266 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5269 if (IS_WALKABLE_INSIDE(Back[x][y]))
5273 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5274 else if (Store[x][y])
5275 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5277 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5279 if (Back[x][y] || Store[x][y])
5280 DrawGraphicThruMask(sx, sy, graphic, frame);
5282 DrawGraphic(sx, sy, graphic, frame);
5285 static void CheckDynamite(int x, int y)
5287 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5291 if (MovDelay[x][y] != 0)
5294 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5300 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5305 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5307 boolean num_checked_players = 0;
5310 for (i = 0; i < MAX_PLAYERS; i++)
5312 if (stored_player[i].active)
5314 int sx = stored_player[i].jx;
5315 int sy = stored_player[i].jy;
5317 if (num_checked_players == 0)
5324 *sx1 = MIN(*sx1, sx);
5325 *sy1 = MIN(*sy1, sy);
5326 *sx2 = MAX(*sx2, sx);
5327 *sy2 = MAX(*sy2, sy);
5330 num_checked_players++;
5335 static boolean checkIfAllPlayersFitToScreen_RND(void)
5337 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5339 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5341 return (sx2 - sx1 < SCR_FIELDX &&
5342 sy2 - sy1 < SCR_FIELDY);
5345 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5347 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5349 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5351 *sx = (sx1 + sx2) / 2;
5352 *sy = (sy1 + sy2) / 2;
5355 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5356 boolean center_screen, boolean quick_relocation)
5358 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5359 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5360 boolean no_delay = (tape.warp_forward);
5361 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5362 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5363 int new_scroll_x, new_scroll_y;
5365 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5367 // case 1: quick relocation inside visible screen (without scrolling)
5374 if (!level.shifted_relocation || center_screen)
5376 // relocation _with_ centering of screen
5378 new_scroll_x = SCROLL_POSITION_X(x);
5379 new_scroll_y = SCROLL_POSITION_Y(y);
5383 // relocation _without_ centering of screen
5385 int center_scroll_x = SCROLL_POSITION_X(old_x);
5386 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5387 int offset_x = x + (scroll_x - center_scroll_x);
5388 int offset_y = y + (scroll_y - center_scroll_y);
5390 // for new screen position, apply previous offset to center position
5391 new_scroll_x = SCROLL_POSITION_X(offset_x);
5392 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5395 if (quick_relocation)
5397 // case 2: quick relocation (redraw without visible scrolling)
5399 scroll_x = new_scroll_x;
5400 scroll_y = new_scroll_y;
5407 // case 3: visible relocation (with scrolling to new position)
5409 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5411 SetVideoFrameDelay(wait_delay_value);
5413 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5415 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5416 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5418 if (dx == 0 && dy == 0) // no scrolling needed at all
5424 // set values for horizontal/vertical screen scrolling (half tile size)
5425 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5426 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5427 int pos_x = dx * TILEX / 2;
5428 int pos_y = dy * TILEY / 2;
5429 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5430 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5432 ScrollLevel(dx, dy);
5435 // scroll in two steps of half tile size to make things smoother
5436 BlitScreenToBitmapExt_RND(window, fx, fy);
5438 // scroll second step to align at full tile size
5439 BlitScreenToBitmap(window);
5445 SetVideoFrameDelay(frame_delay_value_old);
5448 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5450 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5451 int player_nr = GET_PLAYER_NR(el_player);
5452 struct PlayerInfo *player = &stored_player[player_nr];
5453 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5454 boolean no_delay = (tape.warp_forward);
5455 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5456 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5457 int old_jx = player->jx;
5458 int old_jy = player->jy;
5459 int old_element = Tile[old_jx][old_jy];
5460 int element = Tile[jx][jy];
5461 boolean player_relocated = (old_jx != jx || old_jy != jy);
5463 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5464 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5465 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5466 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5467 int leave_side_horiz = move_dir_horiz;
5468 int leave_side_vert = move_dir_vert;
5469 int enter_side = enter_side_horiz | enter_side_vert;
5470 int leave_side = leave_side_horiz | leave_side_vert;
5472 if (player->buried) // do not reanimate dead player
5475 if (!player_relocated) // no need to relocate the player
5478 if (IS_PLAYER(jx, jy)) // player already placed at new position
5480 RemoveField(jx, jy); // temporarily remove newly placed player
5481 DrawLevelField(jx, jy);
5484 if (player->present)
5486 while (player->MovPos)
5488 ScrollPlayer(player, SCROLL_GO_ON);
5489 ScrollScreen(NULL, SCROLL_GO_ON);
5491 AdvanceFrameAndPlayerCounters(player->index_nr);
5495 BackToFront_WithFrameDelay(wait_delay_value);
5498 DrawPlayer(player); // needed here only to cleanup last field
5499 DrawLevelField(player->jx, player->jy); // remove player graphic
5501 player->is_moving = FALSE;
5504 if (IS_CUSTOM_ELEMENT(old_element))
5505 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5507 player->index_bit, leave_side);
5509 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5511 player->index_bit, leave_side);
5513 Tile[jx][jy] = el_player;
5514 InitPlayerField(jx, jy, el_player, TRUE);
5516 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5517 possible that the relocation target field did not contain a player element,
5518 but a walkable element, to which the new player was relocated -- in this
5519 case, restore that (already initialized!) element on the player field */
5520 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5522 Tile[jx][jy] = element; // restore previously existing element
5525 // only visually relocate centered player
5526 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5527 FALSE, level.instant_relocation);
5529 TestIfPlayerTouchesBadThing(jx, jy);
5530 TestIfPlayerTouchesCustomElement(jx, jy);
5532 if (IS_CUSTOM_ELEMENT(element))
5533 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5534 player->index_bit, enter_side);
5536 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5537 player->index_bit, enter_side);
5539 if (player->is_switching)
5541 /* ensure that relocation while still switching an element does not cause
5542 a new element to be treated as also switched directly after relocation
5543 (this is important for teleporter switches that teleport the player to
5544 a place where another teleporter switch is in the same direction, which
5545 would then incorrectly be treated as immediately switched before the
5546 direction key that caused the switch was released) */
5548 player->switch_x += jx - old_jx;
5549 player->switch_y += jy - old_jy;
5553 static void Explode(int ex, int ey, int phase, int mode)
5559 // !!! eliminate this variable !!!
5560 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5562 if (game.explosions_delayed)
5564 ExplodeField[ex][ey] = mode;
5568 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5570 int center_element = Tile[ex][ey];
5571 int artwork_element, explosion_element; // set these values later
5573 // remove things displayed in background while burning dynamite
5574 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5577 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5579 // put moving element to center field (and let it explode there)
5580 center_element = MovingOrBlocked2Element(ex, ey);
5581 RemoveMovingField(ex, ey);
5582 Tile[ex][ey] = center_element;
5585 // now "center_element" is finally determined -- set related values now
5586 artwork_element = center_element; // for custom player artwork
5587 explosion_element = center_element; // for custom player artwork
5589 if (IS_PLAYER(ex, ey))
5591 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5593 artwork_element = stored_player[player_nr].artwork_element;
5595 if (level.use_explosion_element[player_nr])
5597 explosion_element = level.explosion_element[player_nr];
5598 artwork_element = explosion_element;
5602 if (mode == EX_TYPE_NORMAL ||
5603 mode == EX_TYPE_CENTER ||
5604 mode == EX_TYPE_CROSS)
5605 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5607 last_phase = element_info[explosion_element].explosion_delay + 1;
5609 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5611 int xx = x - ex + 1;
5612 int yy = y - ey + 1;
5615 if (!IN_LEV_FIELD(x, y) ||
5616 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5617 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5620 element = Tile[x][y];
5622 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5624 element = MovingOrBlocked2Element(x, y);
5626 if (!IS_EXPLOSION_PROOF(element))
5627 RemoveMovingField(x, y);
5630 // indestructible elements can only explode in center (but not flames)
5631 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5632 mode == EX_TYPE_BORDER)) ||
5633 element == EL_FLAMES)
5636 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5637 behaviour, for example when touching a yamyam that explodes to rocks
5638 with active deadly shield, a rock is created under the player !!! */
5639 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5641 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5642 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5643 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5645 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5648 if (IS_ACTIVE_BOMB(element))
5650 // re-activate things under the bomb like gate or penguin
5651 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5658 // save walkable background elements while explosion on same tile
5659 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5660 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5661 Back[x][y] = element;
5663 // ignite explodable elements reached by other explosion
5664 if (element == EL_EXPLOSION)
5665 element = Store2[x][y];
5667 if (AmoebaNr[x][y] &&
5668 (element == EL_AMOEBA_FULL ||
5669 element == EL_BD_AMOEBA ||
5670 element == EL_AMOEBA_GROWING))
5672 AmoebaCnt[AmoebaNr[x][y]]--;
5673 AmoebaCnt2[AmoebaNr[x][y]]--;
5678 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5680 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5682 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5684 if (PLAYERINFO(ex, ey)->use_murphy)
5685 Store[x][y] = EL_EMPTY;
5688 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5689 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5690 else if (ELEM_IS_PLAYER(center_element))
5691 Store[x][y] = EL_EMPTY;
5692 else if (center_element == EL_YAMYAM)
5693 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5694 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5695 Store[x][y] = element_info[center_element].content.e[xx][yy];
5697 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5698 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5699 // otherwise) -- FIX THIS !!!
5700 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5701 Store[x][y] = element_info[element].content.e[1][1];
5703 else if (!CAN_EXPLODE(element))
5704 Store[x][y] = element_info[element].content.e[1][1];
5707 Store[x][y] = EL_EMPTY;
5709 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5710 center_element == EL_AMOEBA_TO_DIAMOND)
5711 Store2[x][y] = element;
5713 Tile[x][y] = EL_EXPLOSION;
5714 GfxElement[x][y] = artwork_element;
5716 ExplodePhase[x][y] = 1;
5717 ExplodeDelay[x][y] = last_phase;
5722 if (center_element == EL_YAMYAM)
5723 game.yamyam_content_nr =
5724 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5736 GfxFrame[x][y] = 0; // restart explosion animation
5738 last_phase = ExplodeDelay[x][y];
5740 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5742 // this can happen if the player leaves an explosion just in time
5743 if (GfxElement[x][y] == EL_UNDEFINED)
5744 GfxElement[x][y] = EL_EMPTY;
5746 border_element = Store2[x][y];
5747 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5748 border_element = StorePlayer[x][y];
5750 if (phase == element_info[border_element].ignition_delay ||
5751 phase == last_phase)
5753 boolean border_explosion = FALSE;
5755 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5756 !PLAYER_EXPLOSION_PROTECTED(x, y))
5758 KillPlayerUnlessExplosionProtected(x, y);
5759 border_explosion = TRUE;
5761 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5763 Tile[x][y] = Store2[x][y];
5766 border_explosion = TRUE;
5768 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5770 AmoebaToDiamond(x, y);
5772 border_explosion = TRUE;
5775 // if an element just explodes due to another explosion (chain-reaction),
5776 // do not immediately end the new explosion when it was the last frame of
5777 // the explosion (as it would be done in the following "if"-statement!)
5778 if (border_explosion && phase == last_phase)
5782 if (phase == last_phase)
5786 element = Tile[x][y] = Store[x][y];
5787 Store[x][y] = Store2[x][y] = 0;
5788 GfxElement[x][y] = EL_UNDEFINED;
5790 // player can escape from explosions and might therefore be still alive
5791 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5792 element <= EL_PLAYER_IS_EXPLODING_4)
5794 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5795 int explosion_element = EL_PLAYER_1 + player_nr;
5796 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5797 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5799 if (level.use_explosion_element[player_nr])
5800 explosion_element = level.explosion_element[player_nr];
5802 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5803 element_info[explosion_element].content.e[xx][yy]);
5806 // restore probably existing indestructible background element
5807 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5808 element = Tile[x][y] = Back[x][y];
5811 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5812 GfxDir[x][y] = MV_NONE;
5813 ChangeDelay[x][y] = 0;
5814 ChangePage[x][y] = -1;
5816 CustomValue[x][y] = 0;
5818 InitField_WithBug2(x, y, FALSE);
5820 TEST_DrawLevelField(x, y);
5822 TestIfElementTouchesCustomElement(x, y);
5824 if (GFX_CRUMBLED(element))
5825 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5827 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5828 StorePlayer[x][y] = 0;
5830 if (ELEM_IS_PLAYER(element))
5831 RelocatePlayer(x, y, element);
5833 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5835 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5836 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5839 TEST_DrawLevelFieldCrumbled(x, y);
5841 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5843 DrawLevelElement(x, y, Back[x][y]);
5844 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5846 else if (IS_WALKABLE_UNDER(Back[x][y]))
5848 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5849 DrawLevelElementThruMask(x, y, Back[x][y]);
5851 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5852 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5856 static void DynaExplode(int ex, int ey)
5859 int dynabomb_element = Tile[ex][ey];
5860 int dynabomb_size = 1;
5861 boolean dynabomb_xl = FALSE;
5862 struct PlayerInfo *player;
5863 static int xy[4][2] =
5871 if (IS_ACTIVE_BOMB(dynabomb_element))
5873 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5874 dynabomb_size = player->dynabomb_size;
5875 dynabomb_xl = player->dynabomb_xl;
5876 player->dynabombs_left++;
5879 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5881 for (i = 0; i < NUM_DIRECTIONS; i++)
5883 for (j = 1; j <= dynabomb_size; j++)
5885 int x = ex + j * xy[i][0];
5886 int y = ey + j * xy[i][1];
5889 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5892 element = Tile[x][y];
5894 // do not restart explosions of fields with active bombs
5895 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5898 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5900 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5901 !IS_DIGGABLE(element) && !dynabomb_xl)
5907 void Bang(int x, int y)
5909 int element = MovingOrBlocked2Element(x, y);
5910 int explosion_type = EX_TYPE_NORMAL;
5912 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5914 struct PlayerInfo *player = PLAYERINFO(x, y);
5916 element = Tile[x][y] = player->initial_element;
5918 if (level.use_explosion_element[player->index_nr])
5920 int explosion_element = level.explosion_element[player->index_nr];
5922 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5923 explosion_type = EX_TYPE_CROSS;
5924 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5925 explosion_type = EX_TYPE_CENTER;
5933 case EL_BD_BUTTERFLY:
5936 case EL_DARK_YAMYAM:
5940 RaiseScoreElement(element);
5943 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5944 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5945 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5946 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5947 case EL_DYNABOMB_INCREASE_NUMBER:
5948 case EL_DYNABOMB_INCREASE_SIZE:
5949 case EL_DYNABOMB_INCREASE_POWER:
5950 explosion_type = EX_TYPE_DYNA;
5953 case EL_DC_LANDMINE:
5954 explosion_type = EX_TYPE_CENTER;
5959 case EL_LAMP_ACTIVE:
5960 case EL_AMOEBA_TO_DIAMOND:
5961 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5962 explosion_type = EX_TYPE_CENTER;
5966 if (element_info[element].explosion_type == EXPLODES_CROSS)
5967 explosion_type = EX_TYPE_CROSS;
5968 else if (element_info[element].explosion_type == EXPLODES_1X1)
5969 explosion_type = EX_TYPE_CENTER;
5973 if (explosion_type == EX_TYPE_DYNA)
5976 Explode(x, y, EX_PHASE_START, explosion_type);
5978 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5981 static void SplashAcid(int x, int y)
5983 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5984 (!IN_LEV_FIELD(x - 1, y - 2) ||
5985 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5986 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5988 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5989 (!IN_LEV_FIELD(x + 1, y - 2) ||
5990 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5991 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5993 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5996 static void InitBeltMovement(void)
5998 static int belt_base_element[4] =
6000 EL_CONVEYOR_BELT_1_LEFT,
6001 EL_CONVEYOR_BELT_2_LEFT,
6002 EL_CONVEYOR_BELT_3_LEFT,
6003 EL_CONVEYOR_BELT_4_LEFT
6005 static int belt_base_active_element[4] =
6007 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6008 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6009 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6010 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6015 // set frame order for belt animation graphic according to belt direction
6016 for (i = 0; i < NUM_BELTS; i++)
6020 for (j = 0; j < NUM_BELT_PARTS; j++)
6022 int element = belt_base_active_element[belt_nr] + j;
6023 int graphic_1 = el2img(element);
6024 int graphic_2 = el2panelimg(element);
6026 if (game.belt_dir[i] == MV_LEFT)
6028 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6029 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6033 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6034 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6039 SCAN_PLAYFIELD(x, y)
6041 int element = Tile[x][y];
6043 for (i = 0; i < NUM_BELTS; i++)
6045 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6047 int e_belt_nr = getBeltNrFromBeltElement(element);
6050 if (e_belt_nr == belt_nr)
6052 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6054 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6061 static void ToggleBeltSwitch(int x, int y)
6063 static int belt_base_element[4] =
6065 EL_CONVEYOR_BELT_1_LEFT,
6066 EL_CONVEYOR_BELT_2_LEFT,
6067 EL_CONVEYOR_BELT_3_LEFT,
6068 EL_CONVEYOR_BELT_4_LEFT
6070 static int belt_base_active_element[4] =
6072 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6073 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6074 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6075 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6077 static int belt_base_switch_element[4] =
6079 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6080 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6081 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6082 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6084 static int belt_move_dir[4] =
6092 int element = Tile[x][y];
6093 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6094 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6095 int belt_dir = belt_move_dir[belt_dir_nr];
6098 if (!IS_BELT_SWITCH(element))
6101 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6102 game.belt_dir[belt_nr] = belt_dir;
6104 if (belt_dir_nr == 3)
6107 // set frame order for belt animation graphic according to belt direction
6108 for (i = 0; i < NUM_BELT_PARTS; i++)
6110 int element = belt_base_active_element[belt_nr] + i;
6111 int graphic_1 = el2img(element);
6112 int graphic_2 = el2panelimg(element);
6114 if (belt_dir == MV_LEFT)
6116 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6117 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6121 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6122 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6126 SCAN_PLAYFIELD(xx, yy)
6128 int element = Tile[xx][yy];
6130 if (IS_BELT_SWITCH(element))
6132 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6134 if (e_belt_nr == belt_nr)
6136 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6137 TEST_DrawLevelField(xx, yy);
6140 else if (IS_BELT(element) && belt_dir != MV_NONE)
6142 int e_belt_nr = getBeltNrFromBeltElement(element);
6144 if (e_belt_nr == belt_nr)
6146 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6148 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6149 TEST_DrawLevelField(xx, yy);
6152 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6154 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6156 if (e_belt_nr == belt_nr)
6158 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6160 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6161 TEST_DrawLevelField(xx, yy);
6167 static void ToggleSwitchgateSwitch(int x, int y)
6171 game.switchgate_pos = !game.switchgate_pos;
6173 SCAN_PLAYFIELD(xx, yy)
6175 int element = Tile[xx][yy];
6177 if (element == EL_SWITCHGATE_SWITCH_UP)
6179 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6180 TEST_DrawLevelField(xx, yy);
6182 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6184 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6185 TEST_DrawLevelField(xx, yy);
6187 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6189 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6190 TEST_DrawLevelField(xx, yy);
6192 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6194 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6195 TEST_DrawLevelField(xx, yy);
6197 else if (element == EL_SWITCHGATE_OPEN ||
6198 element == EL_SWITCHGATE_OPENING)
6200 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6202 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6204 else if (element == EL_SWITCHGATE_CLOSED ||
6205 element == EL_SWITCHGATE_CLOSING)
6207 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6209 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6214 static int getInvisibleActiveFromInvisibleElement(int element)
6216 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6217 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6218 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6222 static int getInvisibleFromInvisibleActiveElement(int element)
6224 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6225 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6226 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6230 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6234 SCAN_PLAYFIELD(x, y)
6236 int element = Tile[x][y];
6238 if (element == EL_LIGHT_SWITCH &&
6239 game.light_time_left > 0)
6241 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6242 TEST_DrawLevelField(x, y);
6244 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6245 game.light_time_left == 0)
6247 Tile[x][y] = EL_LIGHT_SWITCH;
6248 TEST_DrawLevelField(x, y);
6250 else if (element == EL_EMC_DRIPPER &&
6251 game.light_time_left > 0)
6253 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6254 TEST_DrawLevelField(x, y);
6256 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6257 game.light_time_left == 0)
6259 Tile[x][y] = EL_EMC_DRIPPER;
6260 TEST_DrawLevelField(x, y);
6262 else if (element == EL_INVISIBLE_STEELWALL ||
6263 element == EL_INVISIBLE_WALL ||
6264 element == EL_INVISIBLE_SAND)
6266 if (game.light_time_left > 0)
6267 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6269 TEST_DrawLevelField(x, y);
6271 // uncrumble neighbour fields, if needed
6272 if (element == EL_INVISIBLE_SAND)
6273 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6275 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6276 element == EL_INVISIBLE_WALL_ACTIVE ||
6277 element == EL_INVISIBLE_SAND_ACTIVE)
6279 if (game.light_time_left == 0)
6280 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6282 TEST_DrawLevelField(x, y);
6284 // re-crumble neighbour fields, if needed
6285 if (element == EL_INVISIBLE_SAND)
6286 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6291 static void RedrawAllInvisibleElementsForLenses(void)
6295 SCAN_PLAYFIELD(x, y)
6297 int element = Tile[x][y];
6299 if (element == EL_EMC_DRIPPER &&
6300 game.lenses_time_left > 0)
6302 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6303 TEST_DrawLevelField(x, y);
6305 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6306 game.lenses_time_left == 0)
6308 Tile[x][y] = EL_EMC_DRIPPER;
6309 TEST_DrawLevelField(x, y);
6311 else if (element == EL_INVISIBLE_STEELWALL ||
6312 element == EL_INVISIBLE_WALL ||
6313 element == EL_INVISIBLE_SAND)
6315 if (game.lenses_time_left > 0)
6316 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6318 TEST_DrawLevelField(x, y);
6320 // uncrumble neighbour fields, if needed
6321 if (element == EL_INVISIBLE_SAND)
6322 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6324 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6325 element == EL_INVISIBLE_WALL_ACTIVE ||
6326 element == EL_INVISIBLE_SAND_ACTIVE)
6328 if (game.lenses_time_left == 0)
6329 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6331 TEST_DrawLevelField(x, y);
6333 // re-crumble neighbour fields, if needed
6334 if (element == EL_INVISIBLE_SAND)
6335 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6340 static void RedrawAllInvisibleElementsForMagnifier(void)
6344 SCAN_PLAYFIELD(x, y)
6346 int element = Tile[x][y];
6348 if (element == EL_EMC_FAKE_GRASS &&
6349 game.magnify_time_left > 0)
6351 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6352 TEST_DrawLevelField(x, y);
6354 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6355 game.magnify_time_left == 0)
6357 Tile[x][y] = EL_EMC_FAKE_GRASS;
6358 TEST_DrawLevelField(x, y);
6360 else if (IS_GATE_GRAY(element) &&
6361 game.magnify_time_left > 0)
6363 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6364 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6365 IS_EM_GATE_GRAY(element) ?
6366 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6367 IS_EMC_GATE_GRAY(element) ?
6368 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6369 IS_DC_GATE_GRAY(element) ?
6370 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6372 TEST_DrawLevelField(x, y);
6374 else if (IS_GATE_GRAY_ACTIVE(element) &&
6375 game.magnify_time_left == 0)
6377 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6378 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6379 IS_EM_GATE_GRAY_ACTIVE(element) ?
6380 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6381 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6382 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6383 IS_DC_GATE_GRAY_ACTIVE(element) ?
6384 EL_DC_GATE_WHITE_GRAY :
6386 TEST_DrawLevelField(x, y);
6391 static void ToggleLightSwitch(int x, int y)
6393 int element = Tile[x][y];
6395 game.light_time_left =
6396 (element == EL_LIGHT_SWITCH ?
6397 level.time_light * FRAMES_PER_SECOND : 0);
6399 RedrawAllLightSwitchesAndInvisibleElements();
6402 static void ActivateTimegateSwitch(int x, int y)
6406 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6408 SCAN_PLAYFIELD(xx, yy)
6410 int element = Tile[xx][yy];
6412 if (element == EL_TIMEGATE_CLOSED ||
6413 element == EL_TIMEGATE_CLOSING)
6415 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6416 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6420 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6422 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6423 TEST_DrawLevelField(xx, yy);
6429 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6430 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6433 static void Impact(int x, int y)
6435 boolean last_line = (y == lev_fieldy - 1);
6436 boolean object_hit = FALSE;
6437 boolean impact = (last_line || object_hit);
6438 int element = Tile[x][y];
6439 int smashed = EL_STEELWALL;
6441 if (!last_line) // check if element below was hit
6443 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6446 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6447 MovDir[x][y + 1] != MV_DOWN ||
6448 MovPos[x][y + 1] <= TILEY / 2));
6450 // do not smash moving elements that left the smashed field in time
6451 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6452 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6455 #if USE_QUICKSAND_IMPACT_BUGFIX
6456 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6458 RemoveMovingField(x, y + 1);
6459 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6460 Tile[x][y + 2] = EL_ROCK;
6461 TEST_DrawLevelField(x, y + 2);
6466 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6468 RemoveMovingField(x, y + 1);
6469 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6470 Tile[x][y + 2] = EL_ROCK;
6471 TEST_DrawLevelField(x, y + 2);
6478 smashed = MovingOrBlocked2Element(x, y + 1);
6480 impact = (last_line || object_hit);
6483 if (!last_line && smashed == EL_ACID) // element falls into acid
6485 SplashAcid(x, y + 1);
6489 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6490 // only reset graphic animation if graphic really changes after impact
6492 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6494 ResetGfxAnimation(x, y);
6495 TEST_DrawLevelField(x, y);
6498 if (impact && CAN_EXPLODE_IMPACT(element))
6503 else if (impact && element == EL_PEARL &&
6504 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6506 ResetGfxAnimation(x, y);
6508 Tile[x][y] = EL_PEARL_BREAKING;
6509 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6512 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6514 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6519 if (impact && element == EL_AMOEBA_DROP)
6521 if (object_hit && IS_PLAYER(x, y + 1))
6522 KillPlayerUnlessEnemyProtected(x, y + 1);
6523 else if (object_hit && smashed == EL_PENGUIN)
6527 Tile[x][y] = EL_AMOEBA_GROWING;
6528 Store[x][y] = EL_AMOEBA_WET;
6530 ResetRandomAnimationValue(x, y);
6535 if (object_hit) // check which object was hit
6537 if ((CAN_PASS_MAGIC_WALL(element) &&
6538 (smashed == EL_MAGIC_WALL ||
6539 smashed == EL_BD_MAGIC_WALL)) ||
6540 (CAN_PASS_DC_MAGIC_WALL(element) &&
6541 smashed == EL_DC_MAGIC_WALL))
6544 int activated_magic_wall =
6545 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6546 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6547 EL_DC_MAGIC_WALL_ACTIVE);
6549 // activate magic wall / mill
6550 SCAN_PLAYFIELD(xx, yy)
6552 if (Tile[xx][yy] == smashed)
6553 Tile[xx][yy] = activated_magic_wall;
6556 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6557 game.magic_wall_active = TRUE;
6559 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6560 SND_MAGIC_WALL_ACTIVATING :
6561 smashed == EL_BD_MAGIC_WALL ?
6562 SND_BD_MAGIC_WALL_ACTIVATING :
6563 SND_DC_MAGIC_WALL_ACTIVATING));
6566 if (IS_PLAYER(x, y + 1))
6568 if (CAN_SMASH_PLAYER(element))
6570 KillPlayerUnlessEnemyProtected(x, y + 1);
6574 else if (smashed == EL_PENGUIN)
6576 if (CAN_SMASH_PLAYER(element))
6582 else if (element == EL_BD_DIAMOND)
6584 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6590 else if (((element == EL_SP_INFOTRON ||
6591 element == EL_SP_ZONK) &&
6592 (smashed == EL_SP_SNIKSNAK ||
6593 smashed == EL_SP_ELECTRON ||
6594 smashed == EL_SP_DISK_ORANGE)) ||
6595 (element == EL_SP_INFOTRON &&
6596 smashed == EL_SP_DISK_YELLOW))
6601 else if (CAN_SMASH_EVERYTHING(element))
6603 if (IS_CLASSIC_ENEMY(smashed) ||
6604 CAN_EXPLODE_SMASHED(smashed))
6609 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6611 if (smashed == EL_LAMP ||
6612 smashed == EL_LAMP_ACTIVE)
6617 else if (smashed == EL_NUT)
6619 Tile[x][y + 1] = EL_NUT_BREAKING;
6620 PlayLevelSound(x, y, SND_NUT_BREAKING);
6621 RaiseScoreElement(EL_NUT);
6624 else if (smashed == EL_PEARL)
6626 ResetGfxAnimation(x, y);
6628 Tile[x][y + 1] = EL_PEARL_BREAKING;
6629 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6632 else if (smashed == EL_DIAMOND)
6634 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6635 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6638 else if (IS_BELT_SWITCH(smashed))
6640 ToggleBeltSwitch(x, y + 1);
6642 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6643 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6644 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6645 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6647 ToggleSwitchgateSwitch(x, y + 1);
6649 else if (smashed == EL_LIGHT_SWITCH ||
6650 smashed == EL_LIGHT_SWITCH_ACTIVE)
6652 ToggleLightSwitch(x, y + 1);
6656 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6658 CheckElementChangeBySide(x, y + 1, smashed, element,
6659 CE_SWITCHED, CH_SIDE_TOP);
6660 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6666 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6671 // play sound of magic wall / mill
6673 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6674 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6675 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6677 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6678 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6679 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6680 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6681 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6682 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6687 // play sound of object that hits the ground
6688 if (last_line || object_hit)
6689 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6692 static void TurnRoundExt(int x, int y)
6704 { 0, 0 }, { 0, 0 }, { 0, 0 },
6709 int left, right, back;
6713 { MV_DOWN, MV_UP, MV_RIGHT },
6714 { MV_UP, MV_DOWN, MV_LEFT },
6716 { MV_LEFT, MV_RIGHT, MV_DOWN },
6720 { MV_RIGHT, MV_LEFT, MV_UP }
6723 int element = Tile[x][y];
6724 int move_pattern = element_info[element].move_pattern;
6726 int old_move_dir = MovDir[x][y];
6727 int left_dir = turn[old_move_dir].left;
6728 int right_dir = turn[old_move_dir].right;
6729 int back_dir = turn[old_move_dir].back;
6731 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6732 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6733 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6734 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6736 int left_x = x + left_dx, left_y = y + left_dy;
6737 int right_x = x + right_dx, right_y = y + right_dy;
6738 int move_x = x + move_dx, move_y = y + move_dy;
6742 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6744 TestIfBadThingTouchesOtherBadThing(x, y);
6746 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6747 MovDir[x][y] = right_dir;
6748 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6749 MovDir[x][y] = left_dir;
6751 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6753 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6756 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6758 TestIfBadThingTouchesOtherBadThing(x, y);
6760 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6761 MovDir[x][y] = left_dir;
6762 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6763 MovDir[x][y] = right_dir;
6765 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6767 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6770 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6772 TestIfBadThingTouchesOtherBadThing(x, y);
6774 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6775 MovDir[x][y] = left_dir;
6776 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6777 MovDir[x][y] = right_dir;
6779 if (MovDir[x][y] != old_move_dir)
6782 else if (element == EL_YAMYAM)
6784 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6785 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6787 if (can_turn_left && can_turn_right)
6788 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6789 else if (can_turn_left)
6790 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6791 else if (can_turn_right)
6792 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6794 MovDir[x][y] = back_dir;
6796 MovDelay[x][y] = 16 + 16 * RND(3);
6798 else if (element == EL_DARK_YAMYAM)
6800 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6802 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6805 if (can_turn_left && can_turn_right)
6806 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6807 else if (can_turn_left)
6808 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6809 else if (can_turn_right)
6810 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6812 MovDir[x][y] = back_dir;
6814 MovDelay[x][y] = 16 + 16 * RND(3);
6816 else if (element == EL_PACMAN)
6818 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6819 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6821 if (can_turn_left && can_turn_right)
6822 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6823 else if (can_turn_left)
6824 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6825 else if (can_turn_right)
6826 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6828 MovDir[x][y] = back_dir;
6830 MovDelay[x][y] = 6 + RND(40);
6832 else if (element == EL_PIG)
6834 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6835 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6836 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6837 boolean should_turn_left, should_turn_right, should_move_on;
6839 int rnd = RND(rnd_value);
6841 should_turn_left = (can_turn_left &&
6843 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6844 y + back_dy + left_dy)));
6845 should_turn_right = (can_turn_right &&
6847 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6848 y + back_dy + right_dy)));
6849 should_move_on = (can_move_on &&
6852 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6853 y + move_dy + left_dy) ||
6854 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6855 y + move_dy + right_dy)));
6857 if (should_turn_left || should_turn_right || should_move_on)
6859 if (should_turn_left && should_turn_right && should_move_on)
6860 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6861 rnd < 2 * rnd_value / 3 ? right_dir :
6863 else if (should_turn_left && should_turn_right)
6864 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6865 else if (should_turn_left && should_move_on)
6866 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6867 else if (should_turn_right && should_move_on)
6868 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6869 else if (should_turn_left)
6870 MovDir[x][y] = left_dir;
6871 else if (should_turn_right)
6872 MovDir[x][y] = right_dir;
6873 else if (should_move_on)
6874 MovDir[x][y] = old_move_dir;
6876 else if (can_move_on && rnd > rnd_value / 8)
6877 MovDir[x][y] = old_move_dir;
6878 else if (can_turn_left && can_turn_right)
6879 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6880 else if (can_turn_left && rnd > rnd_value / 8)
6881 MovDir[x][y] = left_dir;
6882 else if (can_turn_right && rnd > rnd_value/8)
6883 MovDir[x][y] = right_dir;
6885 MovDir[x][y] = back_dir;
6887 xx = x + move_xy[MovDir[x][y]].dx;
6888 yy = y + move_xy[MovDir[x][y]].dy;
6890 if (!IN_LEV_FIELD(xx, yy) ||
6891 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6892 MovDir[x][y] = old_move_dir;
6896 else if (element == EL_DRAGON)
6898 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6899 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6900 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6902 int rnd = RND(rnd_value);
6904 if (can_move_on && rnd > rnd_value / 8)
6905 MovDir[x][y] = old_move_dir;
6906 else if (can_turn_left && can_turn_right)
6907 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6908 else if (can_turn_left && rnd > rnd_value / 8)
6909 MovDir[x][y] = left_dir;
6910 else if (can_turn_right && rnd > rnd_value / 8)
6911 MovDir[x][y] = right_dir;
6913 MovDir[x][y] = back_dir;
6915 xx = x + move_xy[MovDir[x][y]].dx;
6916 yy = y + move_xy[MovDir[x][y]].dy;
6918 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6919 MovDir[x][y] = old_move_dir;
6923 else if (element == EL_MOLE)
6925 boolean can_move_on =
6926 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6927 IS_AMOEBOID(Tile[move_x][move_y]) ||
6928 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6931 boolean can_turn_left =
6932 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6933 IS_AMOEBOID(Tile[left_x][left_y])));
6935 boolean can_turn_right =
6936 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6937 IS_AMOEBOID(Tile[right_x][right_y])));
6939 if (can_turn_left && can_turn_right)
6940 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6941 else if (can_turn_left)
6942 MovDir[x][y] = left_dir;
6944 MovDir[x][y] = right_dir;
6947 if (MovDir[x][y] != old_move_dir)
6950 else if (element == EL_BALLOON)
6952 MovDir[x][y] = game.wind_direction;
6955 else if (element == EL_SPRING)
6957 if (MovDir[x][y] & MV_HORIZONTAL)
6959 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6960 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6962 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6963 ResetGfxAnimation(move_x, move_y);
6964 TEST_DrawLevelField(move_x, move_y);
6966 MovDir[x][y] = back_dir;
6968 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6969 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6970 MovDir[x][y] = MV_NONE;
6975 else if (element == EL_ROBOT ||
6976 element == EL_SATELLITE ||
6977 element == EL_PENGUIN ||
6978 element == EL_EMC_ANDROID)
6980 int attr_x = -1, attr_y = -1;
6982 if (game.all_players_gone)
6984 attr_x = game.exit_x;
6985 attr_y = game.exit_y;
6991 for (i = 0; i < MAX_PLAYERS; i++)
6993 struct PlayerInfo *player = &stored_player[i];
6994 int jx = player->jx, jy = player->jy;
6996 if (!player->active)
7000 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7008 if (element == EL_ROBOT &&
7009 game.robot_wheel_x >= 0 &&
7010 game.robot_wheel_y >= 0 &&
7011 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7012 game.engine_version < VERSION_IDENT(3,1,0,0)))
7014 attr_x = game.robot_wheel_x;
7015 attr_y = game.robot_wheel_y;
7018 if (element == EL_PENGUIN)
7021 static int xy[4][2] =
7029 for (i = 0; i < NUM_DIRECTIONS; i++)
7031 int ex = x + xy[i][0];
7032 int ey = y + xy[i][1];
7034 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7035 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7036 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7037 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7046 MovDir[x][y] = MV_NONE;
7048 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7049 else if (attr_x > x)
7050 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7052 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7053 else if (attr_y > y)
7054 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7056 if (element == EL_ROBOT)
7060 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7061 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7062 Moving2Blocked(x, y, &newx, &newy);
7064 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7065 MovDelay[x][y] = 8 + 8 * !RND(3);
7067 MovDelay[x][y] = 16;
7069 else if (element == EL_PENGUIN)
7075 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7077 boolean first_horiz = RND(2);
7078 int new_move_dir = MovDir[x][y];
7081 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7082 Moving2Blocked(x, y, &newx, &newy);
7084 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7088 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7089 Moving2Blocked(x, y, &newx, &newy);
7091 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7094 MovDir[x][y] = old_move_dir;
7098 else if (element == EL_SATELLITE)
7104 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7106 boolean first_horiz = RND(2);
7107 int new_move_dir = MovDir[x][y];
7110 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7111 Moving2Blocked(x, y, &newx, &newy);
7113 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7117 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7118 Moving2Blocked(x, y, &newx, &newy);
7120 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7123 MovDir[x][y] = old_move_dir;
7127 else if (element == EL_EMC_ANDROID)
7129 static int check_pos[16] =
7131 -1, // 0 => (invalid)
7134 -1, // 3 => (invalid)
7136 0, // 5 => MV_LEFT | MV_UP
7137 2, // 6 => MV_RIGHT | MV_UP
7138 -1, // 7 => (invalid)
7140 6, // 9 => MV_LEFT | MV_DOWN
7141 4, // 10 => MV_RIGHT | MV_DOWN
7142 -1, // 11 => (invalid)
7143 -1, // 12 => (invalid)
7144 -1, // 13 => (invalid)
7145 -1, // 14 => (invalid)
7146 -1, // 15 => (invalid)
7154 { -1, -1, MV_LEFT | MV_UP },
7156 { +1, -1, MV_RIGHT | MV_UP },
7157 { +1, 0, MV_RIGHT },
7158 { +1, +1, MV_RIGHT | MV_DOWN },
7160 { -1, +1, MV_LEFT | MV_DOWN },
7163 int start_pos, check_order;
7164 boolean can_clone = FALSE;
7167 // check if there is any free field around current position
7168 for (i = 0; i < 8; i++)
7170 int newx = x + check_xy[i].dx;
7171 int newy = y + check_xy[i].dy;
7173 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7181 if (can_clone) // randomly find an element to clone
7185 start_pos = check_pos[RND(8)];
7186 check_order = (RND(2) ? -1 : +1);
7188 for (i = 0; i < 8; i++)
7190 int pos_raw = start_pos + i * check_order;
7191 int pos = (pos_raw + 8) % 8;
7192 int newx = x + check_xy[pos].dx;
7193 int newy = y + check_xy[pos].dy;
7195 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7197 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7198 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7200 Store[x][y] = Tile[newx][newy];
7209 if (can_clone) // randomly find a direction to move
7213 start_pos = check_pos[RND(8)];
7214 check_order = (RND(2) ? -1 : +1);
7216 for (i = 0; i < 8; i++)
7218 int pos_raw = start_pos + i * check_order;
7219 int pos = (pos_raw + 8) % 8;
7220 int newx = x + check_xy[pos].dx;
7221 int newy = y + check_xy[pos].dy;
7222 int new_move_dir = check_xy[pos].dir;
7224 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7226 MovDir[x][y] = new_move_dir;
7227 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7236 if (can_clone) // cloning and moving successful
7239 // cannot clone -- try to move towards player
7241 start_pos = check_pos[MovDir[x][y] & 0x0f];
7242 check_order = (RND(2) ? -1 : +1);
7244 for (i = 0; i < 3; i++)
7246 // first check start_pos, then previous/next or (next/previous) pos
7247 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7248 int pos = (pos_raw + 8) % 8;
7249 int newx = x + check_xy[pos].dx;
7250 int newy = y + check_xy[pos].dy;
7251 int new_move_dir = check_xy[pos].dir;
7253 if (IS_PLAYER(newx, newy))
7256 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7258 MovDir[x][y] = new_move_dir;
7259 MovDelay[x][y] = level.android_move_time * 8 + 1;
7266 else if (move_pattern == MV_TURNING_LEFT ||
7267 move_pattern == MV_TURNING_RIGHT ||
7268 move_pattern == MV_TURNING_LEFT_RIGHT ||
7269 move_pattern == MV_TURNING_RIGHT_LEFT ||
7270 move_pattern == MV_TURNING_RANDOM ||
7271 move_pattern == MV_ALL_DIRECTIONS)
7273 boolean can_turn_left =
7274 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7275 boolean can_turn_right =
7276 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7278 if (element_info[element].move_stepsize == 0) // "not moving"
7281 if (move_pattern == MV_TURNING_LEFT)
7282 MovDir[x][y] = left_dir;
7283 else if (move_pattern == MV_TURNING_RIGHT)
7284 MovDir[x][y] = right_dir;
7285 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7286 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7287 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7288 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7289 else if (move_pattern == MV_TURNING_RANDOM)
7290 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7291 can_turn_right && !can_turn_left ? right_dir :
7292 RND(2) ? left_dir : right_dir);
7293 else if (can_turn_left && can_turn_right)
7294 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7295 else if (can_turn_left)
7296 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7297 else if (can_turn_right)
7298 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7300 MovDir[x][y] = back_dir;
7302 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7304 else if (move_pattern == MV_HORIZONTAL ||
7305 move_pattern == MV_VERTICAL)
7307 if (move_pattern & old_move_dir)
7308 MovDir[x][y] = back_dir;
7309 else if (move_pattern == MV_HORIZONTAL)
7310 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7311 else if (move_pattern == MV_VERTICAL)
7312 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7314 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7316 else if (move_pattern & MV_ANY_DIRECTION)
7318 MovDir[x][y] = move_pattern;
7319 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7321 else if (move_pattern & MV_WIND_DIRECTION)
7323 MovDir[x][y] = game.wind_direction;
7324 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7326 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7328 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7329 MovDir[x][y] = left_dir;
7330 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7331 MovDir[x][y] = right_dir;
7333 if (MovDir[x][y] != old_move_dir)
7334 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7336 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7338 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7339 MovDir[x][y] = right_dir;
7340 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7341 MovDir[x][y] = left_dir;
7343 if (MovDir[x][y] != old_move_dir)
7344 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7346 else if (move_pattern == MV_TOWARDS_PLAYER ||
7347 move_pattern == MV_AWAY_FROM_PLAYER)
7349 int attr_x = -1, attr_y = -1;
7351 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7353 if (game.all_players_gone)
7355 attr_x = game.exit_x;
7356 attr_y = game.exit_y;
7362 for (i = 0; i < MAX_PLAYERS; i++)
7364 struct PlayerInfo *player = &stored_player[i];
7365 int jx = player->jx, jy = player->jy;
7367 if (!player->active)
7371 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7379 MovDir[x][y] = MV_NONE;
7381 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7382 else if (attr_x > x)
7383 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7385 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7386 else if (attr_y > y)
7387 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7389 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7391 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7393 boolean first_horiz = RND(2);
7394 int new_move_dir = MovDir[x][y];
7396 if (element_info[element].move_stepsize == 0) // "not moving"
7398 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7399 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7405 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7406 Moving2Blocked(x, y, &newx, &newy);
7408 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7412 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7413 Moving2Blocked(x, y, &newx, &newy);
7415 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7418 MovDir[x][y] = old_move_dir;
7421 else if (move_pattern == MV_WHEN_PUSHED ||
7422 move_pattern == MV_WHEN_DROPPED)
7424 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7425 MovDir[x][y] = MV_NONE;
7429 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7431 static int test_xy[7][2] =
7441 static int test_dir[7] =
7451 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7452 int move_preference = -1000000; // start with very low preference
7453 int new_move_dir = MV_NONE;
7454 int start_test = RND(4);
7457 for (i = 0; i < NUM_DIRECTIONS; i++)
7459 int move_dir = test_dir[start_test + i];
7460 int move_dir_preference;
7462 xx = x + test_xy[start_test + i][0];
7463 yy = y + test_xy[start_test + i][1];
7465 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7466 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7468 new_move_dir = move_dir;
7473 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7476 move_dir_preference = -1 * RunnerVisit[xx][yy];
7477 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7478 move_dir_preference = PlayerVisit[xx][yy];
7480 if (move_dir_preference > move_preference)
7482 // prefer field that has not been visited for the longest time
7483 move_preference = move_dir_preference;
7484 new_move_dir = move_dir;
7486 else if (move_dir_preference == move_preference &&
7487 move_dir == old_move_dir)
7489 // prefer last direction when all directions are preferred equally
7490 move_preference = move_dir_preference;
7491 new_move_dir = move_dir;
7495 MovDir[x][y] = new_move_dir;
7496 if (old_move_dir != new_move_dir)
7497 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7501 static void TurnRound(int x, int y)
7503 int direction = MovDir[x][y];
7507 GfxDir[x][y] = MovDir[x][y];
7509 if (direction != MovDir[x][y])
7513 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7515 ResetGfxFrame(x, y);
7518 static boolean JustBeingPushed(int x, int y)
7522 for (i = 0; i < MAX_PLAYERS; i++)
7524 struct PlayerInfo *player = &stored_player[i];
7526 if (player->active && player->is_pushing && player->MovPos)
7528 int next_jx = player->jx + (player->jx - player->last_jx);
7529 int next_jy = player->jy + (player->jy - player->last_jy);
7531 if (x == next_jx && y == next_jy)
7539 static void StartMoving(int x, int y)
7541 boolean started_moving = FALSE; // some elements can fall _and_ move
7542 int element = Tile[x][y];
7547 if (MovDelay[x][y] == 0)
7548 GfxAction[x][y] = ACTION_DEFAULT;
7550 if (CAN_FALL(element) && y < lev_fieldy - 1)
7552 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7553 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7554 if (JustBeingPushed(x, y))
7557 if (element == EL_QUICKSAND_FULL)
7559 if (IS_FREE(x, y + 1))
7561 InitMovingField(x, y, MV_DOWN);
7562 started_moving = TRUE;
7564 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7565 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7566 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7567 Store[x][y] = EL_ROCK;
7569 Store[x][y] = EL_ROCK;
7572 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7574 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7576 if (!MovDelay[x][y])
7578 MovDelay[x][y] = TILEY + 1;
7580 ResetGfxAnimation(x, y);
7581 ResetGfxAnimation(x, y + 1);
7586 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7587 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7594 Tile[x][y] = EL_QUICKSAND_EMPTY;
7595 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7596 Store[x][y + 1] = Store[x][y];
7599 PlayLevelSoundAction(x, y, ACTION_FILLING);
7601 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7603 if (!MovDelay[x][y])
7605 MovDelay[x][y] = TILEY + 1;
7607 ResetGfxAnimation(x, y);
7608 ResetGfxAnimation(x, y + 1);
7613 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7614 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7621 Tile[x][y] = EL_QUICKSAND_EMPTY;
7622 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7623 Store[x][y + 1] = Store[x][y];
7626 PlayLevelSoundAction(x, y, ACTION_FILLING);
7629 else if (element == EL_QUICKSAND_FAST_FULL)
7631 if (IS_FREE(x, y + 1))
7633 InitMovingField(x, y, MV_DOWN);
7634 started_moving = TRUE;
7636 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7637 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7638 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7639 Store[x][y] = EL_ROCK;
7641 Store[x][y] = EL_ROCK;
7644 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7646 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7648 if (!MovDelay[x][y])
7650 MovDelay[x][y] = TILEY + 1;
7652 ResetGfxAnimation(x, y);
7653 ResetGfxAnimation(x, y + 1);
7658 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7659 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7666 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7667 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7668 Store[x][y + 1] = Store[x][y];
7671 PlayLevelSoundAction(x, y, ACTION_FILLING);
7673 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7675 if (!MovDelay[x][y])
7677 MovDelay[x][y] = TILEY + 1;
7679 ResetGfxAnimation(x, y);
7680 ResetGfxAnimation(x, y + 1);
7685 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7686 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7693 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7694 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7695 Store[x][y + 1] = Store[x][y];
7698 PlayLevelSoundAction(x, y, ACTION_FILLING);
7701 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7702 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7704 InitMovingField(x, y, MV_DOWN);
7705 started_moving = TRUE;
7707 Tile[x][y] = EL_QUICKSAND_FILLING;
7708 Store[x][y] = element;
7710 PlayLevelSoundAction(x, y, ACTION_FILLING);
7712 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7713 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7715 InitMovingField(x, y, MV_DOWN);
7716 started_moving = TRUE;
7718 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7719 Store[x][y] = element;
7721 PlayLevelSoundAction(x, y, ACTION_FILLING);
7723 else if (element == EL_MAGIC_WALL_FULL)
7725 if (IS_FREE(x, y + 1))
7727 InitMovingField(x, y, MV_DOWN);
7728 started_moving = TRUE;
7730 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7731 Store[x][y] = EL_CHANGED(Store[x][y]);
7733 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7735 if (!MovDelay[x][y])
7736 MovDelay[x][y] = TILEY / 4 + 1;
7745 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7746 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7747 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7751 else if (element == EL_BD_MAGIC_WALL_FULL)
7753 if (IS_FREE(x, y + 1))
7755 InitMovingField(x, y, MV_DOWN);
7756 started_moving = TRUE;
7758 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7759 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7761 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7763 if (!MovDelay[x][y])
7764 MovDelay[x][y] = TILEY / 4 + 1;
7773 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7774 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7775 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7779 else if (element == EL_DC_MAGIC_WALL_FULL)
7781 if (IS_FREE(x, y + 1))
7783 InitMovingField(x, y, MV_DOWN);
7784 started_moving = TRUE;
7786 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7787 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7789 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7791 if (!MovDelay[x][y])
7792 MovDelay[x][y] = TILEY / 4 + 1;
7801 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7802 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7803 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7807 else if ((CAN_PASS_MAGIC_WALL(element) &&
7808 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7809 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7810 (CAN_PASS_DC_MAGIC_WALL(element) &&
7811 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7814 InitMovingField(x, y, MV_DOWN);
7815 started_moving = TRUE;
7818 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7819 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7820 EL_DC_MAGIC_WALL_FILLING);
7821 Store[x][y] = element;
7823 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7825 SplashAcid(x, y + 1);
7827 InitMovingField(x, y, MV_DOWN);
7828 started_moving = TRUE;
7830 Store[x][y] = EL_ACID;
7833 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7834 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7835 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7836 CAN_FALL(element) && WasJustFalling[x][y] &&
7837 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7839 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7840 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7841 (Tile[x][y + 1] == EL_BLOCKED)))
7843 /* this is needed for a special case not covered by calling "Impact()"
7844 from "ContinueMoving()": if an element moves to a tile directly below
7845 another element which was just falling on that tile (which was empty
7846 in the previous frame), the falling element above would just stop
7847 instead of smashing the element below (in previous version, the above
7848 element was just checked for "moving" instead of "falling", resulting
7849 in incorrect smashes caused by horizontal movement of the above
7850 element; also, the case of the player being the element to smash was
7851 simply not covered here... :-/ ) */
7853 CheckCollision[x][y] = 0;
7854 CheckImpact[x][y] = 0;
7858 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7860 if (MovDir[x][y] == MV_NONE)
7862 InitMovingField(x, y, MV_DOWN);
7863 started_moving = TRUE;
7866 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7868 if (WasJustFalling[x][y]) // prevent animation from being restarted
7869 MovDir[x][y] = MV_DOWN;
7871 InitMovingField(x, y, MV_DOWN);
7872 started_moving = TRUE;
7874 else if (element == EL_AMOEBA_DROP)
7876 Tile[x][y] = EL_AMOEBA_GROWING;
7877 Store[x][y] = EL_AMOEBA_WET;
7879 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7880 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7881 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7882 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7884 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7885 (IS_FREE(x - 1, y + 1) ||
7886 Tile[x - 1][y + 1] == EL_ACID));
7887 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7888 (IS_FREE(x + 1, y + 1) ||
7889 Tile[x + 1][y + 1] == EL_ACID));
7890 boolean can_fall_any = (can_fall_left || can_fall_right);
7891 boolean can_fall_both = (can_fall_left && can_fall_right);
7892 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7894 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7896 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7897 can_fall_right = FALSE;
7898 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7899 can_fall_left = FALSE;
7900 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7901 can_fall_right = FALSE;
7902 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7903 can_fall_left = FALSE;
7905 can_fall_any = (can_fall_left || can_fall_right);
7906 can_fall_both = FALSE;
7911 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7912 can_fall_right = FALSE; // slip down on left side
7914 can_fall_left = !(can_fall_right = RND(2));
7916 can_fall_both = FALSE;
7921 // if not determined otherwise, prefer left side for slipping down
7922 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7923 started_moving = TRUE;
7926 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7928 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7929 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7930 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7931 int belt_dir = game.belt_dir[belt_nr];
7933 if ((belt_dir == MV_LEFT && left_is_free) ||
7934 (belt_dir == MV_RIGHT && right_is_free))
7936 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7938 InitMovingField(x, y, belt_dir);
7939 started_moving = TRUE;
7941 Pushed[x][y] = TRUE;
7942 Pushed[nextx][y] = TRUE;
7944 GfxAction[x][y] = ACTION_DEFAULT;
7948 MovDir[x][y] = 0; // if element was moving, stop it
7953 // not "else if" because of elements that can fall and move (EL_SPRING)
7954 if (CAN_MOVE(element) && !started_moving)
7956 int move_pattern = element_info[element].move_pattern;
7959 Moving2Blocked(x, y, &newx, &newy);
7961 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7964 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7965 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7967 WasJustMoving[x][y] = 0;
7968 CheckCollision[x][y] = 0;
7970 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7972 if (Tile[x][y] != element) // element has changed
7976 if (!MovDelay[x][y]) // start new movement phase
7978 // all objects that can change their move direction after each step
7979 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7981 if (element != EL_YAMYAM &&
7982 element != EL_DARK_YAMYAM &&
7983 element != EL_PACMAN &&
7984 !(move_pattern & MV_ANY_DIRECTION) &&
7985 move_pattern != MV_TURNING_LEFT &&
7986 move_pattern != MV_TURNING_RIGHT &&
7987 move_pattern != MV_TURNING_LEFT_RIGHT &&
7988 move_pattern != MV_TURNING_RIGHT_LEFT &&
7989 move_pattern != MV_TURNING_RANDOM)
7993 if (MovDelay[x][y] && (element == EL_BUG ||
7994 element == EL_SPACESHIP ||
7995 element == EL_SP_SNIKSNAK ||
7996 element == EL_SP_ELECTRON ||
7997 element == EL_MOLE))
7998 TEST_DrawLevelField(x, y);
8002 if (MovDelay[x][y]) // wait some time before next movement
8006 if (element == EL_ROBOT ||
8007 element == EL_YAMYAM ||
8008 element == EL_DARK_YAMYAM)
8010 DrawLevelElementAnimationIfNeeded(x, y, element);
8011 PlayLevelSoundAction(x, y, ACTION_WAITING);
8013 else if (element == EL_SP_ELECTRON)
8014 DrawLevelElementAnimationIfNeeded(x, y, element);
8015 else if (element == EL_DRAGON)
8018 int dir = MovDir[x][y];
8019 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8020 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8021 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8022 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8023 dir == MV_UP ? IMG_FLAMES_1_UP :
8024 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8025 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8027 GfxAction[x][y] = ACTION_ATTACKING;
8029 if (IS_PLAYER(x, y))
8030 DrawPlayerField(x, y);
8032 TEST_DrawLevelField(x, y);
8034 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8036 for (i = 1; i <= 3; i++)
8038 int xx = x + i * dx;
8039 int yy = y + i * dy;
8040 int sx = SCREENX(xx);
8041 int sy = SCREENY(yy);
8042 int flame_graphic = graphic + (i - 1);
8044 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8049 int flamed = MovingOrBlocked2Element(xx, yy);
8051 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8054 RemoveMovingField(xx, yy);
8056 ChangeDelay[xx][yy] = 0;
8058 Tile[xx][yy] = EL_FLAMES;
8060 if (IN_SCR_FIELD(sx, sy))
8062 TEST_DrawLevelFieldCrumbled(xx, yy);
8063 DrawGraphic(sx, sy, flame_graphic, frame);
8068 if (Tile[xx][yy] == EL_FLAMES)
8069 Tile[xx][yy] = EL_EMPTY;
8070 TEST_DrawLevelField(xx, yy);
8075 if (MovDelay[x][y]) // element still has to wait some time
8077 PlayLevelSoundAction(x, y, ACTION_WAITING);
8083 // now make next step
8085 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8087 if (DONT_COLLIDE_WITH(element) &&
8088 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8089 !PLAYER_ENEMY_PROTECTED(newx, newy))
8091 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8096 else if (CAN_MOVE_INTO_ACID(element) &&
8097 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8098 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8099 (MovDir[x][y] == MV_DOWN ||
8100 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8102 SplashAcid(newx, newy);
8103 Store[x][y] = EL_ACID;
8105 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8107 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8108 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8109 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8110 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8113 TEST_DrawLevelField(x, y);
8115 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8116 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8117 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8119 game.friends_still_needed--;
8120 if (!game.friends_still_needed &&
8122 game.all_players_gone)
8127 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8129 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8130 TEST_DrawLevelField(newx, newy);
8132 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8134 else if (!IS_FREE(newx, newy))
8136 GfxAction[x][y] = ACTION_WAITING;
8138 if (IS_PLAYER(x, y))
8139 DrawPlayerField(x, y);
8141 TEST_DrawLevelField(x, y);
8146 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8148 if (IS_FOOD_PIG(Tile[newx][newy]))
8150 if (IS_MOVING(newx, newy))
8151 RemoveMovingField(newx, newy);
8154 Tile[newx][newy] = EL_EMPTY;
8155 TEST_DrawLevelField(newx, newy);
8158 PlayLevelSound(x, y, SND_PIG_DIGGING);
8160 else if (!IS_FREE(newx, newy))
8162 if (IS_PLAYER(x, y))
8163 DrawPlayerField(x, y);
8165 TEST_DrawLevelField(x, y);
8170 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8172 if (Store[x][y] != EL_EMPTY)
8174 boolean can_clone = FALSE;
8177 // check if element to clone is still there
8178 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8180 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8188 // cannot clone or target field not free anymore -- do not clone
8189 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8190 Store[x][y] = EL_EMPTY;
8193 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8195 if (IS_MV_DIAGONAL(MovDir[x][y]))
8197 int diagonal_move_dir = MovDir[x][y];
8198 int stored = Store[x][y];
8199 int change_delay = 8;
8202 // android is moving diagonally
8204 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8206 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8207 GfxElement[x][y] = EL_EMC_ANDROID;
8208 GfxAction[x][y] = ACTION_SHRINKING;
8209 GfxDir[x][y] = diagonal_move_dir;
8210 ChangeDelay[x][y] = change_delay;
8212 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8215 DrawLevelGraphicAnimation(x, y, graphic);
8216 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8218 if (Tile[newx][newy] == EL_ACID)
8220 SplashAcid(newx, newy);
8225 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8227 Store[newx][newy] = EL_EMC_ANDROID;
8228 GfxElement[newx][newy] = EL_EMC_ANDROID;
8229 GfxAction[newx][newy] = ACTION_GROWING;
8230 GfxDir[newx][newy] = diagonal_move_dir;
8231 ChangeDelay[newx][newy] = change_delay;
8233 graphic = el_act_dir2img(GfxElement[newx][newy],
8234 GfxAction[newx][newy], GfxDir[newx][newy]);
8236 DrawLevelGraphicAnimation(newx, newy, graphic);
8237 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8243 Tile[newx][newy] = EL_EMPTY;
8244 TEST_DrawLevelField(newx, newy);
8246 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8249 else if (!IS_FREE(newx, newy))
8254 else if (IS_CUSTOM_ELEMENT(element) &&
8255 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8257 if (!DigFieldByCE(newx, newy, element))
8260 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8262 RunnerVisit[x][y] = FrameCounter;
8263 PlayerVisit[x][y] /= 8; // expire player visit path
8266 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8268 if (!IS_FREE(newx, newy))
8270 if (IS_PLAYER(x, y))
8271 DrawPlayerField(x, y);
8273 TEST_DrawLevelField(x, y);
8279 boolean wanna_flame = !RND(10);
8280 int dx = newx - x, dy = newy - y;
8281 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8282 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8283 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8284 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8285 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8286 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8289 IS_CLASSIC_ENEMY(element1) ||
8290 IS_CLASSIC_ENEMY(element2)) &&
8291 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8292 element1 != EL_FLAMES && element2 != EL_FLAMES)
8294 ResetGfxAnimation(x, y);
8295 GfxAction[x][y] = ACTION_ATTACKING;
8297 if (IS_PLAYER(x, y))
8298 DrawPlayerField(x, y);
8300 TEST_DrawLevelField(x, y);
8302 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8304 MovDelay[x][y] = 50;
8306 Tile[newx][newy] = EL_FLAMES;
8307 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8308 Tile[newx1][newy1] = EL_FLAMES;
8309 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8310 Tile[newx2][newy2] = EL_FLAMES;
8316 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8317 Tile[newx][newy] == EL_DIAMOND)
8319 if (IS_MOVING(newx, newy))
8320 RemoveMovingField(newx, newy);
8323 Tile[newx][newy] = EL_EMPTY;
8324 TEST_DrawLevelField(newx, newy);
8327 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8329 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8330 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8332 if (AmoebaNr[newx][newy])
8334 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8335 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8336 Tile[newx][newy] == EL_BD_AMOEBA)
8337 AmoebaCnt[AmoebaNr[newx][newy]]--;
8340 if (IS_MOVING(newx, newy))
8342 RemoveMovingField(newx, newy);
8346 Tile[newx][newy] = EL_EMPTY;
8347 TEST_DrawLevelField(newx, newy);
8350 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8352 else if ((element == EL_PACMAN || element == EL_MOLE)
8353 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8355 if (AmoebaNr[newx][newy])
8357 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8358 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8359 Tile[newx][newy] == EL_BD_AMOEBA)
8360 AmoebaCnt[AmoebaNr[newx][newy]]--;
8363 if (element == EL_MOLE)
8365 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8366 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8368 ResetGfxAnimation(x, y);
8369 GfxAction[x][y] = ACTION_DIGGING;
8370 TEST_DrawLevelField(x, y);
8372 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8374 return; // wait for shrinking amoeba
8376 else // element == EL_PACMAN
8378 Tile[newx][newy] = EL_EMPTY;
8379 TEST_DrawLevelField(newx, newy);
8380 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8383 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8384 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8385 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8387 // wait for shrinking amoeba to completely disappear
8390 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8392 // object was running against a wall
8396 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8397 DrawLevelElementAnimation(x, y, element);
8399 if (DONT_TOUCH(element))
8400 TestIfBadThingTouchesPlayer(x, y);
8405 InitMovingField(x, y, MovDir[x][y]);
8407 PlayLevelSoundAction(x, y, ACTION_MOVING);
8411 ContinueMoving(x, y);
8414 void ContinueMoving(int x, int y)
8416 int element = Tile[x][y];
8417 struct ElementInfo *ei = &element_info[element];
8418 int direction = MovDir[x][y];
8419 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8420 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8421 int newx = x + dx, newy = y + dy;
8422 int stored = Store[x][y];
8423 int stored_new = Store[newx][newy];
8424 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8425 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8426 boolean last_line = (newy == lev_fieldy - 1);
8428 MovPos[x][y] += getElementMoveStepsize(x, y);
8430 if (pushed_by_player) // special case: moving object pushed by player
8431 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8433 if (ABS(MovPos[x][y]) < TILEX)
8435 TEST_DrawLevelField(x, y);
8437 return; // element is still moving
8440 // element reached destination field
8442 Tile[x][y] = EL_EMPTY;
8443 Tile[newx][newy] = element;
8444 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8446 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8448 element = Tile[newx][newy] = EL_ACID;
8450 else if (element == EL_MOLE)
8452 Tile[x][y] = EL_SAND;
8454 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8456 else if (element == EL_QUICKSAND_FILLING)
8458 element = Tile[newx][newy] = get_next_element(element);
8459 Store[newx][newy] = Store[x][y];
8461 else if (element == EL_QUICKSAND_EMPTYING)
8463 Tile[x][y] = get_next_element(element);
8464 element = Tile[newx][newy] = Store[x][y];
8466 else if (element == EL_QUICKSAND_FAST_FILLING)
8468 element = Tile[newx][newy] = get_next_element(element);
8469 Store[newx][newy] = Store[x][y];
8471 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8473 Tile[x][y] = get_next_element(element);
8474 element = Tile[newx][newy] = Store[x][y];
8476 else if (element == EL_MAGIC_WALL_FILLING)
8478 element = Tile[newx][newy] = get_next_element(element);
8479 if (!game.magic_wall_active)
8480 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8481 Store[newx][newy] = Store[x][y];
8483 else if (element == EL_MAGIC_WALL_EMPTYING)
8485 Tile[x][y] = get_next_element(element);
8486 if (!game.magic_wall_active)
8487 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8488 element = Tile[newx][newy] = Store[x][y];
8490 InitField(newx, newy, FALSE);
8492 else if (element == EL_BD_MAGIC_WALL_FILLING)
8494 element = Tile[newx][newy] = get_next_element(element);
8495 if (!game.magic_wall_active)
8496 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8497 Store[newx][newy] = Store[x][y];
8499 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8501 Tile[x][y] = get_next_element(element);
8502 if (!game.magic_wall_active)
8503 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8504 element = Tile[newx][newy] = Store[x][y];
8506 InitField(newx, newy, FALSE);
8508 else if (element == EL_DC_MAGIC_WALL_FILLING)
8510 element = Tile[newx][newy] = get_next_element(element);
8511 if (!game.magic_wall_active)
8512 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8513 Store[newx][newy] = Store[x][y];
8515 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8517 Tile[x][y] = get_next_element(element);
8518 if (!game.magic_wall_active)
8519 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8520 element = Tile[newx][newy] = Store[x][y];
8522 InitField(newx, newy, FALSE);
8524 else if (element == EL_AMOEBA_DROPPING)
8526 Tile[x][y] = get_next_element(element);
8527 element = Tile[newx][newy] = Store[x][y];
8529 else if (element == EL_SOKOBAN_OBJECT)
8532 Tile[x][y] = Back[x][y];
8534 if (Back[newx][newy])
8535 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8537 Back[x][y] = Back[newx][newy] = 0;
8540 Store[x][y] = EL_EMPTY;
8545 MovDelay[newx][newy] = 0;
8547 if (CAN_CHANGE_OR_HAS_ACTION(element))
8549 // copy element change control values to new field
8550 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8551 ChangePage[newx][newy] = ChangePage[x][y];
8552 ChangeCount[newx][newy] = ChangeCount[x][y];
8553 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8556 CustomValue[newx][newy] = CustomValue[x][y];
8558 ChangeDelay[x][y] = 0;
8559 ChangePage[x][y] = -1;
8560 ChangeCount[x][y] = 0;
8561 ChangeEvent[x][y] = -1;
8563 CustomValue[x][y] = 0;
8565 // copy animation control values to new field
8566 GfxFrame[newx][newy] = GfxFrame[x][y];
8567 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8568 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8569 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8571 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8573 // some elements can leave other elements behind after moving
8574 if (ei->move_leave_element != EL_EMPTY &&
8575 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8576 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8578 int move_leave_element = ei->move_leave_element;
8580 // this makes it possible to leave the removed element again
8581 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8582 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8584 Tile[x][y] = move_leave_element;
8586 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8587 MovDir[x][y] = direction;
8589 InitField(x, y, FALSE);
8591 if (GFX_CRUMBLED(Tile[x][y]))
8592 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8594 if (ELEM_IS_PLAYER(move_leave_element))
8595 RelocatePlayer(x, y, move_leave_element);
8598 // do this after checking for left-behind element
8599 ResetGfxAnimation(x, y); // reset animation values for old field
8601 if (!CAN_MOVE(element) ||
8602 (CAN_FALL(element) && direction == MV_DOWN &&
8603 (element == EL_SPRING ||
8604 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8605 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8606 GfxDir[x][y] = MovDir[newx][newy] = 0;
8608 TEST_DrawLevelField(x, y);
8609 TEST_DrawLevelField(newx, newy);
8611 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8613 // prevent pushed element from moving on in pushed direction
8614 if (pushed_by_player && CAN_MOVE(element) &&
8615 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8616 !(element_info[element].move_pattern & direction))
8617 TurnRound(newx, newy);
8619 // prevent elements on conveyor belt from moving on in last direction
8620 if (pushed_by_conveyor && CAN_FALL(element) &&
8621 direction & MV_HORIZONTAL)
8622 MovDir[newx][newy] = 0;
8624 if (!pushed_by_player)
8626 int nextx = newx + dx, nexty = newy + dy;
8627 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8629 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8631 if (CAN_FALL(element) && direction == MV_DOWN)
8632 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8634 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8635 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8637 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8638 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8641 if (DONT_TOUCH(element)) // object may be nasty to player or others
8643 TestIfBadThingTouchesPlayer(newx, newy);
8644 TestIfBadThingTouchesFriend(newx, newy);
8646 if (!IS_CUSTOM_ELEMENT(element))
8647 TestIfBadThingTouchesOtherBadThing(newx, newy);
8649 else if (element == EL_PENGUIN)
8650 TestIfFriendTouchesBadThing(newx, newy);
8652 if (DONT_GET_HIT_BY(element))
8654 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8657 // give the player one last chance (one more frame) to move away
8658 if (CAN_FALL(element) && direction == MV_DOWN &&
8659 (last_line || (!IS_FREE(x, newy + 1) &&
8660 (!IS_PLAYER(x, newy + 1) ||
8661 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8664 if (pushed_by_player && !game.use_change_when_pushing_bug)
8666 int push_side = MV_DIR_OPPOSITE(direction);
8667 struct PlayerInfo *player = PLAYERINFO(x, y);
8669 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8670 player->index_bit, push_side);
8671 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8672 player->index_bit, push_side);
8675 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8676 MovDelay[newx][newy] = 1;
8678 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8680 TestIfElementTouchesCustomElement(x, y); // empty or new element
8681 TestIfElementHitsCustomElement(newx, newy, direction);
8682 TestIfPlayerTouchesCustomElement(newx, newy);
8683 TestIfElementTouchesCustomElement(newx, newy);
8685 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8686 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8687 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8688 MV_DIR_OPPOSITE(direction));
8691 int AmoebaNeighbourNr(int ax, int ay)
8694 int element = Tile[ax][ay];
8696 static int xy[4][2] =
8704 for (i = 0; i < NUM_DIRECTIONS; i++)
8706 int x = ax + xy[i][0];
8707 int y = ay + xy[i][1];
8709 if (!IN_LEV_FIELD(x, y))
8712 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8713 group_nr = AmoebaNr[x][y];
8719 static void AmoebaMerge(int ax, int ay)
8721 int i, x, y, xx, yy;
8722 int new_group_nr = AmoebaNr[ax][ay];
8723 static int xy[4][2] =
8731 if (new_group_nr == 0)
8734 for (i = 0; i < NUM_DIRECTIONS; i++)
8739 if (!IN_LEV_FIELD(x, y))
8742 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8743 Tile[x][y] == EL_BD_AMOEBA ||
8744 Tile[x][y] == EL_AMOEBA_DEAD) &&
8745 AmoebaNr[x][y] != new_group_nr)
8747 int old_group_nr = AmoebaNr[x][y];
8749 if (old_group_nr == 0)
8752 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8753 AmoebaCnt[old_group_nr] = 0;
8754 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8755 AmoebaCnt2[old_group_nr] = 0;
8757 SCAN_PLAYFIELD(xx, yy)
8759 if (AmoebaNr[xx][yy] == old_group_nr)
8760 AmoebaNr[xx][yy] = new_group_nr;
8766 void AmoebaToDiamond(int ax, int ay)
8770 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8772 int group_nr = AmoebaNr[ax][ay];
8777 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8778 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8784 SCAN_PLAYFIELD(x, y)
8786 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8789 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8793 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8794 SND_AMOEBA_TURNING_TO_GEM :
8795 SND_AMOEBA_TURNING_TO_ROCK));
8800 static int xy[4][2] =
8808 for (i = 0; i < NUM_DIRECTIONS; i++)
8813 if (!IN_LEV_FIELD(x, y))
8816 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8818 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8819 SND_AMOEBA_TURNING_TO_GEM :
8820 SND_AMOEBA_TURNING_TO_ROCK));
8827 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8830 int group_nr = AmoebaNr[ax][ay];
8831 boolean done = FALSE;
8836 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8837 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8843 SCAN_PLAYFIELD(x, y)
8845 if (AmoebaNr[x][y] == group_nr &&
8846 (Tile[x][y] == EL_AMOEBA_DEAD ||
8847 Tile[x][y] == EL_BD_AMOEBA ||
8848 Tile[x][y] == EL_AMOEBA_GROWING))
8851 Tile[x][y] = new_element;
8852 InitField(x, y, FALSE);
8853 TEST_DrawLevelField(x, y);
8859 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8860 SND_BD_AMOEBA_TURNING_TO_ROCK :
8861 SND_BD_AMOEBA_TURNING_TO_GEM));
8864 static void AmoebaGrowing(int x, int y)
8866 static unsigned int sound_delay = 0;
8867 static unsigned int sound_delay_value = 0;
8869 if (!MovDelay[x][y]) // start new growing cycle
8873 if (DelayReached(&sound_delay, sound_delay_value))
8875 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8876 sound_delay_value = 30;
8880 if (MovDelay[x][y]) // wait some time before growing bigger
8883 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8885 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8886 6 - MovDelay[x][y]);
8888 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8891 if (!MovDelay[x][y])
8893 Tile[x][y] = Store[x][y];
8895 TEST_DrawLevelField(x, y);
8900 static void AmoebaShrinking(int x, int y)
8902 static unsigned int sound_delay = 0;
8903 static unsigned int sound_delay_value = 0;
8905 if (!MovDelay[x][y]) // start new shrinking cycle
8909 if (DelayReached(&sound_delay, sound_delay_value))
8910 sound_delay_value = 30;
8913 if (MovDelay[x][y]) // wait some time before shrinking
8916 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8918 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8919 6 - MovDelay[x][y]);
8921 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8924 if (!MovDelay[x][y])
8926 Tile[x][y] = EL_EMPTY;
8927 TEST_DrawLevelField(x, y);
8929 // don't let mole enter this field in this cycle;
8930 // (give priority to objects falling to this field from above)
8936 static void AmoebaReproduce(int ax, int ay)
8939 int element = Tile[ax][ay];
8940 int graphic = el2img(element);
8941 int newax = ax, neway = ay;
8942 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8943 static int xy[4][2] =
8951 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8953 Tile[ax][ay] = EL_AMOEBA_DEAD;
8954 TEST_DrawLevelField(ax, ay);
8958 if (IS_ANIMATED(graphic))
8959 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8961 if (!MovDelay[ax][ay]) // start making new amoeba field
8962 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8964 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8967 if (MovDelay[ax][ay])
8971 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8974 int x = ax + xy[start][0];
8975 int y = ay + xy[start][1];
8977 if (!IN_LEV_FIELD(x, y))
8980 if (IS_FREE(x, y) ||
8981 CAN_GROW_INTO(Tile[x][y]) ||
8982 Tile[x][y] == EL_QUICKSAND_EMPTY ||
8983 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
8989 if (newax == ax && neway == ay)
8992 else // normal or "filled" (BD style) amoeba
8995 boolean waiting_for_player = FALSE;
8997 for (i = 0; i < NUM_DIRECTIONS; i++)
8999 int j = (start + i) % 4;
9000 int x = ax + xy[j][0];
9001 int y = ay + xy[j][1];
9003 if (!IN_LEV_FIELD(x, y))
9006 if (IS_FREE(x, y) ||
9007 CAN_GROW_INTO(Tile[x][y]) ||
9008 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9009 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9015 else if (IS_PLAYER(x, y))
9016 waiting_for_player = TRUE;
9019 if (newax == ax && neway == ay) // amoeba cannot grow
9021 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9023 Tile[ax][ay] = EL_AMOEBA_DEAD;
9024 TEST_DrawLevelField(ax, ay);
9025 AmoebaCnt[AmoebaNr[ax][ay]]--;
9027 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9029 if (element == EL_AMOEBA_FULL)
9030 AmoebaToDiamond(ax, ay);
9031 else if (element == EL_BD_AMOEBA)
9032 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9037 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9039 // amoeba gets larger by growing in some direction
9041 int new_group_nr = AmoebaNr[ax][ay];
9044 if (new_group_nr == 0)
9046 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9048 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9054 AmoebaNr[newax][neway] = new_group_nr;
9055 AmoebaCnt[new_group_nr]++;
9056 AmoebaCnt2[new_group_nr]++;
9058 // if amoeba touches other amoeba(s) after growing, unify them
9059 AmoebaMerge(newax, neway);
9061 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9063 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9069 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9070 (neway == lev_fieldy - 1 && newax != ax))
9072 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9073 Store[newax][neway] = element;
9075 else if (neway == ay || element == EL_EMC_DRIPPER)
9077 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9079 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9083 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9084 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9085 Store[ax][ay] = EL_AMOEBA_DROP;
9086 ContinueMoving(ax, ay);
9090 TEST_DrawLevelField(newax, neway);
9093 static void Life(int ax, int ay)
9097 int element = Tile[ax][ay];
9098 int graphic = el2img(element);
9099 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9101 boolean changed = FALSE;
9103 if (IS_ANIMATED(graphic))
9104 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9109 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9110 MovDelay[ax][ay] = life_time;
9112 if (MovDelay[ax][ay]) // wait some time before next cycle
9115 if (MovDelay[ax][ay])
9119 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9121 int xx = ax+x1, yy = ay+y1;
9122 int old_element = Tile[xx][yy];
9123 int num_neighbours = 0;
9125 if (!IN_LEV_FIELD(xx, yy))
9128 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9130 int x = xx+x2, y = yy+y2;
9132 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9135 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9136 boolean is_neighbour = FALSE;
9138 if (level.use_life_bugs)
9140 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9141 (IS_FREE(x, y) && Stop[x][y]));
9144 (Last[x][y] == element || is_player_cell);
9150 boolean is_free = FALSE;
9152 if (level.use_life_bugs)
9153 is_free = (IS_FREE(xx, yy));
9155 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9157 if (xx == ax && yy == ay) // field in the middle
9159 if (num_neighbours < life_parameter[0] ||
9160 num_neighbours > life_parameter[1])
9162 Tile[xx][yy] = EL_EMPTY;
9163 if (Tile[xx][yy] != old_element)
9164 TEST_DrawLevelField(xx, yy);
9165 Stop[xx][yy] = TRUE;
9169 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9170 { // free border field
9171 if (num_neighbours >= life_parameter[2] &&
9172 num_neighbours <= life_parameter[3])
9174 Tile[xx][yy] = element;
9175 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9176 if (Tile[xx][yy] != old_element)
9177 TEST_DrawLevelField(xx, yy);
9178 Stop[xx][yy] = TRUE;
9185 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9186 SND_GAME_OF_LIFE_GROWING);
9189 static void InitRobotWheel(int x, int y)
9191 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9194 static void RunRobotWheel(int x, int y)
9196 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9199 static void StopRobotWheel(int x, int y)
9201 if (game.robot_wheel_x == x &&
9202 game.robot_wheel_y == y)
9204 game.robot_wheel_x = -1;
9205 game.robot_wheel_y = -1;
9206 game.robot_wheel_active = FALSE;
9210 static void InitTimegateWheel(int x, int y)
9212 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9215 static void RunTimegateWheel(int x, int y)
9217 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9220 static void InitMagicBallDelay(int x, int y)
9222 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9225 static void ActivateMagicBall(int bx, int by)
9229 if (level.ball_random)
9231 int pos_border = RND(8); // select one of the eight border elements
9232 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9233 int xx = pos_content % 3;
9234 int yy = pos_content / 3;
9239 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9240 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9244 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9246 int xx = x - bx + 1;
9247 int yy = y - by + 1;
9249 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9250 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9254 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9257 static void CheckExit(int x, int y)
9259 if (game.gems_still_needed > 0 ||
9260 game.sokoban_fields_still_needed > 0 ||
9261 game.sokoban_objects_still_needed > 0 ||
9262 game.lights_still_needed > 0)
9264 int element = Tile[x][y];
9265 int graphic = el2img(element);
9267 if (IS_ANIMATED(graphic))
9268 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9273 // do not re-open exit door closed after last player
9274 if (game.all_players_gone)
9277 Tile[x][y] = EL_EXIT_OPENING;
9279 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9282 static void CheckExitEM(int x, int y)
9284 if (game.gems_still_needed > 0 ||
9285 game.sokoban_fields_still_needed > 0 ||
9286 game.sokoban_objects_still_needed > 0 ||
9287 game.lights_still_needed > 0)
9289 int element = Tile[x][y];
9290 int graphic = el2img(element);
9292 if (IS_ANIMATED(graphic))
9293 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9298 // do not re-open exit door closed after last player
9299 if (game.all_players_gone)
9302 Tile[x][y] = EL_EM_EXIT_OPENING;
9304 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9307 static void CheckExitSteel(int x, int y)
9309 if (game.gems_still_needed > 0 ||
9310 game.sokoban_fields_still_needed > 0 ||
9311 game.sokoban_objects_still_needed > 0 ||
9312 game.lights_still_needed > 0)
9314 int element = Tile[x][y];
9315 int graphic = el2img(element);
9317 if (IS_ANIMATED(graphic))
9318 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9323 // do not re-open exit door closed after last player
9324 if (game.all_players_gone)
9327 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9329 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9332 static void CheckExitSteelEM(int x, int y)
9334 if (game.gems_still_needed > 0 ||
9335 game.sokoban_fields_still_needed > 0 ||
9336 game.sokoban_objects_still_needed > 0 ||
9337 game.lights_still_needed > 0)
9339 int element = Tile[x][y];
9340 int graphic = el2img(element);
9342 if (IS_ANIMATED(graphic))
9343 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9348 // do not re-open exit door closed after last player
9349 if (game.all_players_gone)
9352 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9354 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9357 static void CheckExitSP(int x, int y)
9359 if (game.gems_still_needed > 0)
9361 int element = Tile[x][y];
9362 int graphic = el2img(element);
9364 if (IS_ANIMATED(graphic))
9365 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9370 // do not re-open exit door closed after last player
9371 if (game.all_players_gone)
9374 Tile[x][y] = EL_SP_EXIT_OPENING;
9376 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9379 static void CloseAllOpenTimegates(void)
9383 SCAN_PLAYFIELD(x, y)
9385 int element = Tile[x][y];
9387 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9389 Tile[x][y] = EL_TIMEGATE_CLOSING;
9391 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9396 static void DrawTwinkleOnField(int x, int y)
9398 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9401 if (Tile[x][y] == EL_BD_DIAMOND)
9404 if (MovDelay[x][y] == 0) // next animation frame
9405 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9407 if (MovDelay[x][y] != 0) // wait some time before next frame
9411 DrawLevelElementAnimation(x, y, Tile[x][y]);
9413 if (MovDelay[x][y] != 0)
9415 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9416 10 - MovDelay[x][y]);
9418 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9423 static void MauerWaechst(int x, int y)
9427 if (!MovDelay[x][y]) // next animation frame
9428 MovDelay[x][y] = 3 * delay;
9430 if (MovDelay[x][y]) // wait some time before next frame
9434 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9436 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9437 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9439 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9442 if (!MovDelay[x][y])
9444 if (MovDir[x][y] == MV_LEFT)
9446 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9447 TEST_DrawLevelField(x - 1, y);
9449 else if (MovDir[x][y] == MV_RIGHT)
9451 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9452 TEST_DrawLevelField(x + 1, y);
9454 else if (MovDir[x][y] == MV_UP)
9456 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9457 TEST_DrawLevelField(x, y - 1);
9461 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9462 TEST_DrawLevelField(x, y + 1);
9465 Tile[x][y] = Store[x][y];
9467 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9468 TEST_DrawLevelField(x, y);
9473 static void MauerAbleger(int ax, int ay)
9475 int element = Tile[ax][ay];
9476 int graphic = el2img(element);
9477 boolean oben_frei = FALSE, unten_frei = FALSE;
9478 boolean links_frei = FALSE, rechts_frei = FALSE;
9479 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9480 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9481 boolean new_wall = FALSE;
9483 if (IS_ANIMATED(graphic))
9484 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9486 if (!MovDelay[ax][ay]) // start building new wall
9487 MovDelay[ax][ay] = 6;
9489 if (MovDelay[ax][ay]) // wait some time before building new wall
9492 if (MovDelay[ax][ay])
9496 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9498 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9500 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9502 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9505 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9506 element == EL_EXPANDABLE_WALL_ANY)
9510 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9511 Store[ax][ay-1] = element;
9512 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9513 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9514 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9515 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9520 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9521 Store[ax][ay+1] = element;
9522 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9523 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9524 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9525 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9530 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9531 element == EL_EXPANDABLE_WALL_ANY ||
9532 element == EL_EXPANDABLE_WALL ||
9533 element == EL_BD_EXPANDABLE_WALL)
9537 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9538 Store[ax-1][ay] = element;
9539 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9540 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9541 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9542 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9548 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9549 Store[ax+1][ay] = element;
9550 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9551 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9552 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9553 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9558 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9559 TEST_DrawLevelField(ax, ay);
9561 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9563 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9564 unten_massiv = TRUE;
9565 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9566 links_massiv = TRUE;
9567 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9568 rechts_massiv = TRUE;
9570 if (((oben_massiv && unten_massiv) ||
9571 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9572 element == EL_EXPANDABLE_WALL) &&
9573 ((links_massiv && rechts_massiv) ||
9574 element == EL_EXPANDABLE_WALL_VERTICAL))
9575 Tile[ax][ay] = EL_WALL;
9578 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9581 static void MauerAblegerStahl(int ax, int ay)
9583 int element = Tile[ax][ay];
9584 int graphic = el2img(element);
9585 boolean oben_frei = FALSE, unten_frei = FALSE;
9586 boolean links_frei = FALSE, rechts_frei = FALSE;
9587 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9588 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9589 boolean new_wall = FALSE;
9591 if (IS_ANIMATED(graphic))
9592 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9594 if (!MovDelay[ax][ay]) // start building new wall
9595 MovDelay[ax][ay] = 6;
9597 if (MovDelay[ax][ay]) // wait some time before building new wall
9600 if (MovDelay[ax][ay])
9604 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9606 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9608 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9610 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9613 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9614 element == EL_EXPANDABLE_STEELWALL_ANY)
9618 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9619 Store[ax][ay-1] = element;
9620 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9621 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9622 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9623 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9628 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9629 Store[ax][ay+1] = element;
9630 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9631 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9632 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9633 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9638 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9639 element == EL_EXPANDABLE_STEELWALL_ANY)
9643 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9644 Store[ax-1][ay] = element;
9645 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9646 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9647 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9648 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9654 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9655 Store[ax+1][ay] = element;
9656 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9657 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9658 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9659 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9664 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9666 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9667 unten_massiv = TRUE;
9668 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9669 links_massiv = TRUE;
9670 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9671 rechts_massiv = TRUE;
9673 if (((oben_massiv && unten_massiv) ||
9674 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9675 ((links_massiv && rechts_massiv) ||
9676 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9677 Tile[ax][ay] = EL_STEELWALL;
9680 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9683 static void CheckForDragon(int x, int y)
9686 boolean dragon_found = FALSE;
9687 static int xy[4][2] =
9695 for (i = 0; i < NUM_DIRECTIONS; i++)
9697 for (j = 0; j < 4; j++)
9699 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9701 if (IN_LEV_FIELD(xx, yy) &&
9702 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9704 if (Tile[xx][yy] == EL_DRAGON)
9705 dragon_found = TRUE;
9714 for (i = 0; i < NUM_DIRECTIONS; i++)
9716 for (j = 0; j < 3; j++)
9718 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9720 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9722 Tile[xx][yy] = EL_EMPTY;
9723 TEST_DrawLevelField(xx, yy);
9732 static void InitBuggyBase(int x, int y)
9734 int element = Tile[x][y];
9735 int activating_delay = FRAMES_PER_SECOND / 4;
9738 (element == EL_SP_BUGGY_BASE ?
9739 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9740 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9742 element == EL_SP_BUGGY_BASE_ACTIVE ?
9743 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9746 static void WarnBuggyBase(int x, int y)
9749 static int xy[4][2] =
9757 for (i = 0; i < NUM_DIRECTIONS; i++)
9759 int xx = x + xy[i][0];
9760 int yy = y + xy[i][1];
9762 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9764 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9771 static void InitTrap(int x, int y)
9773 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9776 static void ActivateTrap(int x, int y)
9778 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9781 static void ChangeActiveTrap(int x, int y)
9783 int graphic = IMG_TRAP_ACTIVE;
9785 // if new animation frame was drawn, correct crumbled sand border
9786 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9787 TEST_DrawLevelFieldCrumbled(x, y);
9790 static int getSpecialActionElement(int element, int number, int base_element)
9792 return (element != EL_EMPTY ? element :
9793 number != -1 ? base_element + number - 1 :
9797 static int getModifiedActionNumber(int value_old, int operator, int operand,
9798 int value_min, int value_max)
9800 int value_new = (operator == CA_MODE_SET ? operand :
9801 operator == CA_MODE_ADD ? value_old + operand :
9802 operator == CA_MODE_SUBTRACT ? value_old - operand :
9803 operator == CA_MODE_MULTIPLY ? value_old * operand :
9804 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9805 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9808 return (value_new < value_min ? value_min :
9809 value_new > value_max ? value_max :
9813 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9815 struct ElementInfo *ei = &element_info[element];
9816 struct ElementChangeInfo *change = &ei->change_page[page];
9817 int target_element = change->target_element;
9818 int action_type = change->action_type;
9819 int action_mode = change->action_mode;
9820 int action_arg = change->action_arg;
9821 int action_element = change->action_element;
9824 if (!change->has_action)
9827 // ---------- determine action paramater values -----------------------------
9829 int level_time_value =
9830 (level.time > 0 ? TimeLeft :
9833 int action_arg_element_raw =
9834 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9835 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9836 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9837 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9838 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9839 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9840 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9842 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9844 int action_arg_direction =
9845 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9846 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9847 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9848 change->actual_trigger_side :
9849 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9850 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9853 int action_arg_number_min =
9854 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9857 int action_arg_number_max =
9858 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9859 action_type == CA_SET_LEVEL_GEMS ? 999 :
9860 action_type == CA_SET_LEVEL_TIME ? 9999 :
9861 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9862 action_type == CA_SET_CE_VALUE ? 9999 :
9863 action_type == CA_SET_CE_SCORE ? 9999 :
9866 int action_arg_number_reset =
9867 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9868 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9869 action_type == CA_SET_LEVEL_TIME ? level.time :
9870 action_type == CA_SET_LEVEL_SCORE ? 0 :
9871 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9872 action_type == CA_SET_CE_SCORE ? 0 :
9875 int action_arg_number =
9876 (action_arg <= CA_ARG_MAX ? action_arg :
9877 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9878 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9879 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9880 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9881 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9882 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9883 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9884 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9885 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9886 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9887 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9888 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9889 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9890 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9891 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9892 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9893 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9894 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9895 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9896 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9897 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9900 int action_arg_number_old =
9901 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9902 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9903 action_type == CA_SET_LEVEL_SCORE ? game.score :
9904 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9905 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9908 int action_arg_number_new =
9909 getModifiedActionNumber(action_arg_number_old,
9910 action_mode, action_arg_number,
9911 action_arg_number_min, action_arg_number_max);
9913 int trigger_player_bits =
9914 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9915 change->actual_trigger_player_bits : change->trigger_player);
9917 int action_arg_player_bits =
9918 (action_arg >= CA_ARG_PLAYER_1 &&
9919 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9920 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9921 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9924 // ---------- execute action -----------------------------------------------
9926 switch (action_type)
9933 // ---------- level actions ----------------------------------------------
9935 case CA_RESTART_LEVEL:
9937 game.restart_level = TRUE;
9942 case CA_SHOW_ENVELOPE:
9944 int element = getSpecialActionElement(action_arg_element,
9945 action_arg_number, EL_ENVELOPE_1);
9947 if (IS_ENVELOPE(element))
9948 local_player->show_envelope = element;
9953 case CA_SET_LEVEL_TIME:
9955 if (level.time > 0) // only modify limited time value
9957 TimeLeft = action_arg_number_new;
9959 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9961 DisplayGameControlValues();
9963 if (!TimeLeft && setup.time_limit)
9964 for (i = 0; i < MAX_PLAYERS; i++)
9965 KillPlayer(&stored_player[i]);
9971 case CA_SET_LEVEL_SCORE:
9973 game.score = action_arg_number_new;
9975 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9977 DisplayGameControlValues();
9982 case CA_SET_LEVEL_GEMS:
9984 game.gems_still_needed = action_arg_number_new;
9986 game.snapshot.collected_item = TRUE;
9988 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9990 DisplayGameControlValues();
9995 case CA_SET_LEVEL_WIND:
9997 game.wind_direction = action_arg_direction;
10002 case CA_SET_LEVEL_RANDOM_SEED:
10004 // ensure that setting a new random seed while playing is predictable
10005 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10010 // ---------- player actions ---------------------------------------------
10012 case CA_MOVE_PLAYER:
10013 case CA_MOVE_PLAYER_NEW:
10015 // automatically move to the next field in specified direction
10016 for (i = 0; i < MAX_PLAYERS; i++)
10017 if (trigger_player_bits & (1 << i))
10018 if (action_type == CA_MOVE_PLAYER ||
10019 stored_player[i].MovPos == 0)
10020 stored_player[i].programmed_action = action_arg_direction;
10025 case CA_EXIT_PLAYER:
10027 for (i = 0; i < MAX_PLAYERS; i++)
10028 if (action_arg_player_bits & (1 << i))
10029 ExitPlayer(&stored_player[i]);
10031 if (game.players_still_needed == 0)
10037 case CA_KILL_PLAYER:
10039 for (i = 0; i < MAX_PLAYERS; i++)
10040 if (action_arg_player_bits & (1 << i))
10041 KillPlayer(&stored_player[i]);
10046 case CA_SET_PLAYER_KEYS:
10048 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10049 int element = getSpecialActionElement(action_arg_element,
10050 action_arg_number, EL_KEY_1);
10052 if (IS_KEY(element))
10054 for (i = 0; i < MAX_PLAYERS; i++)
10056 if (trigger_player_bits & (1 << i))
10058 stored_player[i].key[KEY_NR(element)] = key_state;
10060 DrawGameDoorValues();
10068 case CA_SET_PLAYER_SPEED:
10070 for (i = 0; i < MAX_PLAYERS; i++)
10072 if (trigger_player_bits & (1 << i))
10074 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10076 if (action_arg == CA_ARG_SPEED_FASTER &&
10077 stored_player[i].cannot_move)
10079 action_arg_number = STEPSIZE_VERY_SLOW;
10081 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10082 action_arg == CA_ARG_SPEED_FASTER)
10084 action_arg_number = 2;
10085 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10088 else if (action_arg == CA_ARG_NUMBER_RESET)
10090 action_arg_number = level.initial_player_stepsize[i];
10094 getModifiedActionNumber(move_stepsize,
10097 action_arg_number_min,
10098 action_arg_number_max);
10100 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10107 case CA_SET_PLAYER_SHIELD:
10109 for (i = 0; i < MAX_PLAYERS; i++)
10111 if (trigger_player_bits & (1 << i))
10113 if (action_arg == CA_ARG_SHIELD_OFF)
10115 stored_player[i].shield_normal_time_left = 0;
10116 stored_player[i].shield_deadly_time_left = 0;
10118 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10120 stored_player[i].shield_normal_time_left = 999999;
10122 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10124 stored_player[i].shield_normal_time_left = 999999;
10125 stored_player[i].shield_deadly_time_left = 999999;
10133 case CA_SET_PLAYER_GRAVITY:
10135 for (i = 0; i < MAX_PLAYERS; i++)
10137 if (trigger_player_bits & (1 << i))
10139 stored_player[i].gravity =
10140 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10141 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10142 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10143 stored_player[i].gravity);
10150 case CA_SET_PLAYER_ARTWORK:
10152 for (i = 0; i < MAX_PLAYERS; i++)
10154 if (trigger_player_bits & (1 << i))
10156 int artwork_element = action_arg_element;
10158 if (action_arg == CA_ARG_ELEMENT_RESET)
10160 (level.use_artwork_element[i] ? level.artwork_element[i] :
10161 stored_player[i].element_nr);
10163 if (stored_player[i].artwork_element != artwork_element)
10164 stored_player[i].Frame = 0;
10166 stored_player[i].artwork_element = artwork_element;
10168 SetPlayerWaiting(&stored_player[i], FALSE);
10170 // set number of special actions for bored and sleeping animation
10171 stored_player[i].num_special_action_bored =
10172 get_num_special_action(artwork_element,
10173 ACTION_BORING_1, ACTION_BORING_LAST);
10174 stored_player[i].num_special_action_sleeping =
10175 get_num_special_action(artwork_element,
10176 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10183 case CA_SET_PLAYER_INVENTORY:
10185 for (i = 0; i < MAX_PLAYERS; i++)
10187 struct PlayerInfo *player = &stored_player[i];
10190 if (trigger_player_bits & (1 << i))
10192 int inventory_element = action_arg_element;
10194 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10195 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10196 action_arg == CA_ARG_ELEMENT_ACTION)
10198 int element = inventory_element;
10199 int collect_count = element_info[element].collect_count_initial;
10201 if (!IS_CUSTOM_ELEMENT(element))
10204 if (collect_count == 0)
10205 player->inventory_infinite_element = element;
10207 for (k = 0; k < collect_count; k++)
10208 if (player->inventory_size < MAX_INVENTORY_SIZE)
10209 player->inventory_element[player->inventory_size++] =
10212 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10213 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10214 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10216 if (player->inventory_infinite_element != EL_UNDEFINED &&
10217 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10218 action_arg_element_raw))
10219 player->inventory_infinite_element = EL_UNDEFINED;
10221 for (k = 0, j = 0; j < player->inventory_size; j++)
10223 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10224 action_arg_element_raw))
10225 player->inventory_element[k++] = player->inventory_element[j];
10228 player->inventory_size = k;
10230 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10232 if (player->inventory_size > 0)
10234 for (j = 0; j < player->inventory_size - 1; j++)
10235 player->inventory_element[j] = player->inventory_element[j + 1];
10237 player->inventory_size--;
10240 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10242 if (player->inventory_size > 0)
10243 player->inventory_size--;
10245 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10247 player->inventory_infinite_element = EL_UNDEFINED;
10248 player->inventory_size = 0;
10250 else if (action_arg == CA_ARG_INVENTORY_RESET)
10252 player->inventory_infinite_element = EL_UNDEFINED;
10253 player->inventory_size = 0;
10255 if (level.use_initial_inventory[i])
10257 for (j = 0; j < level.initial_inventory_size[i]; j++)
10259 int element = level.initial_inventory_content[i][j];
10260 int collect_count = element_info[element].collect_count_initial;
10262 if (!IS_CUSTOM_ELEMENT(element))
10265 if (collect_count == 0)
10266 player->inventory_infinite_element = element;
10268 for (k = 0; k < collect_count; k++)
10269 if (player->inventory_size < MAX_INVENTORY_SIZE)
10270 player->inventory_element[player->inventory_size++] =
10281 // ---------- CE actions -------------------------------------------------
10283 case CA_SET_CE_VALUE:
10285 int last_ce_value = CustomValue[x][y];
10287 CustomValue[x][y] = action_arg_number_new;
10289 if (CustomValue[x][y] != last_ce_value)
10291 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10292 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10294 if (CustomValue[x][y] == 0)
10296 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10297 ChangeCount[x][y] = 0; // allow at least one more change
10299 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10300 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10307 case CA_SET_CE_SCORE:
10309 int last_ce_score = ei->collect_score;
10311 ei->collect_score = action_arg_number_new;
10313 if (ei->collect_score != last_ce_score)
10315 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10316 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10318 if (ei->collect_score == 0)
10322 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10323 ChangeCount[x][y] = 0; // allow at least one more change
10325 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10326 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10329 This is a very special case that seems to be a mixture between
10330 CheckElementChange() and CheckTriggeredElementChange(): while
10331 the first one only affects single elements that are triggered
10332 directly, the second one affects multiple elements in the playfield
10333 that are triggered indirectly by another element. This is a third
10334 case: Changing the CE score always affects multiple identical CEs,
10335 so every affected CE must be checked, not only the single CE for
10336 which the CE score was changed in the first place (as every instance
10337 of that CE shares the same CE score, and therefore also can change)!
10339 SCAN_PLAYFIELD(xx, yy)
10341 if (Tile[xx][yy] == element)
10342 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10343 CE_SCORE_GETS_ZERO);
10351 case CA_SET_CE_ARTWORK:
10353 int artwork_element = action_arg_element;
10354 boolean reset_frame = FALSE;
10357 if (action_arg == CA_ARG_ELEMENT_RESET)
10358 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10361 if (ei->gfx_element != artwork_element)
10362 reset_frame = TRUE;
10364 ei->gfx_element = artwork_element;
10366 SCAN_PLAYFIELD(xx, yy)
10368 if (Tile[xx][yy] == element)
10372 ResetGfxAnimation(xx, yy);
10373 ResetRandomAnimationValue(xx, yy);
10376 TEST_DrawLevelField(xx, yy);
10383 // ---------- engine actions ---------------------------------------------
10385 case CA_SET_ENGINE_SCAN_MODE:
10387 InitPlayfieldScanMode(action_arg);
10397 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10399 int old_element = Tile[x][y];
10400 int new_element = GetElementFromGroupElement(element);
10401 int previous_move_direction = MovDir[x][y];
10402 int last_ce_value = CustomValue[x][y];
10403 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10404 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10405 boolean add_player_onto_element = (new_element_is_player &&
10406 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10407 IS_WALKABLE(old_element));
10409 if (!add_player_onto_element)
10411 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10412 RemoveMovingField(x, y);
10416 Tile[x][y] = new_element;
10418 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10419 MovDir[x][y] = previous_move_direction;
10421 if (element_info[new_element].use_last_ce_value)
10422 CustomValue[x][y] = last_ce_value;
10424 InitField_WithBug1(x, y, FALSE);
10426 new_element = Tile[x][y]; // element may have changed
10428 ResetGfxAnimation(x, y);
10429 ResetRandomAnimationValue(x, y);
10431 TEST_DrawLevelField(x, y);
10433 if (GFX_CRUMBLED(new_element))
10434 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10437 // check if element under the player changes from accessible to unaccessible
10438 // (needed for special case of dropping element which then changes)
10439 // (must be checked after creating new element for walkable group elements)
10440 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10441 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10448 // "ChangeCount" not set yet to allow "entered by player" change one time
10449 if (new_element_is_player)
10450 RelocatePlayer(x, y, new_element);
10453 ChangeCount[x][y]++; // count number of changes in the same frame
10455 TestIfBadThingTouchesPlayer(x, y);
10456 TestIfPlayerTouchesCustomElement(x, y);
10457 TestIfElementTouchesCustomElement(x, y);
10460 static void CreateField(int x, int y, int element)
10462 CreateFieldExt(x, y, element, FALSE);
10465 static void CreateElementFromChange(int x, int y, int element)
10467 element = GET_VALID_RUNTIME_ELEMENT(element);
10469 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10471 int old_element = Tile[x][y];
10473 // prevent changed element from moving in same engine frame
10474 // unless both old and new element can either fall or move
10475 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10476 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10480 CreateFieldExt(x, y, element, TRUE);
10483 static boolean ChangeElement(int x, int y, int element, int page)
10485 struct ElementInfo *ei = &element_info[element];
10486 struct ElementChangeInfo *change = &ei->change_page[page];
10487 int ce_value = CustomValue[x][y];
10488 int ce_score = ei->collect_score;
10489 int target_element;
10490 int old_element = Tile[x][y];
10492 // always use default change event to prevent running into a loop
10493 if (ChangeEvent[x][y] == -1)
10494 ChangeEvent[x][y] = CE_DELAY;
10496 if (ChangeEvent[x][y] == CE_DELAY)
10498 // reset actual trigger element, trigger player and action element
10499 change->actual_trigger_element = EL_EMPTY;
10500 change->actual_trigger_player = EL_EMPTY;
10501 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10502 change->actual_trigger_side = CH_SIDE_NONE;
10503 change->actual_trigger_ce_value = 0;
10504 change->actual_trigger_ce_score = 0;
10507 // do not change elements more than a specified maximum number of changes
10508 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10511 ChangeCount[x][y]++; // count number of changes in the same frame
10513 if (change->explode)
10520 if (change->use_target_content)
10522 boolean complete_replace = TRUE;
10523 boolean can_replace[3][3];
10526 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10529 boolean is_walkable;
10530 boolean is_diggable;
10531 boolean is_collectible;
10532 boolean is_removable;
10533 boolean is_destructible;
10534 int ex = x + xx - 1;
10535 int ey = y + yy - 1;
10536 int content_element = change->target_content.e[xx][yy];
10539 can_replace[xx][yy] = TRUE;
10541 if (ex == x && ey == y) // do not check changing element itself
10544 if (content_element == EL_EMPTY_SPACE)
10546 can_replace[xx][yy] = FALSE; // do not replace border with space
10551 if (!IN_LEV_FIELD(ex, ey))
10553 can_replace[xx][yy] = FALSE;
10554 complete_replace = FALSE;
10561 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10562 e = MovingOrBlocked2Element(ex, ey);
10564 is_empty = (IS_FREE(ex, ey) ||
10565 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10567 is_walkable = (is_empty || IS_WALKABLE(e));
10568 is_diggable = (is_empty || IS_DIGGABLE(e));
10569 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10570 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10571 is_removable = (is_diggable || is_collectible);
10573 can_replace[xx][yy] =
10574 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10575 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10576 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10577 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10578 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10579 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10580 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10582 if (!can_replace[xx][yy])
10583 complete_replace = FALSE;
10586 if (!change->only_if_complete || complete_replace)
10588 boolean something_has_changed = FALSE;
10590 if (change->only_if_complete && change->use_random_replace &&
10591 RND(100) < change->random_percentage)
10594 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10596 int ex = x + xx - 1;
10597 int ey = y + yy - 1;
10598 int content_element;
10600 if (can_replace[xx][yy] && (!change->use_random_replace ||
10601 RND(100) < change->random_percentage))
10603 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10604 RemoveMovingField(ex, ey);
10606 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10608 content_element = change->target_content.e[xx][yy];
10609 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10610 ce_value, ce_score);
10612 CreateElementFromChange(ex, ey, target_element);
10614 something_has_changed = TRUE;
10616 // for symmetry reasons, freeze newly created border elements
10617 if (ex != x || ey != y)
10618 Stop[ex][ey] = TRUE; // no more moving in this frame
10622 if (something_has_changed)
10624 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10625 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10631 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10632 ce_value, ce_score);
10634 if (element == EL_DIAGONAL_GROWING ||
10635 element == EL_DIAGONAL_SHRINKING)
10637 target_element = Store[x][y];
10639 Store[x][y] = EL_EMPTY;
10642 CreateElementFromChange(x, y, target_element);
10644 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10645 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10648 // this uses direct change before indirect change
10649 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10654 static void HandleElementChange(int x, int y, int page)
10656 int element = MovingOrBlocked2Element(x, y);
10657 struct ElementInfo *ei = &element_info[element];
10658 struct ElementChangeInfo *change = &ei->change_page[page];
10659 boolean handle_action_before_change = FALSE;
10662 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10663 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10665 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10666 x, y, element, element_info[element].token_name);
10667 Debug("game:playing:HandleElementChange", "This should never happen!");
10671 // this can happen with classic bombs on walkable, changing elements
10672 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10677 if (ChangeDelay[x][y] == 0) // initialize element change
10679 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10681 if (change->can_change)
10683 // !!! not clear why graphic animation should be reset at all here !!!
10684 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10685 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10688 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10690 When using an animation frame delay of 1 (this only happens with
10691 "sp_zonk.moving.left/right" in the classic graphics), the default
10692 (non-moving) animation shows wrong animation frames (while the
10693 moving animation, like "sp_zonk.moving.left/right", is correct,
10694 so this graphical bug never shows up with the classic graphics).
10695 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10696 be drawn instead of the correct frames 0,1,2,3. This is caused by
10697 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10698 an element change: First when the change delay ("ChangeDelay[][]")
10699 counter has reached zero after decrementing, then a second time in
10700 the next frame (after "GfxFrame[][]" was already incremented) when
10701 "ChangeDelay[][]" is reset to the initial delay value again.
10703 This causes frame 0 to be drawn twice, while the last frame won't
10704 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10706 As some animations may already be cleverly designed around this bug
10707 (at least the "Snake Bite" snake tail animation does this), it cannot
10708 simply be fixed here without breaking such existing animations.
10709 Unfortunately, it cannot easily be detected if a graphics set was
10710 designed "before" or "after" the bug was fixed. As a workaround,
10711 a new graphics set option "game.graphics_engine_version" was added
10712 to be able to specify the game's major release version for which the
10713 graphics set was designed, which can then be used to decide if the
10714 bugfix should be used (version 4 and above) or not (version 3 or
10715 below, or if no version was specified at all, as with old sets).
10717 (The wrong/fixed animation frames can be tested with the test level set
10718 "test_gfxframe" and level "000", which contains a specially prepared
10719 custom element at level position (x/y) == (11/9) which uses the zonk
10720 animation mentioned above. Using "game.graphics_engine_version: 4"
10721 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10722 This can also be seen from the debug output for this test element.)
10725 // when a custom element is about to change (for example by change delay),
10726 // do not reset graphic animation when the custom element is moving
10727 if (game.graphics_engine_version < 4 &&
10730 ResetGfxAnimation(x, y);
10731 ResetRandomAnimationValue(x, y);
10734 if (change->pre_change_function)
10735 change->pre_change_function(x, y);
10739 ChangeDelay[x][y]--;
10741 if (ChangeDelay[x][y] != 0) // continue element change
10743 if (change->can_change)
10745 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10747 if (IS_ANIMATED(graphic))
10748 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10750 if (change->change_function)
10751 change->change_function(x, y);
10754 else // finish element change
10756 if (ChangePage[x][y] != -1) // remember page from delayed change
10758 page = ChangePage[x][y];
10759 ChangePage[x][y] = -1;
10761 change = &ei->change_page[page];
10764 if (IS_MOVING(x, y)) // never change a running system ;-)
10766 ChangeDelay[x][y] = 1; // try change after next move step
10767 ChangePage[x][y] = page; // remember page to use for change
10772 // special case: set new level random seed before changing element
10773 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10774 handle_action_before_change = TRUE;
10776 if (change->has_action && handle_action_before_change)
10777 ExecuteCustomElementAction(x, y, element, page);
10779 if (change->can_change)
10781 if (ChangeElement(x, y, element, page))
10783 if (change->post_change_function)
10784 change->post_change_function(x, y);
10788 if (change->has_action && !handle_action_before_change)
10789 ExecuteCustomElementAction(x, y, element, page);
10793 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10794 int trigger_element,
10796 int trigger_player,
10800 boolean change_done_any = FALSE;
10801 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10804 if (!(trigger_events[trigger_element][trigger_event]))
10807 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10809 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10811 int element = EL_CUSTOM_START + i;
10812 boolean change_done = FALSE;
10815 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10816 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10819 for (p = 0; p < element_info[element].num_change_pages; p++)
10821 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10823 if (change->can_change_or_has_action &&
10824 change->has_event[trigger_event] &&
10825 change->trigger_side & trigger_side &&
10826 change->trigger_player & trigger_player &&
10827 change->trigger_page & trigger_page_bits &&
10828 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10830 change->actual_trigger_element = trigger_element;
10831 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10832 change->actual_trigger_player_bits = trigger_player;
10833 change->actual_trigger_side = trigger_side;
10834 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10835 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10837 if ((change->can_change && !change_done) || change->has_action)
10841 SCAN_PLAYFIELD(x, y)
10843 if (Tile[x][y] == element)
10845 if (change->can_change && !change_done)
10847 // if element already changed in this frame, not only prevent
10848 // another element change (checked in ChangeElement()), but
10849 // also prevent additional element actions for this element
10851 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10852 !level.use_action_after_change_bug)
10855 ChangeDelay[x][y] = 1;
10856 ChangeEvent[x][y] = trigger_event;
10858 HandleElementChange(x, y, p);
10860 else if (change->has_action)
10862 // if element already changed in this frame, not only prevent
10863 // another element change (checked in ChangeElement()), but
10864 // also prevent additional element actions for this element
10866 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10867 !level.use_action_after_change_bug)
10870 ExecuteCustomElementAction(x, y, element, p);
10871 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10876 if (change->can_change)
10878 change_done = TRUE;
10879 change_done_any = TRUE;
10886 RECURSION_LOOP_DETECTION_END();
10888 return change_done_any;
10891 static boolean CheckElementChangeExt(int x, int y,
10893 int trigger_element,
10895 int trigger_player,
10898 boolean change_done = FALSE;
10901 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10902 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10905 if (Tile[x][y] == EL_BLOCKED)
10907 Blocked2Moving(x, y, &x, &y);
10908 element = Tile[x][y];
10911 // check if element has already changed or is about to change after moving
10912 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10913 Tile[x][y] != element) ||
10915 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10916 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10917 ChangePage[x][y] != -1)))
10920 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10922 for (p = 0; p < element_info[element].num_change_pages; p++)
10924 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10926 /* check trigger element for all events where the element that is checked
10927 for changing interacts with a directly adjacent element -- this is
10928 different to element changes that affect other elements to change on the
10929 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10930 boolean check_trigger_element =
10931 (trigger_event == CE_TOUCHING_X ||
10932 trigger_event == CE_HITTING_X ||
10933 trigger_event == CE_HIT_BY_X ||
10934 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10936 if (change->can_change_or_has_action &&
10937 change->has_event[trigger_event] &&
10938 change->trigger_side & trigger_side &&
10939 change->trigger_player & trigger_player &&
10940 (!check_trigger_element ||
10941 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10943 change->actual_trigger_element = trigger_element;
10944 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10945 change->actual_trigger_player_bits = trigger_player;
10946 change->actual_trigger_side = trigger_side;
10947 change->actual_trigger_ce_value = CustomValue[x][y];
10948 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10950 // special case: trigger element not at (x,y) position for some events
10951 if (check_trigger_element)
10963 { 0, 0 }, { 0, 0 }, { 0, 0 },
10967 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10968 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10970 change->actual_trigger_ce_value = CustomValue[xx][yy];
10971 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10974 if (change->can_change && !change_done)
10976 ChangeDelay[x][y] = 1;
10977 ChangeEvent[x][y] = trigger_event;
10979 HandleElementChange(x, y, p);
10981 change_done = TRUE;
10983 else if (change->has_action)
10985 ExecuteCustomElementAction(x, y, element, p);
10986 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10991 RECURSION_LOOP_DETECTION_END();
10993 return change_done;
10996 static void PlayPlayerSound(struct PlayerInfo *player)
10998 int jx = player->jx, jy = player->jy;
10999 int sound_element = player->artwork_element;
11000 int last_action = player->last_action_waiting;
11001 int action = player->action_waiting;
11003 if (player->is_waiting)
11005 if (action != last_action)
11006 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11008 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11012 if (action != last_action)
11013 StopSound(element_info[sound_element].sound[last_action]);
11015 if (last_action == ACTION_SLEEPING)
11016 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11020 static void PlayAllPlayersSound(void)
11024 for (i = 0; i < MAX_PLAYERS; i++)
11025 if (stored_player[i].active)
11026 PlayPlayerSound(&stored_player[i]);
11029 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11031 boolean last_waiting = player->is_waiting;
11032 int move_dir = player->MovDir;
11034 player->dir_waiting = move_dir;
11035 player->last_action_waiting = player->action_waiting;
11039 if (!last_waiting) // not waiting -> waiting
11041 player->is_waiting = TRUE;
11043 player->frame_counter_bored =
11045 game.player_boring_delay_fixed +
11046 GetSimpleRandom(game.player_boring_delay_random);
11047 player->frame_counter_sleeping =
11049 game.player_sleeping_delay_fixed +
11050 GetSimpleRandom(game.player_sleeping_delay_random);
11052 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11055 if (game.player_sleeping_delay_fixed +
11056 game.player_sleeping_delay_random > 0 &&
11057 player->anim_delay_counter == 0 &&
11058 player->post_delay_counter == 0 &&
11059 FrameCounter >= player->frame_counter_sleeping)
11060 player->is_sleeping = TRUE;
11061 else if (game.player_boring_delay_fixed +
11062 game.player_boring_delay_random > 0 &&
11063 FrameCounter >= player->frame_counter_bored)
11064 player->is_bored = TRUE;
11066 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11067 player->is_bored ? ACTION_BORING :
11070 if (player->is_sleeping && player->use_murphy)
11072 // special case for sleeping Murphy when leaning against non-free tile
11074 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11075 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11076 !IS_MOVING(player->jx - 1, player->jy)))
11077 move_dir = MV_LEFT;
11078 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11079 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11080 !IS_MOVING(player->jx + 1, player->jy)))
11081 move_dir = MV_RIGHT;
11083 player->is_sleeping = FALSE;
11085 player->dir_waiting = move_dir;
11088 if (player->is_sleeping)
11090 if (player->num_special_action_sleeping > 0)
11092 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11094 int last_special_action = player->special_action_sleeping;
11095 int num_special_action = player->num_special_action_sleeping;
11096 int special_action =
11097 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11098 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11099 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11100 last_special_action + 1 : ACTION_SLEEPING);
11101 int special_graphic =
11102 el_act_dir2img(player->artwork_element, special_action, move_dir);
11104 player->anim_delay_counter =
11105 graphic_info[special_graphic].anim_delay_fixed +
11106 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11107 player->post_delay_counter =
11108 graphic_info[special_graphic].post_delay_fixed +
11109 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11111 player->special_action_sleeping = special_action;
11114 if (player->anim_delay_counter > 0)
11116 player->action_waiting = player->special_action_sleeping;
11117 player->anim_delay_counter--;
11119 else if (player->post_delay_counter > 0)
11121 player->post_delay_counter--;
11125 else if (player->is_bored)
11127 if (player->num_special_action_bored > 0)
11129 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11131 int special_action =
11132 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11133 int special_graphic =
11134 el_act_dir2img(player->artwork_element, special_action, move_dir);
11136 player->anim_delay_counter =
11137 graphic_info[special_graphic].anim_delay_fixed +
11138 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11139 player->post_delay_counter =
11140 graphic_info[special_graphic].post_delay_fixed +
11141 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11143 player->special_action_bored = special_action;
11146 if (player->anim_delay_counter > 0)
11148 player->action_waiting = player->special_action_bored;
11149 player->anim_delay_counter--;
11151 else if (player->post_delay_counter > 0)
11153 player->post_delay_counter--;
11158 else if (last_waiting) // waiting -> not waiting
11160 player->is_waiting = FALSE;
11161 player->is_bored = FALSE;
11162 player->is_sleeping = FALSE;
11164 player->frame_counter_bored = -1;
11165 player->frame_counter_sleeping = -1;
11167 player->anim_delay_counter = 0;
11168 player->post_delay_counter = 0;
11170 player->dir_waiting = player->MovDir;
11171 player->action_waiting = ACTION_DEFAULT;
11173 player->special_action_bored = ACTION_DEFAULT;
11174 player->special_action_sleeping = ACTION_DEFAULT;
11178 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11180 if ((!player->is_moving && player->was_moving) ||
11181 (player->MovPos == 0 && player->was_moving) ||
11182 (player->is_snapping && !player->was_snapping) ||
11183 (player->is_dropping && !player->was_dropping))
11185 if (!CheckSaveEngineSnapshotToList())
11188 player->was_moving = FALSE;
11189 player->was_snapping = TRUE;
11190 player->was_dropping = TRUE;
11194 if (player->is_moving)
11195 player->was_moving = TRUE;
11197 if (!player->is_snapping)
11198 player->was_snapping = FALSE;
11200 if (!player->is_dropping)
11201 player->was_dropping = FALSE;
11204 static struct MouseActionInfo mouse_action_last = { 0 };
11205 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11206 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11209 CheckSaveEngineSnapshotToList();
11211 mouse_action_last = mouse_action;
11214 static void CheckSingleStepMode(struct PlayerInfo *player)
11216 if (tape.single_step && tape.recording && !tape.pausing)
11218 /* as it is called "single step mode", just return to pause mode when the
11219 player stopped moving after one tile (or never starts moving at all) */
11220 if (!player->is_moving &&
11221 !player->is_pushing &&
11222 !player->is_dropping_pressed &&
11223 !player->effective_mouse_action.button)
11224 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11227 CheckSaveEngineSnapshot(player);
11230 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11232 int left = player_action & JOY_LEFT;
11233 int right = player_action & JOY_RIGHT;
11234 int up = player_action & JOY_UP;
11235 int down = player_action & JOY_DOWN;
11236 int button1 = player_action & JOY_BUTTON_1;
11237 int button2 = player_action & JOY_BUTTON_2;
11238 int dx = (left ? -1 : right ? 1 : 0);
11239 int dy = (up ? -1 : down ? 1 : 0);
11241 if (!player->active || tape.pausing)
11247 SnapField(player, dx, dy);
11251 DropElement(player);
11253 MovePlayer(player, dx, dy);
11256 CheckSingleStepMode(player);
11258 SetPlayerWaiting(player, FALSE);
11260 return player_action;
11264 // no actions for this player (no input at player's configured device)
11266 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11267 SnapField(player, 0, 0);
11268 CheckGravityMovementWhenNotMoving(player);
11270 if (player->MovPos == 0)
11271 SetPlayerWaiting(player, TRUE);
11273 if (player->MovPos == 0) // needed for tape.playing
11274 player->is_moving = FALSE;
11276 player->is_dropping = FALSE;
11277 player->is_dropping_pressed = FALSE;
11278 player->drop_pressed_delay = 0;
11280 CheckSingleStepMode(player);
11286 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11289 if (!tape.use_mouse_actions)
11292 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11293 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11294 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11297 static void SetTapeActionFromMouseAction(byte *tape_action,
11298 struct MouseActionInfo *mouse_action)
11300 if (!tape.use_mouse_actions)
11303 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11304 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11305 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11308 static void CheckLevelSolved(void)
11310 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11312 if (game_em.level_solved &&
11313 !game_em.game_over) // game won
11317 game_em.game_over = TRUE;
11319 game.all_players_gone = TRUE;
11322 if (game_em.game_over) // game lost
11323 game.all_players_gone = TRUE;
11325 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11327 if (game_sp.level_solved &&
11328 !game_sp.game_over) // game won
11332 game_sp.game_over = TRUE;
11334 game.all_players_gone = TRUE;
11337 if (game_sp.game_over) // game lost
11338 game.all_players_gone = TRUE;
11340 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11342 if (game_mm.level_solved &&
11343 !game_mm.game_over) // game won
11347 game_mm.game_over = TRUE;
11349 game.all_players_gone = TRUE;
11352 if (game_mm.game_over) // game lost
11353 game.all_players_gone = TRUE;
11357 static void CheckLevelTime(void)
11361 if (TimeFrames >= FRAMES_PER_SECOND)
11366 for (i = 0; i < MAX_PLAYERS; i++)
11368 struct PlayerInfo *player = &stored_player[i];
11370 if (SHIELD_ON(player))
11372 player->shield_normal_time_left--;
11374 if (player->shield_deadly_time_left > 0)
11375 player->shield_deadly_time_left--;
11379 if (!game.LevelSolved && !level.use_step_counter)
11387 if (TimeLeft <= 10 && setup.time_limit)
11388 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11390 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11391 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11393 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11395 if (!TimeLeft && setup.time_limit)
11397 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11398 game_em.lev->killed_out_of_time = TRUE;
11400 for (i = 0; i < MAX_PLAYERS; i++)
11401 KillPlayer(&stored_player[i]);
11404 else if (game.no_time_limit && !game.all_players_gone)
11406 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11409 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11412 if (tape.recording || tape.playing)
11413 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11416 if (tape.recording || tape.playing)
11417 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11419 UpdateAndDisplayGameControlValues();
11422 void AdvanceFrameAndPlayerCounters(int player_nr)
11426 // advance frame counters (global frame counter and time frame counter)
11430 // advance player counters (counters for move delay, move animation etc.)
11431 for (i = 0; i < MAX_PLAYERS; i++)
11433 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11434 int move_delay_value = stored_player[i].move_delay_value;
11435 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11437 if (!advance_player_counters) // not all players may be affected
11440 if (move_frames == 0) // less than one move per game frame
11442 int stepsize = TILEX / move_delay_value;
11443 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11444 int count = (stored_player[i].is_moving ?
11445 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11447 if (count % delay == 0)
11451 stored_player[i].Frame += move_frames;
11453 if (stored_player[i].MovPos != 0)
11454 stored_player[i].StepFrame += move_frames;
11456 if (stored_player[i].move_delay > 0)
11457 stored_player[i].move_delay--;
11459 // due to bugs in previous versions, counter must count up, not down
11460 if (stored_player[i].push_delay != -1)
11461 stored_player[i].push_delay++;
11463 if (stored_player[i].drop_delay > 0)
11464 stored_player[i].drop_delay--;
11466 if (stored_player[i].is_dropping_pressed)
11467 stored_player[i].drop_pressed_delay++;
11471 void StartGameActions(boolean init_network_game, boolean record_tape,
11474 unsigned int new_random_seed = InitRND(random_seed);
11477 TapeStartRecording(new_random_seed);
11479 if (init_network_game)
11481 SendToServer_LevelFile();
11482 SendToServer_StartPlaying();
11490 static void GameActionsExt(void)
11493 static unsigned int game_frame_delay = 0;
11495 unsigned int game_frame_delay_value;
11496 byte *recorded_player_action;
11497 byte summarized_player_action = 0;
11498 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11501 // detect endless loops, caused by custom element programming
11502 if (recursion_loop_detected && recursion_loop_depth == 0)
11504 char *message = getStringCat3("Internal Error! Element ",
11505 EL_NAME(recursion_loop_element),
11506 " caused endless loop! Quit the game?");
11508 Warn("element '%s' caused endless loop in game engine",
11509 EL_NAME(recursion_loop_element));
11511 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11513 recursion_loop_detected = FALSE; // if game should be continued
11520 if (game.restart_level)
11521 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11523 CheckLevelSolved();
11525 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11528 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11531 if (game_status != GAME_MODE_PLAYING) // status might have changed
11534 game_frame_delay_value =
11535 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11537 if (tape.playing && tape.warp_forward && !tape.pausing)
11538 game_frame_delay_value = 0;
11540 SetVideoFrameDelay(game_frame_delay_value);
11542 // (de)activate virtual buttons depending on current game status
11543 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11545 if (game.all_players_gone) // if no players there to be controlled anymore
11546 SetOverlayActive(FALSE);
11547 else if (!tape.playing) // if game continues after tape stopped playing
11548 SetOverlayActive(TRUE);
11553 // ---------- main game synchronization point ----------
11555 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11557 Debug("game:playing:skip", "skip == %d", skip);
11560 // ---------- main game synchronization point ----------
11562 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11566 if (network_playing && !network_player_action_received)
11568 // try to get network player actions in time
11570 // last chance to get network player actions without main loop delay
11571 HandleNetworking();
11573 // game was quit by network peer
11574 if (game_status != GAME_MODE_PLAYING)
11577 // check if network player actions still missing and game still running
11578 if (!network_player_action_received && !checkGameEnded())
11579 return; // failed to get network player actions in time
11581 // do not yet reset "network_player_action_received" (for tape.pausing)
11587 // at this point we know that we really continue executing the game
11589 network_player_action_received = FALSE;
11591 // when playing tape, read previously recorded player input from tape data
11592 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11594 local_player->effective_mouse_action = local_player->mouse_action;
11596 if (recorded_player_action != NULL)
11597 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11598 recorded_player_action);
11600 // TapePlayAction() may return NULL when toggling to "pause before death"
11604 if (tape.set_centered_player)
11606 game.centered_player_nr_next = tape.centered_player_nr_next;
11607 game.set_centered_player = TRUE;
11610 for (i = 0; i < MAX_PLAYERS; i++)
11612 summarized_player_action |= stored_player[i].action;
11614 if (!network_playing && (game.team_mode || tape.playing))
11615 stored_player[i].effective_action = stored_player[i].action;
11618 if (network_playing && !checkGameEnded())
11619 SendToServer_MovePlayer(summarized_player_action);
11621 // summarize all actions at local players mapped input device position
11622 // (this allows using different input devices in single player mode)
11623 if (!network.enabled && !game.team_mode)
11624 stored_player[map_player_action[local_player->index_nr]].effective_action =
11625 summarized_player_action;
11627 // summarize all actions at centered player in local team mode
11628 if (tape.recording &&
11629 setup.team_mode && !network.enabled &&
11630 setup.input_on_focus &&
11631 game.centered_player_nr != -1)
11633 for (i = 0; i < MAX_PLAYERS; i++)
11634 stored_player[map_player_action[i]].effective_action =
11635 (i == game.centered_player_nr ? summarized_player_action : 0);
11638 if (recorded_player_action != NULL)
11639 for (i = 0; i < MAX_PLAYERS; i++)
11640 stored_player[i].effective_action = recorded_player_action[i];
11642 for (i = 0; i < MAX_PLAYERS; i++)
11644 tape_action[i] = stored_player[i].effective_action;
11646 /* (this may happen in the RND game engine if a player was not present on
11647 the playfield on level start, but appeared later from a custom element */
11648 if (setup.team_mode &&
11651 !tape.player_participates[i])
11652 tape.player_participates[i] = TRUE;
11655 SetTapeActionFromMouseAction(tape_action,
11656 &local_player->effective_mouse_action);
11658 // only record actions from input devices, but not programmed actions
11659 if (tape.recording)
11660 TapeRecordAction(tape_action);
11662 // remember if game was played (especially after tape stopped playing)
11663 if (!tape.playing && summarized_player_action)
11664 game.GamePlayed = TRUE;
11666 #if USE_NEW_PLAYER_ASSIGNMENTS
11667 // !!! also map player actions in single player mode !!!
11668 // if (game.team_mode)
11671 byte mapped_action[MAX_PLAYERS];
11673 #if DEBUG_PLAYER_ACTIONS
11674 for (i = 0; i < MAX_PLAYERS; i++)
11675 DebugContinued("", "%d, ", stored_player[i].effective_action);
11678 for (i = 0; i < MAX_PLAYERS; i++)
11679 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11681 for (i = 0; i < MAX_PLAYERS; i++)
11682 stored_player[i].effective_action = mapped_action[i];
11684 #if DEBUG_PLAYER_ACTIONS
11685 DebugContinued("", "=> ");
11686 for (i = 0; i < MAX_PLAYERS; i++)
11687 DebugContinued("", "%d, ", stored_player[i].effective_action);
11688 DebugContinued("game:playing:player", "\n");
11691 #if DEBUG_PLAYER_ACTIONS
11694 for (i = 0; i < MAX_PLAYERS; i++)
11695 DebugContinued("", "%d, ", stored_player[i].effective_action);
11696 DebugContinued("game:playing:player", "\n");
11701 for (i = 0; i < MAX_PLAYERS; i++)
11703 // allow engine snapshot in case of changed movement attempt
11704 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11705 (stored_player[i].effective_action & KEY_MOTION))
11706 game.snapshot.changed_action = TRUE;
11708 // allow engine snapshot in case of snapping/dropping attempt
11709 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11710 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11711 game.snapshot.changed_action = TRUE;
11713 game.snapshot.last_action[i] = stored_player[i].effective_action;
11716 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11718 GameActions_EM_Main();
11720 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11722 GameActions_SP_Main();
11724 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11726 GameActions_MM_Main();
11730 GameActions_RND_Main();
11733 BlitScreenToBitmap(backbuffer);
11735 CheckLevelSolved();
11738 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11740 if (global.show_frames_per_second)
11742 static unsigned int fps_counter = 0;
11743 static int fps_frames = 0;
11744 unsigned int fps_delay_ms = Counter() - fps_counter;
11748 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11750 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11753 fps_counter = Counter();
11755 // always draw FPS to screen after FPS value was updated
11756 redraw_mask |= REDRAW_FPS;
11759 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11760 if (GetDrawDeactivationMask() == REDRAW_NONE)
11761 redraw_mask |= REDRAW_FPS;
11765 static void GameActions_CheckSaveEngineSnapshot(void)
11767 if (!game.snapshot.save_snapshot)
11770 // clear flag for saving snapshot _before_ saving snapshot
11771 game.snapshot.save_snapshot = FALSE;
11773 SaveEngineSnapshotToList();
11776 void GameActions(void)
11780 GameActions_CheckSaveEngineSnapshot();
11783 void GameActions_EM_Main(void)
11785 byte effective_action[MAX_PLAYERS];
11786 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11789 for (i = 0; i < MAX_PLAYERS; i++)
11790 effective_action[i] = stored_player[i].effective_action;
11792 GameActions_EM(effective_action, warp_mode);
11795 void GameActions_SP_Main(void)
11797 byte effective_action[MAX_PLAYERS];
11798 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11801 for (i = 0; i < MAX_PLAYERS; i++)
11802 effective_action[i] = stored_player[i].effective_action;
11804 GameActions_SP(effective_action, warp_mode);
11806 for (i = 0; i < MAX_PLAYERS; i++)
11808 if (stored_player[i].force_dropping)
11809 stored_player[i].action |= KEY_BUTTON_DROP;
11811 stored_player[i].force_dropping = FALSE;
11815 void GameActions_MM_Main(void)
11817 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11819 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11822 void GameActions_RND_Main(void)
11827 void GameActions_RND(void)
11829 static struct MouseActionInfo mouse_action_last = { 0 };
11830 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11831 int magic_wall_x = 0, magic_wall_y = 0;
11832 int i, x, y, element, graphic, last_gfx_frame;
11834 InitPlayfieldScanModeVars();
11836 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11838 SCAN_PLAYFIELD(x, y)
11840 ChangeCount[x][y] = 0;
11841 ChangeEvent[x][y] = -1;
11845 if (game.set_centered_player)
11847 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11849 // switching to "all players" only possible if all players fit to screen
11850 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11852 game.centered_player_nr_next = game.centered_player_nr;
11853 game.set_centered_player = FALSE;
11856 // do not switch focus to non-existing (or non-active) player
11857 if (game.centered_player_nr_next >= 0 &&
11858 !stored_player[game.centered_player_nr_next].active)
11860 game.centered_player_nr_next = game.centered_player_nr;
11861 game.set_centered_player = FALSE;
11865 if (game.set_centered_player &&
11866 ScreenMovPos == 0) // screen currently aligned at tile position
11870 if (game.centered_player_nr_next == -1)
11872 setScreenCenteredToAllPlayers(&sx, &sy);
11876 sx = stored_player[game.centered_player_nr_next].jx;
11877 sy = stored_player[game.centered_player_nr_next].jy;
11880 game.centered_player_nr = game.centered_player_nr_next;
11881 game.set_centered_player = FALSE;
11883 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11884 DrawGameDoorValues();
11887 for (i = 0; i < MAX_PLAYERS; i++)
11889 int actual_player_action = stored_player[i].effective_action;
11892 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11893 - rnd_equinox_tetrachloride 048
11894 - rnd_equinox_tetrachloride_ii 096
11895 - rnd_emanuel_schmieg 002
11896 - doctor_sloan_ww 001, 020
11898 if (stored_player[i].MovPos == 0)
11899 CheckGravityMovement(&stored_player[i]);
11902 // overwrite programmed action with tape action
11903 if (stored_player[i].programmed_action)
11904 actual_player_action = stored_player[i].programmed_action;
11906 PlayerActions(&stored_player[i], actual_player_action);
11908 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11911 ScrollScreen(NULL, SCROLL_GO_ON);
11913 /* for backwards compatibility, the following code emulates a fixed bug that
11914 occured when pushing elements (causing elements that just made their last
11915 pushing step to already (if possible) make their first falling step in the
11916 same game frame, which is bad); this code is also needed to use the famous
11917 "spring push bug" which is used in older levels and might be wanted to be
11918 used also in newer levels, but in this case the buggy pushing code is only
11919 affecting the "spring" element and no other elements */
11921 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11923 for (i = 0; i < MAX_PLAYERS; i++)
11925 struct PlayerInfo *player = &stored_player[i];
11926 int x = player->jx;
11927 int y = player->jy;
11929 if (player->active && player->is_pushing && player->is_moving &&
11931 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11932 Tile[x][y] == EL_SPRING))
11934 ContinueMoving(x, y);
11936 // continue moving after pushing (this is actually a bug)
11937 if (!IS_MOVING(x, y))
11938 Stop[x][y] = FALSE;
11943 SCAN_PLAYFIELD(x, y)
11945 Last[x][y] = Tile[x][y];
11947 ChangeCount[x][y] = 0;
11948 ChangeEvent[x][y] = -1;
11950 // this must be handled before main playfield loop
11951 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
11954 if (MovDelay[x][y] <= 0)
11958 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
11961 if (MovDelay[x][y] <= 0)
11964 TEST_DrawLevelField(x, y);
11966 TestIfElementTouchesCustomElement(x, y); // for empty space
11971 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11973 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
11975 Debug("game:playing:GameActions_RND", "This should never happen!");
11977 ChangePage[x][y] = -1;
11981 Stop[x][y] = FALSE;
11982 if (WasJustMoving[x][y] > 0)
11983 WasJustMoving[x][y]--;
11984 if (WasJustFalling[x][y] > 0)
11985 WasJustFalling[x][y]--;
11986 if (CheckCollision[x][y] > 0)
11987 CheckCollision[x][y]--;
11988 if (CheckImpact[x][y] > 0)
11989 CheckImpact[x][y]--;
11993 /* reset finished pushing action (not done in ContinueMoving() to allow
11994 continuous pushing animation for elements with zero push delay) */
11995 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11997 ResetGfxAnimation(x, y);
11998 TEST_DrawLevelField(x, y);
12002 if (IS_BLOCKED(x, y))
12006 Blocked2Moving(x, y, &oldx, &oldy);
12007 if (!IS_MOVING(oldx, oldy))
12009 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12010 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12011 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12012 Debug("game:playing:GameActions_RND", "This should never happen!");
12018 if (mouse_action.button)
12020 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12022 x = mouse_action.lx;
12023 y = mouse_action.ly;
12024 element = Tile[x][y];
12028 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12029 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12032 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12033 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12036 SCAN_PLAYFIELD(x, y)
12038 element = Tile[x][y];
12039 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12040 last_gfx_frame = GfxFrame[x][y];
12042 ResetGfxFrame(x, y);
12044 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12045 DrawLevelGraphicAnimation(x, y, graphic);
12047 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12048 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12049 ResetRandomAnimationValue(x, y);
12051 SetRandomAnimationValue(x, y);
12053 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12055 if (IS_INACTIVE(element))
12057 if (IS_ANIMATED(graphic))
12058 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12063 // this may take place after moving, so 'element' may have changed
12064 if (IS_CHANGING(x, y) &&
12065 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12067 int page = element_info[element].event_page_nr[CE_DELAY];
12069 HandleElementChange(x, y, page);
12071 element = Tile[x][y];
12072 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12075 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12079 element = Tile[x][y];
12080 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12082 if (IS_ANIMATED(graphic) &&
12083 !IS_MOVING(x, y) &&
12085 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12087 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12088 TEST_DrawTwinkleOnField(x, y);
12090 else if (element == EL_ACID)
12093 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12095 else if ((element == EL_EXIT_OPEN ||
12096 element == EL_EM_EXIT_OPEN ||
12097 element == EL_SP_EXIT_OPEN ||
12098 element == EL_STEEL_EXIT_OPEN ||
12099 element == EL_EM_STEEL_EXIT_OPEN ||
12100 element == EL_SP_TERMINAL ||
12101 element == EL_SP_TERMINAL_ACTIVE ||
12102 element == EL_EXTRA_TIME ||
12103 element == EL_SHIELD_NORMAL ||
12104 element == EL_SHIELD_DEADLY) &&
12105 IS_ANIMATED(graphic))
12106 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12107 else if (IS_MOVING(x, y))
12108 ContinueMoving(x, y);
12109 else if (IS_ACTIVE_BOMB(element))
12110 CheckDynamite(x, y);
12111 else if (element == EL_AMOEBA_GROWING)
12112 AmoebaGrowing(x, y);
12113 else if (element == EL_AMOEBA_SHRINKING)
12114 AmoebaShrinking(x, y);
12116 #if !USE_NEW_AMOEBA_CODE
12117 else if (IS_AMOEBALIVE(element))
12118 AmoebaReproduce(x, y);
12121 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12123 else if (element == EL_EXIT_CLOSED)
12125 else if (element == EL_EM_EXIT_CLOSED)
12127 else if (element == EL_STEEL_EXIT_CLOSED)
12128 CheckExitSteel(x, y);
12129 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12130 CheckExitSteelEM(x, y);
12131 else if (element == EL_SP_EXIT_CLOSED)
12133 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12134 element == EL_EXPANDABLE_STEELWALL_GROWING)
12135 MauerWaechst(x, y);
12136 else if (element == EL_EXPANDABLE_WALL ||
12137 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12138 element == EL_EXPANDABLE_WALL_VERTICAL ||
12139 element == EL_EXPANDABLE_WALL_ANY ||
12140 element == EL_BD_EXPANDABLE_WALL)
12141 MauerAbleger(x, y);
12142 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12143 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12144 element == EL_EXPANDABLE_STEELWALL_ANY)
12145 MauerAblegerStahl(x, y);
12146 else if (element == EL_FLAMES)
12147 CheckForDragon(x, y);
12148 else if (element == EL_EXPLOSION)
12149 ; // drawing of correct explosion animation is handled separately
12150 else if (element == EL_ELEMENT_SNAPPING ||
12151 element == EL_DIAGONAL_SHRINKING ||
12152 element == EL_DIAGONAL_GROWING)
12154 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12156 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12158 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12159 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12161 if (IS_BELT_ACTIVE(element))
12162 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12164 if (game.magic_wall_active)
12166 int jx = local_player->jx, jy = local_player->jy;
12168 // play the element sound at the position nearest to the player
12169 if ((element == EL_MAGIC_WALL_FULL ||
12170 element == EL_MAGIC_WALL_ACTIVE ||
12171 element == EL_MAGIC_WALL_EMPTYING ||
12172 element == EL_BD_MAGIC_WALL_FULL ||
12173 element == EL_BD_MAGIC_WALL_ACTIVE ||
12174 element == EL_BD_MAGIC_WALL_EMPTYING ||
12175 element == EL_DC_MAGIC_WALL_FULL ||
12176 element == EL_DC_MAGIC_WALL_ACTIVE ||
12177 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12178 ABS(x - jx) + ABS(y - jy) <
12179 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12187 #if USE_NEW_AMOEBA_CODE
12188 // new experimental amoeba growth stuff
12189 if (!(FrameCounter % 8))
12191 static unsigned int random = 1684108901;
12193 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12195 x = RND(lev_fieldx);
12196 y = RND(lev_fieldy);
12197 element = Tile[x][y];
12199 if (!IS_PLAYER(x,y) &&
12200 (element == EL_EMPTY ||
12201 CAN_GROW_INTO(element) ||
12202 element == EL_QUICKSAND_EMPTY ||
12203 element == EL_QUICKSAND_FAST_EMPTY ||
12204 element == EL_ACID_SPLASH_LEFT ||
12205 element == EL_ACID_SPLASH_RIGHT))
12207 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12208 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12209 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12210 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12211 Tile[x][y] = EL_AMOEBA_DROP;
12214 random = random * 129 + 1;
12219 game.explosions_delayed = FALSE;
12221 SCAN_PLAYFIELD(x, y)
12223 element = Tile[x][y];
12225 if (ExplodeField[x][y])
12226 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12227 else if (element == EL_EXPLOSION)
12228 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12230 ExplodeField[x][y] = EX_TYPE_NONE;
12233 game.explosions_delayed = TRUE;
12235 if (game.magic_wall_active)
12237 if (!(game.magic_wall_time_left % 4))
12239 int element = Tile[magic_wall_x][magic_wall_y];
12241 if (element == EL_BD_MAGIC_WALL_FULL ||
12242 element == EL_BD_MAGIC_WALL_ACTIVE ||
12243 element == EL_BD_MAGIC_WALL_EMPTYING)
12244 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12245 else if (element == EL_DC_MAGIC_WALL_FULL ||
12246 element == EL_DC_MAGIC_WALL_ACTIVE ||
12247 element == EL_DC_MAGIC_WALL_EMPTYING)
12248 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12250 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12253 if (game.magic_wall_time_left > 0)
12255 game.magic_wall_time_left--;
12257 if (!game.magic_wall_time_left)
12259 SCAN_PLAYFIELD(x, y)
12261 element = Tile[x][y];
12263 if (element == EL_MAGIC_WALL_ACTIVE ||
12264 element == EL_MAGIC_WALL_FULL)
12266 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12267 TEST_DrawLevelField(x, y);
12269 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12270 element == EL_BD_MAGIC_WALL_FULL)
12272 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12273 TEST_DrawLevelField(x, y);
12275 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12276 element == EL_DC_MAGIC_WALL_FULL)
12278 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12279 TEST_DrawLevelField(x, y);
12283 game.magic_wall_active = FALSE;
12288 if (game.light_time_left > 0)
12290 game.light_time_left--;
12292 if (game.light_time_left == 0)
12293 RedrawAllLightSwitchesAndInvisibleElements();
12296 if (game.timegate_time_left > 0)
12298 game.timegate_time_left--;
12300 if (game.timegate_time_left == 0)
12301 CloseAllOpenTimegates();
12304 if (game.lenses_time_left > 0)
12306 game.lenses_time_left--;
12308 if (game.lenses_time_left == 0)
12309 RedrawAllInvisibleElementsForLenses();
12312 if (game.magnify_time_left > 0)
12314 game.magnify_time_left--;
12316 if (game.magnify_time_left == 0)
12317 RedrawAllInvisibleElementsForMagnifier();
12320 for (i = 0; i < MAX_PLAYERS; i++)
12322 struct PlayerInfo *player = &stored_player[i];
12324 if (SHIELD_ON(player))
12326 if (player->shield_deadly_time_left)
12327 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12328 else if (player->shield_normal_time_left)
12329 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12333 #if USE_DELAYED_GFX_REDRAW
12334 SCAN_PLAYFIELD(x, y)
12336 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12338 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12339 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12341 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12342 DrawLevelField(x, y);
12344 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12345 DrawLevelFieldCrumbled(x, y);
12347 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12348 DrawLevelFieldCrumbledNeighbours(x, y);
12350 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12351 DrawTwinkleOnField(x, y);
12354 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12359 PlayAllPlayersSound();
12361 for (i = 0; i < MAX_PLAYERS; i++)
12363 struct PlayerInfo *player = &stored_player[i];
12365 if (player->show_envelope != 0 && (!player->active ||
12366 player->MovPos == 0))
12368 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12370 player->show_envelope = 0;
12374 // use random number generator in every frame to make it less predictable
12375 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12378 mouse_action_last = mouse_action;
12381 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12383 int min_x = x, min_y = y, max_x = x, max_y = y;
12386 for (i = 0; i < MAX_PLAYERS; i++)
12388 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12390 if (!stored_player[i].active || &stored_player[i] == player)
12393 min_x = MIN(min_x, jx);
12394 min_y = MIN(min_y, jy);
12395 max_x = MAX(max_x, jx);
12396 max_y = MAX(max_y, jy);
12399 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12402 static boolean AllPlayersInVisibleScreen(void)
12406 for (i = 0; i < MAX_PLAYERS; i++)
12408 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12410 if (!stored_player[i].active)
12413 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12420 void ScrollLevel(int dx, int dy)
12422 int scroll_offset = 2 * TILEX_VAR;
12425 BlitBitmap(drawto_field, drawto_field,
12426 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12427 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12428 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12429 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12430 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12431 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12435 x = (dx == 1 ? BX1 : BX2);
12436 for (y = BY1; y <= BY2; y++)
12437 DrawScreenField(x, y);
12442 y = (dy == 1 ? BY1 : BY2);
12443 for (x = BX1; x <= BX2; x++)
12444 DrawScreenField(x, y);
12447 redraw_mask |= REDRAW_FIELD;
12450 static boolean canFallDown(struct PlayerInfo *player)
12452 int jx = player->jx, jy = player->jy;
12454 return (IN_LEV_FIELD(jx, jy + 1) &&
12455 (IS_FREE(jx, jy + 1) ||
12456 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12457 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12458 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12461 static boolean canPassField(int x, int y, int move_dir)
12463 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12464 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12465 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12466 int nextx = x + dx;
12467 int nexty = y + dy;
12468 int element = Tile[x][y];
12470 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12471 !CAN_MOVE(element) &&
12472 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12473 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12474 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12477 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12479 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12480 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12481 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12485 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12486 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12487 (IS_DIGGABLE(Tile[newx][newy]) ||
12488 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12489 canPassField(newx, newy, move_dir)));
12492 static void CheckGravityMovement(struct PlayerInfo *player)
12494 if (player->gravity && !player->programmed_action)
12496 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12497 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12498 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12499 int jx = player->jx, jy = player->jy;
12500 boolean player_is_moving_to_valid_field =
12501 (!player_is_snapping &&
12502 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12503 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12504 boolean player_can_fall_down = canFallDown(player);
12506 if (player_can_fall_down &&
12507 !player_is_moving_to_valid_field)
12508 player->programmed_action = MV_DOWN;
12512 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12514 return CheckGravityMovement(player);
12516 if (player->gravity && !player->programmed_action)
12518 int jx = player->jx, jy = player->jy;
12519 boolean field_under_player_is_free =
12520 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12521 boolean player_is_standing_on_valid_field =
12522 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12523 (IS_WALKABLE(Tile[jx][jy]) &&
12524 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12526 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12527 player->programmed_action = MV_DOWN;
12532 MovePlayerOneStep()
12533 -----------------------------------------------------------------------------
12534 dx, dy: direction (non-diagonal) to try to move the player to
12535 real_dx, real_dy: direction as read from input device (can be diagonal)
12538 boolean MovePlayerOneStep(struct PlayerInfo *player,
12539 int dx, int dy, int real_dx, int real_dy)
12541 int jx = player->jx, jy = player->jy;
12542 int new_jx = jx + dx, new_jy = jy + dy;
12544 boolean player_can_move = !player->cannot_move;
12546 if (!player->active || (!dx && !dy))
12547 return MP_NO_ACTION;
12549 player->MovDir = (dx < 0 ? MV_LEFT :
12550 dx > 0 ? MV_RIGHT :
12552 dy > 0 ? MV_DOWN : MV_NONE);
12554 if (!IN_LEV_FIELD(new_jx, new_jy))
12555 return MP_NO_ACTION;
12557 if (!player_can_move)
12559 if (player->MovPos == 0)
12561 player->is_moving = FALSE;
12562 player->is_digging = FALSE;
12563 player->is_collecting = FALSE;
12564 player->is_snapping = FALSE;
12565 player->is_pushing = FALSE;
12569 if (!network.enabled && game.centered_player_nr == -1 &&
12570 !AllPlayersInSight(player, new_jx, new_jy))
12571 return MP_NO_ACTION;
12573 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12574 if (can_move != MP_MOVING)
12577 // check if DigField() has caused relocation of the player
12578 if (player->jx != jx || player->jy != jy)
12579 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12581 StorePlayer[jx][jy] = 0;
12582 player->last_jx = jx;
12583 player->last_jy = jy;
12584 player->jx = new_jx;
12585 player->jy = new_jy;
12586 StorePlayer[new_jx][new_jy] = player->element_nr;
12588 if (player->move_delay_value_next != -1)
12590 player->move_delay_value = player->move_delay_value_next;
12591 player->move_delay_value_next = -1;
12595 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12597 player->step_counter++;
12599 PlayerVisit[jx][jy] = FrameCounter;
12601 player->is_moving = TRUE;
12604 // should better be called in MovePlayer(), but this breaks some tapes
12605 ScrollPlayer(player, SCROLL_INIT);
12611 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12613 int jx = player->jx, jy = player->jy;
12614 int old_jx = jx, old_jy = jy;
12615 int moved = MP_NO_ACTION;
12617 if (!player->active)
12622 if (player->MovPos == 0)
12624 player->is_moving = FALSE;
12625 player->is_digging = FALSE;
12626 player->is_collecting = FALSE;
12627 player->is_snapping = FALSE;
12628 player->is_pushing = FALSE;
12634 if (player->move_delay > 0)
12637 player->move_delay = -1; // set to "uninitialized" value
12639 // store if player is automatically moved to next field
12640 player->is_auto_moving = (player->programmed_action != MV_NONE);
12642 // remove the last programmed player action
12643 player->programmed_action = 0;
12645 if (player->MovPos)
12647 // should only happen if pre-1.2 tape recordings are played
12648 // this is only for backward compatibility
12650 int original_move_delay_value = player->move_delay_value;
12653 Debug("game:playing:MovePlayer",
12654 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12658 // scroll remaining steps with finest movement resolution
12659 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12661 while (player->MovPos)
12663 ScrollPlayer(player, SCROLL_GO_ON);
12664 ScrollScreen(NULL, SCROLL_GO_ON);
12666 AdvanceFrameAndPlayerCounters(player->index_nr);
12669 BackToFront_WithFrameDelay(0);
12672 player->move_delay_value = original_move_delay_value;
12675 player->is_active = FALSE;
12677 if (player->last_move_dir & MV_HORIZONTAL)
12679 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12680 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12684 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12685 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12688 if (!moved && !player->is_active)
12690 player->is_moving = FALSE;
12691 player->is_digging = FALSE;
12692 player->is_collecting = FALSE;
12693 player->is_snapping = FALSE;
12694 player->is_pushing = FALSE;
12700 if (moved & MP_MOVING && !ScreenMovPos &&
12701 (player->index_nr == game.centered_player_nr ||
12702 game.centered_player_nr == -1))
12704 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12706 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12708 // actual player has left the screen -- scroll in that direction
12709 if (jx != old_jx) // player has moved horizontally
12710 scroll_x += (jx - old_jx);
12711 else // player has moved vertically
12712 scroll_y += (jy - old_jy);
12716 int offset_raw = game.scroll_delay_value;
12718 if (jx != old_jx) // player has moved horizontally
12720 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12721 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12722 int new_scroll_x = jx - MIDPOSX + offset_x;
12724 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12725 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12726 scroll_x = new_scroll_x;
12728 // don't scroll over playfield boundaries
12729 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12731 // don't scroll more than one field at a time
12732 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12734 // don't scroll against the player's moving direction
12735 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12736 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12737 scroll_x = old_scroll_x;
12739 else // player has moved vertically
12741 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12742 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12743 int new_scroll_y = jy - MIDPOSY + offset_y;
12745 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12746 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12747 scroll_y = new_scroll_y;
12749 // don't scroll over playfield boundaries
12750 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12752 // don't scroll more than one field at a time
12753 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12755 // don't scroll against the player's moving direction
12756 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12757 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12758 scroll_y = old_scroll_y;
12762 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12764 if (!network.enabled && game.centered_player_nr == -1 &&
12765 !AllPlayersInVisibleScreen())
12767 scroll_x = old_scroll_x;
12768 scroll_y = old_scroll_y;
12772 ScrollScreen(player, SCROLL_INIT);
12773 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12778 player->StepFrame = 0;
12780 if (moved & MP_MOVING)
12782 if (old_jx != jx && old_jy == jy)
12783 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12784 else if (old_jx == jx && old_jy != jy)
12785 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12787 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12789 player->last_move_dir = player->MovDir;
12790 player->is_moving = TRUE;
12791 player->is_snapping = FALSE;
12792 player->is_switching = FALSE;
12793 player->is_dropping = FALSE;
12794 player->is_dropping_pressed = FALSE;
12795 player->drop_pressed_delay = 0;
12798 // should better be called here than above, but this breaks some tapes
12799 ScrollPlayer(player, SCROLL_INIT);
12804 CheckGravityMovementWhenNotMoving(player);
12806 player->is_moving = FALSE;
12808 /* at this point, the player is allowed to move, but cannot move right now
12809 (e.g. because of something blocking the way) -- ensure that the player
12810 is also allowed to move in the next frame (in old versions before 3.1.1,
12811 the player was forced to wait again for eight frames before next try) */
12813 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12814 player->move_delay = 0; // allow direct movement in the next frame
12817 if (player->move_delay == -1) // not yet initialized by DigField()
12818 player->move_delay = player->move_delay_value;
12820 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12822 TestIfPlayerTouchesBadThing(jx, jy);
12823 TestIfPlayerTouchesCustomElement(jx, jy);
12826 if (!player->active)
12827 RemovePlayer(player);
12832 void ScrollPlayer(struct PlayerInfo *player, int mode)
12834 int jx = player->jx, jy = player->jy;
12835 int last_jx = player->last_jx, last_jy = player->last_jy;
12836 int move_stepsize = TILEX / player->move_delay_value;
12838 if (!player->active)
12841 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12844 if (mode == SCROLL_INIT)
12846 player->actual_frame_counter = FrameCounter;
12847 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12849 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12850 Tile[last_jx][last_jy] == EL_EMPTY)
12852 int last_field_block_delay = 0; // start with no blocking at all
12853 int block_delay_adjustment = player->block_delay_adjustment;
12855 // if player blocks last field, add delay for exactly one move
12856 if (player->block_last_field)
12858 last_field_block_delay += player->move_delay_value;
12860 // when blocking enabled, prevent moving up despite gravity
12861 if (player->gravity && player->MovDir == MV_UP)
12862 block_delay_adjustment = -1;
12865 // add block delay adjustment (also possible when not blocking)
12866 last_field_block_delay += block_delay_adjustment;
12868 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12869 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12872 if (player->MovPos != 0) // player has not yet reached destination
12875 else if (!FrameReached(&player->actual_frame_counter, 1))
12878 if (player->MovPos != 0)
12880 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12881 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12883 // before DrawPlayer() to draw correct player graphic for this case
12884 if (player->MovPos == 0)
12885 CheckGravityMovement(player);
12888 if (player->MovPos == 0) // player reached destination field
12890 if (player->move_delay_reset_counter > 0)
12892 player->move_delay_reset_counter--;
12894 if (player->move_delay_reset_counter == 0)
12896 // continue with normal speed after quickly moving through gate
12897 HALVE_PLAYER_SPEED(player);
12899 // be able to make the next move without delay
12900 player->move_delay = 0;
12904 player->last_jx = jx;
12905 player->last_jy = jy;
12907 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12908 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12909 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12910 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12911 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12912 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12913 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12914 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12916 ExitPlayer(player);
12918 if (game.players_still_needed == 0 &&
12919 (game.friends_still_needed == 0 ||
12920 IS_SP_ELEMENT(Tile[jx][jy])))
12924 // this breaks one level: "machine", level 000
12926 int move_direction = player->MovDir;
12927 int enter_side = MV_DIR_OPPOSITE(move_direction);
12928 int leave_side = move_direction;
12929 int old_jx = last_jx;
12930 int old_jy = last_jy;
12931 int old_element = Tile[old_jx][old_jy];
12932 int new_element = Tile[jx][jy];
12934 if (IS_CUSTOM_ELEMENT(old_element))
12935 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12937 player->index_bit, leave_side);
12939 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12940 CE_PLAYER_LEAVES_X,
12941 player->index_bit, leave_side);
12943 if (IS_CUSTOM_ELEMENT(new_element))
12944 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12945 player->index_bit, enter_side);
12947 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12948 CE_PLAYER_ENTERS_X,
12949 player->index_bit, enter_side);
12951 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12952 CE_MOVE_OF_X, move_direction);
12955 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12957 TestIfPlayerTouchesBadThing(jx, jy);
12958 TestIfPlayerTouchesCustomElement(jx, jy);
12960 /* needed because pushed element has not yet reached its destination,
12961 so it would trigger a change event at its previous field location */
12962 if (!player->is_pushing)
12963 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12965 if (!player->active)
12966 RemovePlayer(player);
12969 if (!game.LevelSolved && level.use_step_counter)
12979 if (TimeLeft <= 10 && setup.time_limit)
12980 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12982 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12984 DisplayGameControlValues();
12986 if (!TimeLeft && setup.time_limit)
12987 for (i = 0; i < MAX_PLAYERS; i++)
12988 KillPlayer(&stored_player[i]);
12990 else if (game.no_time_limit && !game.all_players_gone)
12992 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12994 DisplayGameControlValues();
12998 if (tape.single_step && tape.recording && !tape.pausing &&
12999 !player->programmed_action)
13000 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13002 if (!player->programmed_action)
13003 CheckSaveEngineSnapshot(player);
13007 void ScrollScreen(struct PlayerInfo *player, int mode)
13009 static unsigned int screen_frame_counter = 0;
13011 if (mode == SCROLL_INIT)
13013 // set scrolling step size according to actual player's moving speed
13014 ScrollStepSize = TILEX / player->move_delay_value;
13016 screen_frame_counter = FrameCounter;
13017 ScreenMovDir = player->MovDir;
13018 ScreenMovPos = player->MovPos;
13019 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13022 else if (!FrameReached(&screen_frame_counter, 1))
13027 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13028 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13029 redraw_mask |= REDRAW_FIELD;
13032 ScreenMovDir = MV_NONE;
13035 void TestIfPlayerTouchesCustomElement(int x, int y)
13037 static int xy[4][2] =
13044 static int trigger_sides[4][2] =
13046 // center side border side
13047 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13048 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13049 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13050 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13052 static int touch_dir[4] =
13054 MV_LEFT | MV_RIGHT,
13059 int center_element = Tile[x][y]; // should always be non-moving!
13062 for (i = 0; i < NUM_DIRECTIONS; i++)
13064 int xx = x + xy[i][0];
13065 int yy = y + xy[i][1];
13066 int center_side = trigger_sides[i][0];
13067 int border_side = trigger_sides[i][1];
13068 int border_element;
13070 if (!IN_LEV_FIELD(xx, yy))
13073 if (IS_PLAYER(x, y)) // player found at center element
13075 struct PlayerInfo *player = PLAYERINFO(x, y);
13077 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13078 border_element = Tile[xx][yy]; // may be moving!
13079 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13080 border_element = Tile[xx][yy];
13081 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13082 border_element = MovingOrBlocked2Element(xx, yy);
13084 continue; // center and border element do not touch
13086 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13087 player->index_bit, border_side);
13088 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13089 CE_PLAYER_TOUCHES_X,
13090 player->index_bit, border_side);
13093 /* use player element that is initially defined in the level playfield,
13094 not the player element that corresponds to the runtime player number
13095 (example: a level that contains EL_PLAYER_3 as the only player would
13096 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13097 int player_element = PLAYERINFO(x, y)->initial_element;
13099 CheckElementChangeBySide(xx, yy, border_element, player_element,
13100 CE_TOUCHING_X, border_side);
13103 else if (IS_PLAYER(xx, yy)) // player found at border element
13105 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13107 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13109 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13110 continue; // center and border element do not touch
13113 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13114 player->index_bit, center_side);
13115 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13116 CE_PLAYER_TOUCHES_X,
13117 player->index_bit, center_side);
13120 /* use player element that is initially defined in the level playfield,
13121 not the player element that corresponds to the runtime player number
13122 (example: a level that contains EL_PLAYER_3 as the only player would
13123 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13124 int player_element = PLAYERINFO(xx, yy)->initial_element;
13126 CheckElementChangeBySide(x, y, center_element, player_element,
13127 CE_TOUCHING_X, center_side);
13135 void TestIfElementTouchesCustomElement(int x, int y)
13137 static int xy[4][2] =
13144 static int trigger_sides[4][2] =
13146 // center side border side
13147 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13148 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13149 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13150 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13152 static int touch_dir[4] =
13154 MV_LEFT | MV_RIGHT,
13159 boolean change_center_element = FALSE;
13160 int center_element = Tile[x][y]; // should always be non-moving!
13161 int border_element_old[NUM_DIRECTIONS];
13164 for (i = 0; i < NUM_DIRECTIONS; i++)
13166 int xx = x + xy[i][0];
13167 int yy = y + xy[i][1];
13168 int border_element;
13170 border_element_old[i] = -1;
13172 if (!IN_LEV_FIELD(xx, yy))
13175 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13176 border_element = Tile[xx][yy]; // may be moving!
13177 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13178 border_element = Tile[xx][yy];
13179 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13180 border_element = MovingOrBlocked2Element(xx, yy);
13182 continue; // center and border element do not touch
13184 border_element_old[i] = border_element;
13187 for (i = 0; i < NUM_DIRECTIONS; i++)
13189 int xx = x + xy[i][0];
13190 int yy = y + xy[i][1];
13191 int center_side = trigger_sides[i][0];
13192 int border_element = border_element_old[i];
13194 if (border_element == -1)
13197 // check for change of border element
13198 CheckElementChangeBySide(xx, yy, border_element, center_element,
13199 CE_TOUCHING_X, center_side);
13201 // (center element cannot be player, so we dont have to check this here)
13204 for (i = 0; i < NUM_DIRECTIONS; i++)
13206 int xx = x + xy[i][0];
13207 int yy = y + xy[i][1];
13208 int border_side = trigger_sides[i][1];
13209 int border_element = border_element_old[i];
13211 if (border_element == -1)
13214 // check for change of center element (but change it only once)
13215 if (!change_center_element)
13216 change_center_element =
13217 CheckElementChangeBySide(x, y, center_element, border_element,
13218 CE_TOUCHING_X, border_side);
13220 if (IS_PLAYER(xx, yy))
13222 /* use player element that is initially defined in the level playfield,
13223 not the player element that corresponds to the runtime player number
13224 (example: a level that contains EL_PLAYER_3 as the only player would
13225 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13226 int player_element = PLAYERINFO(xx, yy)->initial_element;
13228 CheckElementChangeBySide(x, y, center_element, player_element,
13229 CE_TOUCHING_X, border_side);
13234 void TestIfElementHitsCustomElement(int x, int y, int direction)
13236 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13237 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13238 int hitx = x + dx, hity = y + dy;
13239 int hitting_element = Tile[x][y];
13240 int touched_element;
13242 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13245 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13246 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13248 if (IN_LEV_FIELD(hitx, hity))
13250 int opposite_direction = MV_DIR_OPPOSITE(direction);
13251 int hitting_side = direction;
13252 int touched_side = opposite_direction;
13253 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13254 MovDir[hitx][hity] != direction ||
13255 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13261 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13262 CE_HITTING_X, touched_side);
13264 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13265 CE_HIT_BY_X, hitting_side);
13267 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13268 CE_HIT_BY_SOMETHING, opposite_direction);
13270 if (IS_PLAYER(hitx, hity))
13272 /* use player element that is initially defined in the level playfield,
13273 not the player element that corresponds to the runtime player number
13274 (example: a level that contains EL_PLAYER_3 as the only player would
13275 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13276 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13278 CheckElementChangeBySide(x, y, hitting_element, player_element,
13279 CE_HITTING_X, touched_side);
13284 // "hitting something" is also true when hitting the playfield border
13285 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13286 CE_HITTING_SOMETHING, direction);
13289 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13291 int i, kill_x = -1, kill_y = -1;
13293 int bad_element = -1;
13294 static int test_xy[4][2] =
13301 static int test_dir[4] =
13309 for (i = 0; i < NUM_DIRECTIONS; i++)
13311 int test_x, test_y, test_move_dir, test_element;
13313 test_x = good_x + test_xy[i][0];
13314 test_y = good_y + test_xy[i][1];
13316 if (!IN_LEV_FIELD(test_x, test_y))
13320 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13322 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13324 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13325 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13327 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13328 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13332 bad_element = test_element;
13338 if (kill_x != -1 || kill_y != -1)
13340 if (IS_PLAYER(good_x, good_y))
13342 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13344 if (player->shield_deadly_time_left > 0 &&
13345 !IS_INDESTRUCTIBLE(bad_element))
13346 Bang(kill_x, kill_y);
13347 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13348 KillPlayer(player);
13351 Bang(good_x, good_y);
13355 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13357 int i, kill_x = -1, kill_y = -1;
13358 int bad_element = Tile[bad_x][bad_y];
13359 static int test_xy[4][2] =
13366 static int touch_dir[4] =
13368 MV_LEFT | MV_RIGHT,
13373 static int test_dir[4] =
13381 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13384 for (i = 0; i < NUM_DIRECTIONS; i++)
13386 int test_x, test_y, test_move_dir, test_element;
13388 test_x = bad_x + test_xy[i][0];
13389 test_y = bad_y + test_xy[i][1];
13391 if (!IN_LEV_FIELD(test_x, test_y))
13395 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13397 test_element = Tile[test_x][test_y];
13399 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13400 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13402 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13403 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13405 // good thing is player or penguin that does not move away
13406 if (IS_PLAYER(test_x, test_y))
13408 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13410 if (bad_element == EL_ROBOT && player->is_moving)
13411 continue; // robot does not kill player if he is moving
13413 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13415 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13416 continue; // center and border element do not touch
13424 else if (test_element == EL_PENGUIN)
13434 if (kill_x != -1 || kill_y != -1)
13436 if (IS_PLAYER(kill_x, kill_y))
13438 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13440 if (player->shield_deadly_time_left > 0 &&
13441 !IS_INDESTRUCTIBLE(bad_element))
13442 Bang(bad_x, bad_y);
13443 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13444 KillPlayer(player);
13447 Bang(kill_x, kill_y);
13451 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13453 int bad_element = Tile[bad_x][bad_y];
13454 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13455 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13456 int test_x = bad_x + dx, test_y = bad_y + dy;
13457 int test_move_dir, test_element;
13458 int kill_x = -1, kill_y = -1;
13460 if (!IN_LEV_FIELD(test_x, test_y))
13464 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13466 test_element = Tile[test_x][test_y];
13468 if (test_move_dir != bad_move_dir)
13470 // good thing can be player or penguin that does not move away
13471 if (IS_PLAYER(test_x, test_y))
13473 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13475 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13476 player as being hit when he is moving towards the bad thing, because
13477 the "get hit by" condition would be lost after the player stops) */
13478 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13479 return; // player moves away from bad thing
13484 else if (test_element == EL_PENGUIN)
13491 if (kill_x != -1 || kill_y != -1)
13493 if (IS_PLAYER(kill_x, kill_y))
13495 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13497 if (player->shield_deadly_time_left > 0 &&
13498 !IS_INDESTRUCTIBLE(bad_element))
13499 Bang(bad_x, bad_y);
13500 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13501 KillPlayer(player);
13504 Bang(kill_x, kill_y);
13508 void TestIfPlayerTouchesBadThing(int x, int y)
13510 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13513 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13515 TestIfGoodThingHitsBadThing(x, y, move_dir);
13518 void TestIfBadThingTouchesPlayer(int x, int y)
13520 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13523 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13525 TestIfBadThingHitsGoodThing(x, y, move_dir);
13528 void TestIfFriendTouchesBadThing(int x, int y)
13530 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13533 void TestIfBadThingTouchesFriend(int x, int y)
13535 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13538 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13540 int i, kill_x = bad_x, kill_y = bad_y;
13541 static int xy[4][2] =
13549 for (i = 0; i < NUM_DIRECTIONS; i++)
13553 x = bad_x + xy[i][0];
13554 y = bad_y + xy[i][1];
13555 if (!IN_LEV_FIELD(x, y))
13558 element = Tile[x][y];
13559 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13560 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13568 if (kill_x != bad_x || kill_y != bad_y)
13569 Bang(bad_x, bad_y);
13572 void KillPlayer(struct PlayerInfo *player)
13574 int jx = player->jx, jy = player->jy;
13576 if (!player->active)
13580 Debug("game:playing:KillPlayer",
13581 "0: killed == %d, active == %d, reanimated == %d",
13582 player->killed, player->active, player->reanimated);
13585 /* the following code was introduced to prevent an infinite loop when calling
13587 -> CheckTriggeredElementChangeExt()
13588 -> ExecuteCustomElementAction()
13590 -> (infinitely repeating the above sequence of function calls)
13591 which occurs when killing the player while having a CE with the setting
13592 "kill player X when explosion of <player X>"; the solution using a new
13593 field "player->killed" was chosen for backwards compatibility, although
13594 clever use of the fields "player->active" etc. would probably also work */
13596 if (player->killed)
13600 player->killed = TRUE;
13602 // remove accessible field at the player's position
13603 Tile[jx][jy] = EL_EMPTY;
13605 // deactivate shield (else Bang()/Explode() would not work right)
13606 player->shield_normal_time_left = 0;
13607 player->shield_deadly_time_left = 0;
13610 Debug("game:playing:KillPlayer",
13611 "1: killed == %d, active == %d, reanimated == %d",
13612 player->killed, player->active, player->reanimated);
13618 Debug("game:playing:KillPlayer",
13619 "2: killed == %d, active == %d, reanimated == %d",
13620 player->killed, player->active, player->reanimated);
13623 if (player->reanimated) // killed player may have been reanimated
13624 player->killed = player->reanimated = FALSE;
13626 BuryPlayer(player);
13629 static void KillPlayerUnlessEnemyProtected(int x, int y)
13631 if (!PLAYER_ENEMY_PROTECTED(x, y))
13632 KillPlayer(PLAYERINFO(x, y));
13635 static void KillPlayerUnlessExplosionProtected(int x, int y)
13637 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13638 KillPlayer(PLAYERINFO(x, y));
13641 void BuryPlayer(struct PlayerInfo *player)
13643 int jx = player->jx, jy = player->jy;
13645 if (!player->active)
13648 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13649 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13651 RemovePlayer(player);
13653 player->buried = TRUE;
13655 if (game.all_players_gone)
13656 game.GameOver = TRUE;
13659 void RemovePlayer(struct PlayerInfo *player)
13661 int jx = player->jx, jy = player->jy;
13662 int i, found = FALSE;
13664 player->present = FALSE;
13665 player->active = FALSE;
13667 // required for some CE actions (even if the player is not active anymore)
13668 player->MovPos = 0;
13670 if (!ExplodeField[jx][jy])
13671 StorePlayer[jx][jy] = 0;
13673 if (player->is_moving)
13674 TEST_DrawLevelField(player->last_jx, player->last_jy);
13676 for (i = 0; i < MAX_PLAYERS; i++)
13677 if (stored_player[i].active)
13682 game.all_players_gone = TRUE;
13683 game.GameOver = TRUE;
13686 game.exit_x = game.robot_wheel_x = jx;
13687 game.exit_y = game.robot_wheel_y = jy;
13690 void ExitPlayer(struct PlayerInfo *player)
13692 DrawPlayer(player); // needed here only to cleanup last field
13693 RemovePlayer(player);
13695 if (game.players_still_needed > 0)
13696 game.players_still_needed--;
13699 static void setFieldForSnapping(int x, int y, int element, int direction)
13701 struct ElementInfo *ei = &element_info[element];
13702 int direction_bit = MV_DIR_TO_BIT(direction);
13703 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13704 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13705 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13707 Tile[x][y] = EL_ELEMENT_SNAPPING;
13708 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13710 ResetGfxAnimation(x, y);
13712 GfxElement[x][y] = element;
13713 GfxAction[x][y] = action;
13714 GfxDir[x][y] = direction;
13715 GfxFrame[x][y] = -1;
13719 =============================================================================
13720 checkDiagonalPushing()
13721 -----------------------------------------------------------------------------
13722 check if diagonal input device direction results in pushing of object
13723 (by checking if the alternative direction is walkable, diggable, ...)
13724 =============================================================================
13727 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13728 int x, int y, int real_dx, int real_dy)
13730 int jx, jy, dx, dy, xx, yy;
13732 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13735 // diagonal direction: check alternative direction
13740 xx = jx + (dx == 0 ? real_dx : 0);
13741 yy = jy + (dy == 0 ? real_dy : 0);
13743 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13747 =============================================================================
13749 -----------------------------------------------------------------------------
13750 x, y: field next to player (non-diagonal) to try to dig to
13751 real_dx, real_dy: direction as read from input device (can be diagonal)
13752 =============================================================================
13755 static int DigField(struct PlayerInfo *player,
13756 int oldx, int oldy, int x, int y,
13757 int real_dx, int real_dy, int mode)
13759 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13760 boolean player_was_pushing = player->is_pushing;
13761 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13762 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13763 int jx = oldx, jy = oldy;
13764 int dx = x - jx, dy = y - jy;
13765 int nextx = x + dx, nexty = y + dy;
13766 int move_direction = (dx == -1 ? MV_LEFT :
13767 dx == +1 ? MV_RIGHT :
13769 dy == +1 ? MV_DOWN : MV_NONE);
13770 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13771 int dig_side = MV_DIR_OPPOSITE(move_direction);
13772 int old_element = Tile[jx][jy];
13773 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13776 if (is_player) // function can also be called by EL_PENGUIN
13778 if (player->MovPos == 0)
13780 player->is_digging = FALSE;
13781 player->is_collecting = FALSE;
13784 if (player->MovPos == 0) // last pushing move finished
13785 player->is_pushing = FALSE;
13787 if (mode == DF_NO_PUSH) // player just stopped pushing
13789 player->is_switching = FALSE;
13790 player->push_delay = -1;
13792 return MP_NO_ACTION;
13796 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13797 old_element = Back[jx][jy];
13799 // in case of element dropped at player position, check background
13800 else if (Back[jx][jy] != EL_EMPTY &&
13801 game.engine_version >= VERSION_IDENT(2,2,0,0))
13802 old_element = Back[jx][jy];
13804 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13805 return MP_NO_ACTION; // field has no opening in this direction
13807 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13808 return MP_NO_ACTION; // field has no opening in this direction
13810 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13814 Tile[jx][jy] = player->artwork_element;
13815 InitMovingField(jx, jy, MV_DOWN);
13816 Store[jx][jy] = EL_ACID;
13817 ContinueMoving(jx, jy);
13818 BuryPlayer(player);
13820 return MP_DONT_RUN_INTO;
13823 if (player_can_move && DONT_RUN_INTO(element))
13825 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13827 return MP_DONT_RUN_INTO;
13830 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13831 return MP_NO_ACTION;
13833 collect_count = element_info[element].collect_count_initial;
13835 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13836 return MP_NO_ACTION;
13838 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13839 player_can_move = player_can_move_or_snap;
13841 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13842 game.engine_version >= VERSION_IDENT(2,2,0,0))
13844 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13845 player->index_bit, dig_side);
13846 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13847 player->index_bit, dig_side);
13849 if (element == EL_DC_LANDMINE)
13852 if (Tile[x][y] != element) // field changed by snapping
13855 return MP_NO_ACTION;
13858 if (player->gravity && is_player && !player->is_auto_moving &&
13859 canFallDown(player) && move_direction != MV_DOWN &&
13860 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13861 return MP_NO_ACTION; // player cannot walk here due to gravity
13863 if (player_can_move &&
13864 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13866 int sound_element = SND_ELEMENT(element);
13867 int sound_action = ACTION_WALKING;
13869 if (IS_RND_GATE(element))
13871 if (!player->key[RND_GATE_NR(element)])
13872 return MP_NO_ACTION;
13874 else if (IS_RND_GATE_GRAY(element))
13876 if (!player->key[RND_GATE_GRAY_NR(element)])
13877 return MP_NO_ACTION;
13879 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13881 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13882 return MP_NO_ACTION;
13884 else if (element == EL_EXIT_OPEN ||
13885 element == EL_EM_EXIT_OPEN ||
13886 element == EL_EM_EXIT_OPENING ||
13887 element == EL_STEEL_EXIT_OPEN ||
13888 element == EL_EM_STEEL_EXIT_OPEN ||
13889 element == EL_EM_STEEL_EXIT_OPENING ||
13890 element == EL_SP_EXIT_OPEN ||
13891 element == EL_SP_EXIT_OPENING)
13893 sound_action = ACTION_PASSING; // player is passing exit
13895 else if (element == EL_EMPTY)
13897 sound_action = ACTION_MOVING; // nothing to walk on
13900 // play sound from background or player, whatever is available
13901 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13902 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13904 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13906 else if (player_can_move &&
13907 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13909 if (!ACCESS_FROM(element, opposite_direction))
13910 return MP_NO_ACTION; // field not accessible from this direction
13912 if (CAN_MOVE(element)) // only fixed elements can be passed!
13913 return MP_NO_ACTION;
13915 if (IS_EM_GATE(element))
13917 if (!player->key[EM_GATE_NR(element)])
13918 return MP_NO_ACTION;
13920 else if (IS_EM_GATE_GRAY(element))
13922 if (!player->key[EM_GATE_GRAY_NR(element)])
13923 return MP_NO_ACTION;
13925 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13927 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13928 return MP_NO_ACTION;
13930 else if (IS_EMC_GATE(element))
13932 if (!player->key[EMC_GATE_NR(element)])
13933 return MP_NO_ACTION;
13935 else if (IS_EMC_GATE_GRAY(element))
13937 if (!player->key[EMC_GATE_GRAY_NR(element)])
13938 return MP_NO_ACTION;
13940 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13942 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13943 return MP_NO_ACTION;
13945 else if (element == EL_DC_GATE_WHITE ||
13946 element == EL_DC_GATE_WHITE_GRAY ||
13947 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13949 if (player->num_white_keys == 0)
13950 return MP_NO_ACTION;
13952 player->num_white_keys--;
13954 else if (IS_SP_PORT(element))
13956 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13957 element == EL_SP_GRAVITY_PORT_RIGHT ||
13958 element == EL_SP_GRAVITY_PORT_UP ||
13959 element == EL_SP_GRAVITY_PORT_DOWN)
13960 player->gravity = !player->gravity;
13961 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13962 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13963 element == EL_SP_GRAVITY_ON_PORT_UP ||
13964 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13965 player->gravity = TRUE;
13966 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13967 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13968 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13969 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13970 player->gravity = FALSE;
13973 // automatically move to the next field with double speed
13974 player->programmed_action = move_direction;
13976 if (player->move_delay_reset_counter == 0)
13978 player->move_delay_reset_counter = 2; // two double speed steps
13980 DOUBLE_PLAYER_SPEED(player);
13983 PlayLevelSoundAction(x, y, ACTION_PASSING);
13985 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13989 if (mode != DF_SNAP)
13991 GfxElement[x][y] = GFX_ELEMENT(element);
13992 player->is_digging = TRUE;
13995 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13997 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13998 player->index_bit, dig_side);
14000 // if digging triggered player relocation, finish digging tile
14001 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14002 setFieldForSnapping(x, y, element, move_direction);
14004 if (mode == DF_SNAP)
14006 if (level.block_snap_field)
14007 setFieldForSnapping(x, y, element, move_direction);
14009 TestIfElementTouchesCustomElement(x, y); // for empty space
14011 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14012 player->index_bit, dig_side);
14015 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14019 if (is_player && mode != DF_SNAP)
14021 GfxElement[x][y] = element;
14022 player->is_collecting = TRUE;
14025 if (element == EL_SPEED_PILL)
14027 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14029 else if (element == EL_EXTRA_TIME && level.time > 0)
14031 TimeLeft += level.extra_time;
14033 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14035 DisplayGameControlValues();
14037 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14039 player->shield_normal_time_left += level.shield_normal_time;
14040 if (element == EL_SHIELD_DEADLY)
14041 player->shield_deadly_time_left += level.shield_deadly_time;
14043 else if (element == EL_DYNAMITE ||
14044 element == EL_EM_DYNAMITE ||
14045 element == EL_SP_DISK_RED)
14047 if (player->inventory_size < MAX_INVENTORY_SIZE)
14048 player->inventory_element[player->inventory_size++] = element;
14050 DrawGameDoorValues();
14052 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14054 player->dynabomb_count++;
14055 player->dynabombs_left++;
14057 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14059 player->dynabomb_size++;
14061 else if (element == EL_DYNABOMB_INCREASE_POWER)
14063 player->dynabomb_xl = TRUE;
14065 else if (IS_KEY(element))
14067 player->key[KEY_NR(element)] = TRUE;
14069 DrawGameDoorValues();
14071 else if (element == EL_DC_KEY_WHITE)
14073 player->num_white_keys++;
14075 // display white keys?
14076 // DrawGameDoorValues();
14078 else if (IS_ENVELOPE(element))
14080 player->show_envelope = element;
14082 else if (element == EL_EMC_LENSES)
14084 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14086 RedrawAllInvisibleElementsForLenses();
14088 else if (element == EL_EMC_MAGNIFIER)
14090 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14092 RedrawAllInvisibleElementsForMagnifier();
14094 else if (IS_DROPPABLE(element) ||
14095 IS_THROWABLE(element)) // can be collected and dropped
14099 if (collect_count == 0)
14100 player->inventory_infinite_element = element;
14102 for (i = 0; i < collect_count; i++)
14103 if (player->inventory_size < MAX_INVENTORY_SIZE)
14104 player->inventory_element[player->inventory_size++] = element;
14106 DrawGameDoorValues();
14108 else if (collect_count > 0)
14110 game.gems_still_needed -= collect_count;
14111 if (game.gems_still_needed < 0)
14112 game.gems_still_needed = 0;
14114 game.snapshot.collected_item = TRUE;
14116 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14118 DisplayGameControlValues();
14121 RaiseScoreElement(element);
14122 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14126 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14127 player->index_bit, dig_side);
14129 // if collecting triggered player relocation, finish collecting tile
14130 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14131 setFieldForSnapping(x, y, element, move_direction);
14134 if (mode == DF_SNAP)
14136 if (level.block_snap_field)
14137 setFieldForSnapping(x, y, element, move_direction);
14139 TestIfElementTouchesCustomElement(x, y); // for empty space
14141 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14142 player->index_bit, dig_side);
14145 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14147 if (mode == DF_SNAP && element != EL_BD_ROCK)
14148 return MP_NO_ACTION;
14150 if (CAN_FALL(element) && dy)
14151 return MP_NO_ACTION;
14153 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14154 !(element == EL_SPRING && level.use_spring_bug))
14155 return MP_NO_ACTION;
14157 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14158 ((move_direction & MV_VERTICAL &&
14159 ((element_info[element].move_pattern & MV_LEFT &&
14160 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14161 (element_info[element].move_pattern & MV_RIGHT &&
14162 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14163 (move_direction & MV_HORIZONTAL &&
14164 ((element_info[element].move_pattern & MV_UP &&
14165 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14166 (element_info[element].move_pattern & MV_DOWN &&
14167 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14168 return MP_NO_ACTION;
14170 // do not push elements already moving away faster than player
14171 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14172 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14173 return MP_NO_ACTION;
14175 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14177 if (player->push_delay_value == -1 || !player_was_pushing)
14178 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14180 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14182 if (player->push_delay_value == -1)
14183 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14185 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14187 if (!player->is_pushing)
14188 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14191 player->is_pushing = TRUE;
14192 player->is_active = TRUE;
14194 if (!(IN_LEV_FIELD(nextx, nexty) &&
14195 (IS_FREE(nextx, nexty) ||
14196 (IS_SB_ELEMENT(element) &&
14197 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14198 (IS_CUSTOM_ELEMENT(element) &&
14199 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14200 return MP_NO_ACTION;
14202 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14203 return MP_NO_ACTION;
14205 if (player->push_delay == -1) // new pushing; restart delay
14206 player->push_delay = 0;
14208 if (player->push_delay < player->push_delay_value &&
14209 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14210 element != EL_SPRING && element != EL_BALLOON)
14212 // make sure that there is no move delay before next try to push
14213 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14214 player->move_delay = 0;
14216 return MP_NO_ACTION;
14219 if (IS_CUSTOM_ELEMENT(element) &&
14220 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14222 if (!DigFieldByCE(nextx, nexty, element))
14223 return MP_NO_ACTION;
14226 if (IS_SB_ELEMENT(element))
14228 boolean sokoban_task_solved = FALSE;
14230 if (element == EL_SOKOBAN_FIELD_FULL)
14232 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14234 IncrementSokobanFieldsNeeded();
14235 IncrementSokobanObjectsNeeded();
14238 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14240 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14242 DecrementSokobanFieldsNeeded();
14243 DecrementSokobanObjectsNeeded();
14245 // sokoban object was pushed from empty field to sokoban field
14246 if (Back[x][y] == EL_EMPTY)
14247 sokoban_task_solved = TRUE;
14250 Tile[x][y] = EL_SOKOBAN_OBJECT;
14252 if (Back[x][y] == Back[nextx][nexty])
14253 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14254 else if (Back[x][y] != 0)
14255 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14258 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14261 if (sokoban_task_solved &&
14262 game.sokoban_fields_still_needed == 0 &&
14263 game.sokoban_objects_still_needed == 0 &&
14264 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14266 game.players_still_needed = 0;
14270 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14274 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14276 InitMovingField(x, y, move_direction);
14277 GfxAction[x][y] = ACTION_PUSHING;
14279 if (mode == DF_SNAP)
14280 ContinueMoving(x, y);
14282 MovPos[x][y] = (dx != 0 ? dx : dy);
14284 Pushed[x][y] = TRUE;
14285 Pushed[nextx][nexty] = TRUE;
14287 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14288 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14290 player->push_delay_value = -1; // get new value later
14292 // check for element change _after_ element has been pushed
14293 if (game.use_change_when_pushing_bug)
14295 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14296 player->index_bit, dig_side);
14297 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14298 player->index_bit, dig_side);
14301 else if (IS_SWITCHABLE(element))
14303 if (PLAYER_SWITCHING(player, x, y))
14305 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14306 player->index_bit, dig_side);
14311 player->is_switching = TRUE;
14312 player->switch_x = x;
14313 player->switch_y = y;
14315 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14317 if (element == EL_ROBOT_WHEEL)
14319 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14321 game.robot_wheel_x = x;
14322 game.robot_wheel_y = y;
14323 game.robot_wheel_active = TRUE;
14325 TEST_DrawLevelField(x, y);
14327 else if (element == EL_SP_TERMINAL)
14331 SCAN_PLAYFIELD(xx, yy)
14333 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14337 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14339 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14341 ResetGfxAnimation(xx, yy);
14342 TEST_DrawLevelField(xx, yy);
14346 else if (IS_BELT_SWITCH(element))
14348 ToggleBeltSwitch(x, y);
14350 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14351 element == EL_SWITCHGATE_SWITCH_DOWN ||
14352 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14353 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14355 ToggleSwitchgateSwitch(x, y);
14357 else if (element == EL_LIGHT_SWITCH ||
14358 element == EL_LIGHT_SWITCH_ACTIVE)
14360 ToggleLightSwitch(x, y);
14362 else if (element == EL_TIMEGATE_SWITCH ||
14363 element == EL_DC_TIMEGATE_SWITCH)
14365 ActivateTimegateSwitch(x, y);
14367 else if (element == EL_BALLOON_SWITCH_LEFT ||
14368 element == EL_BALLOON_SWITCH_RIGHT ||
14369 element == EL_BALLOON_SWITCH_UP ||
14370 element == EL_BALLOON_SWITCH_DOWN ||
14371 element == EL_BALLOON_SWITCH_NONE ||
14372 element == EL_BALLOON_SWITCH_ANY)
14374 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14375 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14376 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14377 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14378 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14381 else if (element == EL_LAMP)
14383 Tile[x][y] = EL_LAMP_ACTIVE;
14384 game.lights_still_needed--;
14386 ResetGfxAnimation(x, y);
14387 TEST_DrawLevelField(x, y);
14389 else if (element == EL_TIME_ORB_FULL)
14391 Tile[x][y] = EL_TIME_ORB_EMPTY;
14393 if (level.time > 0 || level.use_time_orb_bug)
14395 TimeLeft += level.time_orb_time;
14396 game.no_time_limit = FALSE;
14398 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14400 DisplayGameControlValues();
14403 ResetGfxAnimation(x, y);
14404 TEST_DrawLevelField(x, y);
14406 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14407 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14411 game.ball_active = !game.ball_active;
14413 SCAN_PLAYFIELD(xx, yy)
14415 int e = Tile[xx][yy];
14417 if (game.ball_active)
14419 if (e == EL_EMC_MAGIC_BALL)
14420 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14421 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14422 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14426 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14427 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14428 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14429 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14434 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14435 player->index_bit, dig_side);
14437 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14438 player->index_bit, dig_side);
14440 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14441 player->index_bit, dig_side);
14447 if (!PLAYER_SWITCHING(player, x, y))
14449 player->is_switching = TRUE;
14450 player->switch_x = x;
14451 player->switch_y = y;
14453 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14454 player->index_bit, dig_side);
14455 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14456 player->index_bit, dig_side);
14458 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14459 player->index_bit, dig_side);
14460 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14461 player->index_bit, dig_side);
14464 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14465 player->index_bit, dig_side);
14466 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14467 player->index_bit, dig_side);
14469 return MP_NO_ACTION;
14472 player->push_delay = -1;
14474 if (is_player) // function can also be called by EL_PENGUIN
14476 if (Tile[x][y] != element) // really digged/collected something
14478 player->is_collecting = !player->is_digging;
14479 player->is_active = TRUE;
14486 static boolean DigFieldByCE(int x, int y, int digging_element)
14488 int element = Tile[x][y];
14490 if (!IS_FREE(x, y))
14492 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14493 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14496 // no element can dig solid indestructible elements
14497 if (IS_INDESTRUCTIBLE(element) &&
14498 !IS_DIGGABLE(element) &&
14499 !IS_COLLECTIBLE(element))
14502 if (AmoebaNr[x][y] &&
14503 (element == EL_AMOEBA_FULL ||
14504 element == EL_BD_AMOEBA ||
14505 element == EL_AMOEBA_GROWING))
14507 AmoebaCnt[AmoebaNr[x][y]]--;
14508 AmoebaCnt2[AmoebaNr[x][y]]--;
14511 if (IS_MOVING(x, y))
14512 RemoveMovingField(x, y);
14516 TEST_DrawLevelField(x, y);
14519 // if digged element was about to explode, prevent the explosion
14520 ExplodeField[x][y] = EX_TYPE_NONE;
14522 PlayLevelSoundAction(x, y, action);
14525 Store[x][y] = EL_EMPTY;
14527 // this makes it possible to leave the removed element again
14528 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14529 Store[x][y] = element;
14534 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14536 int jx = player->jx, jy = player->jy;
14537 int x = jx + dx, y = jy + dy;
14538 int snap_direction = (dx == -1 ? MV_LEFT :
14539 dx == +1 ? MV_RIGHT :
14541 dy == +1 ? MV_DOWN : MV_NONE);
14542 boolean can_continue_snapping = (level.continuous_snapping &&
14543 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14545 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14548 if (!player->active || !IN_LEV_FIELD(x, y))
14556 if (player->MovPos == 0)
14557 player->is_pushing = FALSE;
14559 player->is_snapping = FALSE;
14561 if (player->MovPos == 0)
14563 player->is_moving = FALSE;
14564 player->is_digging = FALSE;
14565 player->is_collecting = FALSE;
14571 // prevent snapping with already pressed snap key when not allowed
14572 if (player->is_snapping && !can_continue_snapping)
14575 player->MovDir = snap_direction;
14577 if (player->MovPos == 0)
14579 player->is_moving = FALSE;
14580 player->is_digging = FALSE;
14581 player->is_collecting = FALSE;
14584 player->is_dropping = FALSE;
14585 player->is_dropping_pressed = FALSE;
14586 player->drop_pressed_delay = 0;
14588 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14591 player->is_snapping = TRUE;
14592 player->is_active = TRUE;
14594 if (player->MovPos == 0)
14596 player->is_moving = FALSE;
14597 player->is_digging = FALSE;
14598 player->is_collecting = FALSE;
14601 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14602 TEST_DrawLevelField(player->last_jx, player->last_jy);
14604 TEST_DrawLevelField(x, y);
14609 static boolean DropElement(struct PlayerInfo *player)
14611 int old_element, new_element;
14612 int dropx = player->jx, dropy = player->jy;
14613 int drop_direction = player->MovDir;
14614 int drop_side = drop_direction;
14615 int drop_element = get_next_dropped_element(player);
14617 /* do not drop an element on top of another element; when holding drop key
14618 pressed without moving, dropped element must move away before the next
14619 element can be dropped (this is especially important if the next element
14620 is dynamite, which can be placed on background for historical reasons) */
14621 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14624 if (IS_THROWABLE(drop_element))
14626 dropx += GET_DX_FROM_DIR(drop_direction);
14627 dropy += GET_DY_FROM_DIR(drop_direction);
14629 if (!IN_LEV_FIELD(dropx, dropy))
14633 old_element = Tile[dropx][dropy]; // old element at dropping position
14634 new_element = drop_element; // default: no change when dropping
14636 // check if player is active, not moving and ready to drop
14637 if (!player->active || player->MovPos || player->drop_delay > 0)
14640 // check if player has anything that can be dropped
14641 if (new_element == EL_UNDEFINED)
14644 // only set if player has anything that can be dropped
14645 player->is_dropping_pressed = TRUE;
14647 // check if drop key was pressed long enough for EM style dynamite
14648 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14651 // check if anything can be dropped at the current position
14652 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14655 // collected custom elements can only be dropped on empty fields
14656 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14659 if (old_element != EL_EMPTY)
14660 Back[dropx][dropy] = old_element; // store old element on this field
14662 ResetGfxAnimation(dropx, dropy);
14663 ResetRandomAnimationValue(dropx, dropy);
14665 if (player->inventory_size > 0 ||
14666 player->inventory_infinite_element != EL_UNDEFINED)
14668 if (player->inventory_size > 0)
14670 player->inventory_size--;
14672 DrawGameDoorValues();
14674 if (new_element == EL_DYNAMITE)
14675 new_element = EL_DYNAMITE_ACTIVE;
14676 else if (new_element == EL_EM_DYNAMITE)
14677 new_element = EL_EM_DYNAMITE_ACTIVE;
14678 else if (new_element == EL_SP_DISK_RED)
14679 new_element = EL_SP_DISK_RED_ACTIVE;
14682 Tile[dropx][dropy] = new_element;
14684 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14685 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14686 el2img(Tile[dropx][dropy]), 0);
14688 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14690 // needed if previous element just changed to "empty" in the last frame
14691 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14693 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14694 player->index_bit, drop_side);
14695 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14697 player->index_bit, drop_side);
14699 TestIfElementTouchesCustomElement(dropx, dropy);
14701 else // player is dropping a dyna bomb
14703 player->dynabombs_left--;
14705 Tile[dropx][dropy] = new_element;
14707 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14708 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14709 el2img(Tile[dropx][dropy]), 0);
14711 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14714 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14715 InitField_WithBug1(dropx, dropy, FALSE);
14717 new_element = Tile[dropx][dropy]; // element might have changed
14719 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14720 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14722 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14723 MovDir[dropx][dropy] = drop_direction;
14725 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14727 // do not cause impact style collision by dropping elements that can fall
14728 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14731 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14732 player->is_dropping = TRUE;
14734 player->drop_pressed_delay = 0;
14735 player->is_dropping_pressed = FALSE;
14737 player->drop_x = dropx;
14738 player->drop_y = dropy;
14743 // ----------------------------------------------------------------------------
14744 // game sound playing functions
14745 // ----------------------------------------------------------------------------
14747 static int *loop_sound_frame = NULL;
14748 static int *loop_sound_volume = NULL;
14750 void InitPlayLevelSound(void)
14752 int num_sounds = getSoundListSize();
14754 checked_free(loop_sound_frame);
14755 checked_free(loop_sound_volume);
14757 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14758 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14761 static void PlayLevelSound(int x, int y, int nr)
14763 int sx = SCREENX(x), sy = SCREENY(y);
14764 int volume, stereo_position;
14765 int max_distance = 8;
14766 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14768 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14769 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14772 if (!IN_LEV_FIELD(x, y) ||
14773 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14774 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14777 volume = SOUND_MAX_VOLUME;
14779 if (!IN_SCR_FIELD(sx, sy))
14781 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14782 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14784 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14787 stereo_position = (SOUND_MAX_LEFT +
14788 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14789 (SCR_FIELDX + 2 * max_distance));
14791 if (IS_LOOP_SOUND(nr))
14793 /* This assures that quieter loop sounds do not overwrite louder ones,
14794 while restarting sound volume comparison with each new game frame. */
14796 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14799 loop_sound_volume[nr] = volume;
14800 loop_sound_frame[nr] = FrameCounter;
14803 PlaySoundExt(nr, volume, stereo_position, type);
14806 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14808 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14809 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14810 y < LEVELY(BY1) ? LEVELY(BY1) :
14811 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14815 static void PlayLevelSoundAction(int x, int y, int action)
14817 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14820 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14822 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14824 if (sound_effect != SND_UNDEFINED)
14825 PlayLevelSound(x, y, sound_effect);
14828 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14831 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14833 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14834 PlayLevelSound(x, y, sound_effect);
14837 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14839 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14841 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14842 PlayLevelSound(x, y, sound_effect);
14845 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14847 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14849 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14850 StopSound(sound_effect);
14853 static int getLevelMusicNr(void)
14855 if (levelset.music[level_nr] != MUS_UNDEFINED)
14856 return levelset.music[level_nr]; // from config file
14858 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14861 static void FadeLevelSounds(void)
14866 static void FadeLevelMusic(void)
14868 int music_nr = getLevelMusicNr();
14869 char *curr_music = getCurrentlyPlayingMusicFilename();
14870 char *next_music = getMusicInfoEntryFilename(music_nr);
14872 if (!strEqual(curr_music, next_music))
14876 void FadeLevelSoundsAndMusic(void)
14882 static void PlayLevelMusic(void)
14884 int music_nr = getLevelMusicNr();
14885 char *curr_music = getCurrentlyPlayingMusicFilename();
14886 char *next_music = getMusicInfoEntryFilename(music_nr);
14888 if (!strEqual(curr_music, next_music))
14889 PlayMusicLoop(music_nr);
14892 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14894 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14896 int x = xx - offset;
14897 int y = yy - offset;
14902 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14906 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14910 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14914 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14918 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14922 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14926 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14929 case SOUND_android_clone:
14930 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14933 case SOUND_android_move:
14934 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14938 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14942 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14946 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14949 case SOUND_eater_eat:
14950 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14954 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14957 case SOUND_collect:
14958 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14961 case SOUND_diamond:
14962 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14966 // !!! CHECK THIS !!!
14968 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14970 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14974 case SOUND_wonderfall:
14975 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14979 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14983 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14987 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14991 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14995 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14999 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15003 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15007 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15010 case SOUND_exit_open:
15011 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15014 case SOUND_exit_leave:
15015 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15018 case SOUND_dynamite:
15019 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15023 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15027 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15031 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15035 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15039 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15043 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15047 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15052 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15054 int element = map_element_SP_to_RND(element_sp);
15055 int action = map_action_SP_to_RND(action_sp);
15056 int offset = (setup.sp_show_border_elements ? 0 : 1);
15057 int x = xx - offset;
15058 int y = yy - offset;
15060 PlayLevelSoundElementAction(x, y, element, action);
15063 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15065 int element = map_element_MM_to_RND(element_mm);
15066 int action = map_action_MM_to_RND(action_mm);
15068 int x = xx - offset;
15069 int y = yy - offset;
15071 if (!IS_MM_ELEMENT(element))
15072 element = EL_MM_DEFAULT;
15074 PlayLevelSoundElementAction(x, y, element, action);
15077 void PlaySound_MM(int sound_mm)
15079 int sound = map_sound_MM_to_RND(sound_mm);
15081 if (sound == SND_UNDEFINED)
15087 void PlaySoundLoop_MM(int sound_mm)
15089 int sound = map_sound_MM_to_RND(sound_mm);
15091 if (sound == SND_UNDEFINED)
15094 PlaySoundLoop(sound);
15097 void StopSound_MM(int sound_mm)
15099 int sound = map_sound_MM_to_RND(sound_mm);
15101 if (sound == SND_UNDEFINED)
15107 void RaiseScore(int value)
15109 game.score += value;
15111 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15113 DisplayGameControlValues();
15116 void RaiseScoreElement(int element)
15121 case EL_BD_DIAMOND:
15122 case EL_EMERALD_YELLOW:
15123 case EL_EMERALD_RED:
15124 case EL_EMERALD_PURPLE:
15125 case EL_SP_INFOTRON:
15126 RaiseScore(level.score[SC_EMERALD]);
15129 RaiseScore(level.score[SC_DIAMOND]);
15132 RaiseScore(level.score[SC_CRYSTAL]);
15135 RaiseScore(level.score[SC_PEARL]);
15138 case EL_BD_BUTTERFLY:
15139 case EL_SP_ELECTRON:
15140 RaiseScore(level.score[SC_BUG]);
15143 case EL_BD_FIREFLY:
15144 case EL_SP_SNIKSNAK:
15145 RaiseScore(level.score[SC_SPACESHIP]);
15148 case EL_DARK_YAMYAM:
15149 RaiseScore(level.score[SC_YAMYAM]);
15152 RaiseScore(level.score[SC_ROBOT]);
15155 RaiseScore(level.score[SC_PACMAN]);
15158 RaiseScore(level.score[SC_NUT]);
15161 case EL_EM_DYNAMITE:
15162 case EL_SP_DISK_RED:
15163 case EL_DYNABOMB_INCREASE_NUMBER:
15164 case EL_DYNABOMB_INCREASE_SIZE:
15165 case EL_DYNABOMB_INCREASE_POWER:
15166 RaiseScore(level.score[SC_DYNAMITE]);
15168 case EL_SHIELD_NORMAL:
15169 case EL_SHIELD_DEADLY:
15170 RaiseScore(level.score[SC_SHIELD]);
15172 case EL_EXTRA_TIME:
15173 RaiseScore(level.extra_time_score);
15187 case EL_DC_KEY_WHITE:
15188 RaiseScore(level.score[SC_KEY]);
15191 RaiseScore(element_info[element].collect_score);
15196 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15198 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15200 // closing door required in case of envelope style request dialogs
15203 // prevent short reactivation of overlay buttons while closing door
15204 SetOverlayActive(FALSE);
15206 CloseDoor(DOOR_CLOSE_1);
15209 if (network.enabled)
15210 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15214 FadeSkipNextFadeIn();
15216 SetGameStatus(GAME_MODE_MAIN);
15221 else // continue playing the game
15223 if (tape.playing && tape.deactivate_display)
15224 TapeDeactivateDisplayOff(TRUE);
15226 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15228 if (tape.playing && tape.deactivate_display)
15229 TapeDeactivateDisplayOn();
15233 void RequestQuitGame(boolean ask_if_really_quit)
15235 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15236 boolean skip_request = game.all_players_gone || quick_quit;
15238 RequestQuitGameExt(skip_request, quick_quit,
15239 "Do you really want to quit the game?");
15242 void RequestRestartGame(char *message)
15244 game.restart_game_message = NULL;
15246 boolean has_started_game = hasStartedNetworkGame();
15247 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15249 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15251 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15255 SetGameStatus(GAME_MODE_MAIN);
15261 void CheckGameOver(void)
15263 static boolean last_game_over = FALSE;
15264 static int game_over_delay = 0;
15265 int game_over_delay_value = 50;
15266 boolean game_over = checkGameFailed();
15268 // do not handle game over if request dialog is already active
15269 if (game.request_active)
15272 // do not ask to play again if game was never actually played
15273 if (!game.GamePlayed)
15278 last_game_over = FALSE;
15279 game_over_delay = game_over_delay_value;
15284 if (game_over_delay > 0)
15291 if (last_game_over != game_over)
15292 game.restart_game_message = (hasStartedNetworkGame() ?
15293 "Game over! Play it again?" :
15296 last_game_over = game_over;
15299 boolean checkGameSolved(void)
15301 // set for all game engines if level was solved
15302 return game.LevelSolved_GameEnd;
15305 boolean checkGameFailed(void)
15307 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15308 return (game_em.game_over && !game_em.level_solved);
15309 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15310 return (game_sp.game_over && !game_sp.level_solved);
15311 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15312 return (game_mm.game_over && !game_mm.level_solved);
15313 else // GAME_ENGINE_TYPE_RND
15314 return (game.GameOver && !game.LevelSolved);
15317 boolean checkGameEnded(void)
15319 return (checkGameSolved() || checkGameFailed());
15323 // ----------------------------------------------------------------------------
15324 // random generator functions
15325 // ----------------------------------------------------------------------------
15327 unsigned int InitEngineRandom_RND(int seed)
15329 game.num_random_calls = 0;
15331 return InitEngineRandom(seed);
15334 unsigned int RND(int max)
15338 game.num_random_calls++;
15340 return GetEngineRandom(max);
15347 // ----------------------------------------------------------------------------
15348 // game engine snapshot handling functions
15349 // ----------------------------------------------------------------------------
15351 struct EngineSnapshotInfo
15353 // runtime values for custom element collect score
15354 int collect_score[NUM_CUSTOM_ELEMENTS];
15356 // runtime values for group element choice position
15357 int choice_pos[NUM_GROUP_ELEMENTS];
15359 // runtime values for belt position animations
15360 int belt_graphic[4][NUM_BELT_PARTS];
15361 int belt_anim_mode[4][NUM_BELT_PARTS];
15364 static struct EngineSnapshotInfo engine_snapshot_rnd;
15365 static char *snapshot_level_identifier = NULL;
15366 static int snapshot_level_nr = -1;
15368 static void SaveEngineSnapshotValues_RND(void)
15370 static int belt_base_active_element[4] =
15372 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15373 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15374 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15375 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15379 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15381 int element = EL_CUSTOM_START + i;
15383 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15386 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15388 int element = EL_GROUP_START + i;
15390 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15393 for (i = 0; i < 4; i++)
15395 for (j = 0; j < NUM_BELT_PARTS; j++)
15397 int element = belt_base_active_element[i] + j;
15398 int graphic = el2img(element);
15399 int anim_mode = graphic_info[graphic].anim_mode;
15401 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15402 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15407 static void LoadEngineSnapshotValues_RND(void)
15409 unsigned int num_random_calls = game.num_random_calls;
15412 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15414 int element = EL_CUSTOM_START + i;
15416 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15419 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15421 int element = EL_GROUP_START + i;
15423 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15426 for (i = 0; i < 4; i++)
15428 for (j = 0; j < NUM_BELT_PARTS; j++)
15430 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15431 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15433 graphic_info[graphic].anim_mode = anim_mode;
15437 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15439 InitRND(tape.random_seed);
15440 for (i = 0; i < num_random_calls; i++)
15444 if (game.num_random_calls != num_random_calls)
15446 Error("number of random calls out of sync");
15447 Error("number of random calls should be %d", num_random_calls);
15448 Error("number of random calls is %d", game.num_random_calls);
15450 Fail("this should not happen -- please debug");
15454 void FreeEngineSnapshotSingle(void)
15456 FreeSnapshotSingle();
15458 setString(&snapshot_level_identifier, NULL);
15459 snapshot_level_nr = -1;
15462 void FreeEngineSnapshotList(void)
15464 FreeSnapshotList();
15467 static ListNode *SaveEngineSnapshotBuffers(void)
15469 ListNode *buffers = NULL;
15471 // copy some special values to a structure better suited for the snapshot
15473 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15474 SaveEngineSnapshotValues_RND();
15475 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15476 SaveEngineSnapshotValues_EM();
15477 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15478 SaveEngineSnapshotValues_SP(&buffers);
15479 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15480 SaveEngineSnapshotValues_MM(&buffers);
15482 // save values stored in special snapshot structure
15484 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15486 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15488 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15489 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15490 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15493 // save further RND engine values
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15511 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15514 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15515 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15516 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15517 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15518 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15519 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15520 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15521 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15522 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15523 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15524 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15525 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15526 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15527 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15528 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15529 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15530 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15531 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15533 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15534 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15536 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15537 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15538 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15540 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15541 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15543 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15544 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15545 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15546 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15547 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15549 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15550 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15553 ListNode *node = engine_snapshot_list_rnd;
15556 while (node != NULL)
15558 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15563 Debug("game:playing:SaveEngineSnapshotBuffers",
15564 "size of engine snapshot: %d bytes", num_bytes);
15570 void SaveEngineSnapshotSingle(void)
15572 ListNode *buffers = SaveEngineSnapshotBuffers();
15574 // finally save all snapshot buffers to single snapshot
15575 SaveSnapshotSingle(buffers);
15577 // save level identification information
15578 setString(&snapshot_level_identifier, leveldir_current->identifier);
15579 snapshot_level_nr = level_nr;
15582 boolean CheckSaveEngineSnapshotToList(void)
15584 boolean save_snapshot =
15585 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15586 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15587 game.snapshot.changed_action) ||
15588 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15589 game.snapshot.collected_item));
15591 game.snapshot.changed_action = FALSE;
15592 game.snapshot.collected_item = FALSE;
15593 game.snapshot.save_snapshot = save_snapshot;
15595 return save_snapshot;
15598 void SaveEngineSnapshotToList(void)
15600 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15604 ListNode *buffers = SaveEngineSnapshotBuffers();
15606 // finally save all snapshot buffers to snapshot list
15607 SaveSnapshotToList(buffers);
15610 void SaveEngineSnapshotToListInitial(void)
15612 FreeEngineSnapshotList();
15614 SaveEngineSnapshotToList();
15617 static void LoadEngineSnapshotValues(void)
15619 // restore special values from snapshot structure
15621 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15622 LoadEngineSnapshotValues_RND();
15623 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15624 LoadEngineSnapshotValues_EM();
15625 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15626 LoadEngineSnapshotValues_SP();
15627 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15628 LoadEngineSnapshotValues_MM();
15631 void LoadEngineSnapshotSingle(void)
15633 LoadSnapshotSingle();
15635 LoadEngineSnapshotValues();
15638 static void LoadEngineSnapshot_Undo(int steps)
15640 LoadSnapshotFromList_Older(steps);
15642 LoadEngineSnapshotValues();
15645 static void LoadEngineSnapshot_Redo(int steps)
15647 LoadSnapshotFromList_Newer(steps);
15649 LoadEngineSnapshotValues();
15652 boolean CheckEngineSnapshotSingle(void)
15654 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15655 snapshot_level_nr == level_nr);
15658 boolean CheckEngineSnapshotList(void)
15660 return CheckSnapshotList();
15664 // ---------- new game button stuff -------------------------------------------
15671 boolean *setup_value;
15672 boolean allowed_on_tape;
15673 boolean is_touch_button;
15675 } gamebutton_info[NUM_GAME_BUTTONS] =
15678 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15679 GAME_CTRL_ID_STOP, NULL,
15680 TRUE, FALSE, "stop game"
15683 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15684 GAME_CTRL_ID_PAUSE, NULL,
15685 TRUE, FALSE, "pause game"
15688 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15689 GAME_CTRL_ID_PLAY, NULL,
15690 TRUE, FALSE, "play game"
15693 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15694 GAME_CTRL_ID_UNDO, NULL,
15695 TRUE, FALSE, "undo step"
15698 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15699 GAME_CTRL_ID_REDO, NULL,
15700 TRUE, FALSE, "redo step"
15703 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15704 GAME_CTRL_ID_SAVE, NULL,
15705 TRUE, FALSE, "save game"
15708 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15709 GAME_CTRL_ID_PAUSE2, NULL,
15710 TRUE, FALSE, "pause game"
15713 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15714 GAME_CTRL_ID_LOAD, NULL,
15715 TRUE, FALSE, "load game"
15718 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15719 GAME_CTRL_ID_PANEL_STOP, NULL,
15720 FALSE, FALSE, "stop game"
15723 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15724 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15725 FALSE, FALSE, "pause game"
15728 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15729 GAME_CTRL_ID_PANEL_PLAY, NULL,
15730 FALSE, FALSE, "play game"
15733 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15734 GAME_CTRL_ID_TOUCH_STOP, NULL,
15735 FALSE, TRUE, "stop game"
15738 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15739 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15740 FALSE, TRUE, "pause game"
15743 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15744 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15745 TRUE, FALSE, "background music on/off"
15748 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15749 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15750 TRUE, FALSE, "sound loops on/off"
15753 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15754 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15755 TRUE, FALSE, "normal sounds on/off"
15758 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15759 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15760 FALSE, FALSE, "background music on/off"
15763 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15764 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15765 FALSE, FALSE, "sound loops on/off"
15768 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15769 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15770 FALSE, FALSE, "normal sounds on/off"
15774 void CreateGameButtons(void)
15778 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15780 int graphic = gamebutton_info[i].graphic;
15781 struct GraphicInfo *gfx = &graphic_info[graphic];
15782 struct XY *pos = gamebutton_info[i].pos;
15783 struct GadgetInfo *gi;
15786 unsigned int event_mask;
15787 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15788 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15789 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15790 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15791 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15792 int gd_x = gfx->src_x;
15793 int gd_y = gfx->src_y;
15794 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15795 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15796 int gd_xa = gfx->src_x + gfx->active_xoffset;
15797 int gd_ya = gfx->src_y + gfx->active_yoffset;
15798 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15799 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15800 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15801 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15804 if (gfx->bitmap == NULL)
15806 game_gadget[id] = NULL;
15811 if (id == GAME_CTRL_ID_STOP ||
15812 id == GAME_CTRL_ID_PANEL_STOP ||
15813 id == GAME_CTRL_ID_TOUCH_STOP ||
15814 id == GAME_CTRL_ID_PLAY ||
15815 id == GAME_CTRL_ID_PANEL_PLAY ||
15816 id == GAME_CTRL_ID_SAVE ||
15817 id == GAME_CTRL_ID_LOAD)
15819 button_type = GD_TYPE_NORMAL_BUTTON;
15821 event_mask = GD_EVENT_RELEASED;
15823 else if (id == GAME_CTRL_ID_UNDO ||
15824 id == GAME_CTRL_ID_REDO)
15826 button_type = GD_TYPE_NORMAL_BUTTON;
15828 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15832 button_type = GD_TYPE_CHECK_BUTTON;
15833 checked = (gamebutton_info[i].setup_value != NULL ?
15834 *gamebutton_info[i].setup_value : FALSE);
15835 event_mask = GD_EVENT_PRESSED;
15838 gi = CreateGadget(GDI_CUSTOM_ID, id,
15839 GDI_IMAGE_ID, graphic,
15840 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15843 GDI_WIDTH, gfx->width,
15844 GDI_HEIGHT, gfx->height,
15845 GDI_TYPE, button_type,
15846 GDI_STATE, GD_BUTTON_UNPRESSED,
15847 GDI_CHECKED, checked,
15848 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15849 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15850 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15851 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15852 GDI_DIRECT_DRAW, FALSE,
15853 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15854 GDI_EVENT_MASK, event_mask,
15855 GDI_CALLBACK_ACTION, HandleGameButtons,
15859 Fail("cannot create gadget");
15861 game_gadget[id] = gi;
15865 void FreeGameButtons(void)
15869 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15870 FreeGadget(game_gadget[i]);
15873 static void UnmapGameButtonsAtSamePosition(int id)
15877 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15879 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15880 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15881 UnmapGadget(game_gadget[i]);
15884 static void UnmapGameButtonsAtSamePosition_All(void)
15886 if (setup.show_snapshot_buttons)
15888 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15889 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15890 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15894 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15895 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15896 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15898 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15899 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15900 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15904 static void MapGameButtonsAtSamePosition(int id)
15908 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15910 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15911 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15912 MapGadget(game_gadget[i]);
15914 UnmapGameButtonsAtSamePosition_All();
15917 void MapUndoRedoButtons(void)
15919 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15920 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15922 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15923 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15926 void UnmapUndoRedoButtons(void)
15928 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15929 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15931 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15932 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15935 void ModifyPauseButtons(void)
15939 GAME_CTRL_ID_PAUSE,
15940 GAME_CTRL_ID_PAUSE2,
15941 GAME_CTRL_ID_PANEL_PAUSE,
15942 GAME_CTRL_ID_TOUCH_PAUSE,
15947 for (i = 0; ids[i] > -1; i++)
15948 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15951 static void MapGameButtonsExt(boolean on_tape)
15955 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15956 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15957 i != GAME_CTRL_ID_UNDO &&
15958 i != GAME_CTRL_ID_REDO)
15959 MapGadget(game_gadget[i]);
15961 UnmapGameButtonsAtSamePosition_All();
15963 RedrawGameButtons();
15966 static void UnmapGameButtonsExt(boolean on_tape)
15970 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15971 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15972 UnmapGadget(game_gadget[i]);
15975 static void RedrawGameButtonsExt(boolean on_tape)
15979 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15980 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15981 RedrawGadget(game_gadget[i]);
15984 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15989 gi->checked = state;
15992 static void RedrawSoundButtonGadget(int id)
15994 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15995 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15996 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15997 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15998 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15999 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16002 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16003 RedrawGadget(game_gadget[id2]);
16006 void MapGameButtons(void)
16008 MapGameButtonsExt(FALSE);
16011 void UnmapGameButtons(void)
16013 UnmapGameButtonsExt(FALSE);
16016 void RedrawGameButtons(void)
16018 RedrawGameButtonsExt(FALSE);
16021 void MapGameButtonsOnTape(void)
16023 MapGameButtonsExt(TRUE);
16026 void UnmapGameButtonsOnTape(void)
16028 UnmapGameButtonsExt(TRUE);
16031 void RedrawGameButtonsOnTape(void)
16033 RedrawGameButtonsExt(TRUE);
16036 static void GameUndoRedoExt(void)
16038 ClearPlayerAction();
16040 tape.pausing = TRUE;
16043 UpdateAndDisplayGameControlValues();
16045 DrawCompleteVideoDisplay();
16046 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16047 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16048 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16053 static void GameUndo(int steps)
16055 if (!CheckEngineSnapshotList())
16058 LoadEngineSnapshot_Undo(steps);
16063 static void GameRedo(int steps)
16065 if (!CheckEngineSnapshotList())
16068 LoadEngineSnapshot_Redo(steps);
16073 static void HandleGameButtonsExt(int id, int button)
16075 static boolean game_undo_executed = FALSE;
16076 int steps = BUTTON_STEPSIZE(button);
16077 boolean handle_game_buttons =
16078 (game_status == GAME_MODE_PLAYING ||
16079 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16081 if (!handle_game_buttons)
16086 case GAME_CTRL_ID_STOP:
16087 case GAME_CTRL_ID_PANEL_STOP:
16088 case GAME_CTRL_ID_TOUCH_STOP:
16089 if (game_status == GAME_MODE_MAIN)
16095 RequestQuitGame(TRUE);
16099 case GAME_CTRL_ID_PAUSE:
16100 case GAME_CTRL_ID_PAUSE2:
16101 case GAME_CTRL_ID_PANEL_PAUSE:
16102 case GAME_CTRL_ID_TOUCH_PAUSE:
16103 if (network.enabled && game_status == GAME_MODE_PLAYING)
16106 SendToServer_ContinuePlaying();
16108 SendToServer_PausePlaying();
16111 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16113 game_undo_executed = FALSE;
16117 case GAME_CTRL_ID_PLAY:
16118 case GAME_CTRL_ID_PANEL_PLAY:
16119 if (game_status == GAME_MODE_MAIN)
16121 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16123 else if (tape.pausing)
16125 if (network.enabled)
16126 SendToServer_ContinuePlaying();
16128 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16132 case GAME_CTRL_ID_UNDO:
16133 // Important: When using "save snapshot when collecting an item" mode,
16134 // load last (current) snapshot for first "undo" after pressing "pause"
16135 // (else the last-but-one snapshot would be loaded, because the snapshot
16136 // pointer already points to the last snapshot when pressing "pause",
16137 // which is fine for "every step/move" mode, but not for "every collect")
16138 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16139 !game_undo_executed)
16142 game_undo_executed = TRUE;
16147 case GAME_CTRL_ID_REDO:
16151 case GAME_CTRL_ID_SAVE:
16155 case GAME_CTRL_ID_LOAD:
16159 case SOUND_CTRL_ID_MUSIC:
16160 case SOUND_CTRL_ID_PANEL_MUSIC:
16161 if (setup.sound_music)
16163 setup.sound_music = FALSE;
16167 else if (audio.music_available)
16169 setup.sound = setup.sound_music = TRUE;
16171 SetAudioMode(setup.sound);
16173 if (game_status == GAME_MODE_PLAYING)
16177 RedrawSoundButtonGadget(id);
16181 case SOUND_CTRL_ID_LOOPS:
16182 case SOUND_CTRL_ID_PANEL_LOOPS:
16183 if (setup.sound_loops)
16184 setup.sound_loops = FALSE;
16185 else if (audio.loops_available)
16187 setup.sound = setup.sound_loops = TRUE;
16189 SetAudioMode(setup.sound);
16192 RedrawSoundButtonGadget(id);
16196 case SOUND_CTRL_ID_SIMPLE:
16197 case SOUND_CTRL_ID_PANEL_SIMPLE:
16198 if (setup.sound_simple)
16199 setup.sound_simple = FALSE;
16200 else if (audio.sound_available)
16202 setup.sound = setup.sound_simple = TRUE;
16204 SetAudioMode(setup.sound);
16207 RedrawSoundButtonGadget(id);
16216 static void HandleGameButtons(struct GadgetInfo *gi)
16218 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16221 void HandleSoundButtonKeys(Key key)
16223 if (key == setup.shortcut.sound_simple)
16224 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16225 else if (key == setup.shortcut.sound_loops)
16226 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16227 else if (key == setup.shortcut.sound_music)
16228 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);