1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://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 Feld[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 Feld[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 Feld[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, Feld[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[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, Feld[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(Feld[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
971 Feld[x][y] == EL_EM_EXIT_OPEN || \
972 Feld[x][y] == EL_STEEL_EXIT_OPEN || \
973 Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Feld[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) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \
986 Feld[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(Feld[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 AmoebeNachbarNr(int, int);
1096 void AmoebeUmwandeln(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(Feld[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 Feld[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 Feld[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Feld[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] == Feld[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Feld[x][y];
1771 #if DEBUG_INIT_PLAYER
1774 printf("- player element %d activated", player->element_nr);
1775 printf(" (local player is %d and currently %s)\n",
1776 local_player->element_nr,
1777 local_player->active ? "active" : "not active");
1782 Feld[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Feld[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Feld[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1832 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Feld[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(Feld[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[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 Feld[x][y] = Feld[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 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Feld[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 Feld[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(Feld[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Feld[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(Feld[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Feld[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(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2151 Error(ERR_EXIT, "this should not happen -- please debug");
2154 // force update of game controls after initialization
2155 gpc->value = gpc->last_value = -1;
2156 gpc->frame = gpc->last_frame = -1;
2157 gpc->gfx_frame = -1;
2159 // determine panel value width for later calculation of alignment
2160 if (type == TYPE_INTEGER || type == TYPE_STRING)
2162 pos->width = pos->size * getFontWidth(pos->font);
2163 pos->height = getFontHeight(pos->font);
2165 else if (type == TYPE_ELEMENT)
2167 pos->width = pos->size;
2168 pos->height = pos->size;
2171 // fill structure for game panel draw order
2173 gpo->sort_priority = pos->sort_priority;
2176 // sort game panel controls according to sort_priority and control number
2177 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2178 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2181 static void UpdatePlayfieldElementCount(void)
2183 boolean use_element_count = FALSE;
2186 // first check if it is needed at all to calculate playfield element count
2187 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2188 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2189 use_element_count = TRUE;
2191 if (!use_element_count)
2194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2195 element_info[i].element_count = 0;
2197 SCAN_PLAYFIELD(x, y)
2199 element_info[Feld[x][y]].element_count++;
2202 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2203 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2204 if (IS_IN_GROUP(j, i))
2205 element_info[EL_GROUP_START + i].element_count +=
2206 element_info[j].element_count;
2209 static void UpdateGameControlValues(void)
2212 int time = (game.LevelSolved ?
2213 game.LevelSolved_CountingTime :
2214 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 game_sp.time_played :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 game_mm.energy_left :
2220 game.no_time_limit ? TimePlayed : TimeLeft);
2221 int score = (game.LevelSolved ?
2222 game.LevelSolved_CountingScore :
2223 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2224 game_em.lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->gems_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 game_sp.infotrons_still_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2235 game_mm.kettles_still_needed :
2236 game.gems_still_needed);
2237 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed > 0 ||
2243 game_mm.lights_still_needed > 0 :
2244 game.gems_still_needed > 0 ||
2245 game.sokoban_fields_still_needed > 0 ||
2246 game.sokoban_objects_still_needed > 0 ||
2247 game.lights_still_needed > 0);
2248 int health = (game.LevelSolved ?
2249 game.LevelSolved_CountingHealth :
2250 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2251 MM_HEALTH(game_mm.laser_overload_value) :
2254 UpdatePlayfieldElementCount();
2256 // update game panel control values
2258 // used instead of "level_nr" (for network games)
2259 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2260 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2262 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2263 for (i = 0; i < MAX_NUM_KEYS; i++)
2264 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2265 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2268 if (game.centered_player_nr == -1)
2270 for (i = 0; i < MAX_PLAYERS; i++)
2272 // only one player in Supaplex game engine
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2276 for (k = 0; k < MAX_NUM_KEYS; k++)
2278 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2280 if (game_em.ply[i]->keys & (1 << k))
2281 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2282 get_key_element_from_nr(k);
2284 else if (stored_player[i].key[k])
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2289 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2290 getPlayerInventorySize(i);
2292 if (stored_player[i].num_white_keys > 0)
2293 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2296 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2297 stored_player[i].num_white_keys;
2302 int player_nr = game.centered_player_nr;
2304 for (k = 0; k < MAX_NUM_KEYS; k++)
2306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2308 if (game_em.ply[player_nr]->keys & (1 << k))
2309 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2310 get_key_element_from_nr(k);
2312 else if (stored_player[player_nr].key[k])
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2317 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2318 getPlayerInventorySize(player_nr);
2320 if (stored_player[player_nr].num_white_keys > 0)
2321 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2324 stored_player[player_nr].num_white_keys;
2327 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2329 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, i);
2331 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, -i - 1);
2335 game_panel_controls[GAME_PANEL_SCORE].value = score;
2336 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2338 game_panel_controls[GAME_PANEL_TIME].value = time;
2340 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2341 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2342 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2344 if (level.time == 0)
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2349 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2350 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2352 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2354 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2355 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2357 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2358 local_player->shield_normal_time_left;
2359 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2360 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2362 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2363 local_player->shield_deadly_time_left;
2365 game_panel_controls[GAME_PANEL_EXIT].value =
2366 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2369 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2372 EL_EMC_MAGIC_BALL_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2375 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2377 game.light_time_left;
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2380 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2382 game.timegate_time_left;
2384 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2385 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2387 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2388 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2389 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2390 game.lenses_time_left;
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2393 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2395 game.magnify_time_left;
2397 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2398 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2399 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2400 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2401 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2402 EL_BALLOON_SWITCH_NONE);
2404 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2405 local_player->dynabomb_count;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2407 local_player->dynabomb_size;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2409 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2411 game_panel_controls[GAME_PANEL_PENGUINS].value =
2412 game.friends_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2415 game.sokoban_objects_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2417 game.sokoban_fields_still_needed;
2419 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2420 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2422 for (i = 0; i < NUM_BELTS; i++)
2424 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2425 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2426 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2427 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2428 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2432 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2434 game.magic_wall_time_left;
2436 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2437 local_player->gravity;
2439 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2440 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2442 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2443 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2444 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2445 game.panel.element[i].id : EL_UNDEFINED);
2447 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2448 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2449 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2450 element_info[game.panel.element_count[i].id].element_count : 0);
2452 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2453 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2454 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2455 element_info[game.panel.ce_score[i].id].collect_score : 0);
2457 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2458 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2459 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2460 element_info[game.panel.ce_score_element[i].id].collect_score :
2463 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2464 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2467 // update game panel control frames
2469 for (i = 0; game_panel_controls[i].nr != -1; i++)
2471 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2473 if (gpc->type == TYPE_ELEMENT)
2475 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2477 int last_anim_random_frame = gfx.anim_random_frame;
2478 int element = gpc->value;
2479 int graphic = el2panelimg(element);
2481 if (gpc->value != gpc->last_value)
2484 gpc->gfx_random = INIT_GFX_RANDOM();
2490 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2491 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2495 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2496 gfx.anim_random_frame = gpc->gfx_random;
2498 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2499 gpc->gfx_frame = element_info[element].collect_score;
2501 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2504 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2505 gfx.anim_random_frame = last_anim_random_frame;
2508 else if (gpc->type == TYPE_GRAPHIC)
2510 if (gpc->graphic != IMG_UNDEFINED)
2512 int last_anim_random_frame = gfx.anim_random_frame;
2513 int graphic = gpc->graphic;
2515 if (gpc->value != gpc->last_value)
2518 gpc->gfx_random = INIT_GFX_RANDOM();
2524 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2525 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2526 gpc->gfx_random = INIT_GFX_RANDOM();
2529 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2530 gfx.anim_random_frame = gpc->gfx_random;
2532 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2534 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2535 gfx.anim_random_frame = last_anim_random_frame;
2541 static void DisplayGameControlValues(void)
2543 boolean redraw_panel = FALSE;
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (PANEL_DEACTIVATED(gpc->pos))
2553 if (gpc->value == gpc->last_value &&
2554 gpc->frame == gpc->last_frame)
2557 redraw_panel = TRUE;
2563 // copy default game door content to main double buffer
2565 // !!! CHECK AGAIN !!!
2566 SetPanelBackground();
2567 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2568 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2570 // redraw game control buttons
2571 RedrawGameButtons();
2573 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2575 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2577 int nr = game_panel_order[i].nr;
2578 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2579 struct TextPosInfo *pos = gpc->pos;
2580 int type = gpc->type;
2581 int value = gpc->value;
2582 int frame = gpc->frame;
2583 int size = pos->size;
2584 int font = pos->font;
2585 boolean draw_masked = pos->draw_masked;
2586 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2588 if (PANEL_DEACTIVATED(pos))
2591 gpc->last_value = value;
2592 gpc->last_frame = frame;
2594 if (type == TYPE_INTEGER)
2596 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2597 nr == GAME_PANEL_TIME)
2599 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2601 if (use_dynamic_size) // use dynamic number of digits
2603 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2604 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2605 int size2 = size1 + 1;
2606 int font1 = pos->font;
2607 int font2 = pos->font_alt;
2609 size = (value < value_change ? size1 : size2);
2610 font = (value < value_change ? font1 : font2);
2614 // correct text size if "digits" is zero or less
2616 size = strlen(int2str(value, size));
2618 // dynamically correct text alignment
2619 pos->width = size * getFontWidth(font);
2621 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2622 int2str(value, size), font, mask_mode);
2624 else if (type == TYPE_ELEMENT)
2626 int element, graphic;
2630 int dst_x = PANEL_XPOS(pos);
2631 int dst_y = PANEL_YPOS(pos);
2633 if (value != EL_UNDEFINED && value != EL_EMPTY)
2636 graphic = el2panelimg(value);
2638 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2640 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2643 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2646 width = graphic_info[graphic].width * size / TILESIZE;
2647 height = graphic_info[graphic].height * size / TILESIZE;
2650 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2653 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2657 else if (type == TYPE_GRAPHIC)
2659 int graphic = gpc->graphic;
2660 int graphic_active = gpc->graphic_active;
2664 int dst_x = PANEL_XPOS(pos);
2665 int dst_y = PANEL_YPOS(pos);
2666 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2667 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2669 if (graphic != IMG_UNDEFINED && !skip)
2671 if (pos->style == STYLE_REVERSE)
2672 value = 100 - value;
2674 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2676 if (pos->direction & MV_HORIZONTAL)
2678 width = graphic_info[graphic_active].width * value / 100;
2679 height = graphic_info[graphic_active].height;
2681 if (pos->direction == MV_LEFT)
2683 src_x += graphic_info[graphic_active].width - width;
2684 dst_x += graphic_info[graphic_active].width - width;
2689 width = graphic_info[graphic_active].width;
2690 height = graphic_info[graphic_active].height * value / 100;
2692 if (pos->direction == MV_UP)
2694 src_y += graphic_info[graphic_active].height - height;
2695 dst_y += graphic_info[graphic_active].height - height;
2700 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2703 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2708 if (pos->direction & MV_HORIZONTAL)
2710 if (pos->direction == MV_RIGHT)
2717 dst_x = PANEL_XPOS(pos);
2720 width = graphic_info[graphic].width - width;
2724 if (pos->direction == MV_DOWN)
2731 dst_y = PANEL_YPOS(pos);
2734 height = graphic_info[graphic].height - height;
2738 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2741 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2745 else if (type == TYPE_STRING)
2747 boolean active = (value != 0);
2748 char *state_normal = "off";
2749 char *state_active = "on";
2750 char *state = (active ? state_active : state_normal);
2751 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2752 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2753 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2754 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2756 if (nr == GAME_PANEL_GRAVITY_STATE)
2758 int font1 = pos->font; // (used for normal state)
2759 int font2 = pos->font_alt; // (used for active state)
2761 font = (active ? font2 : font1);
2770 // don't truncate output if "chars" is zero or less
2773 // dynamically correct text alignment
2774 pos->width = size * getFontWidth(font);
2777 s_cut = getStringCopyN(s, size);
2779 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2780 s_cut, font, mask_mode);
2786 redraw_mask |= REDRAW_DOOR_1;
2789 SetGameStatus(GAME_MODE_PLAYING);
2792 void UpdateAndDisplayGameControlValues(void)
2794 if (tape.deactivate_display)
2797 UpdateGameControlValues();
2798 DisplayGameControlValues();
2802 static void UpdateGameDoorValues(void)
2804 UpdateGameControlValues();
2808 void DrawGameDoorValues(void)
2810 DisplayGameControlValues();
2814 // ============================================================================
2816 // ----------------------------------------------------------------------------
2817 // initialize game engine due to level / tape version number
2818 // ============================================================================
2820 static void InitGameEngine(void)
2822 int i, j, k, l, x, y;
2824 // set game engine from tape file when re-playing, else from level file
2825 game.engine_version = (tape.playing ? tape.engine_version :
2826 level.game_version);
2828 // set single or multi-player game mode (needed for re-playing tapes)
2829 game.team_mode = setup.team_mode;
2833 int num_players = 0;
2835 for (i = 0; i < MAX_PLAYERS; i++)
2836 if (tape.player_participates[i])
2839 // multi-player tapes contain input data for more than one player
2840 game.team_mode = (num_players > 1);
2843 // --------------------------------------------------------------------------
2844 // set flags for bugs and changes according to active game engine version
2845 // --------------------------------------------------------------------------
2848 Summary of bugfix/change:
2849 Fixed move speed of elements entering or leaving magic wall.
2851 Fixed/changed in version:
2855 Before 2.0.1, move speed of elements entering or leaving magic wall was
2856 twice as fast as it is now.
2857 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2859 Affected levels/tapes:
2860 The first condition is generally needed for all levels/tapes before version
2861 2.0.1, which might use the old behaviour before it was changed; known tapes
2862 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2863 The second condition is an exception from the above case and is needed for
2864 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2865 above, but before it was known that this change would break tapes like the
2866 above and was fixed in 4.1.4.2, so that the changed behaviour was active
2867 although the engine version while recording maybe was before 2.0.1. There
2868 are a lot of tapes that are affected by this exception, like tape 006 from
2869 the level set "rnd_conor_mancone".
2872 boolean use_old_move_stepsize_for_magic_wall =
2873 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2875 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2876 tape.game_version < VERSION_IDENT(4,1,4,2)));
2879 Summary of bugfix/change:
2880 Fixed handling for custom elements that change when pushed by the player.
2882 Fixed/changed in version:
2886 Before 3.1.0, custom elements that "change when pushing" changed directly
2887 after the player started pushing them (until then handled in "DigField()").
2888 Since 3.1.0, these custom elements are not changed until the "pushing"
2889 move of the element is finished (now handled in "ContinueMoving()").
2891 Affected levels/tapes:
2892 The first condition is generally needed for all levels/tapes before version
2893 3.1.0, which might use the old behaviour before it was changed; known tapes
2894 that are affected are some tapes from the level set "Walpurgis Gardens" by
2896 The second condition is an exception from the above case and is needed for
2897 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2898 above (including some development versions of 3.1.0), but before it was
2899 known that this change would break tapes like the above and was fixed in
2900 3.1.1, so that the changed behaviour was active although the engine version
2901 while recording maybe was before 3.1.0. There is at least one tape that is
2902 affected by this exception, which is the tape for the one-level set "Bug
2903 Machine" by Juergen Bonhagen.
2906 game.use_change_when_pushing_bug =
2907 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2909 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2910 tape.game_version < VERSION_IDENT(3,1,1,0)));
2913 Summary of bugfix/change:
2914 Fixed handling for blocking the field the player leaves when moving.
2916 Fixed/changed in version:
2920 Before 3.1.1, when "block last field when moving" was enabled, the field
2921 the player is leaving when moving was blocked for the time of the move,
2922 and was directly unblocked afterwards. This resulted in the last field
2923 being blocked for exactly one less than the number of frames of one player
2924 move. Additionally, even when blocking was disabled, the last field was
2925 blocked for exactly one frame.
2926 Since 3.1.1, due to changes in player movement handling, the last field
2927 is not blocked at all when blocking is disabled. When blocking is enabled,
2928 the last field is blocked for exactly the number of frames of one player
2929 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2930 last field is blocked for exactly one more than the number of frames of
2933 Affected levels/tapes:
2934 (!!! yet to be determined -- probably many !!!)
2937 game.use_block_last_field_bug =
2938 (game.engine_version < VERSION_IDENT(3,1,1,0));
2940 /* various special flags and settings for native Emerald Mine game engine */
2942 game_em.use_single_button =
2943 (game.engine_version > VERSION_IDENT(4,0,0,2));
2945 game_em.use_snap_key_bug =
2946 (game.engine_version < VERSION_IDENT(4,0,1,0));
2948 game_em.use_old_explosions =
2949 (game.engine_version < VERSION_IDENT(4,1,4,2));
2951 // --------------------------------------------------------------------------
2953 // set maximal allowed number of custom element changes per game frame
2954 game.max_num_changes_per_frame = 1;
2956 // default scan direction: scan playfield from top/left to bottom/right
2957 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2959 // dynamically adjust element properties according to game engine version
2960 InitElementPropertiesEngine(game.engine_version);
2963 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2964 printf(" tape version == %06d [%s] [file: %06d]\n",
2965 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2967 printf(" => game.engine_version == %06d\n", game.engine_version);
2970 // ---------- initialize player's initial move delay ------------------------
2972 // dynamically adjust player properties according to level information
2973 for (i = 0; i < MAX_PLAYERS; i++)
2974 game.initial_move_delay_value[i] =
2975 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2977 // dynamically adjust player properties according to game engine version
2978 for (i = 0; i < MAX_PLAYERS; i++)
2979 game.initial_move_delay[i] =
2980 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2981 game.initial_move_delay_value[i] : 0);
2983 // ---------- initialize player's initial push delay ------------------------
2985 // dynamically adjust player properties according to game engine version
2986 game.initial_push_delay_value =
2987 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2989 // ---------- initialize changing elements ----------------------------------
2991 // initialize changing elements information
2992 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2994 struct ElementInfo *ei = &element_info[i];
2996 // this pointer might have been changed in the level editor
2997 ei->change = &ei->change_page[0];
2999 if (!IS_CUSTOM_ELEMENT(i))
3001 ei->change->target_element = EL_EMPTY_SPACE;
3002 ei->change->delay_fixed = 0;
3003 ei->change->delay_random = 0;
3004 ei->change->delay_frames = 1;
3007 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3009 ei->has_change_event[j] = FALSE;
3011 ei->event_page_nr[j] = 0;
3012 ei->event_page[j] = &ei->change_page[0];
3016 // add changing elements from pre-defined list
3017 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3019 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3020 struct ElementInfo *ei = &element_info[ch_delay->element];
3022 ei->change->target_element = ch_delay->target_element;
3023 ei->change->delay_fixed = ch_delay->change_delay;
3025 ei->change->pre_change_function = ch_delay->pre_change_function;
3026 ei->change->change_function = ch_delay->change_function;
3027 ei->change->post_change_function = ch_delay->post_change_function;
3029 ei->change->can_change = TRUE;
3030 ei->change->can_change_or_has_action = TRUE;
3032 ei->has_change_event[CE_DELAY] = TRUE;
3034 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3035 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3038 // ---------- initialize internal run-time variables ------------------------
3040 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3042 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3044 for (j = 0; j < ei->num_change_pages; j++)
3046 ei->change_page[j].can_change_or_has_action =
3047 (ei->change_page[j].can_change |
3048 ei->change_page[j].has_action);
3052 // add change events from custom element configuration
3053 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3055 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3057 for (j = 0; j < ei->num_change_pages; j++)
3059 if (!ei->change_page[j].can_change_or_has_action)
3062 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3064 // only add event page for the first page found with this event
3065 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3067 ei->has_change_event[k] = TRUE;
3069 ei->event_page_nr[k] = j;
3070 ei->event_page[k] = &ei->change_page[j];
3076 // ---------- initialize reference elements in change conditions ------------
3078 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3080 int element = EL_CUSTOM_START + i;
3081 struct ElementInfo *ei = &element_info[element];
3083 for (j = 0; j < ei->num_change_pages; j++)
3085 int trigger_element = ei->change_page[j].initial_trigger_element;
3087 if (trigger_element >= EL_PREV_CE_8 &&
3088 trigger_element <= EL_NEXT_CE_8)
3089 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3091 ei->change_page[j].trigger_element = trigger_element;
3095 // ---------- initialize run-time trigger player and element ----------------
3097 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3099 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3101 for (j = 0; j < ei->num_change_pages; j++)
3103 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3104 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3105 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3106 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3107 ei->change_page[j].actual_trigger_ce_value = 0;
3108 ei->change_page[j].actual_trigger_ce_score = 0;
3112 // ---------- initialize trigger events -------------------------------------
3114 // initialize trigger events information
3115 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3116 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3117 trigger_events[i][j] = FALSE;
3119 // add trigger events from element change event properties
3120 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3122 struct ElementInfo *ei = &element_info[i];
3124 for (j = 0; j < ei->num_change_pages; j++)
3126 if (!ei->change_page[j].can_change_or_has_action)
3129 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3131 int trigger_element = ei->change_page[j].trigger_element;
3133 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3135 if (ei->change_page[j].has_event[k])
3137 if (IS_GROUP_ELEMENT(trigger_element))
3139 struct ElementGroupInfo *group =
3140 element_info[trigger_element].group;
3142 for (l = 0; l < group->num_elements_resolved; l++)
3143 trigger_events[group->element_resolved[l]][k] = TRUE;
3145 else if (trigger_element == EL_ANY_ELEMENT)
3146 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3147 trigger_events[l][k] = TRUE;
3149 trigger_events[trigger_element][k] = TRUE;
3156 // ---------- initialize push delay -----------------------------------------
3158 // initialize push delay values to default
3159 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3161 if (!IS_CUSTOM_ELEMENT(i))
3163 // set default push delay values (corrected since version 3.0.7-1)
3164 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3166 element_info[i].push_delay_fixed = 2;
3167 element_info[i].push_delay_random = 8;
3171 element_info[i].push_delay_fixed = 8;
3172 element_info[i].push_delay_random = 8;
3177 // set push delay value for certain elements from pre-defined list
3178 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3180 int e = push_delay_list[i].element;
3182 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3183 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3186 // set push delay value for Supaplex elements for newer engine versions
3187 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3189 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3191 if (IS_SP_ELEMENT(i))
3193 // set SP push delay to just enough to push under a falling zonk
3194 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3196 element_info[i].push_delay_fixed = delay;
3197 element_info[i].push_delay_random = 0;
3202 // ---------- initialize move stepsize --------------------------------------
3204 // initialize move stepsize values to default
3205 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3206 if (!IS_CUSTOM_ELEMENT(i))
3207 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3209 // set move stepsize value for certain elements from pre-defined list
3210 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3212 int e = move_stepsize_list[i].element;
3214 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3216 // set move stepsize value for certain elements for older engine versions
3217 if (use_old_move_stepsize_for_magic_wall)
3219 if (e == EL_MAGIC_WALL_FILLING ||
3220 e == EL_MAGIC_WALL_EMPTYING ||
3221 e == EL_BD_MAGIC_WALL_FILLING ||
3222 e == EL_BD_MAGIC_WALL_EMPTYING)
3223 element_info[e].move_stepsize *= 2;
3227 // ---------- initialize collect score --------------------------------------
3229 // initialize collect score values for custom elements from initial value
3230 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3231 if (IS_CUSTOM_ELEMENT(i))
3232 element_info[i].collect_score = element_info[i].collect_score_initial;
3234 // ---------- initialize collect count --------------------------------------
3236 // initialize collect count values for non-custom elements
3237 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3238 if (!IS_CUSTOM_ELEMENT(i))
3239 element_info[i].collect_count_initial = 0;
3241 // add collect count values for all elements from pre-defined list
3242 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3243 element_info[collect_count_list[i].element].collect_count_initial =
3244 collect_count_list[i].count;
3246 // ---------- initialize access direction -----------------------------------
3248 // initialize access direction values to default (access from every side)
3249 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3250 if (!IS_CUSTOM_ELEMENT(i))
3251 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3253 // set access direction value for certain elements from pre-defined list
3254 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3255 element_info[access_direction_list[i].element].access_direction =
3256 access_direction_list[i].direction;
3258 // ---------- initialize explosion content ----------------------------------
3259 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3261 if (IS_CUSTOM_ELEMENT(i))
3264 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3266 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3268 element_info[i].content.e[x][y] =
3269 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3270 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3271 i == EL_PLAYER_3 ? EL_EMERALD :
3272 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3273 i == EL_MOLE ? EL_EMERALD_RED :
3274 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3275 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3276 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3277 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3278 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3279 i == EL_WALL_EMERALD ? EL_EMERALD :
3280 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3281 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3282 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3283 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3284 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3285 i == EL_WALL_PEARL ? EL_PEARL :
3286 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3291 // ---------- initialize recursion detection --------------------------------
3292 recursion_loop_depth = 0;
3293 recursion_loop_detected = FALSE;
3294 recursion_loop_element = EL_UNDEFINED;
3296 // ---------- initialize graphics engine ------------------------------------
3297 game.scroll_delay_value =
3298 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3299 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3300 !setup.forced_scroll_delay ? 0 :
3301 setup.scroll_delay ? setup.scroll_delay_value : 0);
3302 game.scroll_delay_value =
3303 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3305 // ---------- initialize game engine snapshots ------------------------------
3306 for (i = 0; i < MAX_PLAYERS; i++)
3307 game.snapshot.last_action[i] = 0;
3308 game.snapshot.changed_action = FALSE;
3309 game.snapshot.collected_item = FALSE;
3310 game.snapshot.mode =
3311 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3312 SNAPSHOT_MODE_EVERY_STEP :
3313 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3314 SNAPSHOT_MODE_EVERY_MOVE :
3315 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3316 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3317 game.snapshot.save_snapshot = FALSE;
3319 // ---------- initialize level time for Supaplex engine ---------------------
3320 // Supaplex levels with time limit currently unsupported -- should be added
3321 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3324 // ---------- initialize flags for handling game actions --------------------
3326 // set flags for game actions to default values
3327 game.use_key_actions = TRUE;
3328 game.use_mouse_actions = FALSE;
3330 // when using Mirror Magic game engine, handle mouse events only
3331 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3333 game.use_key_actions = FALSE;
3334 game.use_mouse_actions = TRUE;
3337 // check for custom elements with mouse click events
3338 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3340 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3342 int element = EL_CUSTOM_START + i;
3344 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3345 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3346 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3347 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3348 game.use_mouse_actions = TRUE;
3353 static int get_num_special_action(int element, int action_first,
3356 int num_special_action = 0;
3359 for (i = action_first; i <= action_last; i++)
3361 boolean found = FALSE;
3363 for (j = 0; j < NUM_DIRECTIONS; j++)
3364 if (el_act_dir2img(element, i, j) !=
3365 el_act_dir2img(element, ACTION_DEFAULT, j))
3369 num_special_action++;
3374 return num_special_action;
3378 // ============================================================================
3380 // ----------------------------------------------------------------------------
3381 // initialize and start new game
3382 // ============================================================================
3384 #if DEBUG_INIT_PLAYER
3385 static void DebugPrintPlayerStatus(char *message)
3392 printf("%s:\n", message);
3394 for (i = 0; i < MAX_PLAYERS; i++)
3396 struct PlayerInfo *player = &stored_player[i];
3398 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3402 player->connected_locally,
3403 player->connected_network,
3406 if (local_player == player)
3407 printf(" (local player)");
3416 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3417 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3418 int fade_mask = REDRAW_FIELD;
3420 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3421 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3422 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3423 int initial_move_dir = MV_DOWN;
3426 // required here to update video display before fading (FIX THIS)
3427 DrawMaskedBorder(REDRAW_DOOR_2);
3429 if (!game.restart_level)
3430 CloseDoor(DOOR_CLOSE_1);
3432 SetGameStatus(GAME_MODE_PLAYING);
3434 if (level_editor_test_game)
3435 FadeSkipNextFadeOut();
3437 FadeSetEnterScreen();
3440 fade_mask = REDRAW_ALL;
3442 FadeLevelSoundsAndMusic();
3444 ExpireSoundLoops(TRUE);
3448 if (level_editor_test_game)
3449 FadeSkipNextFadeIn();
3451 // needed if different viewport properties defined for playing
3452 ChangeViewportPropertiesIfNeeded();
3456 DrawCompleteVideoDisplay();
3458 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3461 InitGameControlValues();
3463 // initialize tape actions from game when recording tape
3466 tape.use_key_actions = game.use_key_actions;
3467 tape.use_mouse_actions = game.use_mouse_actions;
3470 // don't play tapes over network
3471 network_playing = (network.enabled && !tape.playing);
3473 for (i = 0; i < MAX_PLAYERS; i++)
3475 struct PlayerInfo *player = &stored_player[i];
3477 player->index_nr = i;
3478 player->index_bit = (1 << i);
3479 player->element_nr = EL_PLAYER_1 + i;
3481 player->present = FALSE;
3482 player->active = FALSE;
3483 player->mapped = FALSE;
3485 player->killed = FALSE;
3486 player->reanimated = FALSE;
3487 player->buried = FALSE;
3490 player->effective_action = 0;
3491 player->programmed_action = 0;
3492 player->snap_action = 0;
3494 player->mouse_action.lx = 0;
3495 player->mouse_action.ly = 0;
3496 player->mouse_action.button = 0;
3497 player->mouse_action.button_hint = 0;
3499 player->effective_mouse_action.lx = 0;
3500 player->effective_mouse_action.ly = 0;
3501 player->effective_mouse_action.button = 0;
3502 player->effective_mouse_action.button_hint = 0;
3504 for (j = 0; j < MAX_NUM_KEYS; j++)
3505 player->key[j] = FALSE;
3507 player->num_white_keys = 0;
3509 player->dynabomb_count = 0;
3510 player->dynabomb_size = 1;
3511 player->dynabombs_left = 0;
3512 player->dynabomb_xl = FALSE;
3514 player->MovDir = initial_move_dir;
3517 player->GfxDir = initial_move_dir;
3518 player->GfxAction = ACTION_DEFAULT;
3520 player->StepFrame = 0;
3522 player->initial_element = player->element_nr;
3523 player->artwork_element =
3524 (level.use_artwork_element[i] ? level.artwork_element[i] :
3525 player->element_nr);
3526 player->use_murphy = FALSE;
3528 player->block_last_field = FALSE; // initialized in InitPlayerField()
3529 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3531 player->gravity = level.initial_player_gravity[i];
3533 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3535 player->actual_frame_counter = 0;
3537 player->step_counter = 0;
3539 player->last_move_dir = initial_move_dir;
3541 player->is_active = FALSE;
3543 player->is_waiting = FALSE;
3544 player->is_moving = FALSE;
3545 player->is_auto_moving = FALSE;
3546 player->is_digging = FALSE;
3547 player->is_snapping = FALSE;
3548 player->is_collecting = FALSE;
3549 player->is_pushing = FALSE;
3550 player->is_switching = FALSE;
3551 player->is_dropping = FALSE;
3552 player->is_dropping_pressed = FALSE;
3554 player->is_bored = FALSE;
3555 player->is_sleeping = FALSE;
3557 player->was_waiting = TRUE;
3558 player->was_moving = FALSE;
3559 player->was_snapping = FALSE;
3560 player->was_dropping = FALSE;
3562 player->force_dropping = FALSE;
3564 player->frame_counter_bored = -1;
3565 player->frame_counter_sleeping = -1;
3567 player->anim_delay_counter = 0;
3568 player->post_delay_counter = 0;
3570 player->dir_waiting = initial_move_dir;
3571 player->action_waiting = ACTION_DEFAULT;
3572 player->last_action_waiting = ACTION_DEFAULT;
3573 player->special_action_bored = ACTION_DEFAULT;
3574 player->special_action_sleeping = ACTION_DEFAULT;
3576 player->switch_x = -1;
3577 player->switch_y = -1;
3579 player->drop_x = -1;
3580 player->drop_y = -1;
3582 player->show_envelope = 0;
3584 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3586 player->push_delay = -1; // initialized when pushing starts
3587 player->push_delay_value = game.initial_push_delay_value;
3589 player->drop_delay = 0;
3590 player->drop_pressed_delay = 0;
3592 player->last_jx = -1;
3593 player->last_jy = -1;
3597 player->shield_normal_time_left = 0;
3598 player->shield_deadly_time_left = 0;
3600 player->inventory_infinite_element = EL_UNDEFINED;
3601 player->inventory_size = 0;
3603 if (level.use_initial_inventory[i])
3605 for (j = 0; j < level.initial_inventory_size[i]; j++)
3607 int element = level.initial_inventory_content[i][j];
3608 int collect_count = element_info[element].collect_count_initial;
3611 if (!IS_CUSTOM_ELEMENT(element))
3614 if (collect_count == 0)
3615 player->inventory_infinite_element = element;
3617 for (k = 0; k < collect_count; k++)
3618 if (player->inventory_size < MAX_INVENTORY_SIZE)
3619 player->inventory_element[player->inventory_size++] = element;
3623 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3624 SnapField(player, 0, 0);
3626 map_player_action[i] = i;
3629 network_player_action_received = FALSE;
3631 // initial null action
3632 if (network_playing)
3633 SendToServer_MovePlayer(MV_NONE);
3638 TimeLeft = level.time;
3641 ScreenMovDir = MV_NONE;
3645 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3647 game.robot_wheel_x = -1;
3648 game.robot_wheel_y = -1;
3653 game.all_players_gone = FALSE;
3655 game.LevelSolved = FALSE;
3656 game.GameOver = FALSE;
3658 game.GamePlayed = !tape.playing;
3660 game.LevelSolved_GameWon = FALSE;
3661 game.LevelSolved_GameEnd = FALSE;
3662 game.LevelSolved_SaveTape = FALSE;
3663 game.LevelSolved_SaveScore = FALSE;
3665 game.LevelSolved_CountingTime = 0;
3666 game.LevelSolved_CountingScore = 0;
3667 game.LevelSolved_CountingHealth = 0;
3669 game.panel.active = TRUE;
3671 game.no_time_limit = (level.time == 0);
3673 game.yamyam_content_nr = 0;
3674 game.robot_wheel_active = FALSE;
3675 game.magic_wall_active = FALSE;
3676 game.magic_wall_time_left = 0;
3677 game.light_time_left = 0;
3678 game.timegate_time_left = 0;
3679 game.switchgate_pos = 0;
3680 game.wind_direction = level.wind_direction_initial;
3683 game.score_final = 0;
3685 game.health = MAX_HEALTH;
3686 game.health_final = MAX_HEALTH;
3688 game.gems_still_needed = level.gems_needed;
3689 game.sokoban_fields_still_needed = 0;
3690 game.sokoban_objects_still_needed = 0;
3691 game.lights_still_needed = 0;
3692 game.players_still_needed = 0;
3693 game.friends_still_needed = 0;
3695 game.lenses_time_left = 0;
3696 game.magnify_time_left = 0;
3698 game.ball_active = level.ball_active_initial;
3699 game.ball_content_nr = 0;
3701 game.explosions_delayed = TRUE;
3703 game.envelope_active = FALSE;
3705 for (i = 0; i < NUM_BELTS; i++)
3707 game.belt_dir[i] = MV_NONE;
3708 game.belt_dir_nr[i] = 3; // not moving, next moving left
3711 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3712 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3714 #if DEBUG_INIT_PLAYER
3715 DebugPrintPlayerStatus("Player status at level initialization");
3718 SCAN_PLAYFIELD(x, y)
3720 Feld[x][y] = Last[x][y] = level.field[x][y];
3721 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3722 ChangeDelay[x][y] = 0;
3723 ChangePage[x][y] = -1;
3724 CustomValue[x][y] = 0; // initialized in InitField()
3725 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3727 WasJustMoving[x][y] = 0;
3728 WasJustFalling[x][y] = 0;
3729 CheckCollision[x][y] = 0;
3730 CheckImpact[x][y] = 0;
3732 Pushed[x][y] = FALSE;
3734 ChangeCount[x][y] = 0;
3735 ChangeEvent[x][y] = -1;
3737 ExplodePhase[x][y] = 0;
3738 ExplodeDelay[x][y] = 0;
3739 ExplodeField[x][y] = EX_TYPE_NONE;
3741 RunnerVisit[x][y] = 0;
3742 PlayerVisit[x][y] = 0;
3745 GfxRandom[x][y] = INIT_GFX_RANDOM();
3746 GfxElement[x][y] = EL_UNDEFINED;
3747 GfxAction[x][y] = ACTION_DEFAULT;
3748 GfxDir[x][y] = MV_NONE;
3749 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3752 SCAN_PLAYFIELD(x, y)
3754 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3756 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3758 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3761 InitField(x, y, TRUE);
3763 ResetGfxAnimation(x, y);
3768 for (i = 0; i < MAX_PLAYERS; i++)
3770 struct PlayerInfo *player = &stored_player[i];
3772 // set number of special actions for bored and sleeping animation
3773 player->num_special_action_bored =
3774 get_num_special_action(player->artwork_element,
3775 ACTION_BORING_1, ACTION_BORING_LAST);
3776 player->num_special_action_sleeping =
3777 get_num_special_action(player->artwork_element,
3778 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3781 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3782 emulate_sb ? EMU_SOKOBAN :
3783 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3785 // initialize type of slippery elements
3786 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3788 if (!IS_CUSTOM_ELEMENT(i))
3790 // default: elements slip down either to the left or right randomly
3791 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3793 // SP style elements prefer to slip down on the left side
3794 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3795 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3797 // BD style elements prefer to slip down on the left side
3798 if (game.emulation == EMU_BOULDERDASH)
3799 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3803 // initialize explosion and ignition delay
3804 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3806 if (!IS_CUSTOM_ELEMENT(i))
3809 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3810 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3811 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3812 int last_phase = (num_phase + 1) * delay;
3813 int half_phase = (num_phase / 2) * delay;
3815 element_info[i].explosion_delay = last_phase - 1;
3816 element_info[i].ignition_delay = half_phase;
3818 if (i == EL_BLACK_ORB)
3819 element_info[i].ignition_delay = 1;
3823 // correct non-moving belts to start moving left
3824 for (i = 0; i < NUM_BELTS; i++)
3825 if (game.belt_dir[i] == MV_NONE)
3826 game.belt_dir_nr[i] = 3; // not moving, next moving left
3828 #if USE_NEW_PLAYER_ASSIGNMENTS
3829 // use preferred player also in local single-player mode
3830 if (!network.enabled && !game.team_mode)
3832 int new_index_nr = setup.network_player_nr;
3834 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3836 for (i = 0; i < MAX_PLAYERS; i++)
3837 stored_player[i].connected_locally = FALSE;
3839 stored_player[new_index_nr].connected_locally = TRUE;
3843 for (i = 0; i < MAX_PLAYERS; i++)
3845 stored_player[i].connected = FALSE;
3847 // in network game mode, the local player might not be the first player
3848 if (stored_player[i].connected_locally)
3849 local_player = &stored_player[i];
3852 if (!network.enabled)
3853 local_player->connected = TRUE;
3857 for (i = 0; i < MAX_PLAYERS; i++)
3858 stored_player[i].connected = tape.player_participates[i];
3860 else if (network.enabled)
3862 // add team mode players connected over the network (needed for correct
3863 // assignment of player figures from level to locally playing players)
3865 for (i = 0; i < MAX_PLAYERS; i++)
3866 if (stored_player[i].connected_network)
3867 stored_player[i].connected = TRUE;
3869 else if (game.team_mode)
3871 // try to guess locally connected team mode players (needed for correct
3872 // assignment of player figures from level to locally playing players)
3874 for (i = 0; i < MAX_PLAYERS; i++)
3875 if (setup.input[i].use_joystick ||
3876 setup.input[i].key.left != KSYM_UNDEFINED)
3877 stored_player[i].connected = TRUE;
3880 #if DEBUG_INIT_PLAYER
3881 DebugPrintPlayerStatus("Player status after level initialization");
3884 #if DEBUG_INIT_PLAYER
3886 printf("Reassigning players ...\n");
3889 // check if any connected player was not found in playfield
3890 for (i = 0; i < MAX_PLAYERS; i++)
3892 struct PlayerInfo *player = &stored_player[i];
3894 if (player->connected && !player->present)
3896 struct PlayerInfo *field_player = NULL;
3898 #if DEBUG_INIT_PLAYER
3900 printf("- looking for field player for player %d ...\n", i + 1);
3903 // assign first free player found that is present in the playfield
3905 // first try: look for unmapped playfield player that is not connected
3906 for (j = 0; j < MAX_PLAYERS; j++)
3907 if (field_player == NULL &&
3908 stored_player[j].present &&
3909 !stored_player[j].mapped &&
3910 !stored_player[j].connected)
3911 field_player = &stored_player[j];
3913 // second try: look for *any* unmapped playfield player
3914 for (j = 0; j < MAX_PLAYERS; j++)
3915 if (field_player == NULL &&
3916 stored_player[j].present &&
3917 !stored_player[j].mapped)
3918 field_player = &stored_player[j];
3920 if (field_player != NULL)
3922 int jx = field_player->jx, jy = field_player->jy;
3924 #if DEBUG_INIT_PLAYER
3926 printf("- found player %d\n", field_player->index_nr + 1);
3929 player->present = FALSE;
3930 player->active = FALSE;
3932 field_player->present = TRUE;
3933 field_player->active = TRUE;
3936 player->initial_element = field_player->initial_element;
3937 player->artwork_element = field_player->artwork_element;
3939 player->block_last_field = field_player->block_last_field;
3940 player->block_delay_adjustment = field_player->block_delay_adjustment;
3943 StorePlayer[jx][jy] = field_player->element_nr;
3945 field_player->jx = field_player->last_jx = jx;
3946 field_player->jy = field_player->last_jy = jy;
3948 if (local_player == player)
3949 local_player = field_player;
3951 map_player_action[field_player->index_nr] = i;
3953 field_player->mapped = TRUE;
3955 #if DEBUG_INIT_PLAYER
3957 printf("- map_player_action[%d] == %d\n",
3958 field_player->index_nr + 1, i + 1);
3963 if (player->connected && player->present)
3964 player->mapped = TRUE;
3967 #if DEBUG_INIT_PLAYER
3968 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3973 // check if any connected player was not found in playfield
3974 for (i = 0; i < MAX_PLAYERS; i++)
3976 struct PlayerInfo *player = &stored_player[i];
3978 if (player->connected && !player->present)
3980 for (j = 0; j < MAX_PLAYERS; j++)
3982 struct PlayerInfo *field_player = &stored_player[j];
3983 int jx = field_player->jx, jy = field_player->jy;
3985 // assign first free player found that is present in the playfield
3986 if (field_player->present && !field_player->connected)
3988 player->present = TRUE;
3989 player->active = TRUE;
3991 field_player->present = FALSE;
3992 field_player->active = FALSE;
3994 player->initial_element = field_player->initial_element;
3995 player->artwork_element = field_player->artwork_element;
3997 player->block_last_field = field_player->block_last_field;
3998 player->block_delay_adjustment = field_player->block_delay_adjustment;
4000 StorePlayer[jx][jy] = player->element_nr;
4002 player->jx = player->last_jx = jx;
4003 player->jy = player->last_jy = jy;
4013 printf("::: local_player->present == %d\n", local_player->present);
4016 // set focus to local player for network games, else to all players
4017 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4018 game.centered_player_nr_next = game.centered_player_nr;
4019 game.set_centered_player = FALSE;
4020 game.set_centered_player_wrap = FALSE;
4022 if (network_playing && tape.recording)
4024 // store client dependent player focus when recording network games
4025 tape.centered_player_nr_next = game.centered_player_nr_next;
4026 tape.set_centered_player = TRUE;
4031 // when playing a tape, eliminate all players who do not participate
4033 #if USE_NEW_PLAYER_ASSIGNMENTS
4035 if (!game.team_mode)
4037 for (i = 0; i < MAX_PLAYERS; i++)
4039 if (stored_player[i].active &&
4040 !tape.player_participates[map_player_action[i]])
4042 struct PlayerInfo *player = &stored_player[i];
4043 int jx = player->jx, jy = player->jy;
4045 #if DEBUG_INIT_PLAYER
4047 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4050 player->active = FALSE;
4051 StorePlayer[jx][jy] = 0;
4052 Feld[jx][jy] = EL_EMPTY;
4059 for (i = 0; i < MAX_PLAYERS; i++)
4061 if (stored_player[i].active &&
4062 !tape.player_participates[i])
4064 struct PlayerInfo *player = &stored_player[i];
4065 int jx = player->jx, jy = player->jy;
4067 player->active = FALSE;
4068 StorePlayer[jx][jy] = 0;
4069 Feld[jx][jy] = EL_EMPTY;
4074 else if (!network.enabled && !game.team_mode) // && !tape.playing
4076 // when in single player mode, eliminate all but the local player
4078 for (i = 0; i < MAX_PLAYERS; i++)
4080 struct PlayerInfo *player = &stored_player[i];
4082 if (player->active && player != local_player)
4084 int jx = player->jx, jy = player->jy;
4086 player->active = FALSE;
4087 player->present = FALSE;
4089 StorePlayer[jx][jy] = 0;
4090 Feld[jx][jy] = EL_EMPTY;
4095 for (i = 0; i < MAX_PLAYERS; i++)
4096 if (stored_player[i].active)
4097 game.players_still_needed++;
4099 if (level.solved_by_one_player)
4100 game.players_still_needed = 1;
4102 // when recording the game, store which players take part in the game
4105 #if USE_NEW_PLAYER_ASSIGNMENTS
4106 for (i = 0; i < MAX_PLAYERS; i++)
4107 if (stored_player[i].connected)
4108 tape.player_participates[i] = TRUE;
4110 for (i = 0; i < MAX_PLAYERS; i++)
4111 if (stored_player[i].active)
4112 tape.player_participates[i] = TRUE;
4116 #if DEBUG_INIT_PLAYER
4117 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4120 if (BorderElement == EL_EMPTY)
4123 SBX_Right = lev_fieldx - SCR_FIELDX;
4125 SBY_Lower = lev_fieldy - SCR_FIELDY;
4130 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4132 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4135 if (full_lev_fieldx <= SCR_FIELDX)
4136 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4137 if (full_lev_fieldy <= SCR_FIELDY)
4138 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4140 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4142 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4145 // if local player not found, look for custom element that might create
4146 // the player (make some assumptions about the right custom element)
4147 if (!local_player->present)
4149 int start_x = 0, start_y = 0;
4150 int found_rating = 0;
4151 int found_element = EL_UNDEFINED;
4152 int player_nr = local_player->index_nr;
4154 SCAN_PLAYFIELD(x, y)
4156 int element = Feld[x][y];
4161 if (level.use_start_element[player_nr] &&
4162 level.start_element[player_nr] == element &&
4169 found_element = element;
4172 if (!IS_CUSTOM_ELEMENT(element))
4175 if (CAN_CHANGE(element))
4177 for (i = 0; i < element_info[element].num_change_pages; i++)
4179 // check for player created from custom element as single target
4180 content = element_info[element].change_page[i].target_element;
4181 is_player = ELEM_IS_PLAYER(content);
4183 if (is_player && (found_rating < 3 ||
4184 (found_rating == 3 && element < found_element)))
4190 found_element = element;
4195 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4197 // check for player created from custom element as explosion content
4198 content = element_info[element].content.e[xx][yy];
4199 is_player = ELEM_IS_PLAYER(content);
4201 if (is_player && (found_rating < 2 ||
4202 (found_rating == 2 && element < found_element)))
4204 start_x = x + xx - 1;
4205 start_y = y + yy - 1;
4208 found_element = element;
4211 if (!CAN_CHANGE(element))
4214 for (i = 0; i < element_info[element].num_change_pages; i++)
4216 // check for player created from custom element as extended target
4218 element_info[element].change_page[i].target_content.e[xx][yy];
4220 is_player = ELEM_IS_PLAYER(content);
4222 if (is_player && (found_rating < 1 ||
4223 (found_rating == 1 && element < found_element)))
4225 start_x = x + xx - 1;
4226 start_y = y + yy - 1;
4229 found_element = element;
4235 scroll_x = SCROLL_POSITION_X(start_x);
4236 scroll_y = SCROLL_POSITION_Y(start_y);
4240 scroll_x = SCROLL_POSITION_X(local_player->jx);
4241 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4244 // !!! FIX THIS (START) !!!
4245 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4247 InitGameEngine_EM();
4249 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4251 InitGameEngine_SP();
4253 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4255 InitGameEngine_MM();
4259 DrawLevel(REDRAW_FIELD);
4262 // after drawing the level, correct some elements
4263 if (game.timegate_time_left == 0)
4264 CloseAllOpenTimegates();
4267 // blit playfield from scroll buffer to normal back buffer for fading in
4268 BlitScreenToBitmap(backbuffer);
4269 // !!! FIX THIS (END) !!!
4271 DrawMaskedBorder(fade_mask);
4276 // full screen redraw is required at this point in the following cases:
4277 // - special editor door undrawn when game was started from level editor
4278 // - drawing area (playfield) was changed and has to be removed completely
4279 redraw_mask = REDRAW_ALL;
4283 if (!game.restart_level)
4285 // copy default game door content to main double buffer
4287 // !!! CHECK AGAIN !!!
4288 SetPanelBackground();
4289 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4290 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4293 SetPanelBackground();
4294 SetDrawBackgroundMask(REDRAW_DOOR_1);
4296 UpdateAndDisplayGameControlValues();
4298 if (!game.restart_level)
4304 CreateGameButtons();
4309 // copy actual game door content to door double buffer for OpenDoor()
4310 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4312 OpenDoor(DOOR_OPEN_ALL);
4314 KeyboardAutoRepeatOffUnlessAutoplay();
4316 #if DEBUG_INIT_PLAYER
4317 DebugPrintPlayerStatus("Player status (final)");
4326 if (!game.restart_level && !tape.playing)
4328 LevelStats_incPlayed(level_nr);
4330 SaveLevelSetup_SeriesInfo();
4333 game.restart_level = FALSE;
4334 game.restart_game_message = NULL;
4335 game.request_active = FALSE;
4337 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4338 InitGameActions_MM();
4340 SaveEngineSnapshotToListInitial();
4342 if (!game.restart_level)
4344 PlaySound(SND_GAME_STARTING);
4346 if (setup.sound_music)
4351 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4352 int actual_player_x, int actual_player_y)
4354 // this is used for non-R'n'D game engines to update certain engine values
4356 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4358 actual_player_x = correctLevelPosX_EM(actual_player_x);
4359 actual_player_y = correctLevelPosY_EM(actual_player_y);
4362 // needed to determine if sounds are played within the visible screen area
4363 scroll_x = actual_scroll_x;
4364 scroll_y = actual_scroll_y;
4366 // needed to get player position for "follow finger" playing input method
4367 local_player->jx = actual_player_x;
4368 local_player->jy = actual_player_y;
4371 void InitMovDir(int x, int y)
4373 int i, element = Feld[x][y];
4374 static int xy[4][2] =
4381 static int direction[3][4] =
4383 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4384 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4385 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4394 Feld[x][y] = EL_BUG;
4395 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4398 case EL_SPACESHIP_RIGHT:
4399 case EL_SPACESHIP_UP:
4400 case EL_SPACESHIP_LEFT:
4401 case EL_SPACESHIP_DOWN:
4402 Feld[x][y] = EL_SPACESHIP;
4403 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4406 case EL_BD_BUTTERFLY_RIGHT:
4407 case EL_BD_BUTTERFLY_UP:
4408 case EL_BD_BUTTERFLY_LEFT:
4409 case EL_BD_BUTTERFLY_DOWN:
4410 Feld[x][y] = EL_BD_BUTTERFLY;
4411 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4414 case EL_BD_FIREFLY_RIGHT:
4415 case EL_BD_FIREFLY_UP:
4416 case EL_BD_FIREFLY_LEFT:
4417 case EL_BD_FIREFLY_DOWN:
4418 Feld[x][y] = EL_BD_FIREFLY;
4419 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4422 case EL_PACMAN_RIGHT:
4424 case EL_PACMAN_LEFT:
4425 case EL_PACMAN_DOWN:
4426 Feld[x][y] = EL_PACMAN;
4427 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4430 case EL_YAMYAM_LEFT:
4431 case EL_YAMYAM_RIGHT:
4433 case EL_YAMYAM_DOWN:
4434 Feld[x][y] = EL_YAMYAM;
4435 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4438 case EL_SP_SNIKSNAK:
4439 MovDir[x][y] = MV_UP;
4442 case EL_SP_ELECTRON:
4443 MovDir[x][y] = MV_LEFT;
4450 Feld[x][y] = EL_MOLE;
4451 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4455 if (IS_CUSTOM_ELEMENT(element))
4457 struct ElementInfo *ei = &element_info[element];
4458 int move_direction_initial = ei->move_direction_initial;
4459 int move_pattern = ei->move_pattern;
4461 if (move_direction_initial == MV_START_PREVIOUS)
4463 if (MovDir[x][y] != MV_NONE)
4466 move_direction_initial = MV_START_AUTOMATIC;
4469 if (move_direction_initial == MV_START_RANDOM)
4470 MovDir[x][y] = 1 << RND(4);
4471 else if (move_direction_initial & MV_ANY_DIRECTION)
4472 MovDir[x][y] = move_direction_initial;
4473 else if (move_pattern == MV_ALL_DIRECTIONS ||
4474 move_pattern == MV_TURNING_LEFT ||
4475 move_pattern == MV_TURNING_RIGHT ||
4476 move_pattern == MV_TURNING_LEFT_RIGHT ||
4477 move_pattern == MV_TURNING_RIGHT_LEFT ||
4478 move_pattern == MV_TURNING_RANDOM)
4479 MovDir[x][y] = 1 << RND(4);
4480 else if (move_pattern == MV_HORIZONTAL)
4481 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4482 else if (move_pattern == MV_VERTICAL)
4483 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4484 else if (move_pattern & MV_ANY_DIRECTION)
4485 MovDir[x][y] = element_info[element].move_pattern;
4486 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4487 move_pattern == MV_ALONG_RIGHT_SIDE)
4489 // use random direction as default start direction
4490 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4491 MovDir[x][y] = 1 << RND(4);
4493 for (i = 0; i < NUM_DIRECTIONS; i++)
4495 int x1 = x + xy[i][0];
4496 int y1 = y + xy[i][1];
4498 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4500 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4501 MovDir[x][y] = direction[0][i];
4503 MovDir[x][y] = direction[1][i];
4512 MovDir[x][y] = 1 << RND(4);
4514 if (element != EL_BUG &&
4515 element != EL_SPACESHIP &&
4516 element != EL_BD_BUTTERFLY &&
4517 element != EL_BD_FIREFLY)
4520 for (i = 0; i < NUM_DIRECTIONS; i++)
4522 int x1 = x + xy[i][0];
4523 int y1 = y + xy[i][1];
4525 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4527 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4529 MovDir[x][y] = direction[0][i];
4532 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4533 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4535 MovDir[x][y] = direction[1][i];
4544 GfxDir[x][y] = MovDir[x][y];
4547 void InitAmoebaNr(int x, int y)
4550 int group_nr = AmoebeNachbarNr(x, y);
4554 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4556 if (AmoebaCnt[i] == 0)
4564 AmoebaNr[x][y] = group_nr;
4565 AmoebaCnt[group_nr]++;
4566 AmoebaCnt2[group_nr]++;
4569 static void LevelSolved(void)
4571 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4572 game.players_still_needed > 0)
4575 game.LevelSolved = TRUE;
4576 game.GameOver = TRUE;
4578 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4579 game_em.lev->score :
4580 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4583 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4584 MM_HEALTH(game_mm.laser_overload_value) :
4587 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4588 game.LevelSolved_CountingScore = game.score_final;
4589 game.LevelSolved_CountingHealth = game.health_final;
4594 static int time_count_steps;
4595 static int time, time_final;
4596 static int score, score_final;
4597 static int health, health_final;
4598 static int game_over_delay_1 = 0;
4599 static int game_over_delay_2 = 0;
4600 static int game_over_delay_3 = 0;
4601 int game_over_delay_value_1 = 50;
4602 int game_over_delay_value_2 = 25;
4603 int game_over_delay_value_3 = 50;
4605 if (!game.LevelSolved_GameWon)
4609 // do not start end game actions before the player stops moving (to exit)
4610 if (local_player->active && local_player->MovPos)
4613 game.LevelSolved_GameWon = TRUE;
4614 game.LevelSolved_SaveTape = tape.recording;
4615 game.LevelSolved_SaveScore = !tape.playing;
4619 LevelStats_incSolved(level_nr);
4621 SaveLevelSetup_SeriesInfo();
4624 if (tape.auto_play) // tape might already be stopped here
4625 tape.auto_play_level_solved = TRUE;
4629 game_over_delay_1 = 0;
4630 game_over_delay_2 = 0;
4631 game_over_delay_3 = game_over_delay_value_3;
4633 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4634 score = score_final = game.score_final;
4635 health = health_final = game.health_final;
4637 if (level.score[SC_TIME_BONUS] > 0)
4642 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4644 else if (game.no_time_limit && TimePlayed < 999)
4647 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4650 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4652 game_over_delay_1 = game_over_delay_value_1;
4654 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4657 score_final += health * level.score[SC_TIME_BONUS];
4659 game_over_delay_2 = game_over_delay_value_2;
4662 game.score_final = score_final;
4663 game.health_final = health_final;
4666 if (level_editor_test_game)
4669 score = score_final;
4671 game.LevelSolved_CountingTime = time;
4672 game.LevelSolved_CountingScore = score;
4674 game_panel_controls[GAME_PANEL_TIME].value = time;
4675 game_panel_controls[GAME_PANEL_SCORE].value = score;
4677 DisplayGameControlValues();
4680 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4682 // check if last player has left the level
4683 if (game.exit_x >= 0 &&
4686 int x = game.exit_x;
4687 int y = game.exit_y;
4688 int element = Feld[x][y];
4690 // close exit door after last player
4691 if ((game.all_players_gone &&
4692 (element == EL_EXIT_OPEN ||
4693 element == EL_SP_EXIT_OPEN ||
4694 element == EL_STEEL_EXIT_OPEN)) ||
4695 element == EL_EM_EXIT_OPEN ||
4696 element == EL_EM_STEEL_EXIT_OPEN)
4700 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4701 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4702 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4703 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4704 EL_EM_STEEL_EXIT_CLOSING);
4706 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4709 // player disappears
4710 DrawLevelField(x, y);
4713 for (i = 0; i < MAX_PLAYERS; i++)
4715 struct PlayerInfo *player = &stored_player[i];
4717 if (player->present)
4719 RemovePlayer(player);
4721 // player disappears
4722 DrawLevelField(player->jx, player->jy);
4727 PlaySound(SND_GAME_WINNING);
4730 if (game_over_delay_1 > 0)
4732 game_over_delay_1--;
4737 if (time != time_final)
4739 int time_to_go = ABS(time_final - time);
4740 int time_count_dir = (time < time_final ? +1 : -1);
4742 if (time_to_go < time_count_steps)
4743 time_count_steps = 1;
4745 time += time_count_steps * time_count_dir;
4746 score += time_count_steps * level.score[SC_TIME_BONUS];
4748 game.LevelSolved_CountingTime = time;
4749 game.LevelSolved_CountingScore = score;
4751 game_panel_controls[GAME_PANEL_TIME].value = time;
4752 game_panel_controls[GAME_PANEL_SCORE].value = score;
4754 DisplayGameControlValues();
4756 if (time == time_final)
4757 StopSound(SND_GAME_LEVELTIME_BONUS);
4758 else if (setup.sound_loops)
4759 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4761 PlaySound(SND_GAME_LEVELTIME_BONUS);
4766 if (game_over_delay_2 > 0)
4768 game_over_delay_2--;
4773 if (health != health_final)
4775 int health_count_dir = (health < health_final ? +1 : -1);
4777 health += health_count_dir;
4778 score += level.score[SC_TIME_BONUS];
4780 game.LevelSolved_CountingHealth = health;
4781 game.LevelSolved_CountingScore = score;
4783 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4784 game_panel_controls[GAME_PANEL_SCORE].value = score;
4786 DisplayGameControlValues();
4788 if (health == health_final)
4789 StopSound(SND_GAME_LEVELTIME_BONUS);
4790 else if (setup.sound_loops)
4791 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4793 PlaySound(SND_GAME_LEVELTIME_BONUS);
4798 game.panel.active = FALSE;
4800 if (game_over_delay_3 > 0)
4802 game_over_delay_3--;
4812 // used instead of "level_nr" (needed for network games)
4813 int last_level_nr = levelset.level_nr;
4816 game.LevelSolved_GameEnd = TRUE;
4818 if (game.LevelSolved_SaveTape)
4820 // make sure that request dialog to save tape does not open door again
4821 if (!global.use_envelope_request)
4822 CloseDoor(DOOR_CLOSE_1);
4824 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4827 // if no tape is to be saved, close both doors simultaneously
4828 CloseDoor(DOOR_CLOSE_ALL);
4830 if (level_editor_test_game)
4832 SetGameStatus(GAME_MODE_MAIN);
4839 if (!game.LevelSolved_SaveScore)
4841 SetGameStatus(GAME_MODE_MAIN);
4848 if (level_nr == leveldir_current->handicap_level)
4850 leveldir_current->handicap_level++;
4852 SaveLevelSetup_SeriesInfo();
4855 if (setup.increment_levels &&
4856 level_nr < leveldir_current->last_level &&
4859 level_nr++; // advance to next level
4860 TapeErase(); // start with empty tape
4862 if (setup.auto_play_next_level)
4864 LoadLevel(level_nr);
4866 SaveLevelSetup_SeriesInfo();
4870 hi_pos = NewHiScore(last_level_nr);
4872 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4874 SetGameStatus(GAME_MODE_SCORES);
4876 DrawHallOfFame(last_level_nr, hi_pos);
4878 else if (setup.auto_play_next_level && setup.increment_levels &&
4879 last_level_nr < leveldir_current->last_level &&
4882 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4886 SetGameStatus(GAME_MODE_MAIN);
4892 int NewHiScore(int level_nr)
4896 boolean one_score_entry_per_name = !program.many_scores_per_name;
4898 LoadScore(level_nr);
4900 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4901 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4904 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4906 if (game.score_final > highscore[k].Score)
4908 // player has made it to the hall of fame
4910 if (k < MAX_SCORE_ENTRIES - 1)
4912 int m = MAX_SCORE_ENTRIES - 1;
4914 if (one_score_entry_per_name)
4916 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4917 if (strEqual(setup.player_name, highscore[l].Name))
4920 if (m == k) // player's new highscore overwrites his old one
4924 for (l = m; l > k; l--)
4926 strcpy(highscore[l].Name, highscore[l - 1].Name);
4927 highscore[l].Score = highscore[l - 1].Score;
4933 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4934 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4935 highscore[k].Score = game.score_final;
4940 else if (one_score_entry_per_name &&
4941 !strncmp(setup.player_name, highscore[k].Name,
4942 MAX_PLAYER_NAME_LEN))
4943 break; // player already there with a higher score
4947 SaveScore(level_nr);
4952 static int getElementMoveStepsizeExt(int x, int y, int direction)
4954 int element = Feld[x][y];
4955 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4956 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4957 int horiz_move = (dx != 0);
4958 int sign = (horiz_move ? dx : dy);
4959 int step = sign * element_info[element].move_stepsize;
4961 // special values for move stepsize for spring and things on conveyor belt
4964 if (CAN_FALL(element) &&
4965 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4966 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4967 else if (element == EL_SPRING)
4968 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4974 static int getElementMoveStepsize(int x, int y)
4976 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4979 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4981 if (player->GfxAction != action || player->GfxDir != dir)
4983 player->GfxAction = action;
4984 player->GfxDir = dir;
4986 player->StepFrame = 0;
4990 static void ResetGfxFrame(int x, int y)
4992 // profiling showed that "autotest" spends 10~20% of its time in this function
4993 if (DrawingDeactivatedField())
4996 int element = Feld[x][y];
4997 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4999 if (graphic_info[graphic].anim_global_sync)
5000 GfxFrame[x][y] = FrameCounter;
5001 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5002 GfxFrame[x][y] = CustomValue[x][y];
5003 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5004 GfxFrame[x][y] = element_info[element].collect_score;
5005 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5006 GfxFrame[x][y] = ChangeDelay[x][y];
5009 static void ResetGfxAnimation(int x, int y)
5011 GfxAction[x][y] = ACTION_DEFAULT;
5012 GfxDir[x][y] = MovDir[x][y];
5015 ResetGfxFrame(x, y);
5018 static void ResetRandomAnimationValue(int x, int y)
5020 GfxRandom[x][y] = INIT_GFX_RANDOM();
5023 static void InitMovingField(int x, int y, int direction)
5025 int element = Feld[x][y];
5026 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5027 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5030 boolean is_moving_before, is_moving_after;
5032 // check if element was/is moving or being moved before/after mode change
5033 is_moving_before = (WasJustMoving[x][y] != 0);
5034 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5036 // reset animation only for moving elements which change direction of moving
5037 // or which just started or stopped moving
5038 // (else CEs with property "can move" / "not moving" are reset each frame)
5039 if (is_moving_before != is_moving_after ||
5040 direction != MovDir[x][y])
5041 ResetGfxAnimation(x, y);
5043 MovDir[x][y] = direction;
5044 GfxDir[x][y] = direction;
5046 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5047 direction == MV_DOWN && CAN_FALL(element) ?
5048 ACTION_FALLING : ACTION_MOVING);
5050 // this is needed for CEs with property "can move" / "not moving"
5052 if (is_moving_after)
5054 if (Feld[newx][newy] == EL_EMPTY)
5055 Feld[newx][newy] = EL_BLOCKED;
5057 MovDir[newx][newy] = MovDir[x][y];
5059 CustomValue[newx][newy] = CustomValue[x][y];
5061 GfxFrame[newx][newy] = GfxFrame[x][y];
5062 GfxRandom[newx][newy] = GfxRandom[x][y];
5063 GfxAction[newx][newy] = GfxAction[x][y];
5064 GfxDir[newx][newy] = GfxDir[x][y];
5068 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5070 int direction = MovDir[x][y];
5071 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5072 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5078 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5080 int oldx = x, oldy = y;
5081 int direction = MovDir[x][y];
5083 if (direction == MV_LEFT)
5085 else if (direction == MV_RIGHT)
5087 else if (direction == MV_UP)
5089 else if (direction == MV_DOWN)
5092 *comes_from_x = oldx;
5093 *comes_from_y = oldy;
5096 static int MovingOrBlocked2Element(int x, int y)
5098 int element = Feld[x][y];
5100 if (element == EL_BLOCKED)
5104 Blocked2Moving(x, y, &oldx, &oldy);
5105 return Feld[oldx][oldy];
5111 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5113 // like MovingOrBlocked2Element(), but if element is moving
5114 // and (x,y) is the field the moving element is just leaving,
5115 // return EL_BLOCKED instead of the element value
5116 int element = Feld[x][y];
5118 if (IS_MOVING(x, y))
5120 if (element == EL_BLOCKED)
5124 Blocked2Moving(x, y, &oldx, &oldy);
5125 return Feld[oldx][oldy];
5134 static void RemoveField(int x, int y)
5136 Feld[x][y] = EL_EMPTY;
5142 CustomValue[x][y] = 0;
5145 ChangeDelay[x][y] = 0;
5146 ChangePage[x][y] = -1;
5147 Pushed[x][y] = FALSE;
5149 GfxElement[x][y] = EL_UNDEFINED;
5150 GfxAction[x][y] = ACTION_DEFAULT;
5151 GfxDir[x][y] = MV_NONE;
5154 static void RemoveMovingField(int x, int y)
5156 int oldx = x, oldy = y, newx = x, newy = y;
5157 int element = Feld[x][y];
5158 int next_element = EL_UNDEFINED;
5160 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5163 if (IS_MOVING(x, y))
5165 Moving2Blocked(x, y, &newx, &newy);
5167 if (Feld[newx][newy] != EL_BLOCKED)
5169 // element is moving, but target field is not free (blocked), but
5170 // already occupied by something different (example: acid pool);
5171 // in this case, only remove the moving field, but not the target
5173 RemoveField(oldx, oldy);
5175 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5177 TEST_DrawLevelField(oldx, oldy);
5182 else if (element == EL_BLOCKED)
5184 Blocked2Moving(x, y, &oldx, &oldy);
5185 if (!IS_MOVING(oldx, oldy))
5189 if (element == EL_BLOCKED &&
5190 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5191 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5192 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5193 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5194 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5195 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5196 next_element = get_next_element(Feld[oldx][oldy]);
5198 RemoveField(oldx, oldy);
5199 RemoveField(newx, newy);
5201 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5203 if (next_element != EL_UNDEFINED)
5204 Feld[oldx][oldy] = next_element;
5206 TEST_DrawLevelField(oldx, oldy);
5207 TEST_DrawLevelField(newx, newy);
5210 void DrawDynamite(int x, int y)
5212 int sx = SCREENX(x), sy = SCREENY(y);
5213 int graphic = el2img(Feld[x][y]);
5216 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5219 if (IS_WALKABLE_INSIDE(Back[x][y]))
5223 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5224 else if (Store[x][y])
5225 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5227 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5229 if (Back[x][y] || Store[x][y])
5230 DrawGraphicThruMask(sx, sy, graphic, frame);
5232 DrawGraphic(sx, sy, graphic, frame);
5235 static void CheckDynamite(int x, int y)
5237 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5241 if (MovDelay[x][y] != 0)
5244 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5250 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5255 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5257 boolean num_checked_players = 0;
5260 for (i = 0; i < MAX_PLAYERS; i++)
5262 if (stored_player[i].active)
5264 int sx = stored_player[i].jx;
5265 int sy = stored_player[i].jy;
5267 if (num_checked_players == 0)
5274 *sx1 = MIN(*sx1, sx);
5275 *sy1 = MIN(*sy1, sy);
5276 *sx2 = MAX(*sx2, sx);
5277 *sy2 = MAX(*sy2, sy);
5280 num_checked_players++;
5285 static boolean checkIfAllPlayersFitToScreen_RND(void)
5287 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5289 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5291 return (sx2 - sx1 < SCR_FIELDX &&
5292 sy2 - sy1 < SCR_FIELDY);
5295 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5297 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5299 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5301 *sx = (sx1 + sx2) / 2;
5302 *sy = (sy1 + sy2) / 2;
5305 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5306 boolean center_screen, boolean quick_relocation)
5308 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5309 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5310 boolean no_delay = (tape.warp_forward);
5311 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5312 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5313 int new_scroll_x, new_scroll_y;
5315 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5317 // case 1: quick relocation inside visible screen (without scrolling)
5324 if (!level.shifted_relocation || center_screen)
5326 // relocation _with_ centering of screen
5328 new_scroll_x = SCROLL_POSITION_X(x);
5329 new_scroll_y = SCROLL_POSITION_Y(y);
5333 // relocation _without_ centering of screen
5335 int center_scroll_x = SCROLL_POSITION_X(old_x);
5336 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5337 int offset_x = x + (scroll_x - center_scroll_x);
5338 int offset_y = y + (scroll_y - center_scroll_y);
5340 // for new screen position, apply previous offset to center position
5341 new_scroll_x = SCROLL_POSITION_X(offset_x);
5342 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5345 if (quick_relocation)
5347 // case 2: quick relocation (redraw without visible scrolling)
5349 scroll_x = new_scroll_x;
5350 scroll_y = new_scroll_y;
5357 // case 3: visible relocation (with scrolling to new position)
5359 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5361 SetVideoFrameDelay(wait_delay_value);
5363 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5365 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5366 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5368 if (dx == 0 && dy == 0) // no scrolling needed at all
5374 // set values for horizontal/vertical screen scrolling (half tile size)
5375 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5376 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5377 int pos_x = dx * TILEX / 2;
5378 int pos_y = dy * TILEY / 2;
5379 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5380 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5382 ScrollLevel(dx, dy);
5385 // scroll in two steps of half tile size to make things smoother
5386 BlitScreenToBitmapExt_RND(window, fx, fy);
5388 // scroll second step to align at full tile size
5389 BlitScreenToBitmap(window);
5395 SetVideoFrameDelay(frame_delay_value_old);
5398 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5400 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5401 int player_nr = GET_PLAYER_NR(el_player);
5402 struct PlayerInfo *player = &stored_player[player_nr];
5403 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5404 boolean no_delay = (tape.warp_forward);
5405 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5406 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5407 int old_jx = player->jx;
5408 int old_jy = player->jy;
5409 int old_element = Feld[old_jx][old_jy];
5410 int element = Feld[jx][jy];
5411 boolean player_relocated = (old_jx != jx || old_jy != jy);
5413 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5414 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5415 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5416 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5417 int leave_side_horiz = move_dir_horiz;
5418 int leave_side_vert = move_dir_vert;
5419 int enter_side = enter_side_horiz | enter_side_vert;
5420 int leave_side = leave_side_horiz | leave_side_vert;
5422 if (player->buried) // do not reanimate dead player
5425 if (!player_relocated) // no need to relocate the player
5428 if (IS_PLAYER(jx, jy)) // player already placed at new position
5430 RemoveField(jx, jy); // temporarily remove newly placed player
5431 DrawLevelField(jx, jy);
5434 if (player->present)
5436 while (player->MovPos)
5438 ScrollPlayer(player, SCROLL_GO_ON);
5439 ScrollScreen(NULL, SCROLL_GO_ON);
5441 AdvanceFrameAndPlayerCounters(player->index_nr);
5445 BackToFront_WithFrameDelay(wait_delay_value);
5448 DrawPlayer(player); // needed here only to cleanup last field
5449 DrawLevelField(player->jx, player->jy); // remove player graphic
5451 player->is_moving = FALSE;
5454 if (IS_CUSTOM_ELEMENT(old_element))
5455 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5457 player->index_bit, leave_side);
5459 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5461 player->index_bit, leave_side);
5463 Feld[jx][jy] = el_player;
5464 InitPlayerField(jx, jy, el_player, TRUE);
5466 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5467 possible that the relocation target field did not contain a player element,
5468 but a walkable element, to which the new player was relocated -- in this
5469 case, restore that (already initialized!) element on the player field */
5470 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5472 Feld[jx][jy] = element; // restore previously existing element
5475 // only visually relocate centered player
5476 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5477 FALSE, level.instant_relocation);
5479 TestIfPlayerTouchesBadThing(jx, jy);
5480 TestIfPlayerTouchesCustomElement(jx, jy);
5482 if (IS_CUSTOM_ELEMENT(element))
5483 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5484 player->index_bit, enter_side);
5486 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5487 player->index_bit, enter_side);
5489 if (player->is_switching)
5491 /* ensure that relocation while still switching an element does not cause
5492 a new element to be treated as also switched directly after relocation
5493 (this is important for teleporter switches that teleport the player to
5494 a place where another teleporter switch is in the same direction, which
5495 would then incorrectly be treated as immediately switched before the
5496 direction key that caused the switch was released) */
5498 player->switch_x += jx - old_jx;
5499 player->switch_y += jy - old_jy;
5503 static void Explode(int ex, int ey, int phase, int mode)
5509 // !!! eliminate this variable !!!
5510 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5512 if (game.explosions_delayed)
5514 ExplodeField[ex][ey] = mode;
5518 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5520 int center_element = Feld[ex][ey];
5521 int artwork_element, explosion_element; // set these values later
5523 // remove things displayed in background while burning dynamite
5524 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5527 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5529 // put moving element to center field (and let it explode there)
5530 center_element = MovingOrBlocked2Element(ex, ey);
5531 RemoveMovingField(ex, ey);
5532 Feld[ex][ey] = center_element;
5535 // now "center_element" is finally determined -- set related values now
5536 artwork_element = center_element; // for custom player artwork
5537 explosion_element = center_element; // for custom player artwork
5539 if (IS_PLAYER(ex, ey))
5541 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5543 artwork_element = stored_player[player_nr].artwork_element;
5545 if (level.use_explosion_element[player_nr])
5547 explosion_element = level.explosion_element[player_nr];
5548 artwork_element = explosion_element;
5552 if (mode == EX_TYPE_NORMAL ||
5553 mode == EX_TYPE_CENTER ||
5554 mode == EX_TYPE_CROSS)
5555 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5557 last_phase = element_info[explosion_element].explosion_delay + 1;
5559 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5561 int xx = x - ex + 1;
5562 int yy = y - ey + 1;
5565 if (!IN_LEV_FIELD(x, y) ||
5566 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5567 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5570 element = Feld[x][y];
5572 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5574 element = MovingOrBlocked2Element(x, y);
5576 if (!IS_EXPLOSION_PROOF(element))
5577 RemoveMovingField(x, y);
5580 // indestructible elements can only explode in center (but not flames)
5581 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5582 mode == EX_TYPE_BORDER)) ||
5583 element == EL_FLAMES)
5586 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5587 behaviour, for example when touching a yamyam that explodes to rocks
5588 with active deadly shield, a rock is created under the player !!! */
5589 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5591 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5592 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5593 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5595 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5598 if (IS_ACTIVE_BOMB(element))
5600 // re-activate things under the bomb like gate or penguin
5601 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5608 // save walkable background elements while explosion on same tile
5609 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5610 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5611 Back[x][y] = element;
5613 // ignite explodable elements reached by other explosion
5614 if (element == EL_EXPLOSION)
5615 element = Store2[x][y];
5617 if (AmoebaNr[x][y] &&
5618 (element == EL_AMOEBA_FULL ||
5619 element == EL_BD_AMOEBA ||
5620 element == EL_AMOEBA_GROWING))
5622 AmoebaCnt[AmoebaNr[x][y]]--;
5623 AmoebaCnt2[AmoebaNr[x][y]]--;
5628 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5630 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5632 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5634 if (PLAYERINFO(ex, ey)->use_murphy)
5635 Store[x][y] = EL_EMPTY;
5638 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5639 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5640 else if (ELEM_IS_PLAYER(center_element))
5641 Store[x][y] = EL_EMPTY;
5642 else if (center_element == EL_YAMYAM)
5643 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5644 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5645 Store[x][y] = element_info[center_element].content.e[xx][yy];
5647 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5648 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5649 // otherwise) -- FIX THIS !!!
5650 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5651 Store[x][y] = element_info[element].content.e[1][1];
5653 else if (!CAN_EXPLODE(element))
5654 Store[x][y] = element_info[element].content.e[1][1];
5657 Store[x][y] = EL_EMPTY;
5659 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5660 center_element == EL_AMOEBA_TO_DIAMOND)
5661 Store2[x][y] = element;
5663 Feld[x][y] = EL_EXPLOSION;
5664 GfxElement[x][y] = artwork_element;
5666 ExplodePhase[x][y] = 1;
5667 ExplodeDelay[x][y] = last_phase;
5672 if (center_element == EL_YAMYAM)
5673 game.yamyam_content_nr =
5674 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5686 GfxFrame[x][y] = 0; // restart explosion animation
5688 last_phase = ExplodeDelay[x][y];
5690 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5692 // this can happen if the player leaves an explosion just in time
5693 if (GfxElement[x][y] == EL_UNDEFINED)
5694 GfxElement[x][y] = EL_EMPTY;
5696 border_element = Store2[x][y];
5697 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5698 border_element = StorePlayer[x][y];
5700 if (phase == element_info[border_element].ignition_delay ||
5701 phase == last_phase)
5703 boolean border_explosion = FALSE;
5705 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5706 !PLAYER_EXPLOSION_PROTECTED(x, y))
5708 KillPlayerUnlessExplosionProtected(x, y);
5709 border_explosion = TRUE;
5711 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5713 Feld[x][y] = Store2[x][y];
5716 border_explosion = TRUE;
5718 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5720 AmoebeUmwandeln(x, y);
5722 border_explosion = TRUE;
5725 // if an element just explodes due to another explosion (chain-reaction),
5726 // do not immediately end the new explosion when it was the last frame of
5727 // the explosion (as it would be done in the following "if"-statement!)
5728 if (border_explosion && phase == last_phase)
5732 if (phase == last_phase)
5736 element = Feld[x][y] = Store[x][y];
5737 Store[x][y] = Store2[x][y] = 0;
5738 GfxElement[x][y] = EL_UNDEFINED;
5740 // player can escape from explosions and might therefore be still alive
5741 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5742 element <= EL_PLAYER_IS_EXPLODING_4)
5744 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5745 int explosion_element = EL_PLAYER_1 + player_nr;
5746 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5747 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5749 if (level.use_explosion_element[player_nr])
5750 explosion_element = level.explosion_element[player_nr];
5752 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5753 element_info[explosion_element].content.e[xx][yy]);
5756 // restore probably existing indestructible background element
5757 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5758 element = Feld[x][y] = Back[x][y];
5761 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5762 GfxDir[x][y] = MV_NONE;
5763 ChangeDelay[x][y] = 0;
5764 ChangePage[x][y] = -1;
5766 CustomValue[x][y] = 0;
5768 InitField_WithBug2(x, y, FALSE);
5770 TEST_DrawLevelField(x, y);
5772 TestIfElementTouchesCustomElement(x, y);
5774 if (GFX_CRUMBLED(element))
5775 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5777 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5778 StorePlayer[x][y] = 0;
5780 if (ELEM_IS_PLAYER(element))
5781 RelocatePlayer(x, y, element);
5783 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5785 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5786 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5789 TEST_DrawLevelFieldCrumbled(x, y);
5791 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5793 DrawLevelElement(x, y, Back[x][y]);
5794 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5796 else if (IS_WALKABLE_UNDER(Back[x][y]))
5798 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5799 DrawLevelElementThruMask(x, y, Back[x][y]);
5801 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5802 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5806 static void DynaExplode(int ex, int ey)
5809 int dynabomb_element = Feld[ex][ey];
5810 int dynabomb_size = 1;
5811 boolean dynabomb_xl = FALSE;
5812 struct PlayerInfo *player;
5813 static int xy[4][2] =
5821 if (IS_ACTIVE_BOMB(dynabomb_element))
5823 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5824 dynabomb_size = player->dynabomb_size;
5825 dynabomb_xl = player->dynabomb_xl;
5826 player->dynabombs_left++;
5829 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5831 for (i = 0; i < NUM_DIRECTIONS; i++)
5833 for (j = 1; j <= dynabomb_size; j++)
5835 int x = ex + j * xy[i][0];
5836 int y = ey + j * xy[i][1];
5839 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5842 element = Feld[x][y];
5844 // do not restart explosions of fields with active bombs
5845 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5848 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5850 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5851 !IS_DIGGABLE(element) && !dynabomb_xl)
5857 void Bang(int x, int y)
5859 int element = MovingOrBlocked2Element(x, y);
5860 int explosion_type = EX_TYPE_NORMAL;
5862 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5864 struct PlayerInfo *player = PLAYERINFO(x, y);
5866 element = Feld[x][y] = player->initial_element;
5868 if (level.use_explosion_element[player->index_nr])
5870 int explosion_element = level.explosion_element[player->index_nr];
5872 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5873 explosion_type = EX_TYPE_CROSS;
5874 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5875 explosion_type = EX_TYPE_CENTER;
5883 case EL_BD_BUTTERFLY:
5886 case EL_DARK_YAMYAM:
5890 RaiseScoreElement(element);
5893 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5894 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5895 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5896 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5897 case EL_DYNABOMB_INCREASE_NUMBER:
5898 case EL_DYNABOMB_INCREASE_SIZE:
5899 case EL_DYNABOMB_INCREASE_POWER:
5900 explosion_type = EX_TYPE_DYNA;
5903 case EL_DC_LANDMINE:
5904 explosion_type = EX_TYPE_CENTER;
5909 case EL_LAMP_ACTIVE:
5910 case EL_AMOEBA_TO_DIAMOND:
5911 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5912 explosion_type = EX_TYPE_CENTER;
5916 if (element_info[element].explosion_type == EXPLODES_CROSS)
5917 explosion_type = EX_TYPE_CROSS;
5918 else if (element_info[element].explosion_type == EXPLODES_1X1)
5919 explosion_type = EX_TYPE_CENTER;
5923 if (explosion_type == EX_TYPE_DYNA)
5926 Explode(x, y, EX_PHASE_START, explosion_type);
5928 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5931 static void SplashAcid(int x, int y)
5933 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5934 (!IN_LEV_FIELD(x - 1, y - 2) ||
5935 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5936 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5938 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5939 (!IN_LEV_FIELD(x + 1, y - 2) ||
5940 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5941 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5943 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5946 static void InitBeltMovement(void)
5948 static int belt_base_element[4] =
5950 EL_CONVEYOR_BELT_1_LEFT,
5951 EL_CONVEYOR_BELT_2_LEFT,
5952 EL_CONVEYOR_BELT_3_LEFT,
5953 EL_CONVEYOR_BELT_4_LEFT
5955 static int belt_base_active_element[4] =
5957 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5958 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5959 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5960 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5965 // set frame order for belt animation graphic according to belt direction
5966 for (i = 0; i < NUM_BELTS; i++)
5970 for (j = 0; j < NUM_BELT_PARTS; j++)
5972 int element = belt_base_active_element[belt_nr] + j;
5973 int graphic_1 = el2img(element);
5974 int graphic_2 = el2panelimg(element);
5976 if (game.belt_dir[i] == MV_LEFT)
5978 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5979 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5983 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5984 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5989 SCAN_PLAYFIELD(x, y)
5991 int element = Feld[x][y];
5993 for (i = 0; i < NUM_BELTS; i++)
5995 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5997 int e_belt_nr = getBeltNrFromBeltElement(element);
6000 if (e_belt_nr == belt_nr)
6002 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
6004 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
6011 static void ToggleBeltSwitch(int x, int y)
6013 static int belt_base_element[4] =
6015 EL_CONVEYOR_BELT_1_LEFT,
6016 EL_CONVEYOR_BELT_2_LEFT,
6017 EL_CONVEYOR_BELT_3_LEFT,
6018 EL_CONVEYOR_BELT_4_LEFT
6020 static int belt_base_active_element[4] =
6022 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6023 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6024 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6025 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6027 static int belt_base_switch_element[4] =
6029 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6030 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6031 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6032 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6034 static int belt_move_dir[4] =
6042 int element = Feld[x][y];
6043 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6044 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6045 int belt_dir = belt_move_dir[belt_dir_nr];
6048 if (!IS_BELT_SWITCH(element))
6051 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6052 game.belt_dir[belt_nr] = belt_dir;
6054 if (belt_dir_nr == 3)
6057 // set frame order for belt animation graphic according to belt direction
6058 for (i = 0; i < NUM_BELT_PARTS; i++)
6060 int element = belt_base_active_element[belt_nr] + i;
6061 int graphic_1 = el2img(element);
6062 int graphic_2 = el2panelimg(element);
6064 if (belt_dir == MV_LEFT)
6066 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6067 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6071 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6072 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6076 SCAN_PLAYFIELD(xx, yy)
6078 int element = Feld[xx][yy];
6080 if (IS_BELT_SWITCH(element))
6082 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6084 if (e_belt_nr == belt_nr)
6086 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6087 TEST_DrawLevelField(xx, yy);
6090 else if (IS_BELT(element) && belt_dir != MV_NONE)
6092 int e_belt_nr = getBeltNrFromBeltElement(element);
6094 if (e_belt_nr == belt_nr)
6096 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6098 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6099 TEST_DrawLevelField(xx, yy);
6102 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6104 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6106 if (e_belt_nr == belt_nr)
6108 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6110 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6111 TEST_DrawLevelField(xx, yy);
6117 static void ToggleSwitchgateSwitch(int x, int y)
6121 game.switchgate_pos = !game.switchgate_pos;
6123 SCAN_PLAYFIELD(xx, yy)
6125 int element = Feld[xx][yy];
6127 if (element == EL_SWITCHGATE_SWITCH_UP)
6129 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6130 TEST_DrawLevelField(xx, yy);
6132 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6134 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6135 TEST_DrawLevelField(xx, yy);
6137 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6139 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6140 TEST_DrawLevelField(xx, yy);
6142 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6144 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6145 TEST_DrawLevelField(xx, yy);
6147 else if (element == EL_SWITCHGATE_OPEN ||
6148 element == EL_SWITCHGATE_OPENING)
6150 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6152 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6154 else if (element == EL_SWITCHGATE_CLOSED ||
6155 element == EL_SWITCHGATE_CLOSING)
6157 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6159 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6164 static int getInvisibleActiveFromInvisibleElement(int element)
6166 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6167 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6168 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6172 static int getInvisibleFromInvisibleActiveElement(int element)
6174 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6175 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6176 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6180 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6184 SCAN_PLAYFIELD(x, y)
6186 int element = Feld[x][y];
6188 if (element == EL_LIGHT_SWITCH &&
6189 game.light_time_left > 0)
6191 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6192 TEST_DrawLevelField(x, y);
6194 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6195 game.light_time_left == 0)
6197 Feld[x][y] = EL_LIGHT_SWITCH;
6198 TEST_DrawLevelField(x, y);
6200 else if (element == EL_EMC_DRIPPER &&
6201 game.light_time_left > 0)
6203 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6204 TEST_DrawLevelField(x, y);
6206 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6207 game.light_time_left == 0)
6209 Feld[x][y] = EL_EMC_DRIPPER;
6210 TEST_DrawLevelField(x, y);
6212 else if (element == EL_INVISIBLE_STEELWALL ||
6213 element == EL_INVISIBLE_WALL ||
6214 element == EL_INVISIBLE_SAND)
6216 if (game.light_time_left > 0)
6217 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6219 TEST_DrawLevelField(x, y);
6221 // uncrumble neighbour fields, if needed
6222 if (element == EL_INVISIBLE_SAND)
6223 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6225 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6226 element == EL_INVISIBLE_WALL_ACTIVE ||
6227 element == EL_INVISIBLE_SAND_ACTIVE)
6229 if (game.light_time_left == 0)
6230 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6232 TEST_DrawLevelField(x, y);
6234 // re-crumble neighbour fields, if needed
6235 if (element == EL_INVISIBLE_SAND)
6236 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6241 static void RedrawAllInvisibleElementsForLenses(void)
6245 SCAN_PLAYFIELD(x, y)
6247 int element = Feld[x][y];
6249 if (element == EL_EMC_DRIPPER &&
6250 game.lenses_time_left > 0)
6252 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6253 TEST_DrawLevelField(x, y);
6255 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6256 game.lenses_time_left == 0)
6258 Feld[x][y] = EL_EMC_DRIPPER;
6259 TEST_DrawLevelField(x, y);
6261 else if (element == EL_INVISIBLE_STEELWALL ||
6262 element == EL_INVISIBLE_WALL ||
6263 element == EL_INVISIBLE_SAND)
6265 if (game.lenses_time_left > 0)
6266 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6268 TEST_DrawLevelField(x, y);
6270 // uncrumble neighbour fields, if needed
6271 if (element == EL_INVISIBLE_SAND)
6272 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6274 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6275 element == EL_INVISIBLE_WALL_ACTIVE ||
6276 element == EL_INVISIBLE_SAND_ACTIVE)
6278 if (game.lenses_time_left == 0)
6279 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6281 TEST_DrawLevelField(x, y);
6283 // re-crumble neighbour fields, if needed
6284 if (element == EL_INVISIBLE_SAND)
6285 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6290 static void RedrawAllInvisibleElementsForMagnifier(void)
6294 SCAN_PLAYFIELD(x, y)
6296 int element = Feld[x][y];
6298 if (element == EL_EMC_FAKE_GRASS &&
6299 game.magnify_time_left > 0)
6301 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6302 TEST_DrawLevelField(x, y);
6304 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6305 game.magnify_time_left == 0)
6307 Feld[x][y] = EL_EMC_FAKE_GRASS;
6308 TEST_DrawLevelField(x, y);
6310 else if (IS_GATE_GRAY(element) &&
6311 game.magnify_time_left > 0)
6313 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6314 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6315 IS_EM_GATE_GRAY(element) ?
6316 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6317 IS_EMC_GATE_GRAY(element) ?
6318 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6319 IS_DC_GATE_GRAY(element) ?
6320 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6322 TEST_DrawLevelField(x, y);
6324 else if (IS_GATE_GRAY_ACTIVE(element) &&
6325 game.magnify_time_left == 0)
6327 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6328 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6329 IS_EM_GATE_GRAY_ACTIVE(element) ?
6330 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6331 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6332 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6333 IS_DC_GATE_GRAY_ACTIVE(element) ?
6334 EL_DC_GATE_WHITE_GRAY :
6336 TEST_DrawLevelField(x, y);
6341 static void ToggleLightSwitch(int x, int y)
6343 int element = Feld[x][y];
6345 game.light_time_left =
6346 (element == EL_LIGHT_SWITCH ?
6347 level.time_light * FRAMES_PER_SECOND : 0);
6349 RedrawAllLightSwitchesAndInvisibleElements();
6352 static void ActivateTimegateSwitch(int x, int y)
6356 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6358 SCAN_PLAYFIELD(xx, yy)
6360 int element = Feld[xx][yy];
6362 if (element == EL_TIMEGATE_CLOSED ||
6363 element == EL_TIMEGATE_CLOSING)
6365 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6366 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6370 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6372 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6373 TEST_DrawLevelField(xx, yy);
6379 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6380 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6383 static void Impact(int x, int y)
6385 boolean last_line = (y == lev_fieldy - 1);
6386 boolean object_hit = FALSE;
6387 boolean impact = (last_line || object_hit);
6388 int element = Feld[x][y];
6389 int smashed = EL_STEELWALL;
6391 if (!last_line) // check if element below was hit
6393 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6396 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6397 MovDir[x][y + 1] != MV_DOWN ||
6398 MovPos[x][y + 1] <= TILEY / 2));
6400 // do not smash moving elements that left the smashed field in time
6401 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6402 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6405 #if USE_QUICKSAND_IMPACT_BUGFIX
6406 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6408 RemoveMovingField(x, y + 1);
6409 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6410 Feld[x][y + 2] = EL_ROCK;
6411 TEST_DrawLevelField(x, y + 2);
6416 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6418 RemoveMovingField(x, y + 1);
6419 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6420 Feld[x][y + 2] = EL_ROCK;
6421 TEST_DrawLevelField(x, y + 2);
6428 smashed = MovingOrBlocked2Element(x, y + 1);
6430 impact = (last_line || object_hit);
6433 if (!last_line && smashed == EL_ACID) // element falls into acid
6435 SplashAcid(x, y + 1);
6439 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6440 // only reset graphic animation if graphic really changes after impact
6442 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6444 ResetGfxAnimation(x, y);
6445 TEST_DrawLevelField(x, y);
6448 if (impact && CAN_EXPLODE_IMPACT(element))
6453 else if (impact && element == EL_PEARL &&
6454 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6456 ResetGfxAnimation(x, y);
6458 Feld[x][y] = EL_PEARL_BREAKING;
6459 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6462 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6464 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6469 if (impact && element == EL_AMOEBA_DROP)
6471 if (object_hit && IS_PLAYER(x, y + 1))
6472 KillPlayerUnlessEnemyProtected(x, y + 1);
6473 else if (object_hit && smashed == EL_PENGUIN)
6477 Feld[x][y] = EL_AMOEBA_GROWING;
6478 Store[x][y] = EL_AMOEBA_WET;
6480 ResetRandomAnimationValue(x, y);
6485 if (object_hit) // check which object was hit
6487 if ((CAN_PASS_MAGIC_WALL(element) &&
6488 (smashed == EL_MAGIC_WALL ||
6489 smashed == EL_BD_MAGIC_WALL)) ||
6490 (CAN_PASS_DC_MAGIC_WALL(element) &&
6491 smashed == EL_DC_MAGIC_WALL))
6494 int activated_magic_wall =
6495 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6496 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6497 EL_DC_MAGIC_WALL_ACTIVE);
6499 // activate magic wall / mill
6500 SCAN_PLAYFIELD(xx, yy)
6502 if (Feld[xx][yy] == smashed)
6503 Feld[xx][yy] = activated_magic_wall;
6506 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6507 game.magic_wall_active = TRUE;
6509 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6510 SND_MAGIC_WALL_ACTIVATING :
6511 smashed == EL_BD_MAGIC_WALL ?
6512 SND_BD_MAGIC_WALL_ACTIVATING :
6513 SND_DC_MAGIC_WALL_ACTIVATING));
6516 if (IS_PLAYER(x, y + 1))
6518 if (CAN_SMASH_PLAYER(element))
6520 KillPlayerUnlessEnemyProtected(x, y + 1);
6524 else if (smashed == EL_PENGUIN)
6526 if (CAN_SMASH_PLAYER(element))
6532 else if (element == EL_BD_DIAMOND)
6534 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6540 else if (((element == EL_SP_INFOTRON ||
6541 element == EL_SP_ZONK) &&
6542 (smashed == EL_SP_SNIKSNAK ||
6543 smashed == EL_SP_ELECTRON ||
6544 smashed == EL_SP_DISK_ORANGE)) ||
6545 (element == EL_SP_INFOTRON &&
6546 smashed == EL_SP_DISK_YELLOW))
6551 else if (CAN_SMASH_EVERYTHING(element))
6553 if (IS_CLASSIC_ENEMY(smashed) ||
6554 CAN_EXPLODE_SMASHED(smashed))
6559 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6561 if (smashed == EL_LAMP ||
6562 smashed == EL_LAMP_ACTIVE)
6567 else if (smashed == EL_NUT)
6569 Feld[x][y + 1] = EL_NUT_BREAKING;
6570 PlayLevelSound(x, y, SND_NUT_BREAKING);
6571 RaiseScoreElement(EL_NUT);
6574 else if (smashed == EL_PEARL)
6576 ResetGfxAnimation(x, y);
6578 Feld[x][y + 1] = EL_PEARL_BREAKING;
6579 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6582 else if (smashed == EL_DIAMOND)
6584 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6585 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6588 else if (IS_BELT_SWITCH(smashed))
6590 ToggleBeltSwitch(x, y + 1);
6592 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6593 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6594 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6595 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6597 ToggleSwitchgateSwitch(x, y + 1);
6599 else if (smashed == EL_LIGHT_SWITCH ||
6600 smashed == EL_LIGHT_SWITCH_ACTIVE)
6602 ToggleLightSwitch(x, y + 1);
6606 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6608 CheckElementChangeBySide(x, y + 1, smashed, element,
6609 CE_SWITCHED, CH_SIDE_TOP);
6610 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6616 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6621 // play sound of magic wall / mill
6623 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6624 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6625 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6627 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6628 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6629 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6630 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6631 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6632 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6637 // play sound of object that hits the ground
6638 if (last_line || object_hit)
6639 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6642 static void TurnRoundExt(int x, int y)
6654 { 0, 0 }, { 0, 0 }, { 0, 0 },
6659 int left, right, back;
6663 { MV_DOWN, MV_UP, MV_RIGHT },
6664 { MV_UP, MV_DOWN, MV_LEFT },
6666 { MV_LEFT, MV_RIGHT, MV_DOWN },
6670 { MV_RIGHT, MV_LEFT, MV_UP }
6673 int element = Feld[x][y];
6674 int move_pattern = element_info[element].move_pattern;
6676 int old_move_dir = MovDir[x][y];
6677 int left_dir = turn[old_move_dir].left;
6678 int right_dir = turn[old_move_dir].right;
6679 int back_dir = turn[old_move_dir].back;
6681 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6682 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6683 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6684 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6686 int left_x = x + left_dx, left_y = y + left_dy;
6687 int right_x = x + right_dx, right_y = y + right_dy;
6688 int move_x = x + move_dx, move_y = y + move_dy;
6692 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6694 TestIfBadThingTouchesOtherBadThing(x, y);
6696 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6697 MovDir[x][y] = right_dir;
6698 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6699 MovDir[x][y] = left_dir;
6701 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6703 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6706 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6708 TestIfBadThingTouchesOtherBadThing(x, y);
6710 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6711 MovDir[x][y] = left_dir;
6712 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6713 MovDir[x][y] = right_dir;
6715 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6717 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6720 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6722 TestIfBadThingTouchesOtherBadThing(x, y);
6724 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6725 MovDir[x][y] = left_dir;
6726 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6727 MovDir[x][y] = right_dir;
6729 if (MovDir[x][y] != old_move_dir)
6732 else if (element == EL_YAMYAM)
6734 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6735 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6737 if (can_turn_left && can_turn_right)
6738 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6739 else if (can_turn_left)
6740 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6741 else if (can_turn_right)
6742 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6744 MovDir[x][y] = back_dir;
6746 MovDelay[x][y] = 16 + 16 * RND(3);
6748 else if (element == EL_DARK_YAMYAM)
6750 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6752 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6755 if (can_turn_left && can_turn_right)
6756 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6757 else if (can_turn_left)
6758 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6759 else if (can_turn_right)
6760 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6762 MovDir[x][y] = back_dir;
6764 MovDelay[x][y] = 16 + 16 * RND(3);
6766 else if (element == EL_PACMAN)
6768 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6769 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6771 if (can_turn_left && can_turn_right)
6772 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6773 else if (can_turn_left)
6774 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6775 else if (can_turn_right)
6776 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6778 MovDir[x][y] = back_dir;
6780 MovDelay[x][y] = 6 + RND(40);
6782 else if (element == EL_PIG)
6784 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6785 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6786 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6787 boolean should_turn_left, should_turn_right, should_move_on;
6789 int rnd = RND(rnd_value);
6791 should_turn_left = (can_turn_left &&
6793 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6794 y + back_dy + left_dy)));
6795 should_turn_right = (can_turn_right &&
6797 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6798 y + back_dy + right_dy)));
6799 should_move_on = (can_move_on &&
6802 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6803 y + move_dy + left_dy) ||
6804 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6805 y + move_dy + right_dy)));
6807 if (should_turn_left || should_turn_right || should_move_on)
6809 if (should_turn_left && should_turn_right && should_move_on)
6810 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6811 rnd < 2 * rnd_value / 3 ? right_dir :
6813 else if (should_turn_left && should_turn_right)
6814 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6815 else if (should_turn_left && should_move_on)
6816 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6817 else if (should_turn_right && should_move_on)
6818 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6819 else if (should_turn_left)
6820 MovDir[x][y] = left_dir;
6821 else if (should_turn_right)
6822 MovDir[x][y] = right_dir;
6823 else if (should_move_on)
6824 MovDir[x][y] = old_move_dir;
6826 else if (can_move_on && rnd > rnd_value / 8)
6827 MovDir[x][y] = old_move_dir;
6828 else if (can_turn_left && can_turn_right)
6829 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6830 else if (can_turn_left && rnd > rnd_value / 8)
6831 MovDir[x][y] = left_dir;
6832 else if (can_turn_right && rnd > rnd_value/8)
6833 MovDir[x][y] = right_dir;
6835 MovDir[x][y] = back_dir;
6837 xx = x + move_xy[MovDir[x][y]].dx;
6838 yy = y + move_xy[MovDir[x][y]].dy;
6840 if (!IN_LEV_FIELD(xx, yy) ||
6841 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6842 MovDir[x][y] = old_move_dir;
6846 else if (element == EL_DRAGON)
6848 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6849 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6850 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6852 int rnd = RND(rnd_value);
6854 if (can_move_on && rnd > rnd_value / 8)
6855 MovDir[x][y] = old_move_dir;
6856 else if (can_turn_left && can_turn_right)
6857 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6858 else if (can_turn_left && rnd > rnd_value / 8)
6859 MovDir[x][y] = left_dir;
6860 else if (can_turn_right && rnd > rnd_value / 8)
6861 MovDir[x][y] = right_dir;
6863 MovDir[x][y] = back_dir;
6865 xx = x + move_xy[MovDir[x][y]].dx;
6866 yy = y + move_xy[MovDir[x][y]].dy;
6868 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6869 MovDir[x][y] = old_move_dir;
6873 else if (element == EL_MOLE)
6875 boolean can_move_on =
6876 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6877 IS_AMOEBOID(Feld[move_x][move_y]) ||
6878 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6881 boolean can_turn_left =
6882 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6883 IS_AMOEBOID(Feld[left_x][left_y])));
6885 boolean can_turn_right =
6886 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6887 IS_AMOEBOID(Feld[right_x][right_y])));
6889 if (can_turn_left && can_turn_right)
6890 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6891 else if (can_turn_left)
6892 MovDir[x][y] = left_dir;
6894 MovDir[x][y] = right_dir;
6897 if (MovDir[x][y] != old_move_dir)
6900 else if (element == EL_BALLOON)
6902 MovDir[x][y] = game.wind_direction;
6905 else if (element == EL_SPRING)
6907 if (MovDir[x][y] & MV_HORIZONTAL)
6909 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6910 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6912 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6913 ResetGfxAnimation(move_x, move_y);
6914 TEST_DrawLevelField(move_x, move_y);
6916 MovDir[x][y] = back_dir;
6918 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6919 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6920 MovDir[x][y] = MV_NONE;
6925 else if (element == EL_ROBOT ||
6926 element == EL_SATELLITE ||
6927 element == EL_PENGUIN ||
6928 element == EL_EMC_ANDROID)
6930 int attr_x = -1, attr_y = -1;
6932 if (game.all_players_gone)
6934 attr_x = game.exit_x;
6935 attr_y = game.exit_y;
6941 for (i = 0; i < MAX_PLAYERS; i++)
6943 struct PlayerInfo *player = &stored_player[i];
6944 int jx = player->jx, jy = player->jy;
6946 if (!player->active)
6950 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6958 if (element == EL_ROBOT &&
6959 game.robot_wheel_x >= 0 &&
6960 game.robot_wheel_y >= 0 &&
6961 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6962 game.engine_version < VERSION_IDENT(3,1,0,0)))
6964 attr_x = game.robot_wheel_x;
6965 attr_y = game.robot_wheel_y;
6968 if (element == EL_PENGUIN)
6971 static int xy[4][2] =
6979 for (i = 0; i < NUM_DIRECTIONS; i++)
6981 int ex = x + xy[i][0];
6982 int ey = y + xy[i][1];
6984 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6985 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6986 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6987 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6996 MovDir[x][y] = MV_NONE;
6998 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6999 else if (attr_x > x)
7000 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7002 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7003 else if (attr_y > y)
7004 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7006 if (element == EL_ROBOT)
7010 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7011 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7012 Moving2Blocked(x, y, &newx, &newy);
7014 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7015 MovDelay[x][y] = 8 + 8 * !RND(3);
7017 MovDelay[x][y] = 16;
7019 else if (element == EL_PENGUIN)
7025 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7027 boolean first_horiz = RND(2);
7028 int new_move_dir = MovDir[x][y];
7031 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7032 Moving2Blocked(x, y, &newx, &newy);
7034 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7038 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7039 Moving2Blocked(x, y, &newx, &newy);
7041 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7044 MovDir[x][y] = old_move_dir;
7048 else if (element == EL_SATELLITE)
7054 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7056 boolean first_horiz = RND(2);
7057 int new_move_dir = MovDir[x][y];
7060 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7061 Moving2Blocked(x, y, &newx, &newy);
7063 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7067 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7068 Moving2Blocked(x, y, &newx, &newy);
7070 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7073 MovDir[x][y] = old_move_dir;
7077 else if (element == EL_EMC_ANDROID)
7079 static int check_pos[16] =
7081 -1, // 0 => (invalid)
7084 -1, // 3 => (invalid)
7086 0, // 5 => MV_LEFT | MV_UP
7087 2, // 6 => MV_RIGHT | MV_UP
7088 -1, // 7 => (invalid)
7090 6, // 9 => MV_LEFT | MV_DOWN
7091 4, // 10 => MV_RIGHT | MV_DOWN
7092 -1, // 11 => (invalid)
7093 -1, // 12 => (invalid)
7094 -1, // 13 => (invalid)
7095 -1, // 14 => (invalid)
7096 -1, // 15 => (invalid)
7104 { -1, -1, MV_LEFT | MV_UP },
7106 { +1, -1, MV_RIGHT | MV_UP },
7107 { +1, 0, MV_RIGHT },
7108 { +1, +1, MV_RIGHT | MV_DOWN },
7110 { -1, +1, MV_LEFT | MV_DOWN },
7113 int start_pos, check_order;
7114 boolean can_clone = FALSE;
7117 // check if there is any free field around current position
7118 for (i = 0; i < 8; i++)
7120 int newx = x + check_xy[i].dx;
7121 int newy = y + check_xy[i].dy;
7123 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7131 if (can_clone) // randomly find an element to clone
7135 start_pos = check_pos[RND(8)];
7136 check_order = (RND(2) ? -1 : +1);
7138 for (i = 0; i < 8; i++)
7140 int pos_raw = start_pos + i * check_order;
7141 int pos = (pos_raw + 8) % 8;
7142 int newx = x + check_xy[pos].dx;
7143 int newy = y + check_xy[pos].dy;
7145 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7147 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7148 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7150 Store[x][y] = Feld[newx][newy];
7159 if (can_clone) // randomly find a direction to move
7163 start_pos = check_pos[RND(8)];
7164 check_order = (RND(2) ? -1 : +1);
7166 for (i = 0; i < 8; i++)
7168 int pos_raw = start_pos + i * check_order;
7169 int pos = (pos_raw + 8) % 8;
7170 int newx = x + check_xy[pos].dx;
7171 int newy = y + check_xy[pos].dy;
7172 int new_move_dir = check_xy[pos].dir;
7174 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7176 MovDir[x][y] = new_move_dir;
7177 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7186 if (can_clone) // cloning and moving successful
7189 // cannot clone -- try to move towards player
7191 start_pos = check_pos[MovDir[x][y] & 0x0f];
7192 check_order = (RND(2) ? -1 : +1);
7194 for (i = 0; i < 3; i++)
7196 // first check start_pos, then previous/next or (next/previous) pos
7197 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7198 int pos = (pos_raw + 8) % 8;
7199 int newx = x + check_xy[pos].dx;
7200 int newy = y + check_xy[pos].dy;
7201 int new_move_dir = check_xy[pos].dir;
7203 if (IS_PLAYER(newx, newy))
7206 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7208 MovDir[x][y] = new_move_dir;
7209 MovDelay[x][y] = level.android_move_time * 8 + 1;
7216 else if (move_pattern == MV_TURNING_LEFT ||
7217 move_pattern == MV_TURNING_RIGHT ||
7218 move_pattern == MV_TURNING_LEFT_RIGHT ||
7219 move_pattern == MV_TURNING_RIGHT_LEFT ||
7220 move_pattern == MV_TURNING_RANDOM ||
7221 move_pattern == MV_ALL_DIRECTIONS)
7223 boolean can_turn_left =
7224 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7225 boolean can_turn_right =
7226 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7228 if (element_info[element].move_stepsize == 0) // "not moving"
7231 if (move_pattern == MV_TURNING_LEFT)
7232 MovDir[x][y] = left_dir;
7233 else if (move_pattern == MV_TURNING_RIGHT)
7234 MovDir[x][y] = right_dir;
7235 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7236 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7237 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7238 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7239 else if (move_pattern == MV_TURNING_RANDOM)
7240 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7241 can_turn_right && !can_turn_left ? right_dir :
7242 RND(2) ? left_dir : right_dir);
7243 else if (can_turn_left && can_turn_right)
7244 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7245 else if (can_turn_left)
7246 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7247 else if (can_turn_right)
7248 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7250 MovDir[x][y] = back_dir;
7252 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7254 else if (move_pattern == MV_HORIZONTAL ||
7255 move_pattern == MV_VERTICAL)
7257 if (move_pattern & old_move_dir)
7258 MovDir[x][y] = back_dir;
7259 else if (move_pattern == MV_HORIZONTAL)
7260 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7261 else if (move_pattern == MV_VERTICAL)
7262 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7264 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7266 else if (move_pattern & MV_ANY_DIRECTION)
7268 MovDir[x][y] = move_pattern;
7269 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7271 else if (move_pattern & MV_WIND_DIRECTION)
7273 MovDir[x][y] = game.wind_direction;
7274 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7276 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7278 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7279 MovDir[x][y] = left_dir;
7280 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7281 MovDir[x][y] = right_dir;
7283 if (MovDir[x][y] != old_move_dir)
7284 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7286 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7288 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7289 MovDir[x][y] = right_dir;
7290 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7291 MovDir[x][y] = left_dir;
7293 if (MovDir[x][y] != old_move_dir)
7294 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7296 else if (move_pattern == MV_TOWARDS_PLAYER ||
7297 move_pattern == MV_AWAY_FROM_PLAYER)
7299 int attr_x = -1, attr_y = -1;
7301 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7303 if (game.all_players_gone)
7305 attr_x = game.exit_x;
7306 attr_y = game.exit_y;
7312 for (i = 0; i < MAX_PLAYERS; i++)
7314 struct PlayerInfo *player = &stored_player[i];
7315 int jx = player->jx, jy = player->jy;
7317 if (!player->active)
7321 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7329 MovDir[x][y] = MV_NONE;
7331 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7332 else if (attr_x > x)
7333 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7335 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7336 else if (attr_y > y)
7337 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7339 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7341 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7343 boolean first_horiz = RND(2);
7344 int new_move_dir = MovDir[x][y];
7346 if (element_info[element].move_stepsize == 0) // "not moving"
7348 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7349 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7355 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7356 Moving2Blocked(x, y, &newx, &newy);
7358 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7362 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7363 Moving2Blocked(x, y, &newx, &newy);
7365 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7368 MovDir[x][y] = old_move_dir;
7371 else if (move_pattern == MV_WHEN_PUSHED ||
7372 move_pattern == MV_WHEN_DROPPED)
7374 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7375 MovDir[x][y] = MV_NONE;
7379 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7381 static int test_xy[7][2] =
7391 static int test_dir[7] =
7401 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7402 int move_preference = -1000000; // start with very low preference
7403 int new_move_dir = MV_NONE;
7404 int start_test = RND(4);
7407 for (i = 0; i < NUM_DIRECTIONS; i++)
7409 int move_dir = test_dir[start_test + i];
7410 int move_dir_preference;
7412 xx = x + test_xy[start_test + i][0];
7413 yy = y + test_xy[start_test + i][1];
7415 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7416 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7418 new_move_dir = move_dir;
7423 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7426 move_dir_preference = -1 * RunnerVisit[xx][yy];
7427 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7428 move_dir_preference = PlayerVisit[xx][yy];
7430 if (move_dir_preference > move_preference)
7432 // prefer field that has not been visited for the longest time
7433 move_preference = move_dir_preference;
7434 new_move_dir = move_dir;
7436 else if (move_dir_preference == move_preference &&
7437 move_dir == old_move_dir)
7439 // prefer last direction when all directions are preferred equally
7440 move_preference = move_dir_preference;
7441 new_move_dir = move_dir;
7445 MovDir[x][y] = new_move_dir;
7446 if (old_move_dir != new_move_dir)
7447 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7451 static void TurnRound(int x, int y)
7453 int direction = MovDir[x][y];
7457 GfxDir[x][y] = MovDir[x][y];
7459 if (direction != MovDir[x][y])
7463 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7465 ResetGfxFrame(x, y);
7468 static boolean JustBeingPushed(int x, int y)
7472 for (i = 0; i < MAX_PLAYERS; i++)
7474 struct PlayerInfo *player = &stored_player[i];
7476 if (player->active && player->is_pushing && player->MovPos)
7478 int next_jx = player->jx + (player->jx - player->last_jx);
7479 int next_jy = player->jy + (player->jy - player->last_jy);
7481 if (x == next_jx && y == next_jy)
7489 static void StartMoving(int x, int y)
7491 boolean started_moving = FALSE; // some elements can fall _and_ move
7492 int element = Feld[x][y];
7497 if (MovDelay[x][y] == 0)
7498 GfxAction[x][y] = ACTION_DEFAULT;
7500 if (CAN_FALL(element) && y < lev_fieldy - 1)
7502 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7503 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7504 if (JustBeingPushed(x, y))
7507 if (element == EL_QUICKSAND_FULL)
7509 if (IS_FREE(x, y + 1))
7511 InitMovingField(x, y, MV_DOWN);
7512 started_moving = TRUE;
7514 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7515 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7516 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7517 Store[x][y] = EL_ROCK;
7519 Store[x][y] = EL_ROCK;
7522 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7524 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7526 if (!MovDelay[x][y])
7528 MovDelay[x][y] = TILEY + 1;
7530 ResetGfxAnimation(x, y);
7531 ResetGfxAnimation(x, y + 1);
7536 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7537 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7544 Feld[x][y] = EL_QUICKSAND_EMPTY;
7545 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7546 Store[x][y + 1] = Store[x][y];
7549 PlayLevelSoundAction(x, y, ACTION_FILLING);
7551 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7553 if (!MovDelay[x][y])
7555 MovDelay[x][y] = TILEY + 1;
7557 ResetGfxAnimation(x, y);
7558 ResetGfxAnimation(x, y + 1);
7563 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7564 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7571 Feld[x][y] = EL_QUICKSAND_EMPTY;
7572 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7573 Store[x][y + 1] = Store[x][y];
7576 PlayLevelSoundAction(x, y, ACTION_FILLING);
7579 else if (element == EL_QUICKSAND_FAST_FULL)
7581 if (IS_FREE(x, y + 1))
7583 InitMovingField(x, y, MV_DOWN);
7584 started_moving = TRUE;
7586 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7587 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7588 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7589 Store[x][y] = EL_ROCK;
7591 Store[x][y] = EL_ROCK;
7594 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7596 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7598 if (!MovDelay[x][y])
7600 MovDelay[x][y] = TILEY + 1;
7602 ResetGfxAnimation(x, y);
7603 ResetGfxAnimation(x, y + 1);
7608 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7609 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7616 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7617 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7618 Store[x][y + 1] = Store[x][y];
7621 PlayLevelSoundAction(x, y, ACTION_FILLING);
7623 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7625 if (!MovDelay[x][y])
7627 MovDelay[x][y] = TILEY + 1;
7629 ResetGfxAnimation(x, y);
7630 ResetGfxAnimation(x, y + 1);
7635 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7636 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7643 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7644 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7645 Store[x][y + 1] = Store[x][y];
7648 PlayLevelSoundAction(x, y, ACTION_FILLING);
7651 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7652 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7654 InitMovingField(x, y, MV_DOWN);
7655 started_moving = TRUE;
7657 Feld[x][y] = EL_QUICKSAND_FILLING;
7658 Store[x][y] = element;
7660 PlayLevelSoundAction(x, y, ACTION_FILLING);
7662 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7663 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7665 InitMovingField(x, y, MV_DOWN);
7666 started_moving = TRUE;
7668 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7669 Store[x][y] = element;
7671 PlayLevelSoundAction(x, y, ACTION_FILLING);
7673 else if (element == EL_MAGIC_WALL_FULL)
7675 if (IS_FREE(x, y + 1))
7677 InitMovingField(x, y, MV_DOWN);
7678 started_moving = TRUE;
7680 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7681 Store[x][y] = EL_CHANGED(Store[x][y]);
7683 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7685 if (!MovDelay[x][y])
7686 MovDelay[x][y] = TILEY / 4 + 1;
7695 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7696 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7697 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7701 else if (element == EL_BD_MAGIC_WALL_FULL)
7703 if (IS_FREE(x, y + 1))
7705 InitMovingField(x, y, MV_DOWN);
7706 started_moving = TRUE;
7708 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7709 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7711 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7713 if (!MovDelay[x][y])
7714 MovDelay[x][y] = TILEY / 4 + 1;
7723 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7724 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7725 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7729 else if (element == EL_DC_MAGIC_WALL_FULL)
7731 if (IS_FREE(x, y + 1))
7733 InitMovingField(x, y, MV_DOWN);
7734 started_moving = TRUE;
7736 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7737 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7739 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7741 if (!MovDelay[x][y])
7742 MovDelay[x][y] = TILEY / 4 + 1;
7751 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7752 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7753 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7757 else if ((CAN_PASS_MAGIC_WALL(element) &&
7758 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7759 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7760 (CAN_PASS_DC_MAGIC_WALL(element) &&
7761 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7764 InitMovingField(x, y, MV_DOWN);
7765 started_moving = TRUE;
7768 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7769 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7770 EL_DC_MAGIC_WALL_FILLING);
7771 Store[x][y] = element;
7773 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7775 SplashAcid(x, y + 1);
7777 InitMovingField(x, y, MV_DOWN);
7778 started_moving = TRUE;
7780 Store[x][y] = EL_ACID;
7783 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7784 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7785 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7786 CAN_FALL(element) && WasJustFalling[x][y] &&
7787 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7789 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7790 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7791 (Feld[x][y + 1] == EL_BLOCKED)))
7793 /* this is needed for a special case not covered by calling "Impact()"
7794 from "ContinueMoving()": if an element moves to a tile directly below
7795 another element which was just falling on that tile (which was empty
7796 in the previous frame), the falling element above would just stop
7797 instead of smashing the element below (in previous version, the above
7798 element was just checked for "moving" instead of "falling", resulting
7799 in incorrect smashes caused by horizontal movement of the above
7800 element; also, the case of the player being the element to smash was
7801 simply not covered here... :-/ ) */
7803 CheckCollision[x][y] = 0;
7804 CheckImpact[x][y] = 0;
7808 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7810 if (MovDir[x][y] == MV_NONE)
7812 InitMovingField(x, y, MV_DOWN);
7813 started_moving = TRUE;
7816 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7818 if (WasJustFalling[x][y]) // prevent animation from being restarted
7819 MovDir[x][y] = MV_DOWN;
7821 InitMovingField(x, y, MV_DOWN);
7822 started_moving = TRUE;
7824 else if (element == EL_AMOEBA_DROP)
7826 Feld[x][y] = EL_AMOEBA_GROWING;
7827 Store[x][y] = EL_AMOEBA_WET;
7829 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7830 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7831 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7832 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7834 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7835 (IS_FREE(x - 1, y + 1) ||
7836 Feld[x - 1][y + 1] == EL_ACID));
7837 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7838 (IS_FREE(x + 1, y + 1) ||
7839 Feld[x + 1][y + 1] == EL_ACID));
7840 boolean can_fall_any = (can_fall_left || can_fall_right);
7841 boolean can_fall_both = (can_fall_left && can_fall_right);
7842 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7844 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7846 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7847 can_fall_right = FALSE;
7848 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7849 can_fall_left = FALSE;
7850 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7851 can_fall_right = FALSE;
7852 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7853 can_fall_left = FALSE;
7855 can_fall_any = (can_fall_left || can_fall_right);
7856 can_fall_both = FALSE;
7861 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7862 can_fall_right = FALSE; // slip down on left side
7864 can_fall_left = !(can_fall_right = RND(2));
7866 can_fall_both = FALSE;
7871 // if not determined otherwise, prefer left side for slipping down
7872 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7873 started_moving = TRUE;
7876 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7878 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7879 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7880 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7881 int belt_dir = game.belt_dir[belt_nr];
7883 if ((belt_dir == MV_LEFT && left_is_free) ||
7884 (belt_dir == MV_RIGHT && right_is_free))
7886 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7888 InitMovingField(x, y, belt_dir);
7889 started_moving = TRUE;
7891 Pushed[x][y] = TRUE;
7892 Pushed[nextx][y] = TRUE;
7894 GfxAction[x][y] = ACTION_DEFAULT;
7898 MovDir[x][y] = 0; // if element was moving, stop it
7903 // not "else if" because of elements that can fall and move (EL_SPRING)
7904 if (CAN_MOVE(element) && !started_moving)
7906 int move_pattern = element_info[element].move_pattern;
7909 Moving2Blocked(x, y, &newx, &newy);
7911 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7914 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7915 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7917 WasJustMoving[x][y] = 0;
7918 CheckCollision[x][y] = 0;
7920 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7922 if (Feld[x][y] != element) // element has changed
7926 if (!MovDelay[x][y]) // start new movement phase
7928 // all objects that can change their move direction after each step
7929 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7931 if (element != EL_YAMYAM &&
7932 element != EL_DARK_YAMYAM &&
7933 element != EL_PACMAN &&
7934 !(move_pattern & MV_ANY_DIRECTION) &&
7935 move_pattern != MV_TURNING_LEFT &&
7936 move_pattern != MV_TURNING_RIGHT &&
7937 move_pattern != MV_TURNING_LEFT_RIGHT &&
7938 move_pattern != MV_TURNING_RIGHT_LEFT &&
7939 move_pattern != MV_TURNING_RANDOM)
7943 if (MovDelay[x][y] && (element == EL_BUG ||
7944 element == EL_SPACESHIP ||
7945 element == EL_SP_SNIKSNAK ||
7946 element == EL_SP_ELECTRON ||
7947 element == EL_MOLE))
7948 TEST_DrawLevelField(x, y);
7952 if (MovDelay[x][y]) // wait some time before next movement
7956 if (element == EL_ROBOT ||
7957 element == EL_YAMYAM ||
7958 element == EL_DARK_YAMYAM)
7960 DrawLevelElementAnimationIfNeeded(x, y, element);
7961 PlayLevelSoundAction(x, y, ACTION_WAITING);
7963 else if (element == EL_SP_ELECTRON)
7964 DrawLevelElementAnimationIfNeeded(x, y, element);
7965 else if (element == EL_DRAGON)
7968 int dir = MovDir[x][y];
7969 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7970 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7971 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7972 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7973 dir == MV_UP ? IMG_FLAMES_1_UP :
7974 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7975 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7977 GfxAction[x][y] = ACTION_ATTACKING;
7979 if (IS_PLAYER(x, y))
7980 DrawPlayerField(x, y);
7982 TEST_DrawLevelField(x, y);
7984 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7986 for (i = 1; i <= 3; i++)
7988 int xx = x + i * dx;
7989 int yy = y + i * dy;
7990 int sx = SCREENX(xx);
7991 int sy = SCREENY(yy);
7992 int flame_graphic = graphic + (i - 1);
7994 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7999 int flamed = MovingOrBlocked2Element(xx, yy);
8001 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8004 RemoveMovingField(xx, yy);
8006 ChangeDelay[xx][yy] = 0;
8008 Feld[xx][yy] = EL_FLAMES;
8010 if (IN_SCR_FIELD(sx, sy))
8012 TEST_DrawLevelFieldCrumbled(xx, yy);
8013 DrawGraphic(sx, sy, flame_graphic, frame);
8018 if (Feld[xx][yy] == EL_FLAMES)
8019 Feld[xx][yy] = EL_EMPTY;
8020 TEST_DrawLevelField(xx, yy);
8025 if (MovDelay[x][y]) // element still has to wait some time
8027 PlayLevelSoundAction(x, y, ACTION_WAITING);
8033 // now make next step
8035 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8037 if (DONT_COLLIDE_WITH(element) &&
8038 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8039 !PLAYER_ENEMY_PROTECTED(newx, newy))
8041 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8046 else if (CAN_MOVE_INTO_ACID(element) &&
8047 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8048 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8049 (MovDir[x][y] == MV_DOWN ||
8050 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8052 SplashAcid(newx, newy);
8053 Store[x][y] = EL_ACID;
8055 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8057 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8058 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8059 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8060 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8063 TEST_DrawLevelField(x, y);
8065 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8066 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8067 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8069 game.friends_still_needed--;
8070 if (!game.friends_still_needed &&
8072 game.all_players_gone)
8077 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8079 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8080 TEST_DrawLevelField(newx, newy);
8082 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8084 else if (!IS_FREE(newx, newy))
8086 GfxAction[x][y] = ACTION_WAITING;
8088 if (IS_PLAYER(x, y))
8089 DrawPlayerField(x, y);
8091 TEST_DrawLevelField(x, y);
8096 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8098 if (IS_FOOD_PIG(Feld[newx][newy]))
8100 if (IS_MOVING(newx, newy))
8101 RemoveMovingField(newx, newy);
8104 Feld[newx][newy] = EL_EMPTY;
8105 TEST_DrawLevelField(newx, newy);
8108 PlayLevelSound(x, y, SND_PIG_DIGGING);
8110 else if (!IS_FREE(newx, newy))
8112 if (IS_PLAYER(x, y))
8113 DrawPlayerField(x, y);
8115 TEST_DrawLevelField(x, y);
8120 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8122 if (Store[x][y] != EL_EMPTY)
8124 boolean can_clone = FALSE;
8127 // check if element to clone is still there
8128 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8130 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8138 // cannot clone or target field not free anymore -- do not clone
8139 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8140 Store[x][y] = EL_EMPTY;
8143 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8145 if (IS_MV_DIAGONAL(MovDir[x][y]))
8147 int diagonal_move_dir = MovDir[x][y];
8148 int stored = Store[x][y];
8149 int change_delay = 8;
8152 // android is moving diagonally
8154 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8156 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8157 GfxElement[x][y] = EL_EMC_ANDROID;
8158 GfxAction[x][y] = ACTION_SHRINKING;
8159 GfxDir[x][y] = diagonal_move_dir;
8160 ChangeDelay[x][y] = change_delay;
8162 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8165 DrawLevelGraphicAnimation(x, y, graphic);
8166 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8168 if (Feld[newx][newy] == EL_ACID)
8170 SplashAcid(newx, newy);
8175 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8177 Store[newx][newy] = EL_EMC_ANDROID;
8178 GfxElement[newx][newy] = EL_EMC_ANDROID;
8179 GfxAction[newx][newy] = ACTION_GROWING;
8180 GfxDir[newx][newy] = diagonal_move_dir;
8181 ChangeDelay[newx][newy] = change_delay;
8183 graphic = el_act_dir2img(GfxElement[newx][newy],
8184 GfxAction[newx][newy], GfxDir[newx][newy]);
8186 DrawLevelGraphicAnimation(newx, newy, graphic);
8187 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8193 Feld[newx][newy] = EL_EMPTY;
8194 TEST_DrawLevelField(newx, newy);
8196 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8199 else if (!IS_FREE(newx, newy))
8204 else if (IS_CUSTOM_ELEMENT(element) &&
8205 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8207 if (!DigFieldByCE(newx, newy, element))
8210 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8212 RunnerVisit[x][y] = FrameCounter;
8213 PlayerVisit[x][y] /= 8; // expire player visit path
8216 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8218 if (!IS_FREE(newx, newy))
8220 if (IS_PLAYER(x, y))
8221 DrawPlayerField(x, y);
8223 TEST_DrawLevelField(x, y);
8229 boolean wanna_flame = !RND(10);
8230 int dx = newx - x, dy = newy - y;
8231 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8232 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8233 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8234 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8235 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8236 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8239 IS_CLASSIC_ENEMY(element1) ||
8240 IS_CLASSIC_ENEMY(element2)) &&
8241 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8242 element1 != EL_FLAMES && element2 != EL_FLAMES)
8244 ResetGfxAnimation(x, y);
8245 GfxAction[x][y] = ACTION_ATTACKING;
8247 if (IS_PLAYER(x, y))
8248 DrawPlayerField(x, y);
8250 TEST_DrawLevelField(x, y);
8252 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8254 MovDelay[x][y] = 50;
8256 Feld[newx][newy] = EL_FLAMES;
8257 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8258 Feld[newx1][newy1] = EL_FLAMES;
8259 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8260 Feld[newx2][newy2] = EL_FLAMES;
8266 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8267 Feld[newx][newy] == EL_DIAMOND)
8269 if (IS_MOVING(newx, newy))
8270 RemoveMovingField(newx, newy);
8273 Feld[newx][newy] = EL_EMPTY;
8274 TEST_DrawLevelField(newx, newy);
8277 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8279 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8280 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8282 if (AmoebaNr[newx][newy])
8284 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8285 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8286 Feld[newx][newy] == EL_BD_AMOEBA)
8287 AmoebaCnt[AmoebaNr[newx][newy]]--;
8290 if (IS_MOVING(newx, newy))
8292 RemoveMovingField(newx, newy);
8296 Feld[newx][newy] = EL_EMPTY;
8297 TEST_DrawLevelField(newx, newy);
8300 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8302 else if ((element == EL_PACMAN || element == EL_MOLE)
8303 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8305 if (AmoebaNr[newx][newy])
8307 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8308 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8309 Feld[newx][newy] == EL_BD_AMOEBA)
8310 AmoebaCnt[AmoebaNr[newx][newy]]--;
8313 if (element == EL_MOLE)
8315 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8316 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8318 ResetGfxAnimation(x, y);
8319 GfxAction[x][y] = ACTION_DIGGING;
8320 TEST_DrawLevelField(x, y);
8322 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8324 return; // wait for shrinking amoeba
8326 else // element == EL_PACMAN
8328 Feld[newx][newy] = EL_EMPTY;
8329 TEST_DrawLevelField(newx, newy);
8330 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8333 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8334 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8335 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8337 // wait for shrinking amoeba to completely disappear
8340 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8342 // object was running against a wall
8346 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8347 DrawLevelElementAnimation(x, y, element);
8349 if (DONT_TOUCH(element))
8350 TestIfBadThingTouchesPlayer(x, y);
8355 InitMovingField(x, y, MovDir[x][y]);
8357 PlayLevelSoundAction(x, y, ACTION_MOVING);
8361 ContinueMoving(x, y);
8364 void ContinueMoving(int x, int y)
8366 int element = Feld[x][y];
8367 struct ElementInfo *ei = &element_info[element];
8368 int direction = MovDir[x][y];
8369 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8370 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8371 int newx = x + dx, newy = y + dy;
8372 int stored = Store[x][y];
8373 int stored_new = Store[newx][newy];
8374 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8375 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8376 boolean last_line = (newy == lev_fieldy - 1);
8378 MovPos[x][y] += getElementMoveStepsize(x, y);
8380 if (pushed_by_player) // special case: moving object pushed by player
8381 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8383 if (ABS(MovPos[x][y]) < TILEX)
8385 TEST_DrawLevelField(x, y);
8387 return; // element is still moving
8390 // element reached destination field
8392 Feld[x][y] = EL_EMPTY;
8393 Feld[newx][newy] = element;
8394 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8396 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8398 element = Feld[newx][newy] = EL_ACID;
8400 else if (element == EL_MOLE)
8402 Feld[x][y] = EL_SAND;
8404 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8406 else if (element == EL_QUICKSAND_FILLING)
8408 element = Feld[newx][newy] = get_next_element(element);
8409 Store[newx][newy] = Store[x][y];
8411 else if (element == EL_QUICKSAND_EMPTYING)
8413 Feld[x][y] = get_next_element(element);
8414 element = Feld[newx][newy] = Store[x][y];
8416 else if (element == EL_QUICKSAND_FAST_FILLING)
8418 element = Feld[newx][newy] = get_next_element(element);
8419 Store[newx][newy] = Store[x][y];
8421 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8423 Feld[x][y] = get_next_element(element);
8424 element = Feld[newx][newy] = Store[x][y];
8426 else if (element == EL_MAGIC_WALL_FILLING)
8428 element = Feld[newx][newy] = get_next_element(element);
8429 if (!game.magic_wall_active)
8430 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8431 Store[newx][newy] = Store[x][y];
8433 else if (element == EL_MAGIC_WALL_EMPTYING)
8435 Feld[x][y] = get_next_element(element);
8436 if (!game.magic_wall_active)
8437 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8438 element = Feld[newx][newy] = Store[x][y];
8440 InitField(newx, newy, FALSE);
8442 else if (element == EL_BD_MAGIC_WALL_FILLING)
8444 element = Feld[newx][newy] = get_next_element(element);
8445 if (!game.magic_wall_active)
8446 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8447 Store[newx][newy] = Store[x][y];
8449 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8451 Feld[x][y] = get_next_element(element);
8452 if (!game.magic_wall_active)
8453 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8454 element = Feld[newx][newy] = Store[x][y];
8456 InitField(newx, newy, FALSE);
8458 else if (element == EL_DC_MAGIC_WALL_FILLING)
8460 element = Feld[newx][newy] = get_next_element(element);
8461 if (!game.magic_wall_active)
8462 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8463 Store[newx][newy] = Store[x][y];
8465 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8467 Feld[x][y] = get_next_element(element);
8468 if (!game.magic_wall_active)
8469 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8470 element = Feld[newx][newy] = Store[x][y];
8472 InitField(newx, newy, FALSE);
8474 else if (element == EL_AMOEBA_DROPPING)
8476 Feld[x][y] = get_next_element(element);
8477 element = Feld[newx][newy] = Store[x][y];
8479 else if (element == EL_SOKOBAN_OBJECT)
8482 Feld[x][y] = Back[x][y];
8484 if (Back[newx][newy])
8485 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8487 Back[x][y] = Back[newx][newy] = 0;
8490 Store[x][y] = EL_EMPTY;
8495 MovDelay[newx][newy] = 0;
8497 if (CAN_CHANGE_OR_HAS_ACTION(element))
8499 // copy element change control values to new field
8500 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8501 ChangePage[newx][newy] = ChangePage[x][y];
8502 ChangeCount[newx][newy] = ChangeCount[x][y];
8503 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8506 CustomValue[newx][newy] = CustomValue[x][y];
8508 ChangeDelay[x][y] = 0;
8509 ChangePage[x][y] = -1;
8510 ChangeCount[x][y] = 0;
8511 ChangeEvent[x][y] = -1;
8513 CustomValue[x][y] = 0;
8515 // copy animation control values to new field
8516 GfxFrame[newx][newy] = GfxFrame[x][y];
8517 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8518 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8519 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8521 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8523 // some elements can leave other elements behind after moving
8524 if (ei->move_leave_element != EL_EMPTY &&
8525 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8526 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8528 int move_leave_element = ei->move_leave_element;
8530 // this makes it possible to leave the removed element again
8531 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8532 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8534 Feld[x][y] = move_leave_element;
8536 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8537 MovDir[x][y] = direction;
8539 InitField(x, y, FALSE);
8541 if (GFX_CRUMBLED(Feld[x][y]))
8542 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8544 if (ELEM_IS_PLAYER(move_leave_element))
8545 RelocatePlayer(x, y, move_leave_element);
8548 // do this after checking for left-behind element
8549 ResetGfxAnimation(x, y); // reset animation values for old field
8551 if (!CAN_MOVE(element) ||
8552 (CAN_FALL(element) && direction == MV_DOWN &&
8553 (element == EL_SPRING ||
8554 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8555 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8556 GfxDir[x][y] = MovDir[newx][newy] = 0;
8558 TEST_DrawLevelField(x, y);
8559 TEST_DrawLevelField(newx, newy);
8561 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8563 // prevent pushed element from moving on in pushed direction
8564 if (pushed_by_player && CAN_MOVE(element) &&
8565 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8566 !(element_info[element].move_pattern & direction))
8567 TurnRound(newx, newy);
8569 // prevent elements on conveyor belt from moving on in last direction
8570 if (pushed_by_conveyor && CAN_FALL(element) &&
8571 direction & MV_HORIZONTAL)
8572 MovDir[newx][newy] = 0;
8574 if (!pushed_by_player)
8576 int nextx = newx + dx, nexty = newy + dy;
8577 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8579 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8581 if (CAN_FALL(element) && direction == MV_DOWN)
8582 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8584 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8585 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8587 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8588 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8591 if (DONT_TOUCH(element)) // object may be nasty to player or others
8593 TestIfBadThingTouchesPlayer(newx, newy);
8594 TestIfBadThingTouchesFriend(newx, newy);
8596 if (!IS_CUSTOM_ELEMENT(element))
8597 TestIfBadThingTouchesOtherBadThing(newx, newy);
8599 else if (element == EL_PENGUIN)
8600 TestIfFriendTouchesBadThing(newx, newy);
8602 if (DONT_GET_HIT_BY(element))
8604 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8607 // give the player one last chance (one more frame) to move away
8608 if (CAN_FALL(element) && direction == MV_DOWN &&
8609 (last_line || (!IS_FREE(x, newy + 1) &&
8610 (!IS_PLAYER(x, newy + 1) ||
8611 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8614 if (pushed_by_player && !game.use_change_when_pushing_bug)
8616 int push_side = MV_DIR_OPPOSITE(direction);
8617 struct PlayerInfo *player = PLAYERINFO(x, y);
8619 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8620 player->index_bit, push_side);
8621 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8622 player->index_bit, push_side);
8625 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8626 MovDelay[newx][newy] = 1;
8628 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8630 TestIfElementTouchesCustomElement(x, y); // empty or new element
8631 TestIfElementHitsCustomElement(newx, newy, direction);
8632 TestIfPlayerTouchesCustomElement(newx, newy);
8633 TestIfElementTouchesCustomElement(newx, newy);
8635 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8636 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8637 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8638 MV_DIR_OPPOSITE(direction));
8641 int AmoebeNachbarNr(int ax, int ay)
8644 int element = Feld[ax][ay];
8646 static int xy[4][2] =
8654 for (i = 0; i < NUM_DIRECTIONS; i++)
8656 int x = ax + xy[i][0];
8657 int y = ay + xy[i][1];
8659 if (!IN_LEV_FIELD(x, y))
8662 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8663 group_nr = AmoebaNr[x][y];
8669 static void AmoebenVereinigen(int ax, int ay)
8671 int i, x, y, xx, yy;
8672 int new_group_nr = AmoebaNr[ax][ay];
8673 static int xy[4][2] =
8681 if (new_group_nr == 0)
8684 for (i = 0; i < NUM_DIRECTIONS; i++)
8689 if (!IN_LEV_FIELD(x, y))
8692 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8693 Feld[x][y] == EL_BD_AMOEBA ||
8694 Feld[x][y] == EL_AMOEBA_DEAD) &&
8695 AmoebaNr[x][y] != new_group_nr)
8697 int old_group_nr = AmoebaNr[x][y];
8699 if (old_group_nr == 0)
8702 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8703 AmoebaCnt[old_group_nr] = 0;
8704 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8705 AmoebaCnt2[old_group_nr] = 0;
8707 SCAN_PLAYFIELD(xx, yy)
8709 if (AmoebaNr[xx][yy] == old_group_nr)
8710 AmoebaNr[xx][yy] = new_group_nr;
8716 void AmoebeUmwandeln(int ax, int ay)
8720 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8722 int group_nr = AmoebaNr[ax][ay];
8727 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8728 printf("AmoebeUmwandeln(): This should never happen!\n");
8733 SCAN_PLAYFIELD(x, y)
8735 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8738 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8742 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8743 SND_AMOEBA_TURNING_TO_GEM :
8744 SND_AMOEBA_TURNING_TO_ROCK));
8749 static int xy[4][2] =
8757 for (i = 0; i < NUM_DIRECTIONS; i++)
8762 if (!IN_LEV_FIELD(x, y))
8765 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8767 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8768 SND_AMOEBA_TURNING_TO_GEM :
8769 SND_AMOEBA_TURNING_TO_ROCK));
8776 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8779 int group_nr = AmoebaNr[ax][ay];
8780 boolean done = FALSE;
8785 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8786 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8791 SCAN_PLAYFIELD(x, y)
8793 if (AmoebaNr[x][y] == group_nr &&
8794 (Feld[x][y] == EL_AMOEBA_DEAD ||
8795 Feld[x][y] == EL_BD_AMOEBA ||
8796 Feld[x][y] == EL_AMOEBA_GROWING))
8799 Feld[x][y] = new_element;
8800 InitField(x, y, FALSE);
8801 TEST_DrawLevelField(x, y);
8807 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8808 SND_BD_AMOEBA_TURNING_TO_ROCK :
8809 SND_BD_AMOEBA_TURNING_TO_GEM));
8812 static void AmoebeWaechst(int x, int y)
8814 static unsigned int sound_delay = 0;
8815 static unsigned int sound_delay_value = 0;
8817 if (!MovDelay[x][y]) // start new growing cycle
8821 if (DelayReached(&sound_delay, sound_delay_value))
8823 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8824 sound_delay_value = 30;
8828 if (MovDelay[x][y]) // wait some time before growing bigger
8831 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8833 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8834 6 - MovDelay[x][y]);
8836 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8839 if (!MovDelay[x][y])
8841 Feld[x][y] = Store[x][y];
8843 TEST_DrawLevelField(x, y);
8848 static void AmoebaDisappearing(int x, int y)
8850 static unsigned int sound_delay = 0;
8851 static unsigned int sound_delay_value = 0;
8853 if (!MovDelay[x][y]) // start new shrinking cycle
8857 if (DelayReached(&sound_delay, sound_delay_value))
8858 sound_delay_value = 30;
8861 if (MovDelay[x][y]) // wait some time before shrinking
8864 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8866 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8867 6 - MovDelay[x][y]);
8869 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8872 if (!MovDelay[x][y])
8874 Feld[x][y] = EL_EMPTY;
8875 TEST_DrawLevelField(x, y);
8877 // don't let mole enter this field in this cycle;
8878 // (give priority to objects falling to this field from above)
8884 static void AmoebeAbleger(int ax, int ay)
8887 int element = Feld[ax][ay];
8888 int graphic = el2img(element);
8889 int newax = ax, neway = ay;
8890 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8891 static int xy[4][2] =
8899 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8901 Feld[ax][ay] = EL_AMOEBA_DEAD;
8902 TEST_DrawLevelField(ax, ay);
8906 if (IS_ANIMATED(graphic))
8907 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8909 if (!MovDelay[ax][ay]) // start making new amoeba field
8910 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8912 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8915 if (MovDelay[ax][ay])
8919 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8922 int x = ax + xy[start][0];
8923 int y = ay + xy[start][1];
8925 if (!IN_LEV_FIELD(x, y))
8928 if (IS_FREE(x, y) ||
8929 CAN_GROW_INTO(Feld[x][y]) ||
8930 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8931 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8937 if (newax == ax && neway == ay)
8940 else // normal or "filled" (BD style) amoeba
8943 boolean waiting_for_player = FALSE;
8945 for (i = 0; i < NUM_DIRECTIONS; i++)
8947 int j = (start + i) % 4;
8948 int x = ax + xy[j][0];
8949 int y = ay + xy[j][1];
8951 if (!IN_LEV_FIELD(x, y))
8954 if (IS_FREE(x, y) ||
8955 CAN_GROW_INTO(Feld[x][y]) ||
8956 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8957 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8963 else if (IS_PLAYER(x, y))
8964 waiting_for_player = TRUE;
8967 if (newax == ax && neway == ay) // amoeba cannot grow
8969 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8971 Feld[ax][ay] = EL_AMOEBA_DEAD;
8972 TEST_DrawLevelField(ax, ay);
8973 AmoebaCnt[AmoebaNr[ax][ay]]--;
8975 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8977 if (element == EL_AMOEBA_FULL)
8978 AmoebeUmwandeln(ax, ay);
8979 else if (element == EL_BD_AMOEBA)
8980 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8985 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8987 // amoeba gets larger by growing in some direction
8989 int new_group_nr = AmoebaNr[ax][ay];
8992 if (new_group_nr == 0)
8994 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8995 printf("AmoebeAbleger(): This should never happen!\n");
9000 AmoebaNr[newax][neway] = new_group_nr;
9001 AmoebaCnt[new_group_nr]++;
9002 AmoebaCnt2[new_group_nr]++;
9004 // if amoeba touches other amoeba(s) after growing, unify them
9005 AmoebenVereinigen(newax, neway);
9007 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9009 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
9015 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9016 (neway == lev_fieldy - 1 && newax != ax))
9018 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9019 Store[newax][neway] = element;
9021 else if (neway == ay || element == EL_EMC_DRIPPER)
9023 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9025 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9029 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9030 Feld[ax][ay] = EL_AMOEBA_DROPPING;
9031 Store[ax][ay] = EL_AMOEBA_DROP;
9032 ContinueMoving(ax, ay);
9036 TEST_DrawLevelField(newax, neway);
9039 static void Life(int ax, int ay)
9043 int element = Feld[ax][ay];
9044 int graphic = el2img(element);
9045 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9047 boolean changed = FALSE;
9049 if (IS_ANIMATED(graphic))
9050 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9055 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9056 MovDelay[ax][ay] = life_time;
9058 if (MovDelay[ax][ay]) // wait some time before next cycle
9061 if (MovDelay[ax][ay])
9065 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9067 int xx = ax+x1, yy = ay+y1;
9068 int old_element = Feld[xx][yy];
9069 int num_neighbours = 0;
9071 if (!IN_LEV_FIELD(xx, yy))
9074 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9076 int x = xx+x2, y = yy+y2;
9078 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9081 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9082 boolean is_neighbour = FALSE;
9084 if (level.use_life_bugs)
9086 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9087 (IS_FREE(x, y) && Stop[x][y]));
9090 (Last[x][y] == element || is_player_cell);
9096 boolean is_free = FALSE;
9098 if (level.use_life_bugs)
9099 is_free = (IS_FREE(xx, yy));
9101 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9103 if (xx == ax && yy == ay) // field in the middle
9105 if (num_neighbours < life_parameter[0] ||
9106 num_neighbours > life_parameter[1])
9108 Feld[xx][yy] = EL_EMPTY;
9109 if (Feld[xx][yy] != old_element)
9110 TEST_DrawLevelField(xx, yy);
9111 Stop[xx][yy] = TRUE;
9115 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9116 { // free border field
9117 if (num_neighbours >= life_parameter[2] &&
9118 num_neighbours <= life_parameter[3])
9120 Feld[xx][yy] = element;
9121 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9122 if (Feld[xx][yy] != old_element)
9123 TEST_DrawLevelField(xx, yy);
9124 Stop[xx][yy] = TRUE;
9131 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9132 SND_GAME_OF_LIFE_GROWING);
9135 static void InitRobotWheel(int x, int y)
9137 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9140 static void RunRobotWheel(int x, int y)
9142 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9145 static void StopRobotWheel(int x, int y)
9147 if (game.robot_wheel_x == x &&
9148 game.robot_wheel_y == y)
9150 game.robot_wheel_x = -1;
9151 game.robot_wheel_y = -1;
9152 game.robot_wheel_active = FALSE;
9156 static void InitTimegateWheel(int x, int y)
9158 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9161 static void RunTimegateWheel(int x, int y)
9163 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9166 static void InitMagicBallDelay(int x, int y)
9168 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9171 static void ActivateMagicBall(int bx, int by)
9175 if (level.ball_random)
9177 int pos_border = RND(8); // select one of the eight border elements
9178 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9179 int xx = pos_content % 3;
9180 int yy = pos_content / 3;
9185 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9186 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9190 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9192 int xx = x - bx + 1;
9193 int yy = y - by + 1;
9195 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9196 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9200 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9203 static void CheckExit(int x, int y)
9205 if (game.gems_still_needed > 0 ||
9206 game.sokoban_fields_still_needed > 0 ||
9207 game.sokoban_objects_still_needed > 0 ||
9208 game.lights_still_needed > 0)
9210 int element = Feld[x][y];
9211 int graphic = el2img(element);
9213 if (IS_ANIMATED(graphic))
9214 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9219 // do not re-open exit door closed after last player
9220 if (game.all_players_gone)
9223 Feld[x][y] = EL_EXIT_OPENING;
9225 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9228 static void CheckExitEM(int x, int y)
9230 if (game.gems_still_needed > 0 ||
9231 game.sokoban_fields_still_needed > 0 ||
9232 game.sokoban_objects_still_needed > 0 ||
9233 game.lights_still_needed > 0)
9235 int element = Feld[x][y];
9236 int graphic = el2img(element);
9238 if (IS_ANIMATED(graphic))
9239 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9244 // do not re-open exit door closed after last player
9245 if (game.all_players_gone)
9248 Feld[x][y] = EL_EM_EXIT_OPENING;
9250 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9253 static void CheckExitSteel(int x, int y)
9255 if (game.gems_still_needed > 0 ||
9256 game.sokoban_fields_still_needed > 0 ||
9257 game.sokoban_objects_still_needed > 0 ||
9258 game.lights_still_needed > 0)
9260 int element = Feld[x][y];
9261 int graphic = el2img(element);
9263 if (IS_ANIMATED(graphic))
9264 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9269 // do not re-open exit door closed after last player
9270 if (game.all_players_gone)
9273 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9275 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9278 static void CheckExitSteelEM(int x, int y)
9280 if (game.gems_still_needed > 0 ||
9281 game.sokoban_fields_still_needed > 0 ||
9282 game.sokoban_objects_still_needed > 0 ||
9283 game.lights_still_needed > 0)
9285 int element = Feld[x][y];
9286 int graphic = el2img(element);
9288 if (IS_ANIMATED(graphic))
9289 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9294 // do not re-open exit door closed after last player
9295 if (game.all_players_gone)
9298 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9300 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9303 static void CheckExitSP(int x, int y)
9305 if (game.gems_still_needed > 0)
9307 int element = Feld[x][y];
9308 int graphic = el2img(element);
9310 if (IS_ANIMATED(graphic))
9311 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9316 // do not re-open exit door closed after last player
9317 if (game.all_players_gone)
9320 Feld[x][y] = EL_SP_EXIT_OPENING;
9322 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9325 static void CloseAllOpenTimegates(void)
9329 SCAN_PLAYFIELD(x, y)
9331 int element = Feld[x][y];
9333 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9335 Feld[x][y] = EL_TIMEGATE_CLOSING;
9337 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9342 static void DrawTwinkleOnField(int x, int y)
9344 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9347 if (Feld[x][y] == EL_BD_DIAMOND)
9350 if (MovDelay[x][y] == 0) // next animation frame
9351 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9353 if (MovDelay[x][y] != 0) // wait some time before next frame
9357 DrawLevelElementAnimation(x, y, Feld[x][y]);
9359 if (MovDelay[x][y] != 0)
9361 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9362 10 - MovDelay[x][y]);
9364 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9369 static void MauerWaechst(int x, int y)
9373 if (!MovDelay[x][y]) // next animation frame
9374 MovDelay[x][y] = 3 * delay;
9376 if (MovDelay[x][y]) // wait some time before next frame
9380 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9382 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9383 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9385 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9388 if (!MovDelay[x][y])
9390 if (MovDir[x][y] == MV_LEFT)
9392 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9393 TEST_DrawLevelField(x - 1, y);
9395 else if (MovDir[x][y] == MV_RIGHT)
9397 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9398 TEST_DrawLevelField(x + 1, y);
9400 else if (MovDir[x][y] == MV_UP)
9402 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9403 TEST_DrawLevelField(x, y - 1);
9407 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9408 TEST_DrawLevelField(x, y + 1);
9411 Feld[x][y] = Store[x][y];
9413 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9414 TEST_DrawLevelField(x, y);
9419 static void MauerAbleger(int ax, int ay)
9421 int element = Feld[ax][ay];
9422 int graphic = el2img(element);
9423 boolean oben_frei = FALSE, unten_frei = FALSE;
9424 boolean links_frei = FALSE, rechts_frei = FALSE;
9425 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9426 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9427 boolean new_wall = FALSE;
9429 if (IS_ANIMATED(graphic))
9430 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9432 if (!MovDelay[ax][ay]) // start building new wall
9433 MovDelay[ax][ay] = 6;
9435 if (MovDelay[ax][ay]) // wait some time before building new wall
9438 if (MovDelay[ax][ay])
9442 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9444 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9446 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9448 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9451 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9452 element == EL_EXPANDABLE_WALL_ANY)
9456 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9457 Store[ax][ay-1] = element;
9458 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9459 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9460 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9461 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9466 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9467 Store[ax][ay+1] = element;
9468 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9469 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9470 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9471 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9476 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9477 element == EL_EXPANDABLE_WALL_ANY ||
9478 element == EL_EXPANDABLE_WALL ||
9479 element == EL_BD_EXPANDABLE_WALL)
9483 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9484 Store[ax-1][ay] = element;
9485 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9486 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9487 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9488 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9494 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9495 Store[ax+1][ay] = element;
9496 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9497 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9498 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9499 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9504 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9505 TEST_DrawLevelField(ax, ay);
9507 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9509 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9510 unten_massiv = TRUE;
9511 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9512 links_massiv = TRUE;
9513 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9514 rechts_massiv = TRUE;
9516 if (((oben_massiv && unten_massiv) ||
9517 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9518 element == EL_EXPANDABLE_WALL) &&
9519 ((links_massiv && rechts_massiv) ||
9520 element == EL_EXPANDABLE_WALL_VERTICAL))
9521 Feld[ax][ay] = EL_WALL;
9524 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9527 static void MauerAblegerStahl(int ax, int ay)
9529 int element = Feld[ax][ay];
9530 int graphic = el2img(element);
9531 boolean oben_frei = FALSE, unten_frei = FALSE;
9532 boolean links_frei = FALSE, rechts_frei = FALSE;
9533 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9534 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9535 boolean new_wall = FALSE;
9537 if (IS_ANIMATED(graphic))
9538 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9540 if (!MovDelay[ax][ay]) // start building new wall
9541 MovDelay[ax][ay] = 6;
9543 if (MovDelay[ax][ay]) // wait some time before building new wall
9546 if (MovDelay[ax][ay])
9550 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9552 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9554 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9556 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9559 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9560 element == EL_EXPANDABLE_STEELWALL_ANY)
9564 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9565 Store[ax][ay-1] = element;
9566 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9567 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9568 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9569 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9574 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9575 Store[ax][ay+1] = element;
9576 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9577 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9578 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9579 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9584 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9585 element == EL_EXPANDABLE_STEELWALL_ANY)
9589 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9590 Store[ax-1][ay] = element;
9591 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9592 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9593 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9594 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9600 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9601 Store[ax+1][ay] = element;
9602 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9603 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9604 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9605 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9610 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9612 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9613 unten_massiv = TRUE;
9614 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9615 links_massiv = TRUE;
9616 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9617 rechts_massiv = TRUE;
9619 if (((oben_massiv && unten_massiv) ||
9620 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9621 ((links_massiv && rechts_massiv) ||
9622 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9623 Feld[ax][ay] = EL_STEELWALL;
9626 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9629 static void CheckForDragon(int x, int y)
9632 boolean dragon_found = FALSE;
9633 static int xy[4][2] =
9641 for (i = 0; i < NUM_DIRECTIONS; i++)
9643 for (j = 0; j < 4; j++)
9645 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9647 if (IN_LEV_FIELD(xx, yy) &&
9648 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9650 if (Feld[xx][yy] == EL_DRAGON)
9651 dragon_found = TRUE;
9660 for (i = 0; i < NUM_DIRECTIONS; i++)
9662 for (j = 0; j < 3; j++)
9664 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9666 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9668 Feld[xx][yy] = EL_EMPTY;
9669 TEST_DrawLevelField(xx, yy);
9678 static void InitBuggyBase(int x, int y)
9680 int element = Feld[x][y];
9681 int activating_delay = FRAMES_PER_SECOND / 4;
9684 (element == EL_SP_BUGGY_BASE ?
9685 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9686 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9688 element == EL_SP_BUGGY_BASE_ACTIVE ?
9689 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9692 static void WarnBuggyBase(int x, int y)
9695 static int xy[4][2] =
9703 for (i = 0; i < NUM_DIRECTIONS; i++)
9705 int xx = x + xy[i][0];
9706 int yy = y + xy[i][1];
9708 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9710 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9717 static void InitTrap(int x, int y)
9719 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9722 static void ActivateTrap(int x, int y)
9724 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9727 static void ChangeActiveTrap(int x, int y)
9729 int graphic = IMG_TRAP_ACTIVE;
9731 // if new animation frame was drawn, correct crumbled sand border
9732 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9733 TEST_DrawLevelFieldCrumbled(x, y);
9736 static int getSpecialActionElement(int element, int number, int base_element)
9738 return (element != EL_EMPTY ? element :
9739 number != -1 ? base_element + number - 1 :
9743 static int getModifiedActionNumber(int value_old, int operator, int operand,
9744 int value_min, int value_max)
9746 int value_new = (operator == CA_MODE_SET ? operand :
9747 operator == CA_MODE_ADD ? value_old + operand :
9748 operator == CA_MODE_SUBTRACT ? value_old - operand :
9749 operator == CA_MODE_MULTIPLY ? value_old * operand :
9750 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9751 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9754 return (value_new < value_min ? value_min :
9755 value_new > value_max ? value_max :
9759 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9761 struct ElementInfo *ei = &element_info[element];
9762 struct ElementChangeInfo *change = &ei->change_page[page];
9763 int target_element = change->target_element;
9764 int action_type = change->action_type;
9765 int action_mode = change->action_mode;
9766 int action_arg = change->action_arg;
9767 int action_element = change->action_element;
9770 if (!change->has_action)
9773 // ---------- determine action paramater values -----------------------------
9775 int level_time_value =
9776 (level.time > 0 ? TimeLeft :
9779 int action_arg_element_raw =
9780 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9781 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9782 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9783 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9784 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9785 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9786 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9788 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9790 int action_arg_direction =
9791 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9792 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9793 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9794 change->actual_trigger_side :
9795 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9796 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9799 int action_arg_number_min =
9800 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9803 int action_arg_number_max =
9804 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9805 action_type == CA_SET_LEVEL_GEMS ? 999 :
9806 action_type == CA_SET_LEVEL_TIME ? 9999 :
9807 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9808 action_type == CA_SET_CE_VALUE ? 9999 :
9809 action_type == CA_SET_CE_SCORE ? 9999 :
9812 int action_arg_number_reset =
9813 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9814 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9815 action_type == CA_SET_LEVEL_TIME ? level.time :
9816 action_type == CA_SET_LEVEL_SCORE ? 0 :
9817 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9818 action_type == CA_SET_CE_SCORE ? 0 :
9821 int action_arg_number =
9822 (action_arg <= CA_ARG_MAX ? action_arg :
9823 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9824 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9825 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9826 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9827 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9828 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9829 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9830 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9831 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9832 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9833 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9834 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9835 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9836 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9837 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9838 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9839 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9840 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9841 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9842 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9843 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9846 int action_arg_number_old =
9847 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9848 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9849 action_type == CA_SET_LEVEL_SCORE ? game.score :
9850 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9851 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9854 int action_arg_number_new =
9855 getModifiedActionNumber(action_arg_number_old,
9856 action_mode, action_arg_number,
9857 action_arg_number_min, action_arg_number_max);
9859 int trigger_player_bits =
9860 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9861 change->actual_trigger_player_bits : change->trigger_player);
9863 int action_arg_player_bits =
9864 (action_arg >= CA_ARG_PLAYER_1 &&
9865 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9866 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9867 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9870 // ---------- execute action -----------------------------------------------
9872 switch (action_type)
9879 // ---------- level actions ----------------------------------------------
9881 case CA_RESTART_LEVEL:
9883 game.restart_level = TRUE;
9888 case CA_SHOW_ENVELOPE:
9890 int element = getSpecialActionElement(action_arg_element,
9891 action_arg_number, EL_ENVELOPE_1);
9893 if (IS_ENVELOPE(element))
9894 local_player->show_envelope = element;
9899 case CA_SET_LEVEL_TIME:
9901 if (level.time > 0) // only modify limited time value
9903 TimeLeft = action_arg_number_new;
9905 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9907 DisplayGameControlValues();
9909 if (!TimeLeft && setup.time_limit)
9910 for (i = 0; i < MAX_PLAYERS; i++)
9911 KillPlayer(&stored_player[i]);
9917 case CA_SET_LEVEL_SCORE:
9919 game.score = action_arg_number_new;
9921 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9923 DisplayGameControlValues();
9928 case CA_SET_LEVEL_GEMS:
9930 game.gems_still_needed = action_arg_number_new;
9932 game.snapshot.collected_item = TRUE;
9934 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9936 DisplayGameControlValues();
9941 case CA_SET_LEVEL_WIND:
9943 game.wind_direction = action_arg_direction;
9948 case CA_SET_LEVEL_RANDOM_SEED:
9950 // ensure that setting a new random seed while playing is predictable
9951 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9956 // ---------- player actions ---------------------------------------------
9958 case CA_MOVE_PLAYER:
9959 case CA_MOVE_PLAYER_NEW:
9961 // automatically move to the next field in specified direction
9962 for (i = 0; i < MAX_PLAYERS; i++)
9963 if (trigger_player_bits & (1 << i))
9964 if (action_type == CA_MOVE_PLAYER ||
9965 stored_player[i].MovPos == 0)
9966 stored_player[i].programmed_action = action_arg_direction;
9971 case CA_EXIT_PLAYER:
9973 for (i = 0; i < MAX_PLAYERS; i++)
9974 if (action_arg_player_bits & (1 << i))
9975 ExitPlayer(&stored_player[i]);
9977 if (game.players_still_needed == 0)
9983 case CA_KILL_PLAYER:
9985 for (i = 0; i < MAX_PLAYERS; i++)
9986 if (action_arg_player_bits & (1 << i))
9987 KillPlayer(&stored_player[i]);
9992 case CA_SET_PLAYER_KEYS:
9994 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9995 int element = getSpecialActionElement(action_arg_element,
9996 action_arg_number, EL_KEY_1);
9998 if (IS_KEY(element))
10000 for (i = 0; i < MAX_PLAYERS; i++)
10002 if (trigger_player_bits & (1 << i))
10004 stored_player[i].key[KEY_NR(element)] = key_state;
10006 DrawGameDoorValues();
10014 case CA_SET_PLAYER_SPEED:
10016 for (i = 0; i < MAX_PLAYERS; i++)
10018 if (trigger_player_bits & (1 << i))
10020 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10022 if (action_arg == CA_ARG_SPEED_FASTER &&
10023 stored_player[i].cannot_move)
10025 action_arg_number = STEPSIZE_VERY_SLOW;
10027 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10028 action_arg == CA_ARG_SPEED_FASTER)
10030 action_arg_number = 2;
10031 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10034 else if (action_arg == CA_ARG_NUMBER_RESET)
10036 action_arg_number = level.initial_player_stepsize[i];
10040 getModifiedActionNumber(move_stepsize,
10043 action_arg_number_min,
10044 action_arg_number_max);
10046 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10053 case CA_SET_PLAYER_SHIELD:
10055 for (i = 0; i < MAX_PLAYERS; i++)
10057 if (trigger_player_bits & (1 << i))
10059 if (action_arg == CA_ARG_SHIELD_OFF)
10061 stored_player[i].shield_normal_time_left = 0;
10062 stored_player[i].shield_deadly_time_left = 0;
10064 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10066 stored_player[i].shield_normal_time_left = 999999;
10068 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10070 stored_player[i].shield_normal_time_left = 999999;
10071 stored_player[i].shield_deadly_time_left = 999999;
10079 case CA_SET_PLAYER_GRAVITY:
10081 for (i = 0; i < MAX_PLAYERS; i++)
10083 if (trigger_player_bits & (1 << i))
10085 stored_player[i].gravity =
10086 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10087 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10088 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10089 stored_player[i].gravity);
10096 case CA_SET_PLAYER_ARTWORK:
10098 for (i = 0; i < MAX_PLAYERS; i++)
10100 if (trigger_player_bits & (1 << i))
10102 int artwork_element = action_arg_element;
10104 if (action_arg == CA_ARG_ELEMENT_RESET)
10106 (level.use_artwork_element[i] ? level.artwork_element[i] :
10107 stored_player[i].element_nr);
10109 if (stored_player[i].artwork_element != artwork_element)
10110 stored_player[i].Frame = 0;
10112 stored_player[i].artwork_element = artwork_element;
10114 SetPlayerWaiting(&stored_player[i], FALSE);
10116 // set number of special actions for bored and sleeping animation
10117 stored_player[i].num_special_action_bored =
10118 get_num_special_action(artwork_element,
10119 ACTION_BORING_1, ACTION_BORING_LAST);
10120 stored_player[i].num_special_action_sleeping =
10121 get_num_special_action(artwork_element,
10122 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10129 case CA_SET_PLAYER_INVENTORY:
10131 for (i = 0; i < MAX_PLAYERS; i++)
10133 struct PlayerInfo *player = &stored_player[i];
10136 if (trigger_player_bits & (1 << i))
10138 int inventory_element = action_arg_element;
10140 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10141 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10142 action_arg == CA_ARG_ELEMENT_ACTION)
10144 int element = inventory_element;
10145 int collect_count = element_info[element].collect_count_initial;
10147 if (!IS_CUSTOM_ELEMENT(element))
10150 if (collect_count == 0)
10151 player->inventory_infinite_element = element;
10153 for (k = 0; k < collect_count; k++)
10154 if (player->inventory_size < MAX_INVENTORY_SIZE)
10155 player->inventory_element[player->inventory_size++] =
10158 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10159 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10160 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10162 if (player->inventory_infinite_element != EL_UNDEFINED &&
10163 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10164 action_arg_element_raw))
10165 player->inventory_infinite_element = EL_UNDEFINED;
10167 for (k = 0, j = 0; j < player->inventory_size; j++)
10169 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10170 action_arg_element_raw))
10171 player->inventory_element[k++] = player->inventory_element[j];
10174 player->inventory_size = k;
10176 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10178 if (player->inventory_size > 0)
10180 for (j = 0; j < player->inventory_size - 1; j++)
10181 player->inventory_element[j] = player->inventory_element[j + 1];
10183 player->inventory_size--;
10186 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10188 if (player->inventory_size > 0)
10189 player->inventory_size--;
10191 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10193 player->inventory_infinite_element = EL_UNDEFINED;
10194 player->inventory_size = 0;
10196 else if (action_arg == CA_ARG_INVENTORY_RESET)
10198 player->inventory_infinite_element = EL_UNDEFINED;
10199 player->inventory_size = 0;
10201 if (level.use_initial_inventory[i])
10203 for (j = 0; j < level.initial_inventory_size[i]; j++)
10205 int element = level.initial_inventory_content[i][j];
10206 int collect_count = element_info[element].collect_count_initial;
10208 if (!IS_CUSTOM_ELEMENT(element))
10211 if (collect_count == 0)
10212 player->inventory_infinite_element = element;
10214 for (k = 0; k < collect_count; k++)
10215 if (player->inventory_size < MAX_INVENTORY_SIZE)
10216 player->inventory_element[player->inventory_size++] =
10227 // ---------- CE actions -------------------------------------------------
10229 case CA_SET_CE_VALUE:
10231 int last_ce_value = CustomValue[x][y];
10233 CustomValue[x][y] = action_arg_number_new;
10235 if (CustomValue[x][y] != last_ce_value)
10237 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10238 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10240 if (CustomValue[x][y] == 0)
10242 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10243 ChangeCount[x][y] = 0; // allow at least one more change
10245 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10246 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10253 case CA_SET_CE_SCORE:
10255 int last_ce_score = ei->collect_score;
10257 ei->collect_score = action_arg_number_new;
10259 if (ei->collect_score != last_ce_score)
10261 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10262 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10264 if (ei->collect_score == 0)
10268 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10269 ChangeCount[x][y] = 0; // allow at least one more change
10271 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10272 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10275 This is a very special case that seems to be a mixture between
10276 CheckElementChange() and CheckTriggeredElementChange(): while
10277 the first one only affects single elements that are triggered
10278 directly, the second one affects multiple elements in the playfield
10279 that are triggered indirectly by another element. This is a third
10280 case: Changing the CE score always affects multiple identical CEs,
10281 so every affected CE must be checked, not only the single CE for
10282 which the CE score was changed in the first place (as every instance
10283 of that CE shares the same CE score, and therefore also can change)!
10285 SCAN_PLAYFIELD(xx, yy)
10287 if (Feld[xx][yy] == element)
10288 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10289 CE_SCORE_GETS_ZERO);
10297 case CA_SET_CE_ARTWORK:
10299 int artwork_element = action_arg_element;
10300 boolean reset_frame = FALSE;
10303 if (action_arg == CA_ARG_ELEMENT_RESET)
10304 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10307 if (ei->gfx_element != artwork_element)
10308 reset_frame = TRUE;
10310 ei->gfx_element = artwork_element;
10312 SCAN_PLAYFIELD(xx, yy)
10314 if (Feld[xx][yy] == element)
10318 ResetGfxAnimation(xx, yy);
10319 ResetRandomAnimationValue(xx, yy);
10322 TEST_DrawLevelField(xx, yy);
10329 // ---------- engine actions ---------------------------------------------
10331 case CA_SET_ENGINE_SCAN_MODE:
10333 InitPlayfieldScanMode(action_arg);
10343 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10345 int old_element = Feld[x][y];
10346 int new_element = GetElementFromGroupElement(element);
10347 int previous_move_direction = MovDir[x][y];
10348 int last_ce_value = CustomValue[x][y];
10349 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10350 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10351 boolean add_player_onto_element = (new_element_is_player &&
10352 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10353 IS_WALKABLE(old_element));
10355 if (!add_player_onto_element)
10357 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10358 RemoveMovingField(x, y);
10362 Feld[x][y] = new_element;
10364 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10365 MovDir[x][y] = previous_move_direction;
10367 if (element_info[new_element].use_last_ce_value)
10368 CustomValue[x][y] = last_ce_value;
10370 InitField_WithBug1(x, y, FALSE);
10372 new_element = Feld[x][y]; // element may have changed
10374 ResetGfxAnimation(x, y);
10375 ResetRandomAnimationValue(x, y);
10377 TEST_DrawLevelField(x, y);
10379 if (GFX_CRUMBLED(new_element))
10380 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10383 // check if element under the player changes from accessible to unaccessible
10384 // (needed for special case of dropping element which then changes)
10385 // (must be checked after creating new element for walkable group elements)
10386 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10387 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10394 // "ChangeCount" not set yet to allow "entered by player" change one time
10395 if (new_element_is_player)
10396 RelocatePlayer(x, y, new_element);
10399 ChangeCount[x][y]++; // count number of changes in the same frame
10401 TestIfBadThingTouchesPlayer(x, y);
10402 TestIfPlayerTouchesCustomElement(x, y);
10403 TestIfElementTouchesCustomElement(x, y);
10406 static void CreateField(int x, int y, int element)
10408 CreateFieldExt(x, y, element, FALSE);
10411 static void CreateElementFromChange(int x, int y, int element)
10413 element = GET_VALID_RUNTIME_ELEMENT(element);
10415 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10417 int old_element = Feld[x][y];
10419 // prevent changed element from moving in same engine frame
10420 // unless both old and new element can either fall or move
10421 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10422 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10426 CreateFieldExt(x, y, element, TRUE);
10429 static boolean ChangeElement(int x, int y, int element, int page)
10431 struct ElementInfo *ei = &element_info[element];
10432 struct ElementChangeInfo *change = &ei->change_page[page];
10433 int ce_value = CustomValue[x][y];
10434 int ce_score = ei->collect_score;
10435 int target_element;
10436 int old_element = Feld[x][y];
10438 // always use default change event to prevent running into a loop
10439 if (ChangeEvent[x][y] == -1)
10440 ChangeEvent[x][y] = CE_DELAY;
10442 if (ChangeEvent[x][y] == CE_DELAY)
10444 // reset actual trigger element, trigger player and action element
10445 change->actual_trigger_element = EL_EMPTY;
10446 change->actual_trigger_player = EL_EMPTY;
10447 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10448 change->actual_trigger_side = CH_SIDE_NONE;
10449 change->actual_trigger_ce_value = 0;
10450 change->actual_trigger_ce_score = 0;
10453 // do not change elements more than a specified maximum number of changes
10454 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10457 ChangeCount[x][y]++; // count number of changes in the same frame
10459 if (change->explode)
10466 if (change->use_target_content)
10468 boolean complete_replace = TRUE;
10469 boolean can_replace[3][3];
10472 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10475 boolean is_walkable;
10476 boolean is_diggable;
10477 boolean is_collectible;
10478 boolean is_removable;
10479 boolean is_destructible;
10480 int ex = x + xx - 1;
10481 int ey = y + yy - 1;
10482 int content_element = change->target_content.e[xx][yy];
10485 can_replace[xx][yy] = TRUE;
10487 if (ex == x && ey == y) // do not check changing element itself
10490 if (content_element == EL_EMPTY_SPACE)
10492 can_replace[xx][yy] = FALSE; // do not replace border with space
10497 if (!IN_LEV_FIELD(ex, ey))
10499 can_replace[xx][yy] = FALSE;
10500 complete_replace = FALSE;
10507 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10508 e = MovingOrBlocked2Element(ex, ey);
10510 is_empty = (IS_FREE(ex, ey) ||
10511 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10513 is_walkable = (is_empty || IS_WALKABLE(e));
10514 is_diggable = (is_empty || IS_DIGGABLE(e));
10515 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10516 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10517 is_removable = (is_diggable || is_collectible);
10519 can_replace[xx][yy] =
10520 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10521 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10522 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10523 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10524 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10525 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10526 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10528 if (!can_replace[xx][yy])
10529 complete_replace = FALSE;
10532 if (!change->only_if_complete || complete_replace)
10534 boolean something_has_changed = FALSE;
10536 if (change->only_if_complete && change->use_random_replace &&
10537 RND(100) < change->random_percentage)
10540 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10542 int ex = x + xx - 1;
10543 int ey = y + yy - 1;
10544 int content_element;
10546 if (can_replace[xx][yy] && (!change->use_random_replace ||
10547 RND(100) < change->random_percentage))
10549 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10550 RemoveMovingField(ex, ey);
10552 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10554 content_element = change->target_content.e[xx][yy];
10555 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10556 ce_value, ce_score);
10558 CreateElementFromChange(ex, ey, target_element);
10560 something_has_changed = TRUE;
10562 // for symmetry reasons, freeze newly created border elements
10563 if (ex != x || ey != y)
10564 Stop[ex][ey] = TRUE; // no more moving in this frame
10568 if (something_has_changed)
10570 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10571 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10577 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10578 ce_value, ce_score);
10580 if (element == EL_DIAGONAL_GROWING ||
10581 element == EL_DIAGONAL_SHRINKING)
10583 target_element = Store[x][y];
10585 Store[x][y] = EL_EMPTY;
10588 CreateElementFromChange(x, y, target_element);
10590 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10591 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10594 // this uses direct change before indirect change
10595 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10600 static void HandleElementChange(int x, int y, int page)
10602 int element = MovingOrBlocked2Element(x, y);
10603 struct ElementInfo *ei = &element_info[element];
10604 struct ElementChangeInfo *change = &ei->change_page[page];
10605 boolean handle_action_before_change = FALSE;
10608 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10609 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10612 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10613 x, y, element, element_info[element].token_name);
10614 printf("HandleElementChange(): This should never happen!\n");
10619 // this can happen with classic bombs on walkable, changing elements
10620 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10625 if (ChangeDelay[x][y] == 0) // initialize element change
10627 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10629 if (change->can_change)
10631 // !!! not clear why graphic animation should be reset at all here !!!
10632 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10633 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10636 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10638 When using an animation frame delay of 1 (this only happens with
10639 "sp_zonk.moving.left/right" in the classic graphics), the default
10640 (non-moving) animation shows wrong animation frames (while the
10641 moving animation, like "sp_zonk.moving.left/right", is correct,
10642 so this graphical bug never shows up with the classic graphics).
10643 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10644 be drawn instead of the correct frames 0,1,2,3. This is caused by
10645 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10646 an element change: First when the change delay ("ChangeDelay[][]")
10647 counter has reached zero after decrementing, then a second time in
10648 the next frame (after "GfxFrame[][]" was already incremented) when
10649 "ChangeDelay[][]" is reset to the initial delay value again.
10651 This causes frame 0 to be drawn twice, while the last frame won't
10652 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10654 As some animations may already be cleverly designed around this bug
10655 (at least the "Snake Bite" snake tail animation does this), it cannot
10656 simply be fixed here without breaking such existing animations.
10657 Unfortunately, it cannot easily be detected if a graphics set was
10658 designed "before" or "after" the bug was fixed. As a workaround,
10659 a new graphics set option "game.graphics_engine_version" was added
10660 to be able to specify the game's major release version for which the
10661 graphics set was designed, which can then be used to decide if the
10662 bugfix should be used (version 4 and above) or not (version 3 or
10663 below, or if no version was specified at all, as with old sets).
10665 (The wrong/fixed animation frames can be tested with the test level set
10666 "test_gfxframe" and level "000", which contains a specially prepared
10667 custom element at level position (x/y) == (11/9) which uses the zonk
10668 animation mentioned above. Using "game.graphics_engine_version: 4"
10669 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10670 This can also be seen from the debug output for this test element.)
10673 // when a custom element is about to change (for example by change delay),
10674 // do not reset graphic animation when the custom element is moving
10675 if (game.graphics_engine_version < 4 &&
10678 ResetGfxAnimation(x, y);
10679 ResetRandomAnimationValue(x, y);
10682 if (change->pre_change_function)
10683 change->pre_change_function(x, y);
10687 ChangeDelay[x][y]--;
10689 if (ChangeDelay[x][y] != 0) // continue element change
10691 if (change->can_change)
10693 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10695 if (IS_ANIMATED(graphic))
10696 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10698 if (change->change_function)
10699 change->change_function(x, y);
10702 else // finish element change
10704 if (ChangePage[x][y] != -1) // remember page from delayed change
10706 page = ChangePage[x][y];
10707 ChangePage[x][y] = -1;
10709 change = &ei->change_page[page];
10712 if (IS_MOVING(x, y)) // never change a running system ;-)
10714 ChangeDelay[x][y] = 1; // try change after next move step
10715 ChangePage[x][y] = page; // remember page to use for change
10720 // special case: set new level random seed before changing element
10721 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10722 handle_action_before_change = TRUE;
10724 if (change->has_action && handle_action_before_change)
10725 ExecuteCustomElementAction(x, y, element, page);
10727 if (change->can_change)
10729 if (ChangeElement(x, y, element, page))
10731 if (change->post_change_function)
10732 change->post_change_function(x, y);
10736 if (change->has_action && !handle_action_before_change)
10737 ExecuteCustomElementAction(x, y, element, page);
10741 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10742 int trigger_element,
10744 int trigger_player,
10748 boolean change_done_any = FALSE;
10749 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10752 if (!(trigger_events[trigger_element][trigger_event]))
10755 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10757 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10759 int element = EL_CUSTOM_START + i;
10760 boolean change_done = FALSE;
10763 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10764 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10767 for (p = 0; p < element_info[element].num_change_pages; p++)
10769 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10771 if (change->can_change_or_has_action &&
10772 change->has_event[trigger_event] &&
10773 change->trigger_side & trigger_side &&
10774 change->trigger_player & trigger_player &&
10775 change->trigger_page & trigger_page_bits &&
10776 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10778 change->actual_trigger_element = trigger_element;
10779 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10780 change->actual_trigger_player_bits = trigger_player;
10781 change->actual_trigger_side = trigger_side;
10782 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10783 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10785 if ((change->can_change && !change_done) || change->has_action)
10789 SCAN_PLAYFIELD(x, y)
10791 if (Feld[x][y] == element)
10793 if (change->can_change && !change_done)
10795 // if element already changed in this frame, not only prevent
10796 // another element change (checked in ChangeElement()), but
10797 // also prevent additional element actions for this element
10799 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10800 !level.use_action_after_change_bug)
10803 ChangeDelay[x][y] = 1;
10804 ChangeEvent[x][y] = trigger_event;
10806 HandleElementChange(x, y, p);
10808 else if (change->has_action)
10810 // if element already changed in this frame, not only prevent
10811 // another element change (checked in ChangeElement()), but
10812 // also prevent additional element actions for this element
10814 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10815 !level.use_action_after_change_bug)
10818 ExecuteCustomElementAction(x, y, element, p);
10819 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10824 if (change->can_change)
10826 change_done = TRUE;
10827 change_done_any = TRUE;
10834 RECURSION_LOOP_DETECTION_END();
10836 return change_done_any;
10839 static boolean CheckElementChangeExt(int x, int y,
10841 int trigger_element,
10843 int trigger_player,
10846 boolean change_done = FALSE;
10849 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10850 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10853 if (Feld[x][y] == EL_BLOCKED)
10855 Blocked2Moving(x, y, &x, &y);
10856 element = Feld[x][y];
10859 // check if element has already changed or is about to change after moving
10860 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10861 Feld[x][y] != element) ||
10863 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10864 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10865 ChangePage[x][y] != -1)))
10868 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10870 for (p = 0; p < element_info[element].num_change_pages; p++)
10872 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10874 /* check trigger element for all events where the element that is checked
10875 for changing interacts with a directly adjacent element -- this is
10876 different to element changes that affect other elements to change on the
10877 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10878 boolean check_trigger_element =
10879 (trigger_event == CE_TOUCHING_X ||
10880 trigger_event == CE_HITTING_X ||
10881 trigger_event == CE_HIT_BY_X ||
10882 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10884 if (change->can_change_or_has_action &&
10885 change->has_event[trigger_event] &&
10886 change->trigger_side & trigger_side &&
10887 change->trigger_player & trigger_player &&
10888 (!check_trigger_element ||
10889 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10891 change->actual_trigger_element = trigger_element;
10892 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10893 change->actual_trigger_player_bits = trigger_player;
10894 change->actual_trigger_side = trigger_side;
10895 change->actual_trigger_ce_value = CustomValue[x][y];
10896 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10898 // special case: trigger element not at (x,y) position for some events
10899 if (check_trigger_element)
10911 { 0, 0 }, { 0, 0 }, { 0, 0 },
10915 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10916 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10918 change->actual_trigger_ce_value = CustomValue[xx][yy];
10919 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10922 if (change->can_change && !change_done)
10924 ChangeDelay[x][y] = 1;
10925 ChangeEvent[x][y] = trigger_event;
10927 HandleElementChange(x, y, p);
10929 change_done = TRUE;
10931 else if (change->has_action)
10933 ExecuteCustomElementAction(x, y, element, p);
10934 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10939 RECURSION_LOOP_DETECTION_END();
10941 return change_done;
10944 static void PlayPlayerSound(struct PlayerInfo *player)
10946 int jx = player->jx, jy = player->jy;
10947 int sound_element = player->artwork_element;
10948 int last_action = player->last_action_waiting;
10949 int action = player->action_waiting;
10951 if (player->is_waiting)
10953 if (action != last_action)
10954 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10956 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10960 if (action != last_action)
10961 StopSound(element_info[sound_element].sound[last_action]);
10963 if (last_action == ACTION_SLEEPING)
10964 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10968 static void PlayAllPlayersSound(void)
10972 for (i = 0; i < MAX_PLAYERS; i++)
10973 if (stored_player[i].active)
10974 PlayPlayerSound(&stored_player[i]);
10977 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10979 boolean last_waiting = player->is_waiting;
10980 int move_dir = player->MovDir;
10982 player->dir_waiting = move_dir;
10983 player->last_action_waiting = player->action_waiting;
10987 if (!last_waiting) // not waiting -> waiting
10989 player->is_waiting = TRUE;
10991 player->frame_counter_bored =
10993 game.player_boring_delay_fixed +
10994 GetSimpleRandom(game.player_boring_delay_random);
10995 player->frame_counter_sleeping =
10997 game.player_sleeping_delay_fixed +
10998 GetSimpleRandom(game.player_sleeping_delay_random);
11000 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11003 if (game.player_sleeping_delay_fixed +
11004 game.player_sleeping_delay_random > 0 &&
11005 player->anim_delay_counter == 0 &&
11006 player->post_delay_counter == 0 &&
11007 FrameCounter >= player->frame_counter_sleeping)
11008 player->is_sleeping = TRUE;
11009 else if (game.player_boring_delay_fixed +
11010 game.player_boring_delay_random > 0 &&
11011 FrameCounter >= player->frame_counter_bored)
11012 player->is_bored = TRUE;
11014 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11015 player->is_bored ? ACTION_BORING :
11018 if (player->is_sleeping && player->use_murphy)
11020 // special case for sleeping Murphy when leaning against non-free tile
11022 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11023 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
11024 !IS_MOVING(player->jx - 1, player->jy)))
11025 move_dir = MV_LEFT;
11026 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11027 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
11028 !IS_MOVING(player->jx + 1, player->jy)))
11029 move_dir = MV_RIGHT;
11031 player->is_sleeping = FALSE;
11033 player->dir_waiting = move_dir;
11036 if (player->is_sleeping)
11038 if (player->num_special_action_sleeping > 0)
11040 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11042 int last_special_action = player->special_action_sleeping;
11043 int num_special_action = player->num_special_action_sleeping;
11044 int special_action =
11045 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11046 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11047 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11048 last_special_action + 1 : ACTION_SLEEPING);
11049 int special_graphic =
11050 el_act_dir2img(player->artwork_element, special_action, move_dir);
11052 player->anim_delay_counter =
11053 graphic_info[special_graphic].anim_delay_fixed +
11054 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11055 player->post_delay_counter =
11056 graphic_info[special_graphic].post_delay_fixed +
11057 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11059 player->special_action_sleeping = special_action;
11062 if (player->anim_delay_counter > 0)
11064 player->action_waiting = player->special_action_sleeping;
11065 player->anim_delay_counter--;
11067 else if (player->post_delay_counter > 0)
11069 player->post_delay_counter--;
11073 else if (player->is_bored)
11075 if (player->num_special_action_bored > 0)
11077 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11079 int special_action =
11080 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11081 int special_graphic =
11082 el_act_dir2img(player->artwork_element, special_action, move_dir);
11084 player->anim_delay_counter =
11085 graphic_info[special_graphic].anim_delay_fixed +
11086 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11087 player->post_delay_counter =
11088 graphic_info[special_graphic].post_delay_fixed +
11089 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11091 player->special_action_bored = special_action;
11094 if (player->anim_delay_counter > 0)
11096 player->action_waiting = player->special_action_bored;
11097 player->anim_delay_counter--;
11099 else if (player->post_delay_counter > 0)
11101 player->post_delay_counter--;
11106 else if (last_waiting) // waiting -> not waiting
11108 player->is_waiting = FALSE;
11109 player->is_bored = FALSE;
11110 player->is_sleeping = FALSE;
11112 player->frame_counter_bored = -1;
11113 player->frame_counter_sleeping = -1;
11115 player->anim_delay_counter = 0;
11116 player->post_delay_counter = 0;
11118 player->dir_waiting = player->MovDir;
11119 player->action_waiting = ACTION_DEFAULT;
11121 player->special_action_bored = ACTION_DEFAULT;
11122 player->special_action_sleeping = ACTION_DEFAULT;
11126 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11128 if ((!player->is_moving && player->was_moving) ||
11129 (player->MovPos == 0 && player->was_moving) ||
11130 (player->is_snapping && !player->was_snapping) ||
11131 (player->is_dropping && !player->was_dropping))
11133 if (!CheckSaveEngineSnapshotToList())
11136 player->was_moving = FALSE;
11137 player->was_snapping = TRUE;
11138 player->was_dropping = TRUE;
11142 if (player->is_moving)
11143 player->was_moving = TRUE;
11145 if (!player->is_snapping)
11146 player->was_snapping = FALSE;
11148 if (!player->is_dropping)
11149 player->was_dropping = FALSE;
11153 static void CheckSingleStepMode(struct PlayerInfo *player)
11155 if (tape.single_step && tape.recording && !tape.pausing)
11157 /* as it is called "single step mode", just return to pause mode when the
11158 player stopped moving after one tile (or never starts moving at all) */
11159 if (!player->is_moving &&
11160 !player->is_pushing &&
11161 !player->is_dropping_pressed)
11162 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11165 CheckSaveEngineSnapshot(player);
11168 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11170 int left = player_action & JOY_LEFT;
11171 int right = player_action & JOY_RIGHT;
11172 int up = player_action & JOY_UP;
11173 int down = player_action & JOY_DOWN;
11174 int button1 = player_action & JOY_BUTTON_1;
11175 int button2 = player_action & JOY_BUTTON_2;
11176 int dx = (left ? -1 : right ? 1 : 0);
11177 int dy = (up ? -1 : down ? 1 : 0);
11179 if (!player->active || tape.pausing)
11185 SnapField(player, dx, dy);
11189 DropElement(player);
11191 MovePlayer(player, dx, dy);
11194 CheckSingleStepMode(player);
11196 SetPlayerWaiting(player, FALSE);
11198 return player_action;
11202 // no actions for this player (no input at player's configured device)
11204 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11205 SnapField(player, 0, 0);
11206 CheckGravityMovementWhenNotMoving(player);
11208 if (player->MovPos == 0)
11209 SetPlayerWaiting(player, TRUE);
11211 if (player->MovPos == 0) // needed for tape.playing
11212 player->is_moving = FALSE;
11214 player->is_dropping = FALSE;
11215 player->is_dropping_pressed = FALSE;
11216 player->drop_pressed_delay = 0;
11218 CheckSingleStepMode(player);
11224 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11227 if (!tape.use_mouse_actions)
11230 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11231 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11232 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11235 static void SetTapeActionFromMouseAction(byte *tape_action,
11236 struct MouseActionInfo *mouse_action)
11238 if (!tape.use_mouse_actions)
11241 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11242 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11243 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11246 static void CheckLevelSolved(void)
11248 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11250 if (game_em.level_solved &&
11251 !game_em.game_over) // game won
11255 game_em.game_over = TRUE;
11257 game.all_players_gone = TRUE;
11260 if (game_em.game_over) // game lost
11261 game.all_players_gone = TRUE;
11263 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11265 if (game_sp.level_solved &&
11266 !game_sp.game_over) // game won
11270 game_sp.game_over = TRUE;
11272 game.all_players_gone = TRUE;
11275 if (game_sp.game_over) // game lost
11276 game.all_players_gone = TRUE;
11278 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11280 if (game_mm.level_solved &&
11281 !game_mm.game_over) // game won
11285 game_mm.game_over = TRUE;
11287 game.all_players_gone = TRUE;
11290 if (game_mm.game_over) // game lost
11291 game.all_players_gone = TRUE;
11295 static void CheckLevelTime(void)
11299 if (TimeFrames >= FRAMES_PER_SECOND)
11304 for (i = 0; i < MAX_PLAYERS; i++)
11306 struct PlayerInfo *player = &stored_player[i];
11308 if (SHIELD_ON(player))
11310 player->shield_normal_time_left--;
11312 if (player->shield_deadly_time_left > 0)
11313 player->shield_deadly_time_left--;
11317 if (!game.LevelSolved && !level.use_step_counter)
11325 if (TimeLeft <= 10 && setup.time_limit)
11326 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11328 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11329 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11331 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11333 if (!TimeLeft && setup.time_limit)
11335 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11336 game_em.lev->killed_out_of_time = TRUE;
11338 for (i = 0; i < MAX_PLAYERS; i++)
11339 KillPlayer(&stored_player[i]);
11342 else if (game.no_time_limit && !game.all_players_gone)
11344 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11347 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11350 if (tape.recording || tape.playing)
11351 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11354 if (tape.recording || tape.playing)
11355 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11357 UpdateAndDisplayGameControlValues();
11360 void AdvanceFrameAndPlayerCounters(int player_nr)
11364 // advance frame counters (global frame counter and time frame counter)
11368 // advance player counters (counters for move delay, move animation etc.)
11369 for (i = 0; i < MAX_PLAYERS; i++)
11371 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11372 int move_delay_value = stored_player[i].move_delay_value;
11373 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11375 if (!advance_player_counters) // not all players may be affected
11378 if (move_frames == 0) // less than one move per game frame
11380 int stepsize = TILEX / move_delay_value;
11381 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11382 int count = (stored_player[i].is_moving ?
11383 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11385 if (count % delay == 0)
11389 stored_player[i].Frame += move_frames;
11391 if (stored_player[i].MovPos != 0)
11392 stored_player[i].StepFrame += move_frames;
11394 if (stored_player[i].move_delay > 0)
11395 stored_player[i].move_delay--;
11397 // due to bugs in previous versions, counter must count up, not down
11398 if (stored_player[i].push_delay != -1)
11399 stored_player[i].push_delay++;
11401 if (stored_player[i].drop_delay > 0)
11402 stored_player[i].drop_delay--;
11404 if (stored_player[i].is_dropping_pressed)
11405 stored_player[i].drop_pressed_delay++;
11409 void StartGameActions(boolean init_network_game, boolean record_tape,
11412 unsigned int new_random_seed = InitRND(random_seed);
11415 TapeStartRecording(new_random_seed);
11417 if (init_network_game)
11419 SendToServer_LevelFile();
11420 SendToServer_StartPlaying();
11428 static void GameActionsExt(void)
11431 static unsigned int game_frame_delay = 0;
11433 unsigned int game_frame_delay_value;
11434 byte *recorded_player_action;
11435 byte summarized_player_action = 0;
11436 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11439 // detect endless loops, caused by custom element programming
11440 if (recursion_loop_detected && recursion_loop_depth == 0)
11442 char *message = getStringCat3("Internal Error! Element ",
11443 EL_NAME(recursion_loop_element),
11444 " caused endless loop! Quit the game?");
11446 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11447 EL_NAME(recursion_loop_element));
11449 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11451 recursion_loop_detected = FALSE; // if game should be continued
11458 if (game.restart_level)
11459 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11461 CheckLevelSolved();
11463 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11466 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11469 if (game_status != GAME_MODE_PLAYING) // status might have changed
11472 game_frame_delay_value =
11473 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11475 if (tape.playing && tape.warp_forward && !tape.pausing)
11476 game_frame_delay_value = 0;
11478 SetVideoFrameDelay(game_frame_delay_value);
11480 // (de)activate virtual buttons depending on current game status
11481 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11483 if (game.all_players_gone) // if no players there to be controlled anymore
11484 SetOverlayActive(FALSE);
11485 else if (!tape.playing) // if game continues after tape stopped playing
11486 SetOverlayActive(TRUE);
11491 // ---------- main game synchronization point ----------
11493 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11495 printf("::: skip == %d\n", skip);
11498 // ---------- main game synchronization point ----------
11500 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11504 if (network_playing && !network_player_action_received)
11506 // try to get network player actions in time
11508 // last chance to get network player actions without main loop delay
11509 HandleNetworking();
11511 // game was quit by network peer
11512 if (game_status != GAME_MODE_PLAYING)
11515 // check if network player actions still missing and game still running
11516 if (!network_player_action_received && !checkGameEnded())
11517 return; // failed to get network player actions in time
11519 // do not yet reset "network_player_action_received" (for tape.pausing)
11525 // at this point we know that we really continue executing the game
11527 network_player_action_received = FALSE;
11529 // when playing tape, read previously recorded player input from tape data
11530 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11532 local_player->effective_mouse_action = local_player->mouse_action;
11534 if (recorded_player_action != NULL)
11535 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11536 recorded_player_action);
11538 // TapePlayAction() may return NULL when toggling to "pause before death"
11542 if (tape.set_centered_player)
11544 game.centered_player_nr_next = tape.centered_player_nr_next;
11545 game.set_centered_player = TRUE;
11548 for (i = 0; i < MAX_PLAYERS; i++)
11550 summarized_player_action |= stored_player[i].action;
11552 if (!network_playing && (game.team_mode || tape.playing))
11553 stored_player[i].effective_action = stored_player[i].action;
11556 if (network_playing && !checkGameEnded())
11557 SendToServer_MovePlayer(summarized_player_action);
11559 // summarize all actions at local players mapped input device position
11560 // (this allows using different input devices in single player mode)
11561 if (!network.enabled && !game.team_mode)
11562 stored_player[map_player_action[local_player->index_nr]].effective_action =
11563 summarized_player_action;
11565 // summarize all actions at centered player in local team mode
11566 if (tape.recording &&
11567 setup.team_mode && !network.enabled &&
11568 setup.input_on_focus &&
11569 game.centered_player_nr != -1)
11571 for (i = 0; i < MAX_PLAYERS; i++)
11572 stored_player[map_player_action[i]].effective_action =
11573 (i == game.centered_player_nr ? summarized_player_action : 0);
11576 if (recorded_player_action != NULL)
11577 for (i = 0; i < MAX_PLAYERS; i++)
11578 stored_player[i].effective_action = recorded_player_action[i];
11580 for (i = 0; i < MAX_PLAYERS; i++)
11582 tape_action[i] = stored_player[i].effective_action;
11584 /* (this may happen in the RND game engine if a player was not present on
11585 the playfield on level start, but appeared later from a custom element */
11586 if (setup.team_mode &&
11589 !tape.player_participates[i])
11590 tape.player_participates[i] = TRUE;
11593 SetTapeActionFromMouseAction(tape_action,
11594 &local_player->effective_mouse_action);
11596 // only record actions from input devices, but not programmed actions
11597 if (tape.recording)
11598 TapeRecordAction(tape_action);
11600 // remember if game was played (especially after tape stopped playing)
11601 if (!tape.playing && summarized_player_action)
11602 game.GamePlayed = TRUE;
11604 #if USE_NEW_PLAYER_ASSIGNMENTS
11605 // !!! also map player actions in single player mode !!!
11606 // if (game.team_mode)
11609 byte mapped_action[MAX_PLAYERS];
11611 #if DEBUG_PLAYER_ACTIONS
11613 for (i = 0; i < MAX_PLAYERS; i++)
11614 printf(" %d, ", stored_player[i].effective_action);
11617 for (i = 0; i < MAX_PLAYERS; i++)
11618 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11620 for (i = 0; i < MAX_PLAYERS; i++)
11621 stored_player[i].effective_action = mapped_action[i];
11623 #if DEBUG_PLAYER_ACTIONS
11625 for (i = 0; i < MAX_PLAYERS; i++)
11626 printf(" %d, ", stored_player[i].effective_action);
11630 #if DEBUG_PLAYER_ACTIONS
11634 for (i = 0; i < MAX_PLAYERS; i++)
11635 printf(" %d, ", stored_player[i].effective_action);
11641 for (i = 0; i < MAX_PLAYERS; i++)
11643 // allow engine snapshot in case of changed movement attempt
11644 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11645 (stored_player[i].effective_action & KEY_MOTION))
11646 game.snapshot.changed_action = TRUE;
11648 // allow engine snapshot in case of snapping/dropping attempt
11649 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11650 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11651 game.snapshot.changed_action = TRUE;
11653 game.snapshot.last_action[i] = stored_player[i].effective_action;
11656 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11658 GameActions_EM_Main();
11660 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11662 GameActions_SP_Main();
11664 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11666 GameActions_MM_Main();
11670 GameActions_RND_Main();
11673 BlitScreenToBitmap(backbuffer);
11675 CheckLevelSolved();
11678 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11680 if (global.show_frames_per_second)
11682 static unsigned int fps_counter = 0;
11683 static int fps_frames = 0;
11684 unsigned int fps_delay_ms = Counter() - fps_counter;
11688 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11690 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11693 fps_counter = Counter();
11695 // always draw FPS to screen after FPS value was updated
11696 redraw_mask |= REDRAW_FPS;
11699 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11700 if (GetDrawDeactivationMask() == REDRAW_NONE)
11701 redraw_mask |= REDRAW_FPS;
11705 static void GameActions_CheckSaveEngineSnapshot(void)
11707 if (!game.snapshot.save_snapshot)
11710 // clear flag for saving snapshot _before_ saving snapshot
11711 game.snapshot.save_snapshot = FALSE;
11713 SaveEngineSnapshotToList();
11716 void GameActions(void)
11720 GameActions_CheckSaveEngineSnapshot();
11723 void GameActions_EM_Main(void)
11725 byte effective_action[MAX_PLAYERS];
11726 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11729 for (i = 0; i < MAX_PLAYERS; i++)
11730 effective_action[i] = stored_player[i].effective_action;
11732 GameActions_EM(effective_action, warp_mode);
11735 void GameActions_SP_Main(void)
11737 byte effective_action[MAX_PLAYERS];
11738 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11741 for (i = 0; i < MAX_PLAYERS; i++)
11742 effective_action[i] = stored_player[i].effective_action;
11744 GameActions_SP(effective_action, warp_mode);
11746 for (i = 0; i < MAX_PLAYERS; i++)
11748 if (stored_player[i].force_dropping)
11749 stored_player[i].action |= KEY_BUTTON_DROP;
11751 stored_player[i].force_dropping = FALSE;
11755 void GameActions_MM_Main(void)
11757 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11759 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11762 void GameActions_RND_Main(void)
11767 void GameActions_RND(void)
11769 static struct MouseActionInfo mouse_action_last = { 0 };
11770 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11771 int magic_wall_x = 0, magic_wall_y = 0;
11772 int i, x, y, element, graphic, last_gfx_frame;
11774 InitPlayfieldScanModeVars();
11776 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11778 SCAN_PLAYFIELD(x, y)
11780 ChangeCount[x][y] = 0;
11781 ChangeEvent[x][y] = -1;
11785 if (game.set_centered_player)
11787 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11789 // switching to "all players" only possible if all players fit to screen
11790 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11792 game.centered_player_nr_next = game.centered_player_nr;
11793 game.set_centered_player = FALSE;
11796 // do not switch focus to non-existing (or non-active) player
11797 if (game.centered_player_nr_next >= 0 &&
11798 !stored_player[game.centered_player_nr_next].active)
11800 game.centered_player_nr_next = game.centered_player_nr;
11801 game.set_centered_player = FALSE;
11805 if (game.set_centered_player &&
11806 ScreenMovPos == 0) // screen currently aligned at tile position
11810 if (game.centered_player_nr_next == -1)
11812 setScreenCenteredToAllPlayers(&sx, &sy);
11816 sx = stored_player[game.centered_player_nr_next].jx;
11817 sy = stored_player[game.centered_player_nr_next].jy;
11820 game.centered_player_nr = game.centered_player_nr_next;
11821 game.set_centered_player = FALSE;
11823 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11824 DrawGameDoorValues();
11827 for (i = 0; i < MAX_PLAYERS; i++)
11829 int actual_player_action = stored_player[i].effective_action;
11832 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11833 - rnd_equinox_tetrachloride 048
11834 - rnd_equinox_tetrachloride_ii 096
11835 - rnd_emanuel_schmieg 002
11836 - doctor_sloan_ww 001, 020
11838 if (stored_player[i].MovPos == 0)
11839 CheckGravityMovement(&stored_player[i]);
11842 // overwrite programmed action with tape action
11843 if (stored_player[i].programmed_action)
11844 actual_player_action = stored_player[i].programmed_action;
11846 PlayerActions(&stored_player[i], actual_player_action);
11848 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11851 ScrollScreen(NULL, SCROLL_GO_ON);
11853 /* for backwards compatibility, the following code emulates a fixed bug that
11854 occured when pushing elements (causing elements that just made their last
11855 pushing step to already (if possible) make their first falling step in the
11856 same game frame, which is bad); this code is also needed to use the famous
11857 "spring push bug" which is used in older levels and might be wanted to be
11858 used also in newer levels, but in this case the buggy pushing code is only
11859 affecting the "spring" element and no other elements */
11861 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11863 for (i = 0; i < MAX_PLAYERS; i++)
11865 struct PlayerInfo *player = &stored_player[i];
11866 int x = player->jx;
11867 int y = player->jy;
11869 if (player->active && player->is_pushing && player->is_moving &&
11871 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11872 Feld[x][y] == EL_SPRING))
11874 ContinueMoving(x, y);
11876 // continue moving after pushing (this is actually a bug)
11877 if (!IS_MOVING(x, y))
11878 Stop[x][y] = FALSE;
11883 SCAN_PLAYFIELD(x, y)
11885 Last[x][y] = Feld[x][y];
11887 ChangeCount[x][y] = 0;
11888 ChangeEvent[x][y] = -1;
11890 // this must be handled before main playfield loop
11891 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11894 if (MovDelay[x][y] <= 0)
11898 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11901 if (MovDelay[x][y] <= 0)
11904 TEST_DrawLevelField(x, y);
11906 TestIfElementTouchesCustomElement(x, y); // for empty space
11911 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11913 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11914 printf("GameActions(): This should never happen!\n");
11916 ChangePage[x][y] = -1;
11920 Stop[x][y] = FALSE;
11921 if (WasJustMoving[x][y] > 0)
11922 WasJustMoving[x][y]--;
11923 if (WasJustFalling[x][y] > 0)
11924 WasJustFalling[x][y]--;
11925 if (CheckCollision[x][y] > 0)
11926 CheckCollision[x][y]--;
11927 if (CheckImpact[x][y] > 0)
11928 CheckImpact[x][y]--;
11932 /* reset finished pushing action (not done in ContinueMoving() to allow
11933 continuous pushing animation for elements with zero push delay) */
11934 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11936 ResetGfxAnimation(x, y);
11937 TEST_DrawLevelField(x, y);
11941 if (IS_BLOCKED(x, y))
11945 Blocked2Moving(x, y, &oldx, &oldy);
11946 if (!IS_MOVING(oldx, oldy))
11948 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11949 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11950 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11951 printf("GameActions(): This should never happen!\n");
11957 if (mouse_action.button)
11959 int new_button = (mouse_action.button && mouse_action_last.button == 0);
11961 x = mouse_action.lx;
11962 y = mouse_action.ly;
11963 element = Feld[x][y];
11967 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
11968 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
11971 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
11972 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
11975 SCAN_PLAYFIELD(x, y)
11977 element = Feld[x][y];
11978 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11979 last_gfx_frame = GfxFrame[x][y];
11981 ResetGfxFrame(x, y);
11983 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11984 DrawLevelGraphicAnimation(x, y, graphic);
11986 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11987 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11988 ResetRandomAnimationValue(x, y);
11990 SetRandomAnimationValue(x, y);
11992 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11994 if (IS_INACTIVE(element))
11996 if (IS_ANIMATED(graphic))
11997 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12002 // this may take place after moving, so 'element' may have changed
12003 if (IS_CHANGING(x, y) &&
12004 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12006 int page = element_info[element].event_page_nr[CE_DELAY];
12008 HandleElementChange(x, y, page);
12010 element = Feld[x][y];
12011 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12014 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12018 element = Feld[x][y];
12019 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12021 if (IS_ANIMATED(graphic) &&
12022 !IS_MOVING(x, y) &&
12024 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12026 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12027 TEST_DrawTwinkleOnField(x, y);
12029 else if (element == EL_ACID)
12032 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12034 else if ((element == EL_EXIT_OPEN ||
12035 element == EL_EM_EXIT_OPEN ||
12036 element == EL_SP_EXIT_OPEN ||
12037 element == EL_STEEL_EXIT_OPEN ||
12038 element == EL_EM_STEEL_EXIT_OPEN ||
12039 element == EL_SP_TERMINAL ||
12040 element == EL_SP_TERMINAL_ACTIVE ||
12041 element == EL_EXTRA_TIME ||
12042 element == EL_SHIELD_NORMAL ||
12043 element == EL_SHIELD_DEADLY) &&
12044 IS_ANIMATED(graphic))
12045 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12046 else if (IS_MOVING(x, y))
12047 ContinueMoving(x, y);
12048 else if (IS_ACTIVE_BOMB(element))
12049 CheckDynamite(x, y);
12050 else if (element == EL_AMOEBA_GROWING)
12051 AmoebeWaechst(x, y);
12052 else if (element == EL_AMOEBA_SHRINKING)
12053 AmoebaDisappearing(x, y);
12055 #if !USE_NEW_AMOEBA_CODE
12056 else if (IS_AMOEBALIVE(element))
12057 AmoebeAbleger(x, y);
12060 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12062 else if (element == EL_EXIT_CLOSED)
12064 else if (element == EL_EM_EXIT_CLOSED)
12066 else if (element == EL_STEEL_EXIT_CLOSED)
12067 CheckExitSteel(x, y);
12068 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12069 CheckExitSteelEM(x, y);
12070 else if (element == EL_SP_EXIT_CLOSED)
12072 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12073 element == EL_EXPANDABLE_STEELWALL_GROWING)
12074 MauerWaechst(x, y);
12075 else if (element == EL_EXPANDABLE_WALL ||
12076 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12077 element == EL_EXPANDABLE_WALL_VERTICAL ||
12078 element == EL_EXPANDABLE_WALL_ANY ||
12079 element == EL_BD_EXPANDABLE_WALL)
12080 MauerAbleger(x, y);
12081 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12082 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12083 element == EL_EXPANDABLE_STEELWALL_ANY)
12084 MauerAblegerStahl(x, y);
12085 else if (element == EL_FLAMES)
12086 CheckForDragon(x, y);
12087 else if (element == EL_EXPLOSION)
12088 ; // drawing of correct explosion animation is handled separately
12089 else if (element == EL_ELEMENT_SNAPPING ||
12090 element == EL_DIAGONAL_SHRINKING ||
12091 element == EL_DIAGONAL_GROWING)
12093 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12095 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12097 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12098 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12100 if (IS_BELT_ACTIVE(element))
12101 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12103 if (game.magic_wall_active)
12105 int jx = local_player->jx, jy = local_player->jy;
12107 // play the element sound at the position nearest to the player
12108 if ((element == EL_MAGIC_WALL_FULL ||
12109 element == EL_MAGIC_WALL_ACTIVE ||
12110 element == EL_MAGIC_WALL_EMPTYING ||
12111 element == EL_BD_MAGIC_WALL_FULL ||
12112 element == EL_BD_MAGIC_WALL_ACTIVE ||
12113 element == EL_BD_MAGIC_WALL_EMPTYING ||
12114 element == EL_DC_MAGIC_WALL_FULL ||
12115 element == EL_DC_MAGIC_WALL_ACTIVE ||
12116 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12117 ABS(x - jx) + ABS(y - jy) <
12118 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12126 #if USE_NEW_AMOEBA_CODE
12127 // new experimental amoeba growth stuff
12128 if (!(FrameCounter % 8))
12130 static unsigned int random = 1684108901;
12132 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12134 x = RND(lev_fieldx);
12135 y = RND(lev_fieldy);
12136 element = Feld[x][y];
12138 if (!IS_PLAYER(x,y) &&
12139 (element == EL_EMPTY ||
12140 CAN_GROW_INTO(element) ||
12141 element == EL_QUICKSAND_EMPTY ||
12142 element == EL_QUICKSAND_FAST_EMPTY ||
12143 element == EL_ACID_SPLASH_LEFT ||
12144 element == EL_ACID_SPLASH_RIGHT))
12146 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12147 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12148 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12149 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12150 Feld[x][y] = EL_AMOEBA_DROP;
12153 random = random * 129 + 1;
12158 game.explosions_delayed = FALSE;
12160 SCAN_PLAYFIELD(x, y)
12162 element = Feld[x][y];
12164 if (ExplodeField[x][y])
12165 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12166 else if (element == EL_EXPLOSION)
12167 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12169 ExplodeField[x][y] = EX_TYPE_NONE;
12172 game.explosions_delayed = TRUE;
12174 if (game.magic_wall_active)
12176 if (!(game.magic_wall_time_left % 4))
12178 int element = Feld[magic_wall_x][magic_wall_y];
12180 if (element == EL_BD_MAGIC_WALL_FULL ||
12181 element == EL_BD_MAGIC_WALL_ACTIVE ||
12182 element == EL_BD_MAGIC_WALL_EMPTYING)
12183 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12184 else if (element == EL_DC_MAGIC_WALL_FULL ||
12185 element == EL_DC_MAGIC_WALL_ACTIVE ||
12186 element == EL_DC_MAGIC_WALL_EMPTYING)
12187 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12189 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12192 if (game.magic_wall_time_left > 0)
12194 game.magic_wall_time_left--;
12196 if (!game.magic_wall_time_left)
12198 SCAN_PLAYFIELD(x, y)
12200 element = Feld[x][y];
12202 if (element == EL_MAGIC_WALL_ACTIVE ||
12203 element == EL_MAGIC_WALL_FULL)
12205 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12206 TEST_DrawLevelField(x, y);
12208 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12209 element == EL_BD_MAGIC_WALL_FULL)
12211 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12212 TEST_DrawLevelField(x, y);
12214 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12215 element == EL_DC_MAGIC_WALL_FULL)
12217 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12218 TEST_DrawLevelField(x, y);
12222 game.magic_wall_active = FALSE;
12227 if (game.light_time_left > 0)
12229 game.light_time_left--;
12231 if (game.light_time_left == 0)
12232 RedrawAllLightSwitchesAndInvisibleElements();
12235 if (game.timegate_time_left > 0)
12237 game.timegate_time_left--;
12239 if (game.timegate_time_left == 0)
12240 CloseAllOpenTimegates();
12243 if (game.lenses_time_left > 0)
12245 game.lenses_time_left--;
12247 if (game.lenses_time_left == 0)
12248 RedrawAllInvisibleElementsForLenses();
12251 if (game.magnify_time_left > 0)
12253 game.magnify_time_left--;
12255 if (game.magnify_time_left == 0)
12256 RedrawAllInvisibleElementsForMagnifier();
12259 for (i = 0; i < MAX_PLAYERS; i++)
12261 struct PlayerInfo *player = &stored_player[i];
12263 if (SHIELD_ON(player))
12265 if (player->shield_deadly_time_left)
12266 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12267 else if (player->shield_normal_time_left)
12268 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12272 #if USE_DELAYED_GFX_REDRAW
12273 SCAN_PLAYFIELD(x, y)
12275 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12277 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12278 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12280 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12281 DrawLevelField(x, y);
12283 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12284 DrawLevelFieldCrumbled(x, y);
12286 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12287 DrawLevelFieldCrumbledNeighbours(x, y);
12289 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12290 DrawTwinkleOnField(x, y);
12293 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12298 PlayAllPlayersSound();
12300 for (i = 0; i < MAX_PLAYERS; i++)
12302 struct PlayerInfo *player = &stored_player[i];
12304 if (player->show_envelope != 0 && (!player->active ||
12305 player->MovPos == 0))
12307 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12309 player->show_envelope = 0;
12313 // use random number generator in every frame to make it less predictable
12314 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12317 mouse_action_last = mouse_action;
12320 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12322 int min_x = x, min_y = y, max_x = x, max_y = y;
12325 for (i = 0; i < MAX_PLAYERS; i++)
12327 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12329 if (!stored_player[i].active || &stored_player[i] == player)
12332 min_x = MIN(min_x, jx);
12333 min_y = MIN(min_y, jy);
12334 max_x = MAX(max_x, jx);
12335 max_y = MAX(max_y, jy);
12338 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12341 static boolean AllPlayersInVisibleScreen(void)
12345 for (i = 0; i < MAX_PLAYERS; i++)
12347 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12349 if (!stored_player[i].active)
12352 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12359 void ScrollLevel(int dx, int dy)
12361 int scroll_offset = 2 * TILEX_VAR;
12364 BlitBitmap(drawto_field, drawto_field,
12365 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12366 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12367 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12368 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12369 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12370 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12374 x = (dx == 1 ? BX1 : BX2);
12375 for (y = BY1; y <= BY2; y++)
12376 DrawScreenField(x, y);
12381 y = (dy == 1 ? BY1 : BY2);
12382 for (x = BX1; x <= BX2; x++)
12383 DrawScreenField(x, y);
12386 redraw_mask |= REDRAW_FIELD;
12389 static boolean canFallDown(struct PlayerInfo *player)
12391 int jx = player->jx, jy = player->jy;
12393 return (IN_LEV_FIELD(jx, jy + 1) &&
12394 (IS_FREE(jx, jy + 1) ||
12395 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12396 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12397 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12400 static boolean canPassField(int x, int y, int move_dir)
12402 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12403 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12404 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12405 int nextx = x + dx;
12406 int nexty = y + dy;
12407 int element = Feld[x][y];
12409 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12410 !CAN_MOVE(element) &&
12411 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12412 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12413 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12416 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12418 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12419 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12420 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12424 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12425 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12426 (IS_DIGGABLE(Feld[newx][newy]) ||
12427 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12428 canPassField(newx, newy, move_dir)));
12431 static void CheckGravityMovement(struct PlayerInfo *player)
12433 if (player->gravity && !player->programmed_action)
12435 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12436 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12437 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12438 int jx = player->jx, jy = player->jy;
12439 boolean player_is_moving_to_valid_field =
12440 (!player_is_snapping &&
12441 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12442 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12443 boolean player_can_fall_down = canFallDown(player);
12445 if (player_can_fall_down &&
12446 !player_is_moving_to_valid_field)
12447 player->programmed_action = MV_DOWN;
12451 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12453 return CheckGravityMovement(player);
12455 if (player->gravity && !player->programmed_action)
12457 int jx = player->jx, jy = player->jy;
12458 boolean field_under_player_is_free =
12459 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12460 boolean player_is_standing_on_valid_field =
12461 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12462 (IS_WALKABLE(Feld[jx][jy]) &&
12463 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12465 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12466 player->programmed_action = MV_DOWN;
12471 MovePlayerOneStep()
12472 -----------------------------------------------------------------------------
12473 dx, dy: direction (non-diagonal) to try to move the player to
12474 real_dx, real_dy: direction as read from input device (can be diagonal)
12477 boolean MovePlayerOneStep(struct PlayerInfo *player,
12478 int dx, int dy, int real_dx, int real_dy)
12480 int jx = player->jx, jy = player->jy;
12481 int new_jx = jx + dx, new_jy = jy + dy;
12483 boolean player_can_move = !player->cannot_move;
12485 if (!player->active || (!dx && !dy))
12486 return MP_NO_ACTION;
12488 player->MovDir = (dx < 0 ? MV_LEFT :
12489 dx > 0 ? MV_RIGHT :
12491 dy > 0 ? MV_DOWN : MV_NONE);
12493 if (!IN_LEV_FIELD(new_jx, new_jy))
12494 return MP_NO_ACTION;
12496 if (!player_can_move)
12498 if (player->MovPos == 0)
12500 player->is_moving = FALSE;
12501 player->is_digging = FALSE;
12502 player->is_collecting = FALSE;
12503 player->is_snapping = FALSE;
12504 player->is_pushing = FALSE;
12508 if (!network.enabled && game.centered_player_nr == -1 &&
12509 !AllPlayersInSight(player, new_jx, new_jy))
12510 return MP_NO_ACTION;
12512 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12513 if (can_move != MP_MOVING)
12516 // check if DigField() has caused relocation of the player
12517 if (player->jx != jx || player->jy != jy)
12518 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12520 StorePlayer[jx][jy] = 0;
12521 player->last_jx = jx;
12522 player->last_jy = jy;
12523 player->jx = new_jx;
12524 player->jy = new_jy;
12525 StorePlayer[new_jx][new_jy] = player->element_nr;
12527 if (player->move_delay_value_next != -1)
12529 player->move_delay_value = player->move_delay_value_next;
12530 player->move_delay_value_next = -1;
12534 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12536 player->step_counter++;
12538 PlayerVisit[jx][jy] = FrameCounter;
12540 player->is_moving = TRUE;
12543 // should better be called in MovePlayer(), but this breaks some tapes
12544 ScrollPlayer(player, SCROLL_INIT);
12550 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12552 int jx = player->jx, jy = player->jy;
12553 int old_jx = jx, old_jy = jy;
12554 int moved = MP_NO_ACTION;
12556 if (!player->active)
12561 if (player->MovPos == 0)
12563 player->is_moving = FALSE;
12564 player->is_digging = FALSE;
12565 player->is_collecting = FALSE;
12566 player->is_snapping = FALSE;
12567 player->is_pushing = FALSE;
12573 if (player->move_delay > 0)
12576 player->move_delay = -1; // set to "uninitialized" value
12578 // store if player is automatically moved to next field
12579 player->is_auto_moving = (player->programmed_action != MV_NONE);
12581 // remove the last programmed player action
12582 player->programmed_action = 0;
12584 if (player->MovPos)
12586 // should only happen if pre-1.2 tape recordings are played
12587 // this is only for backward compatibility
12589 int original_move_delay_value = player->move_delay_value;
12592 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12596 // scroll remaining steps with finest movement resolution
12597 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12599 while (player->MovPos)
12601 ScrollPlayer(player, SCROLL_GO_ON);
12602 ScrollScreen(NULL, SCROLL_GO_ON);
12604 AdvanceFrameAndPlayerCounters(player->index_nr);
12607 BackToFront_WithFrameDelay(0);
12610 player->move_delay_value = original_move_delay_value;
12613 player->is_active = FALSE;
12615 if (player->last_move_dir & MV_HORIZONTAL)
12617 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12618 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12622 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12623 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12626 if (!moved && !player->is_active)
12628 player->is_moving = FALSE;
12629 player->is_digging = FALSE;
12630 player->is_collecting = FALSE;
12631 player->is_snapping = FALSE;
12632 player->is_pushing = FALSE;
12638 if (moved & MP_MOVING && !ScreenMovPos &&
12639 (player->index_nr == game.centered_player_nr ||
12640 game.centered_player_nr == -1))
12642 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12644 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12646 // actual player has left the screen -- scroll in that direction
12647 if (jx != old_jx) // player has moved horizontally
12648 scroll_x += (jx - old_jx);
12649 else // player has moved vertically
12650 scroll_y += (jy - old_jy);
12654 int offset_raw = game.scroll_delay_value;
12656 if (jx != old_jx) // player has moved horizontally
12658 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12659 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12660 int new_scroll_x = jx - MIDPOSX + offset_x;
12662 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12663 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12664 scroll_x = new_scroll_x;
12666 // don't scroll over playfield boundaries
12667 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12669 // don't scroll more than one field at a time
12670 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12672 // don't scroll against the player's moving direction
12673 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12674 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12675 scroll_x = old_scroll_x;
12677 else // player has moved vertically
12679 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12680 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12681 int new_scroll_y = jy - MIDPOSY + offset_y;
12683 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12684 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12685 scroll_y = new_scroll_y;
12687 // don't scroll over playfield boundaries
12688 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12690 // don't scroll more than one field at a time
12691 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12693 // don't scroll against the player's moving direction
12694 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12695 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12696 scroll_y = old_scroll_y;
12700 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12702 if (!network.enabled && game.centered_player_nr == -1 &&
12703 !AllPlayersInVisibleScreen())
12705 scroll_x = old_scroll_x;
12706 scroll_y = old_scroll_y;
12710 ScrollScreen(player, SCROLL_INIT);
12711 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12716 player->StepFrame = 0;
12718 if (moved & MP_MOVING)
12720 if (old_jx != jx && old_jy == jy)
12721 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12722 else if (old_jx == jx && old_jy != jy)
12723 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12725 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12727 player->last_move_dir = player->MovDir;
12728 player->is_moving = TRUE;
12729 player->is_snapping = FALSE;
12730 player->is_switching = FALSE;
12731 player->is_dropping = FALSE;
12732 player->is_dropping_pressed = FALSE;
12733 player->drop_pressed_delay = 0;
12736 // should better be called here than above, but this breaks some tapes
12737 ScrollPlayer(player, SCROLL_INIT);
12742 CheckGravityMovementWhenNotMoving(player);
12744 player->is_moving = FALSE;
12746 /* at this point, the player is allowed to move, but cannot move right now
12747 (e.g. because of something blocking the way) -- ensure that the player
12748 is also allowed to move in the next frame (in old versions before 3.1.1,
12749 the player was forced to wait again for eight frames before next try) */
12751 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12752 player->move_delay = 0; // allow direct movement in the next frame
12755 if (player->move_delay == -1) // not yet initialized by DigField()
12756 player->move_delay = player->move_delay_value;
12758 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12760 TestIfPlayerTouchesBadThing(jx, jy);
12761 TestIfPlayerTouchesCustomElement(jx, jy);
12764 if (!player->active)
12765 RemovePlayer(player);
12770 void ScrollPlayer(struct PlayerInfo *player, int mode)
12772 int jx = player->jx, jy = player->jy;
12773 int last_jx = player->last_jx, last_jy = player->last_jy;
12774 int move_stepsize = TILEX / player->move_delay_value;
12776 if (!player->active)
12779 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12782 if (mode == SCROLL_INIT)
12784 player->actual_frame_counter = FrameCounter;
12785 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12787 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12788 Feld[last_jx][last_jy] == EL_EMPTY)
12790 int last_field_block_delay = 0; // start with no blocking at all
12791 int block_delay_adjustment = player->block_delay_adjustment;
12793 // if player blocks last field, add delay for exactly one move
12794 if (player->block_last_field)
12796 last_field_block_delay += player->move_delay_value;
12798 // when blocking enabled, prevent moving up despite gravity
12799 if (player->gravity && player->MovDir == MV_UP)
12800 block_delay_adjustment = -1;
12803 // add block delay adjustment (also possible when not blocking)
12804 last_field_block_delay += block_delay_adjustment;
12806 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12807 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12810 if (player->MovPos != 0) // player has not yet reached destination
12813 else if (!FrameReached(&player->actual_frame_counter, 1))
12816 if (player->MovPos != 0)
12818 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12819 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12821 // before DrawPlayer() to draw correct player graphic for this case
12822 if (player->MovPos == 0)
12823 CheckGravityMovement(player);
12826 if (player->MovPos == 0) // player reached destination field
12828 if (player->move_delay_reset_counter > 0)
12830 player->move_delay_reset_counter--;
12832 if (player->move_delay_reset_counter == 0)
12834 // continue with normal speed after quickly moving through gate
12835 HALVE_PLAYER_SPEED(player);
12837 // be able to make the next move without delay
12838 player->move_delay = 0;
12842 player->last_jx = jx;
12843 player->last_jy = jy;
12845 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12846 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12847 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12848 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12849 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12850 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12851 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12852 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12854 ExitPlayer(player);
12856 if (game.players_still_needed == 0 &&
12857 (game.friends_still_needed == 0 ||
12858 IS_SP_ELEMENT(Feld[jx][jy])))
12862 // this breaks one level: "machine", level 000
12864 int move_direction = player->MovDir;
12865 int enter_side = MV_DIR_OPPOSITE(move_direction);
12866 int leave_side = move_direction;
12867 int old_jx = last_jx;
12868 int old_jy = last_jy;
12869 int old_element = Feld[old_jx][old_jy];
12870 int new_element = Feld[jx][jy];
12872 if (IS_CUSTOM_ELEMENT(old_element))
12873 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12875 player->index_bit, leave_side);
12877 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12878 CE_PLAYER_LEAVES_X,
12879 player->index_bit, leave_side);
12881 if (IS_CUSTOM_ELEMENT(new_element))
12882 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12883 player->index_bit, enter_side);
12885 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12886 CE_PLAYER_ENTERS_X,
12887 player->index_bit, enter_side);
12889 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12890 CE_MOVE_OF_X, move_direction);
12893 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12895 TestIfPlayerTouchesBadThing(jx, jy);
12896 TestIfPlayerTouchesCustomElement(jx, jy);
12898 /* needed because pushed element has not yet reached its destination,
12899 so it would trigger a change event at its previous field location */
12900 if (!player->is_pushing)
12901 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12903 if (!player->active)
12904 RemovePlayer(player);
12907 if (!game.LevelSolved && level.use_step_counter)
12917 if (TimeLeft <= 10 && setup.time_limit)
12918 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12920 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12922 DisplayGameControlValues();
12924 if (!TimeLeft && setup.time_limit)
12925 for (i = 0; i < MAX_PLAYERS; i++)
12926 KillPlayer(&stored_player[i]);
12928 else if (game.no_time_limit && !game.all_players_gone)
12930 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12932 DisplayGameControlValues();
12936 if (tape.single_step && tape.recording && !tape.pausing &&
12937 !player->programmed_action)
12938 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12940 if (!player->programmed_action)
12941 CheckSaveEngineSnapshot(player);
12945 void ScrollScreen(struct PlayerInfo *player, int mode)
12947 static unsigned int screen_frame_counter = 0;
12949 if (mode == SCROLL_INIT)
12951 // set scrolling step size according to actual player's moving speed
12952 ScrollStepSize = TILEX / player->move_delay_value;
12954 screen_frame_counter = FrameCounter;
12955 ScreenMovDir = player->MovDir;
12956 ScreenMovPos = player->MovPos;
12957 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12960 else if (!FrameReached(&screen_frame_counter, 1))
12965 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12966 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12967 redraw_mask |= REDRAW_FIELD;
12970 ScreenMovDir = MV_NONE;
12973 void TestIfPlayerTouchesCustomElement(int x, int y)
12975 static int xy[4][2] =
12982 static int trigger_sides[4][2] =
12984 // center side border side
12985 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12986 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12987 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12988 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12990 static int touch_dir[4] =
12992 MV_LEFT | MV_RIGHT,
12997 int center_element = Feld[x][y]; // should always be non-moving!
13000 for (i = 0; i < NUM_DIRECTIONS; i++)
13002 int xx = x + xy[i][0];
13003 int yy = y + xy[i][1];
13004 int center_side = trigger_sides[i][0];
13005 int border_side = trigger_sides[i][1];
13006 int border_element;
13008 if (!IN_LEV_FIELD(xx, yy))
13011 if (IS_PLAYER(x, y)) // player found at center element
13013 struct PlayerInfo *player = PLAYERINFO(x, y);
13015 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13016 border_element = Feld[xx][yy]; // may be moving!
13017 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13018 border_element = Feld[xx][yy];
13019 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13020 border_element = MovingOrBlocked2Element(xx, yy);
13022 continue; // center and border element do not touch
13024 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13025 player->index_bit, border_side);
13026 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13027 CE_PLAYER_TOUCHES_X,
13028 player->index_bit, border_side);
13031 /* use player element that is initially defined in the level playfield,
13032 not the player element that corresponds to the runtime player number
13033 (example: a level that contains EL_PLAYER_3 as the only player would
13034 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13035 int player_element = PLAYERINFO(x, y)->initial_element;
13037 CheckElementChangeBySide(xx, yy, border_element, player_element,
13038 CE_TOUCHING_X, border_side);
13041 else if (IS_PLAYER(xx, yy)) // player found at border element
13043 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13045 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13047 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13048 continue; // center and border element do not touch
13051 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13052 player->index_bit, center_side);
13053 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13054 CE_PLAYER_TOUCHES_X,
13055 player->index_bit, center_side);
13058 /* use player element that is initially defined in the level playfield,
13059 not the player element that corresponds to the runtime player number
13060 (example: a level that contains EL_PLAYER_3 as the only player would
13061 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13062 int player_element = PLAYERINFO(xx, yy)->initial_element;
13064 CheckElementChangeBySide(x, y, center_element, player_element,
13065 CE_TOUCHING_X, center_side);
13073 void TestIfElementTouchesCustomElement(int x, int y)
13075 static int xy[4][2] =
13082 static int trigger_sides[4][2] =
13084 // center side border side
13085 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13086 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13087 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13088 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13090 static int touch_dir[4] =
13092 MV_LEFT | MV_RIGHT,
13097 boolean change_center_element = FALSE;
13098 int center_element = Feld[x][y]; // should always be non-moving!
13099 int border_element_old[NUM_DIRECTIONS];
13102 for (i = 0; i < NUM_DIRECTIONS; i++)
13104 int xx = x + xy[i][0];
13105 int yy = y + xy[i][1];
13106 int border_element;
13108 border_element_old[i] = -1;
13110 if (!IN_LEV_FIELD(xx, yy))
13113 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13114 border_element = Feld[xx][yy]; // may be moving!
13115 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13116 border_element = Feld[xx][yy];
13117 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13118 border_element = MovingOrBlocked2Element(xx, yy);
13120 continue; // center and border element do not touch
13122 border_element_old[i] = border_element;
13125 for (i = 0; i < NUM_DIRECTIONS; i++)
13127 int xx = x + xy[i][0];
13128 int yy = y + xy[i][1];
13129 int center_side = trigger_sides[i][0];
13130 int border_element = border_element_old[i];
13132 if (border_element == -1)
13135 // check for change of border element
13136 CheckElementChangeBySide(xx, yy, border_element, center_element,
13137 CE_TOUCHING_X, center_side);
13139 // (center element cannot be player, so we dont have to check this here)
13142 for (i = 0; i < NUM_DIRECTIONS; i++)
13144 int xx = x + xy[i][0];
13145 int yy = y + xy[i][1];
13146 int border_side = trigger_sides[i][1];
13147 int border_element = border_element_old[i];
13149 if (border_element == -1)
13152 // check for change of center element (but change it only once)
13153 if (!change_center_element)
13154 change_center_element =
13155 CheckElementChangeBySide(x, y, center_element, border_element,
13156 CE_TOUCHING_X, border_side);
13158 if (IS_PLAYER(xx, yy))
13160 /* use player element that is initially defined in the level playfield,
13161 not the player element that corresponds to the runtime player number
13162 (example: a level that contains EL_PLAYER_3 as the only player would
13163 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13164 int player_element = PLAYERINFO(xx, yy)->initial_element;
13166 CheckElementChangeBySide(x, y, center_element, player_element,
13167 CE_TOUCHING_X, border_side);
13172 void TestIfElementHitsCustomElement(int x, int y, int direction)
13174 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13175 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13176 int hitx = x + dx, hity = y + dy;
13177 int hitting_element = Feld[x][y];
13178 int touched_element;
13180 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13183 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13184 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13186 if (IN_LEV_FIELD(hitx, hity))
13188 int opposite_direction = MV_DIR_OPPOSITE(direction);
13189 int hitting_side = direction;
13190 int touched_side = opposite_direction;
13191 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13192 MovDir[hitx][hity] != direction ||
13193 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13199 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13200 CE_HITTING_X, touched_side);
13202 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13203 CE_HIT_BY_X, hitting_side);
13205 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13206 CE_HIT_BY_SOMETHING, opposite_direction);
13208 if (IS_PLAYER(hitx, hity))
13210 /* use player element that is initially defined in the level playfield,
13211 not the player element that corresponds to the runtime player number
13212 (example: a level that contains EL_PLAYER_3 as the only player would
13213 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13214 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13216 CheckElementChangeBySide(x, y, hitting_element, player_element,
13217 CE_HITTING_X, touched_side);
13222 // "hitting something" is also true when hitting the playfield border
13223 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13224 CE_HITTING_SOMETHING, direction);
13227 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13229 int i, kill_x = -1, kill_y = -1;
13231 int bad_element = -1;
13232 static int test_xy[4][2] =
13239 static int test_dir[4] =
13247 for (i = 0; i < NUM_DIRECTIONS; i++)
13249 int test_x, test_y, test_move_dir, test_element;
13251 test_x = good_x + test_xy[i][0];
13252 test_y = good_y + test_xy[i][1];
13254 if (!IN_LEV_FIELD(test_x, test_y))
13258 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13260 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13262 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13263 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13265 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13266 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13270 bad_element = test_element;
13276 if (kill_x != -1 || kill_y != -1)
13278 if (IS_PLAYER(good_x, good_y))
13280 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13282 if (player->shield_deadly_time_left > 0 &&
13283 !IS_INDESTRUCTIBLE(bad_element))
13284 Bang(kill_x, kill_y);
13285 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13286 KillPlayer(player);
13289 Bang(good_x, good_y);
13293 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13295 int i, kill_x = -1, kill_y = -1;
13296 int bad_element = Feld[bad_x][bad_y];
13297 static int test_xy[4][2] =
13304 static int touch_dir[4] =
13306 MV_LEFT | MV_RIGHT,
13311 static int test_dir[4] =
13319 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13322 for (i = 0; i < NUM_DIRECTIONS; i++)
13324 int test_x, test_y, test_move_dir, test_element;
13326 test_x = bad_x + test_xy[i][0];
13327 test_y = bad_y + test_xy[i][1];
13329 if (!IN_LEV_FIELD(test_x, test_y))
13333 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13335 test_element = Feld[test_x][test_y];
13337 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13338 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13340 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13341 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13343 // good thing is player or penguin that does not move away
13344 if (IS_PLAYER(test_x, test_y))
13346 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13348 if (bad_element == EL_ROBOT && player->is_moving)
13349 continue; // robot does not kill player if he is moving
13351 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13353 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13354 continue; // center and border element do not touch
13362 else if (test_element == EL_PENGUIN)
13372 if (kill_x != -1 || kill_y != -1)
13374 if (IS_PLAYER(kill_x, kill_y))
13376 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13378 if (player->shield_deadly_time_left > 0 &&
13379 !IS_INDESTRUCTIBLE(bad_element))
13380 Bang(bad_x, bad_y);
13381 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13382 KillPlayer(player);
13385 Bang(kill_x, kill_y);
13389 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13391 int bad_element = Feld[bad_x][bad_y];
13392 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13393 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13394 int test_x = bad_x + dx, test_y = bad_y + dy;
13395 int test_move_dir, test_element;
13396 int kill_x = -1, kill_y = -1;
13398 if (!IN_LEV_FIELD(test_x, test_y))
13402 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13404 test_element = Feld[test_x][test_y];
13406 if (test_move_dir != bad_move_dir)
13408 // good thing can be player or penguin that does not move away
13409 if (IS_PLAYER(test_x, test_y))
13411 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13413 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13414 player as being hit when he is moving towards the bad thing, because
13415 the "get hit by" condition would be lost after the player stops) */
13416 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13417 return; // player moves away from bad thing
13422 else if (test_element == EL_PENGUIN)
13429 if (kill_x != -1 || kill_y != -1)
13431 if (IS_PLAYER(kill_x, kill_y))
13433 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13435 if (player->shield_deadly_time_left > 0 &&
13436 !IS_INDESTRUCTIBLE(bad_element))
13437 Bang(bad_x, bad_y);
13438 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13439 KillPlayer(player);
13442 Bang(kill_x, kill_y);
13446 void TestIfPlayerTouchesBadThing(int x, int y)
13448 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13451 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13453 TestIfGoodThingHitsBadThing(x, y, move_dir);
13456 void TestIfBadThingTouchesPlayer(int x, int y)
13458 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13461 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13463 TestIfBadThingHitsGoodThing(x, y, move_dir);
13466 void TestIfFriendTouchesBadThing(int x, int y)
13468 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13471 void TestIfBadThingTouchesFriend(int x, int y)
13473 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13476 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13478 int i, kill_x = bad_x, kill_y = bad_y;
13479 static int xy[4][2] =
13487 for (i = 0; i < NUM_DIRECTIONS; i++)
13491 x = bad_x + xy[i][0];
13492 y = bad_y + xy[i][1];
13493 if (!IN_LEV_FIELD(x, y))
13496 element = Feld[x][y];
13497 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13498 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13506 if (kill_x != bad_x || kill_y != bad_y)
13507 Bang(bad_x, bad_y);
13510 void KillPlayer(struct PlayerInfo *player)
13512 int jx = player->jx, jy = player->jy;
13514 if (!player->active)
13518 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13519 player->killed, player->active, player->reanimated);
13522 /* the following code was introduced to prevent an infinite loop when calling
13524 -> CheckTriggeredElementChangeExt()
13525 -> ExecuteCustomElementAction()
13527 -> (infinitely repeating the above sequence of function calls)
13528 which occurs when killing the player while having a CE with the setting
13529 "kill player X when explosion of <player X>"; the solution using a new
13530 field "player->killed" was chosen for backwards compatibility, although
13531 clever use of the fields "player->active" etc. would probably also work */
13533 if (player->killed)
13537 player->killed = TRUE;
13539 // remove accessible field at the player's position
13540 Feld[jx][jy] = EL_EMPTY;
13542 // deactivate shield (else Bang()/Explode() would not work right)
13543 player->shield_normal_time_left = 0;
13544 player->shield_deadly_time_left = 0;
13547 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13548 player->killed, player->active, player->reanimated);
13554 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13555 player->killed, player->active, player->reanimated);
13558 if (player->reanimated) // killed player may have been reanimated
13559 player->killed = player->reanimated = FALSE;
13561 BuryPlayer(player);
13564 static void KillPlayerUnlessEnemyProtected(int x, int y)
13566 if (!PLAYER_ENEMY_PROTECTED(x, y))
13567 KillPlayer(PLAYERINFO(x, y));
13570 static void KillPlayerUnlessExplosionProtected(int x, int y)
13572 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13573 KillPlayer(PLAYERINFO(x, y));
13576 void BuryPlayer(struct PlayerInfo *player)
13578 int jx = player->jx, jy = player->jy;
13580 if (!player->active)
13583 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13584 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13586 RemovePlayer(player);
13588 player->buried = TRUE;
13590 if (game.all_players_gone)
13591 game.GameOver = TRUE;
13594 void RemovePlayer(struct PlayerInfo *player)
13596 int jx = player->jx, jy = player->jy;
13597 int i, found = FALSE;
13599 player->present = FALSE;
13600 player->active = FALSE;
13602 // required for some CE actions (even if the player is not active anymore)
13603 player->MovPos = 0;
13605 if (!ExplodeField[jx][jy])
13606 StorePlayer[jx][jy] = 0;
13608 if (player->is_moving)
13609 TEST_DrawLevelField(player->last_jx, player->last_jy);
13611 for (i = 0; i < MAX_PLAYERS; i++)
13612 if (stored_player[i].active)
13617 game.all_players_gone = TRUE;
13618 game.GameOver = TRUE;
13621 game.exit_x = game.robot_wheel_x = jx;
13622 game.exit_y = game.robot_wheel_y = jy;
13625 void ExitPlayer(struct PlayerInfo *player)
13627 DrawPlayer(player); // needed here only to cleanup last field
13628 RemovePlayer(player);
13630 if (game.players_still_needed > 0)
13631 game.players_still_needed--;
13634 static void setFieldForSnapping(int x, int y, int element, int direction)
13636 struct ElementInfo *ei = &element_info[element];
13637 int direction_bit = MV_DIR_TO_BIT(direction);
13638 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13639 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13640 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13642 Feld[x][y] = EL_ELEMENT_SNAPPING;
13643 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13645 ResetGfxAnimation(x, y);
13647 GfxElement[x][y] = element;
13648 GfxAction[x][y] = action;
13649 GfxDir[x][y] = direction;
13650 GfxFrame[x][y] = -1;
13654 =============================================================================
13655 checkDiagonalPushing()
13656 -----------------------------------------------------------------------------
13657 check if diagonal input device direction results in pushing of object
13658 (by checking if the alternative direction is walkable, diggable, ...)
13659 =============================================================================
13662 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13663 int x, int y, int real_dx, int real_dy)
13665 int jx, jy, dx, dy, xx, yy;
13667 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13670 // diagonal direction: check alternative direction
13675 xx = jx + (dx == 0 ? real_dx : 0);
13676 yy = jy + (dy == 0 ? real_dy : 0);
13678 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13682 =============================================================================
13684 -----------------------------------------------------------------------------
13685 x, y: field next to player (non-diagonal) to try to dig to
13686 real_dx, real_dy: direction as read from input device (can be diagonal)
13687 =============================================================================
13690 static int DigField(struct PlayerInfo *player,
13691 int oldx, int oldy, int x, int y,
13692 int real_dx, int real_dy, int mode)
13694 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13695 boolean player_was_pushing = player->is_pushing;
13696 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13697 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13698 int jx = oldx, jy = oldy;
13699 int dx = x - jx, dy = y - jy;
13700 int nextx = x + dx, nexty = y + dy;
13701 int move_direction = (dx == -1 ? MV_LEFT :
13702 dx == +1 ? MV_RIGHT :
13704 dy == +1 ? MV_DOWN : MV_NONE);
13705 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13706 int dig_side = MV_DIR_OPPOSITE(move_direction);
13707 int old_element = Feld[jx][jy];
13708 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13711 if (is_player) // function can also be called by EL_PENGUIN
13713 if (player->MovPos == 0)
13715 player->is_digging = FALSE;
13716 player->is_collecting = FALSE;
13719 if (player->MovPos == 0) // last pushing move finished
13720 player->is_pushing = FALSE;
13722 if (mode == DF_NO_PUSH) // player just stopped pushing
13724 player->is_switching = FALSE;
13725 player->push_delay = -1;
13727 return MP_NO_ACTION;
13731 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13732 old_element = Back[jx][jy];
13734 // in case of element dropped at player position, check background
13735 else if (Back[jx][jy] != EL_EMPTY &&
13736 game.engine_version >= VERSION_IDENT(2,2,0,0))
13737 old_element = Back[jx][jy];
13739 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13740 return MP_NO_ACTION; // field has no opening in this direction
13742 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13743 return MP_NO_ACTION; // field has no opening in this direction
13745 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13749 Feld[jx][jy] = player->artwork_element;
13750 InitMovingField(jx, jy, MV_DOWN);
13751 Store[jx][jy] = EL_ACID;
13752 ContinueMoving(jx, jy);
13753 BuryPlayer(player);
13755 return MP_DONT_RUN_INTO;
13758 if (player_can_move && DONT_RUN_INTO(element))
13760 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13762 return MP_DONT_RUN_INTO;
13765 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13766 return MP_NO_ACTION;
13768 collect_count = element_info[element].collect_count_initial;
13770 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13771 return MP_NO_ACTION;
13773 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13774 player_can_move = player_can_move_or_snap;
13776 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13777 game.engine_version >= VERSION_IDENT(2,2,0,0))
13779 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13780 player->index_bit, dig_side);
13781 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13782 player->index_bit, dig_side);
13784 if (element == EL_DC_LANDMINE)
13787 if (Feld[x][y] != element) // field changed by snapping
13790 return MP_NO_ACTION;
13793 if (player->gravity && is_player && !player->is_auto_moving &&
13794 canFallDown(player) && move_direction != MV_DOWN &&
13795 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13796 return MP_NO_ACTION; // player cannot walk here due to gravity
13798 if (player_can_move &&
13799 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13801 int sound_element = SND_ELEMENT(element);
13802 int sound_action = ACTION_WALKING;
13804 if (IS_RND_GATE(element))
13806 if (!player->key[RND_GATE_NR(element)])
13807 return MP_NO_ACTION;
13809 else if (IS_RND_GATE_GRAY(element))
13811 if (!player->key[RND_GATE_GRAY_NR(element)])
13812 return MP_NO_ACTION;
13814 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13816 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13817 return MP_NO_ACTION;
13819 else if (element == EL_EXIT_OPEN ||
13820 element == EL_EM_EXIT_OPEN ||
13821 element == EL_EM_EXIT_OPENING ||
13822 element == EL_STEEL_EXIT_OPEN ||
13823 element == EL_EM_STEEL_EXIT_OPEN ||
13824 element == EL_EM_STEEL_EXIT_OPENING ||
13825 element == EL_SP_EXIT_OPEN ||
13826 element == EL_SP_EXIT_OPENING)
13828 sound_action = ACTION_PASSING; // player is passing exit
13830 else if (element == EL_EMPTY)
13832 sound_action = ACTION_MOVING; // nothing to walk on
13835 // play sound from background or player, whatever is available
13836 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13837 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13839 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13841 else if (player_can_move &&
13842 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13844 if (!ACCESS_FROM(element, opposite_direction))
13845 return MP_NO_ACTION; // field not accessible from this direction
13847 if (CAN_MOVE(element)) // only fixed elements can be passed!
13848 return MP_NO_ACTION;
13850 if (IS_EM_GATE(element))
13852 if (!player->key[EM_GATE_NR(element)])
13853 return MP_NO_ACTION;
13855 else if (IS_EM_GATE_GRAY(element))
13857 if (!player->key[EM_GATE_GRAY_NR(element)])
13858 return MP_NO_ACTION;
13860 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13862 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13863 return MP_NO_ACTION;
13865 else if (IS_EMC_GATE(element))
13867 if (!player->key[EMC_GATE_NR(element)])
13868 return MP_NO_ACTION;
13870 else if (IS_EMC_GATE_GRAY(element))
13872 if (!player->key[EMC_GATE_GRAY_NR(element)])
13873 return MP_NO_ACTION;
13875 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13877 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13878 return MP_NO_ACTION;
13880 else if (element == EL_DC_GATE_WHITE ||
13881 element == EL_DC_GATE_WHITE_GRAY ||
13882 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13884 if (player->num_white_keys == 0)
13885 return MP_NO_ACTION;
13887 player->num_white_keys--;
13889 else if (IS_SP_PORT(element))
13891 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13892 element == EL_SP_GRAVITY_PORT_RIGHT ||
13893 element == EL_SP_GRAVITY_PORT_UP ||
13894 element == EL_SP_GRAVITY_PORT_DOWN)
13895 player->gravity = !player->gravity;
13896 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13897 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13898 element == EL_SP_GRAVITY_ON_PORT_UP ||
13899 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13900 player->gravity = TRUE;
13901 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13902 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13903 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13904 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13905 player->gravity = FALSE;
13908 // automatically move to the next field with double speed
13909 player->programmed_action = move_direction;
13911 if (player->move_delay_reset_counter == 0)
13913 player->move_delay_reset_counter = 2; // two double speed steps
13915 DOUBLE_PLAYER_SPEED(player);
13918 PlayLevelSoundAction(x, y, ACTION_PASSING);
13920 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13924 if (mode != DF_SNAP)
13926 GfxElement[x][y] = GFX_ELEMENT(element);
13927 player->is_digging = TRUE;
13930 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13932 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13933 player->index_bit, dig_side);
13935 if (mode == DF_SNAP)
13937 if (level.block_snap_field)
13938 setFieldForSnapping(x, y, element, move_direction);
13940 TestIfElementTouchesCustomElement(x, y); // for empty space
13942 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13943 player->index_bit, dig_side);
13946 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13950 if (is_player && mode != DF_SNAP)
13952 GfxElement[x][y] = element;
13953 player->is_collecting = TRUE;
13956 if (element == EL_SPEED_PILL)
13958 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13960 else if (element == EL_EXTRA_TIME && level.time > 0)
13962 TimeLeft += level.extra_time;
13964 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13966 DisplayGameControlValues();
13968 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13970 player->shield_normal_time_left += level.shield_normal_time;
13971 if (element == EL_SHIELD_DEADLY)
13972 player->shield_deadly_time_left += level.shield_deadly_time;
13974 else if (element == EL_DYNAMITE ||
13975 element == EL_EM_DYNAMITE ||
13976 element == EL_SP_DISK_RED)
13978 if (player->inventory_size < MAX_INVENTORY_SIZE)
13979 player->inventory_element[player->inventory_size++] = element;
13981 DrawGameDoorValues();
13983 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13985 player->dynabomb_count++;
13986 player->dynabombs_left++;
13988 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13990 player->dynabomb_size++;
13992 else if (element == EL_DYNABOMB_INCREASE_POWER)
13994 player->dynabomb_xl = TRUE;
13996 else if (IS_KEY(element))
13998 player->key[KEY_NR(element)] = TRUE;
14000 DrawGameDoorValues();
14002 else if (element == EL_DC_KEY_WHITE)
14004 player->num_white_keys++;
14006 // display white keys?
14007 // DrawGameDoorValues();
14009 else if (IS_ENVELOPE(element))
14011 player->show_envelope = element;
14013 else if (element == EL_EMC_LENSES)
14015 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14017 RedrawAllInvisibleElementsForLenses();
14019 else if (element == EL_EMC_MAGNIFIER)
14021 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14023 RedrawAllInvisibleElementsForMagnifier();
14025 else if (IS_DROPPABLE(element) ||
14026 IS_THROWABLE(element)) // can be collected and dropped
14030 if (collect_count == 0)
14031 player->inventory_infinite_element = element;
14033 for (i = 0; i < collect_count; i++)
14034 if (player->inventory_size < MAX_INVENTORY_SIZE)
14035 player->inventory_element[player->inventory_size++] = element;
14037 DrawGameDoorValues();
14039 else if (collect_count > 0)
14041 game.gems_still_needed -= collect_count;
14042 if (game.gems_still_needed < 0)
14043 game.gems_still_needed = 0;
14045 game.snapshot.collected_item = TRUE;
14047 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14049 DisplayGameControlValues();
14052 RaiseScoreElement(element);
14053 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14056 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14057 player->index_bit, dig_side);
14059 if (mode == DF_SNAP)
14061 if (level.block_snap_field)
14062 setFieldForSnapping(x, y, element, move_direction);
14064 TestIfElementTouchesCustomElement(x, y); // for empty space
14066 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14067 player->index_bit, dig_side);
14070 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14072 if (mode == DF_SNAP && element != EL_BD_ROCK)
14073 return MP_NO_ACTION;
14075 if (CAN_FALL(element) && dy)
14076 return MP_NO_ACTION;
14078 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14079 !(element == EL_SPRING && level.use_spring_bug))
14080 return MP_NO_ACTION;
14082 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14083 ((move_direction & MV_VERTICAL &&
14084 ((element_info[element].move_pattern & MV_LEFT &&
14085 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14086 (element_info[element].move_pattern & MV_RIGHT &&
14087 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14088 (move_direction & MV_HORIZONTAL &&
14089 ((element_info[element].move_pattern & MV_UP &&
14090 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14091 (element_info[element].move_pattern & MV_DOWN &&
14092 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14093 return MP_NO_ACTION;
14095 // do not push elements already moving away faster than player
14096 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14097 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14098 return MP_NO_ACTION;
14100 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14102 if (player->push_delay_value == -1 || !player_was_pushing)
14103 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14105 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14107 if (player->push_delay_value == -1)
14108 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14110 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14112 if (!player->is_pushing)
14113 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14116 player->is_pushing = TRUE;
14117 player->is_active = TRUE;
14119 if (!(IN_LEV_FIELD(nextx, nexty) &&
14120 (IS_FREE(nextx, nexty) ||
14121 (IS_SB_ELEMENT(element) &&
14122 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14123 (IS_CUSTOM_ELEMENT(element) &&
14124 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14125 return MP_NO_ACTION;
14127 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14128 return MP_NO_ACTION;
14130 if (player->push_delay == -1) // new pushing; restart delay
14131 player->push_delay = 0;
14133 if (player->push_delay < player->push_delay_value &&
14134 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14135 element != EL_SPRING && element != EL_BALLOON)
14137 // make sure that there is no move delay before next try to push
14138 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14139 player->move_delay = 0;
14141 return MP_NO_ACTION;
14144 if (IS_CUSTOM_ELEMENT(element) &&
14145 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14147 if (!DigFieldByCE(nextx, nexty, element))
14148 return MP_NO_ACTION;
14151 if (IS_SB_ELEMENT(element))
14153 boolean sokoban_task_solved = FALSE;
14155 if (element == EL_SOKOBAN_FIELD_FULL)
14157 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14159 IncrementSokobanFieldsNeeded();
14160 IncrementSokobanObjectsNeeded();
14163 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14165 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14167 DecrementSokobanFieldsNeeded();
14168 DecrementSokobanObjectsNeeded();
14170 // sokoban object was pushed from empty field to sokoban field
14171 if (Back[x][y] == EL_EMPTY)
14172 sokoban_task_solved = TRUE;
14175 Feld[x][y] = EL_SOKOBAN_OBJECT;
14177 if (Back[x][y] == Back[nextx][nexty])
14178 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14179 else if (Back[x][y] != 0)
14180 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14183 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14186 if (sokoban_task_solved &&
14187 game.sokoban_fields_still_needed == 0 &&
14188 game.sokoban_objects_still_needed == 0 &&
14189 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14191 game.players_still_needed = 0;
14195 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14199 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14201 InitMovingField(x, y, move_direction);
14202 GfxAction[x][y] = ACTION_PUSHING;
14204 if (mode == DF_SNAP)
14205 ContinueMoving(x, y);
14207 MovPos[x][y] = (dx != 0 ? dx : dy);
14209 Pushed[x][y] = TRUE;
14210 Pushed[nextx][nexty] = TRUE;
14212 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14213 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14215 player->push_delay_value = -1; // get new value later
14217 // check for element change _after_ element has been pushed
14218 if (game.use_change_when_pushing_bug)
14220 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14221 player->index_bit, dig_side);
14222 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14223 player->index_bit, dig_side);
14226 else if (IS_SWITCHABLE(element))
14228 if (PLAYER_SWITCHING(player, x, y))
14230 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14231 player->index_bit, dig_side);
14236 player->is_switching = TRUE;
14237 player->switch_x = x;
14238 player->switch_y = y;
14240 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14242 if (element == EL_ROBOT_WHEEL)
14244 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14246 game.robot_wheel_x = x;
14247 game.robot_wheel_y = y;
14248 game.robot_wheel_active = TRUE;
14250 TEST_DrawLevelField(x, y);
14252 else if (element == EL_SP_TERMINAL)
14256 SCAN_PLAYFIELD(xx, yy)
14258 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14262 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14264 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14266 ResetGfxAnimation(xx, yy);
14267 TEST_DrawLevelField(xx, yy);
14271 else if (IS_BELT_SWITCH(element))
14273 ToggleBeltSwitch(x, y);
14275 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14276 element == EL_SWITCHGATE_SWITCH_DOWN ||
14277 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14278 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14280 ToggleSwitchgateSwitch(x, y);
14282 else if (element == EL_LIGHT_SWITCH ||
14283 element == EL_LIGHT_SWITCH_ACTIVE)
14285 ToggleLightSwitch(x, y);
14287 else if (element == EL_TIMEGATE_SWITCH ||
14288 element == EL_DC_TIMEGATE_SWITCH)
14290 ActivateTimegateSwitch(x, y);
14292 else if (element == EL_BALLOON_SWITCH_LEFT ||
14293 element == EL_BALLOON_SWITCH_RIGHT ||
14294 element == EL_BALLOON_SWITCH_UP ||
14295 element == EL_BALLOON_SWITCH_DOWN ||
14296 element == EL_BALLOON_SWITCH_NONE ||
14297 element == EL_BALLOON_SWITCH_ANY)
14299 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14300 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14301 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14302 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14303 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14306 else if (element == EL_LAMP)
14308 Feld[x][y] = EL_LAMP_ACTIVE;
14309 game.lights_still_needed--;
14311 ResetGfxAnimation(x, y);
14312 TEST_DrawLevelField(x, y);
14314 else if (element == EL_TIME_ORB_FULL)
14316 Feld[x][y] = EL_TIME_ORB_EMPTY;
14318 if (level.time > 0 || level.use_time_orb_bug)
14320 TimeLeft += level.time_orb_time;
14321 game.no_time_limit = FALSE;
14323 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14325 DisplayGameControlValues();
14328 ResetGfxAnimation(x, y);
14329 TEST_DrawLevelField(x, y);
14331 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14332 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14336 game.ball_active = !game.ball_active;
14338 SCAN_PLAYFIELD(xx, yy)
14340 int e = Feld[xx][yy];
14342 if (game.ball_active)
14344 if (e == EL_EMC_MAGIC_BALL)
14345 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14346 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14347 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14351 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14352 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14353 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14354 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14359 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14360 player->index_bit, dig_side);
14362 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14363 player->index_bit, dig_side);
14365 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14366 player->index_bit, dig_side);
14372 if (!PLAYER_SWITCHING(player, x, y))
14374 player->is_switching = TRUE;
14375 player->switch_x = x;
14376 player->switch_y = y;
14378 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14379 player->index_bit, dig_side);
14380 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14381 player->index_bit, dig_side);
14383 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14384 player->index_bit, dig_side);
14385 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14386 player->index_bit, dig_side);
14389 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14390 player->index_bit, dig_side);
14391 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14392 player->index_bit, dig_side);
14394 return MP_NO_ACTION;
14397 player->push_delay = -1;
14399 if (is_player) // function can also be called by EL_PENGUIN
14401 if (Feld[x][y] != element) // really digged/collected something
14403 player->is_collecting = !player->is_digging;
14404 player->is_active = TRUE;
14411 static boolean DigFieldByCE(int x, int y, int digging_element)
14413 int element = Feld[x][y];
14415 if (!IS_FREE(x, y))
14417 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14418 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14421 // no element can dig solid indestructible elements
14422 if (IS_INDESTRUCTIBLE(element) &&
14423 !IS_DIGGABLE(element) &&
14424 !IS_COLLECTIBLE(element))
14427 if (AmoebaNr[x][y] &&
14428 (element == EL_AMOEBA_FULL ||
14429 element == EL_BD_AMOEBA ||
14430 element == EL_AMOEBA_GROWING))
14432 AmoebaCnt[AmoebaNr[x][y]]--;
14433 AmoebaCnt2[AmoebaNr[x][y]]--;
14436 if (IS_MOVING(x, y))
14437 RemoveMovingField(x, y);
14441 TEST_DrawLevelField(x, y);
14444 // if digged element was about to explode, prevent the explosion
14445 ExplodeField[x][y] = EX_TYPE_NONE;
14447 PlayLevelSoundAction(x, y, action);
14450 Store[x][y] = EL_EMPTY;
14452 // this makes it possible to leave the removed element again
14453 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14454 Store[x][y] = element;
14459 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14461 int jx = player->jx, jy = player->jy;
14462 int x = jx + dx, y = jy + dy;
14463 int snap_direction = (dx == -1 ? MV_LEFT :
14464 dx == +1 ? MV_RIGHT :
14466 dy == +1 ? MV_DOWN : MV_NONE);
14467 boolean can_continue_snapping = (level.continuous_snapping &&
14468 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14470 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14473 if (!player->active || !IN_LEV_FIELD(x, y))
14481 if (player->MovPos == 0)
14482 player->is_pushing = FALSE;
14484 player->is_snapping = FALSE;
14486 if (player->MovPos == 0)
14488 player->is_moving = FALSE;
14489 player->is_digging = FALSE;
14490 player->is_collecting = FALSE;
14496 // prevent snapping with already pressed snap key when not allowed
14497 if (player->is_snapping && !can_continue_snapping)
14500 player->MovDir = snap_direction;
14502 if (player->MovPos == 0)
14504 player->is_moving = FALSE;
14505 player->is_digging = FALSE;
14506 player->is_collecting = FALSE;
14509 player->is_dropping = FALSE;
14510 player->is_dropping_pressed = FALSE;
14511 player->drop_pressed_delay = 0;
14513 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14516 player->is_snapping = TRUE;
14517 player->is_active = TRUE;
14519 if (player->MovPos == 0)
14521 player->is_moving = FALSE;
14522 player->is_digging = FALSE;
14523 player->is_collecting = FALSE;
14526 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14527 TEST_DrawLevelField(player->last_jx, player->last_jy);
14529 TEST_DrawLevelField(x, y);
14534 static boolean DropElement(struct PlayerInfo *player)
14536 int old_element, new_element;
14537 int dropx = player->jx, dropy = player->jy;
14538 int drop_direction = player->MovDir;
14539 int drop_side = drop_direction;
14540 int drop_element = get_next_dropped_element(player);
14542 /* do not drop an element on top of another element; when holding drop key
14543 pressed without moving, dropped element must move away before the next
14544 element can be dropped (this is especially important if the next element
14545 is dynamite, which can be placed on background for historical reasons) */
14546 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14549 if (IS_THROWABLE(drop_element))
14551 dropx += GET_DX_FROM_DIR(drop_direction);
14552 dropy += GET_DY_FROM_DIR(drop_direction);
14554 if (!IN_LEV_FIELD(dropx, dropy))
14558 old_element = Feld[dropx][dropy]; // old element at dropping position
14559 new_element = drop_element; // default: no change when dropping
14561 // check if player is active, not moving and ready to drop
14562 if (!player->active || player->MovPos || player->drop_delay > 0)
14565 // check if player has anything that can be dropped
14566 if (new_element == EL_UNDEFINED)
14569 // only set if player has anything that can be dropped
14570 player->is_dropping_pressed = TRUE;
14572 // check if drop key was pressed long enough for EM style dynamite
14573 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14576 // check if anything can be dropped at the current position
14577 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14580 // collected custom elements can only be dropped on empty fields
14581 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14584 if (old_element != EL_EMPTY)
14585 Back[dropx][dropy] = old_element; // store old element on this field
14587 ResetGfxAnimation(dropx, dropy);
14588 ResetRandomAnimationValue(dropx, dropy);
14590 if (player->inventory_size > 0 ||
14591 player->inventory_infinite_element != EL_UNDEFINED)
14593 if (player->inventory_size > 0)
14595 player->inventory_size--;
14597 DrawGameDoorValues();
14599 if (new_element == EL_DYNAMITE)
14600 new_element = EL_DYNAMITE_ACTIVE;
14601 else if (new_element == EL_EM_DYNAMITE)
14602 new_element = EL_EM_DYNAMITE_ACTIVE;
14603 else if (new_element == EL_SP_DISK_RED)
14604 new_element = EL_SP_DISK_RED_ACTIVE;
14607 Feld[dropx][dropy] = new_element;
14609 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14610 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14611 el2img(Feld[dropx][dropy]), 0);
14613 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14615 // needed if previous element just changed to "empty" in the last frame
14616 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14618 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14619 player->index_bit, drop_side);
14620 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14622 player->index_bit, drop_side);
14624 TestIfElementTouchesCustomElement(dropx, dropy);
14626 else // player is dropping a dyna bomb
14628 player->dynabombs_left--;
14630 Feld[dropx][dropy] = new_element;
14632 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14633 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14634 el2img(Feld[dropx][dropy]), 0);
14636 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14639 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14640 InitField_WithBug1(dropx, dropy, FALSE);
14642 new_element = Feld[dropx][dropy]; // element might have changed
14644 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14645 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14647 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14648 MovDir[dropx][dropy] = drop_direction;
14650 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14652 // do not cause impact style collision by dropping elements that can fall
14653 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14656 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14657 player->is_dropping = TRUE;
14659 player->drop_pressed_delay = 0;
14660 player->is_dropping_pressed = FALSE;
14662 player->drop_x = dropx;
14663 player->drop_y = dropy;
14668 // ----------------------------------------------------------------------------
14669 // game sound playing functions
14670 // ----------------------------------------------------------------------------
14672 static int *loop_sound_frame = NULL;
14673 static int *loop_sound_volume = NULL;
14675 void InitPlayLevelSound(void)
14677 int num_sounds = getSoundListSize();
14679 checked_free(loop_sound_frame);
14680 checked_free(loop_sound_volume);
14682 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14683 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14686 static void PlayLevelSound(int x, int y, int nr)
14688 int sx = SCREENX(x), sy = SCREENY(y);
14689 int volume, stereo_position;
14690 int max_distance = 8;
14691 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14693 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14694 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14697 if (!IN_LEV_FIELD(x, y) ||
14698 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14699 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14702 volume = SOUND_MAX_VOLUME;
14704 if (!IN_SCR_FIELD(sx, sy))
14706 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14707 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14709 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14712 stereo_position = (SOUND_MAX_LEFT +
14713 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14714 (SCR_FIELDX + 2 * max_distance));
14716 if (IS_LOOP_SOUND(nr))
14718 /* This assures that quieter loop sounds do not overwrite louder ones,
14719 while restarting sound volume comparison with each new game frame. */
14721 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14724 loop_sound_volume[nr] = volume;
14725 loop_sound_frame[nr] = FrameCounter;
14728 PlaySoundExt(nr, volume, stereo_position, type);
14731 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14733 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14734 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14735 y < LEVELY(BY1) ? LEVELY(BY1) :
14736 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14740 static void PlayLevelSoundAction(int x, int y, int action)
14742 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14745 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14747 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14749 if (sound_effect != SND_UNDEFINED)
14750 PlayLevelSound(x, y, sound_effect);
14753 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14756 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14758 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14759 PlayLevelSound(x, y, sound_effect);
14762 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14764 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14766 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14767 PlayLevelSound(x, y, sound_effect);
14770 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14772 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14774 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14775 StopSound(sound_effect);
14778 static int getLevelMusicNr(void)
14780 if (levelset.music[level_nr] != MUS_UNDEFINED)
14781 return levelset.music[level_nr]; // from config file
14783 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14786 static void FadeLevelSounds(void)
14791 static void FadeLevelMusic(void)
14793 int music_nr = getLevelMusicNr();
14794 char *curr_music = getCurrentlyPlayingMusicFilename();
14795 char *next_music = getMusicInfoEntryFilename(music_nr);
14797 if (!strEqual(curr_music, next_music))
14801 void FadeLevelSoundsAndMusic(void)
14807 static void PlayLevelMusic(void)
14809 int music_nr = getLevelMusicNr();
14810 char *curr_music = getCurrentlyPlayingMusicFilename();
14811 char *next_music = getMusicInfoEntryFilename(music_nr);
14813 if (!strEqual(curr_music, next_music))
14814 PlayMusicLoop(music_nr);
14817 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14819 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14820 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14821 int x = xx - 1 - offset;
14822 int y = yy - 1 - offset;
14827 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14831 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14835 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14839 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14843 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14847 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14851 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14854 case SOUND_android_clone:
14855 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14858 case SOUND_android_move:
14859 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14863 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14867 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14871 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14874 case SOUND_eater_eat:
14875 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14879 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14882 case SOUND_collect:
14883 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14886 case SOUND_diamond:
14887 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14891 // !!! CHECK THIS !!!
14893 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14895 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14899 case SOUND_wonderfall:
14900 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14904 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14908 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14912 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14916 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14920 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14924 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14928 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14932 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14935 case SOUND_exit_open:
14936 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14939 case SOUND_exit_leave:
14940 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14943 case SOUND_dynamite:
14944 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14948 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14952 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14956 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14960 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14964 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14968 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14972 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14977 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14979 int element = map_element_SP_to_RND(element_sp);
14980 int action = map_action_SP_to_RND(action_sp);
14981 int offset = (setup.sp_show_border_elements ? 0 : 1);
14982 int x = xx - offset;
14983 int y = yy - offset;
14985 PlayLevelSoundElementAction(x, y, element, action);
14988 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14990 int element = map_element_MM_to_RND(element_mm);
14991 int action = map_action_MM_to_RND(action_mm);
14993 int x = xx - offset;
14994 int y = yy - offset;
14996 if (!IS_MM_ELEMENT(element))
14997 element = EL_MM_DEFAULT;
14999 PlayLevelSoundElementAction(x, y, element, action);
15002 void PlaySound_MM(int sound_mm)
15004 int sound = map_sound_MM_to_RND(sound_mm);
15006 if (sound == SND_UNDEFINED)
15012 void PlaySoundLoop_MM(int sound_mm)
15014 int sound = map_sound_MM_to_RND(sound_mm);
15016 if (sound == SND_UNDEFINED)
15019 PlaySoundLoop(sound);
15022 void StopSound_MM(int sound_mm)
15024 int sound = map_sound_MM_to_RND(sound_mm);
15026 if (sound == SND_UNDEFINED)
15032 void RaiseScore(int value)
15034 game.score += value;
15036 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15038 DisplayGameControlValues();
15041 void RaiseScoreElement(int element)
15046 case EL_BD_DIAMOND:
15047 case EL_EMERALD_YELLOW:
15048 case EL_EMERALD_RED:
15049 case EL_EMERALD_PURPLE:
15050 case EL_SP_INFOTRON:
15051 RaiseScore(level.score[SC_EMERALD]);
15054 RaiseScore(level.score[SC_DIAMOND]);
15057 RaiseScore(level.score[SC_CRYSTAL]);
15060 RaiseScore(level.score[SC_PEARL]);
15063 case EL_BD_BUTTERFLY:
15064 case EL_SP_ELECTRON:
15065 RaiseScore(level.score[SC_BUG]);
15068 case EL_BD_FIREFLY:
15069 case EL_SP_SNIKSNAK:
15070 RaiseScore(level.score[SC_SPACESHIP]);
15073 case EL_DARK_YAMYAM:
15074 RaiseScore(level.score[SC_YAMYAM]);
15077 RaiseScore(level.score[SC_ROBOT]);
15080 RaiseScore(level.score[SC_PACMAN]);
15083 RaiseScore(level.score[SC_NUT]);
15086 case EL_EM_DYNAMITE:
15087 case EL_SP_DISK_RED:
15088 case EL_DYNABOMB_INCREASE_NUMBER:
15089 case EL_DYNABOMB_INCREASE_SIZE:
15090 case EL_DYNABOMB_INCREASE_POWER:
15091 RaiseScore(level.score[SC_DYNAMITE]);
15093 case EL_SHIELD_NORMAL:
15094 case EL_SHIELD_DEADLY:
15095 RaiseScore(level.score[SC_SHIELD]);
15097 case EL_EXTRA_TIME:
15098 RaiseScore(level.extra_time_score);
15112 case EL_DC_KEY_WHITE:
15113 RaiseScore(level.score[SC_KEY]);
15116 RaiseScore(element_info[element].collect_score);
15121 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15123 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15125 // closing door required in case of envelope style request dialogs
15128 // prevent short reactivation of overlay buttons while closing door
15129 SetOverlayActive(FALSE);
15131 CloseDoor(DOOR_CLOSE_1);
15134 if (network.enabled)
15135 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15139 FadeSkipNextFadeIn();
15141 SetGameStatus(GAME_MODE_MAIN);
15146 else // continue playing the game
15148 if (tape.playing && tape.deactivate_display)
15149 TapeDeactivateDisplayOff(TRUE);
15151 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15153 if (tape.playing && tape.deactivate_display)
15154 TapeDeactivateDisplayOn();
15158 void RequestQuitGame(boolean ask_if_really_quit)
15160 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15161 boolean skip_request = game.all_players_gone || quick_quit;
15163 RequestQuitGameExt(skip_request, quick_quit,
15164 "Do you really want to quit the game?");
15167 void RequestRestartGame(char *message)
15169 game.restart_game_message = NULL;
15171 boolean has_started_game = hasStartedNetworkGame();
15172 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15174 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15176 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15180 SetGameStatus(GAME_MODE_MAIN);
15186 void CheckGameOver(void)
15188 static boolean last_game_over = FALSE;
15189 static int game_over_delay = 0;
15190 int game_over_delay_value = 50;
15191 boolean game_over = checkGameFailed();
15193 // do not handle game over if request dialog is already active
15194 if (game.request_active)
15197 // do not ask to play again if game was never actually played
15198 if (!game.GamePlayed)
15203 last_game_over = FALSE;
15204 game_over_delay = game_over_delay_value;
15209 if (game_over_delay > 0)
15216 if (last_game_over != game_over)
15217 game.restart_game_message = (hasStartedNetworkGame() ?
15218 "Game over! Play it again?" :
15221 last_game_over = game_over;
15224 boolean checkGameSolved(void)
15226 // set for all game engines if level was solved
15227 return game.LevelSolved_GameEnd;
15230 boolean checkGameFailed(void)
15232 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15233 return (game_em.game_over && !game_em.level_solved);
15234 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15235 return (game_sp.game_over && !game_sp.level_solved);
15236 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15237 return (game_mm.game_over && !game_mm.level_solved);
15238 else // GAME_ENGINE_TYPE_RND
15239 return (game.GameOver && !game.LevelSolved);
15242 boolean checkGameEnded(void)
15244 return (checkGameSolved() || checkGameFailed());
15248 // ----------------------------------------------------------------------------
15249 // random generator functions
15250 // ----------------------------------------------------------------------------
15252 unsigned int InitEngineRandom_RND(int seed)
15254 game.num_random_calls = 0;
15256 return InitEngineRandom(seed);
15259 unsigned int RND(int max)
15263 game.num_random_calls++;
15265 return GetEngineRandom(max);
15272 // ----------------------------------------------------------------------------
15273 // game engine snapshot handling functions
15274 // ----------------------------------------------------------------------------
15276 struct EngineSnapshotInfo
15278 // runtime values for custom element collect score
15279 int collect_score[NUM_CUSTOM_ELEMENTS];
15281 // runtime values for group element choice position
15282 int choice_pos[NUM_GROUP_ELEMENTS];
15284 // runtime values for belt position animations
15285 int belt_graphic[4][NUM_BELT_PARTS];
15286 int belt_anim_mode[4][NUM_BELT_PARTS];
15289 static struct EngineSnapshotInfo engine_snapshot_rnd;
15290 static char *snapshot_level_identifier = NULL;
15291 static int snapshot_level_nr = -1;
15293 static void SaveEngineSnapshotValues_RND(void)
15295 static int belt_base_active_element[4] =
15297 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15298 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15299 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15300 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15304 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15306 int element = EL_CUSTOM_START + i;
15308 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15311 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15313 int element = EL_GROUP_START + i;
15315 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15318 for (i = 0; i < 4; i++)
15320 for (j = 0; j < NUM_BELT_PARTS; j++)
15322 int element = belt_base_active_element[i] + j;
15323 int graphic = el2img(element);
15324 int anim_mode = graphic_info[graphic].anim_mode;
15326 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15327 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15332 static void LoadEngineSnapshotValues_RND(void)
15334 unsigned int num_random_calls = game.num_random_calls;
15337 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15339 int element = EL_CUSTOM_START + i;
15341 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15344 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15346 int element = EL_GROUP_START + i;
15348 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15351 for (i = 0; i < 4; i++)
15353 for (j = 0; j < NUM_BELT_PARTS; j++)
15355 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15356 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15358 graphic_info[graphic].anim_mode = anim_mode;
15362 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15364 InitRND(tape.random_seed);
15365 for (i = 0; i < num_random_calls; i++)
15369 if (game.num_random_calls != num_random_calls)
15371 Error(ERR_INFO, "number of random calls out of sync");
15372 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15373 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15374 Error(ERR_EXIT, "this should not happen -- please debug");
15378 void FreeEngineSnapshotSingle(void)
15380 FreeSnapshotSingle();
15382 setString(&snapshot_level_identifier, NULL);
15383 snapshot_level_nr = -1;
15386 void FreeEngineSnapshotList(void)
15388 FreeSnapshotList();
15391 static ListNode *SaveEngineSnapshotBuffers(void)
15393 ListNode *buffers = NULL;
15395 // copy some special values to a structure better suited for the snapshot
15397 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15398 SaveEngineSnapshotValues_RND();
15399 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15400 SaveEngineSnapshotValues_EM();
15401 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15402 SaveEngineSnapshotValues_SP(&buffers);
15403 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15404 SaveEngineSnapshotValues_MM(&buffers);
15406 // save values stored in special snapshot structure
15408 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15409 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15410 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15411 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15412 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15413 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15414 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15415 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15417 // save further RND engine values
15419 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15420 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15421 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15423 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15424 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15425 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15426 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15427 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15429 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15430 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15431 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15433 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15435 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15436 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15438 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15439 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15440 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15441 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15442 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15443 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15444 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15445 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15446 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15447 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15448 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15449 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15450 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15451 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15452 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15453 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15454 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15455 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15457 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15458 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15460 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15461 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15462 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15464 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15465 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15467 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15469 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15470 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15471 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15474 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15477 ListNode *node = engine_snapshot_list_rnd;
15480 while (node != NULL)
15482 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15487 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15493 void SaveEngineSnapshotSingle(void)
15495 ListNode *buffers = SaveEngineSnapshotBuffers();
15497 // finally save all snapshot buffers to single snapshot
15498 SaveSnapshotSingle(buffers);
15500 // save level identification information
15501 setString(&snapshot_level_identifier, leveldir_current->identifier);
15502 snapshot_level_nr = level_nr;
15505 boolean CheckSaveEngineSnapshotToList(void)
15507 boolean save_snapshot =
15508 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15509 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15510 game.snapshot.changed_action) ||
15511 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15512 game.snapshot.collected_item));
15514 game.snapshot.changed_action = FALSE;
15515 game.snapshot.collected_item = FALSE;
15516 game.snapshot.save_snapshot = save_snapshot;
15518 return save_snapshot;
15521 void SaveEngineSnapshotToList(void)
15523 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15527 ListNode *buffers = SaveEngineSnapshotBuffers();
15529 // finally save all snapshot buffers to snapshot list
15530 SaveSnapshotToList(buffers);
15533 void SaveEngineSnapshotToListInitial(void)
15535 FreeEngineSnapshotList();
15537 SaveEngineSnapshotToList();
15540 static void LoadEngineSnapshotValues(void)
15542 // restore special values from snapshot structure
15544 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15545 LoadEngineSnapshotValues_RND();
15546 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15547 LoadEngineSnapshotValues_EM();
15548 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15549 LoadEngineSnapshotValues_SP();
15550 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15551 LoadEngineSnapshotValues_MM();
15554 void LoadEngineSnapshotSingle(void)
15556 LoadSnapshotSingle();
15558 LoadEngineSnapshotValues();
15561 static void LoadEngineSnapshot_Undo(int steps)
15563 LoadSnapshotFromList_Older(steps);
15565 LoadEngineSnapshotValues();
15568 static void LoadEngineSnapshot_Redo(int steps)
15570 LoadSnapshotFromList_Newer(steps);
15572 LoadEngineSnapshotValues();
15575 boolean CheckEngineSnapshotSingle(void)
15577 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15578 snapshot_level_nr == level_nr);
15581 boolean CheckEngineSnapshotList(void)
15583 return CheckSnapshotList();
15587 // ---------- new game button stuff -------------------------------------------
15594 boolean *setup_value;
15595 boolean allowed_on_tape;
15596 boolean is_touch_button;
15598 } gamebutton_info[NUM_GAME_BUTTONS] =
15601 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15602 GAME_CTRL_ID_STOP, NULL,
15603 TRUE, FALSE, "stop game"
15606 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15607 GAME_CTRL_ID_PAUSE, NULL,
15608 TRUE, FALSE, "pause game"
15611 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15612 GAME_CTRL_ID_PLAY, NULL,
15613 TRUE, FALSE, "play game"
15616 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15617 GAME_CTRL_ID_UNDO, NULL,
15618 TRUE, FALSE, "undo step"
15621 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15622 GAME_CTRL_ID_REDO, NULL,
15623 TRUE, FALSE, "redo step"
15626 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15627 GAME_CTRL_ID_SAVE, NULL,
15628 TRUE, FALSE, "save game"
15631 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15632 GAME_CTRL_ID_PAUSE2, NULL,
15633 TRUE, FALSE, "pause game"
15636 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15637 GAME_CTRL_ID_LOAD, NULL,
15638 TRUE, FALSE, "load game"
15641 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15642 GAME_CTRL_ID_PANEL_STOP, NULL,
15643 FALSE, FALSE, "stop game"
15646 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15647 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15648 FALSE, FALSE, "pause game"
15651 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15652 GAME_CTRL_ID_PANEL_PLAY, NULL,
15653 FALSE, FALSE, "play game"
15656 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15657 GAME_CTRL_ID_TOUCH_STOP, NULL,
15658 FALSE, TRUE, "stop game"
15661 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15662 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15663 FALSE, TRUE, "pause game"
15666 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15667 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15668 TRUE, FALSE, "background music on/off"
15671 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15672 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15673 TRUE, FALSE, "sound loops on/off"
15676 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15677 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15678 TRUE, FALSE, "normal sounds on/off"
15681 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15682 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15683 FALSE, FALSE, "background music on/off"
15686 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15687 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15688 FALSE, FALSE, "sound loops on/off"
15691 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15692 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15693 FALSE, FALSE, "normal sounds on/off"
15697 void CreateGameButtons(void)
15701 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15703 int graphic = gamebutton_info[i].graphic;
15704 struct GraphicInfo *gfx = &graphic_info[graphic];
15705 struct XY *pos = gamebutton_info[i].pos;
15706 struct GadgetInfo *gi;
15709 unsigned int event_mask;
15710 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15711 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15712 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15713 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15714 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15715 int gd_x = gfx->src_x;
15716 int gd_y = gfx->src_y;
15717 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15718 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15719 int gd_xa = gfx->src_x + gfx->active_xoffset;
15720 int gd_ya = gfx->src_y + gfx->active_yoffset;
15721 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15722 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15723 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15724 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15727 if (gfx->bitmap == NULL)
15729 game_gadget[id] = NULL;
15734 if (id == GAME_CTRL_ID_STOP ||
15735 id == GAME_CTRL_ID_PANEL_STOP ||
15736 id == GAME_CTRL_ID_TOUCH_STOP ||
15737 id == GAME_CTRL_ID_PLAY ||
15738 id == GAME_CTRL_ID_PANEL_PLAY ||
15739 id == GAME_CTRL_ID_SAVE ||
15740 id == GAME_CTRL_ID_LOAD)
15742 button_type = GD_TYPE_NORMAL_BUTTON;
15744 event_mask = GD_EVENT_RELEASED;
15746 else if (id == GAME_CTRL_ID_UNDO ||
15747 id == GAME_CTRL_ID_REDO)
15749 button_type = GD_TYPE_NORMAL_BUTTON;
15751 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15755 button_type = GD_TYPE_CHECK_BUTTON;
15756 checked = (gamebutton_info[i].setup_value != NULL ?
15757 *gamebutton_info[i].setup_value : FALSE);
15758 event_mask = GD_EVENT_PRESSED;
15761 gi = CreateGadget(GDI_CUSTOM_ID, id,
15762 GDI_IMAGE_ID, graphic,
15763 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15766 GDI_WIDTH, gfx->width,
15767 GDI_HEIGHT, gfx->height,
15768 GDI_TYPE, button_type,
15769 GDI_STATE, GD_BUTTON_UNPRESSED,
15770 GDI_CHECKED, checked,
15771 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15772 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15773 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15774 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15775 GDI_DIRECT_DRAW, FALSE,
15776 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15777 GDI_EVENT_MASK, event_mask,
15778 GDI_CALLBACK_ACTION, HandleGameButtons,
15782 Error(ERR_EXIT, "cannot create gadget");
15784 game_gadget[id] = gi;
15788 void FreeGameButtons(void)
15792 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15793 FreeGadget(game_gadget[i]);
15796 static void UnmapGameButtonsAtSamePosition(int id)
15800 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15802 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15803 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15804 UnmapGadget(game_gadget[i]);
15807 static void UnmapGameButtonsAtSamePosition_All(void)
15809 if (setup.show_snapshot_buttons)
15811 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15812 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15813 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15817 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15818 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15819 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15821 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15822 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15823 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15827 static void MapGameButtonsAtSamePosition(int id)
15831 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15833 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15834 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15835 MapGadget(game_gadget[i]);
15837 UnmapGameButtonsAtSamePosition_All();
15840 void MapUndoRedoButtons(void)
15842 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15843 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15845 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15846 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15849 void UnmapUndoRedoButtons(void)
15851 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15852 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15854 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15855 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15858 void ModifyPauseButtons(void)
15862 GAME_CTRL_ID_PAUSE,
15863 GAME_CTRL_ID_PAUSE2,
15864 GAME_CTRL_ID_PANEL_PAUSE,
15865 GAME_CTRL_ID_TOUCH_PAUSE,
15870 for (i = 0; ids[i] > -1; i++)
15871 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15874 static void MapGameButtonsExt(boolean on_tape)
15878 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15879 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15880 i != GAME_CTRL_ID_UNDO &&
15881 i != GAME_CTRL_ID_REDO)
15882 MapGadget(game_gadget[i]);
15884 UnmapGameButtonsAtSamePosition_All();
15886 RedrawGameButtons();
15889 static void UnmapGameButtonsExt(boolean on_tape)
15893 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15894 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15895 UnmapGadget(game_gadget[i]);
15898 static void RedrawGameButtonsExt(boolean on_tape)
15902 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15903 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15904 RedrawGadget(game_gadget[i]);
15907 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15912 gi->checked = state;
15915 static void RedrawSoundButtonGadget(int id)
15917 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15918 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15919 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15920 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15921 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15922 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15925 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15926 RedrawGadget(game_gadget[id2]);
15929 void MapGameButtons(void)
15931 MapGameButtonsExt(FALSE);
15934 void UnmapGameButtons(void)
15936 UnmapGameButtonsExt(FALSE);
15939 void RedrawGameButtons(void)
15941 RedrawGameButtonsExt(FALSE);
15944 void MapGameButtonsOnTape(void)
15946 MapGameButtonsExt(TRUE);
15949 void UnmapGameButtonsOnTape(void)
15951 UnmapGameButtonsExt(TRUE);
15954 void RedrawGameButtonsOnTape(void)
15956 RedrawGameButtonsExt(TRUE);
15959 static void GameUndoRedoExt(void)
15961 ClearPlayerAction();
15963 tape.pausing = TRUE;
15966 UpdateAndDisplayGameControlValues();
15968 DrawCompleteVideoDisplay();
15969 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15970 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15971 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15976 static void GameUndo(int steps)
15978 if (!CheckEngineSnapshotList())
15981 LoadEngineSnapshot_Undo(steps);
15986 static void GameRedo(int steps)
15988 if (!CheckEngineSnapshotList())
15991 LoadEngineSnapshot_Redo(steps);
15996 static void HandleGameButtonsExt(int id, int button)
15998 static boolean game_undo_executed = FALSE;
15999 int steps = BUTTON_STEPSIZE(button);
16000 boolean handle_game_buttons =
16001 (game_status == GAME_MODE_PLAYING ||
16002 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16004 if (!handle_game_buttons)
16009 case GAME_CTRL_ID_STOP:
16010 case GAME_CTRL_ID_PANEL_STOP:
16011 case GAME_CTRL_ID_TOUCH_STOP:
16012 if (game_status == GAME_MODE_MAIN)
16018 RequestQuitGame(TRUE);
16022 case GAME_CTRL_ID_PAUSE:
16023 case GAME_CTRL_ID_PAUSE2:
16024 case GAME_CTRL_ID_PANEL_PAUSE:
16025 case GAME_CTRL_ID_TOUCH_PAUSE:
16026 if (network.enabled && game_status == GAME_MODE_PLAYING)
16029 SendToServer_ContinuePlaying();
16031 SendToServer_PausePlaying();
16034 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16036 game_undo_executed = FALSE;
16040 case GAME_CTRL_ID_PLAY:
16041 case GAME_CTRL_ID_PANEL_PLAY:
16042 if (game_status == GAME_MODE_MAIN)
16044 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16046 else if (tape.pausing)
16048 if (network.enabled)
16049 SendToServer_ContinuePlaying();
16051 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16055 case GAME_CTRL_ID_UNDO:
16056 // Important: When using "save snapshot when collecting an item" mode,
16057 // load last (current) snapshot for first "undo" after pressing "pause"
16058 // (else the last-but-one snapshot would be loaded, because the snapshot
16059 // pointer already points to the last snapshot when pressing "pause",
16060 // which is fine for "every step/move" mode, but not for "every collect")
16061 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16062 !game_undo_executed)
16065 game_undo_executed = TRUE;
16070 case GAME_CTRL_ID_REDO:
16074 case GAME_CTRL_ID_SAVE:
16078 case GAME_CTRL_ID_LOAD:
16082 case SOUND_CTRL_ID_MUSIC:
16083 case SOUND_CTRL_ID_PANEL_MUSIC:
16084 if (setup.sound_music)
16086 setup.sound_music = FALSE;
16090 else if (audio.music_available)
16092 setup.sound = setup.sound_music = TRUE;
16094 SetAudioMode(setup.sound);
16096 if (game_status == GAME_MODE_PLAYING)
16100 RedrawSoundButtonGadget(id);
16104 case SOUND_CTRL_ID_LOOPS:
16105 case SOUND_CTRL_ID_PANEL_LOOPS:
16106 if (setup.sound_loops)
16107 setup.sound_loops = FALSE;
16108 else if (audio.loops_available)
16110 setup.sound = setup.sound_loops = TRUE;
16112 SetAudioMode(setup.sound);
16115 RedrawSoundButtonGadget(id);
16119 case SOUND_CTRL_ID_SIMPLE:
16120 case SOUND_CTRL_ID_PANEL_SIMPLE:
16121 if (setup.sound_simple)
16122 setup.sound_simple = FALSE;
16123 else if (audio.sound_available)
16125 setup.sound = setup.sound_simple = TRUE;
16127 SetAudioMode(setup.sound);
16130 RedrawSoundButtonGadget(id);
16139 static void HandleGameButtons(struct GadgetInfo *gi)
16141 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16144 void HandleSoundButtonKeys(Key key)
16146 if (key == setup.shortcut.sound_simple)
16147 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16148 else if (key == setup.shortcut.sound_loops)
16149 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16150 else if (key == setup.shortcut.sound_music)
16151 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);