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;
1788 // always check if player was just killed and should be reanimated
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = 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);
2844 printf("level %d: level.game_version == %06d\n", level_nr,
2845 level.game_version);
2846 printf(" tape.file_version == %06d\n",
2848 printf(" tape.game_version == %06d\n",
2850 printf(" tape.engine_version == %06d\n",
2851 tape.engine_version);
2852 printf(" => game.engine_version == %06d [tape mode: %s]\n",
2853 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2856 // --------------------------------------------------------------------------
2857 // set flags for bugs and changes according to active game engine version
2858 // --------------------------------------------------------------------------
2862 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2864 Bug was introduced in version:
2867 Bug was fixed in version:
2871 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2872 but the property "can fall" was missing, which caused some levels to be
2873 unsolvable. This was fixed in version 4.1.4.2.
2875 Affected levels/tapes:
2876 An example for a tape that was fixed by this bugfix is tape 029 from the
2877 level set "rnd_sam_bateman".
2878 The wrong behaviour will still be used for all levels or tapes that were
2879 created/recorded with it. An example for this is tape 023 from the level
2880 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2883 boolean use_amoeba_dropping_cannot_fall_bug =
2884 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2885 game.engine_version <= VERSION_IDENT(4,1,4,1)) ||
2887 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2888 tape.game_version <= VERSION_IDENT(4,1,4,1)));
2891 Summary of bugfix/change:
2892 Fixed move speed of elements entering or leaving magic wall.
2894 Fixed/changed in version:
2898 Before 2.0.1, move speed of elements entering or leaving magic wall was
2899 twice as fast as it is now.
2900 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2902 Affected levels/tapes:
2903 The first condition is generally needed for all levels/tapes before version
2904 2.0.1, which might use the old behaviour before it was changed; known tapes
2905 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2906 The second condition is an exception from the above case and is needed for
2907 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2908 above, but before it was known that this change would break tapes like the
2909 above and was fixed in 4.1.4.2, so that the changed behaviour was active
2910 although the engine version while recording maybe was before 2.0.1. There
2911 are a lot of tapes that are affected by this exception, like tape 006 from
2912 the level set "rnd_conor_mancone".
2915 boolean use_old_move_stepsize_for_magic_wall =
2916 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2918 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2919 tape.game_version < VERSION_IDENT(4,1,4,2)));
2922 Summary of bugfix/change:
2923 Fixed handling for custom elements that change when pushed by the player.
2925 Fixed/changed in version:
2929 Before 3.1.0, custom elements that "change when pushing" changed directly
2930 after the player started pushing them (until then handled in "DigField()").
2931 Since 3.1.0, these custom elements are not changed until the "pushing"
2932 move of the element is finished (now handled in "ContinueMoving()").
2934 Affected levels/tapes:
2935 The first condition is generally needed for all levels/tapes before version
2936 3.1.0, which might use the old behaviour before it was changed; known tapes
2937 that are affected are some tapes from the level set "Walpurgis Gardens" by
2939 The second condition is an exception from the above case and is needed for
2940 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2941 above (including some development versions of 3.1.0), but before it was
2942 known that this change would break tapes like the above and was fixed in
2943 3.1.1, so that the changed behaviour was active although the engine version
2944 while recording maybe was before 3.1.0. There is at least one tape that is
2945 affected by this exception, which is the tape for the one-level set "Bug
2946 Machine" by Juergen Bonhagen.
2949 game.use_change_when_pushing_bug =
2950 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2952 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2953 tape.game_version < VERSION_IDENT(3,1,1,0)));
2956 Summary of bugfix/change:
2957 Fixed handling for blocking the field the player leaves when moving.
2959 Fixed/changed in version:
2963 Before 3.1.1, when "block last field when moving" was enabled, the field
2964 the player is leaving when moving was blocked for the time of the move,
2965 and was directly unblocked afterwards. This resulted in the last field
2966 being blocked for exactly one less than the number of frames of one player
2967 move. Additionally, even when blocking was disabled, the last field was
2968 blocked for exactly one frame.
2969 Since 3.1.1, due to changes in player movement handling, the last field
2970 is not blocked at all when blocking is disabled. When blocking is enabled,
2971 the last field is blocked for exactly the number of frames of one player
2972 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2973 last field is blocked for exactly one more than the number of frames of
2976 Affected levels/tapes:
2977 (!!! yet to be determined -- probably many !!!)
2980 game.use_block_last_field_bug =
2981 (game.engine_version < VERSION_IDENT(3,1,1,0));
2983 /* various special flags and settings for native Emerald Mine game engine */
2985 game_em.use_single_button =
2986 (game.engine_version > VERSION_IDENT(4,0,0,2));
2988 game_em.use_snap_key_bug =
2989 (game.engine_version < VERSION_IDENT(4,0,1,0));
2991 game_em.use_old_explosions =
2992 (game.engine_version < VERSION_IDENT(4,1,4,2));
2994 // --------------------------------------------------------------------------
2996 // set maximal allowed number of custom element changes per game frame
2997 game.max_num_changes_per_frame = 1;
2999 // default scan direction: scan playfield from top/left to bottom/right
3000 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3002 // dynamically adjust element properties according to game engine version
3003 InitElementPropertiesEngine(game.engine_version);
3005 // ---------- initialize special element properties -------------------------
3007 // "EL_AMOEBA_DROPPING" missed property "can fall" between 2.0.1 and 4.1.4.1
3008 if (use_amoeba_dropping_cannot_fall_bug)
3009 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3011 // ---------- initialize player's initial move delay ------------------------
3013 // dynamically adjust player properties according to level information
3014 for (i = 0; i < MAX_PLAYERS; i++)
3015 game.initial_move_delay_value[i] =
3016 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3018 // dynamically adjust player properties according to game engine version
3019 for (i = 0; i < MAX_PLAYERS; i++)
3020 game.initial_move_delay[i] =
3021 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3022 game.initial_move_delay_value[i] : 0);
3024 // ---------- initialize player's initial push delay ------------------------
3026 // dynamically adjust player properties according to game engine version
3027 game.initial_push_delay_value =
3028 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3030 // ---------- initialize changing elements ----------------------------------
3032 // initialize changing elements information
3033 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3035 struct ElementInfo *ei = &element_info[i];
3037 // this pointer might have been changed in the level editor
3038 ei->change = &ei->change_page[0];
3040 if (!IS_CUSTOM_ELEMENT(i))
3042 ei->change->target_element = EL_EMPTY_SPACE;
3043 ei->change->delay_fixed = 0;
3044 ei->change->delay_random = 0;
3045 ei->change->delay_frames = 1;
3048 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3050 ei->has_change_event[j] = FALSE;
3052 ei->event_page_nr[j] = 0;
3053 ei->event_page[j] = &ei->change_page[0];
3057 // add changing elements from pre-defined list
3058 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3060 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3061 struct ElementInfo *ei = &element_info[ch_delay->element];
3063 ei->change->target_element = ch_delay->target_element;
3064 ei->change->delay_fixed = ch_delay->change_delay;
3066 ei->change->pre_change_function = ch_delay->pre_change_function;
3067 ei->change->change_function = ch_delay->change_function;
3068 ei->change->post_change_function = ch_delay->post_change_function;
3070 ei->change->can_change = TRUE;
3071 ei->change->can_change_or_has_action = TRUE;
3073 ei->has_change_event[CE_DELAY] = TRUE;
3075 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3076 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3079 // ---------- initialize internal run-time variables ------------------------
3081 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3083 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3085 for (j = 0; j < ei->num_change_pages; j++)
3087 ei->change_page[j].can_change_or_has_action =
3088 (ei->change_page[j].can_change |
3089 ei->change_page[j].has_action);
3093 // add change events from custom element configuration
3094 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3096 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3098 for (j = 0; j < ei->num_change_pages; j++)
3100 if (!ei->change_page[j].can_change_or_has_action)
3103 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3105 // only add event page for the first page found with this event
3106 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3108 ei->has_change_event[k] = TRUE;
3110 ei->event_page_nr[k] = j;
3111 ei->event_page[k] = &ei->change_page[j];
3117 // ---------- initialize reference elements in change conditions ------------
3119 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3121 int element = EL_CUSTOM_START + i;
3122 struct ElementInfo *ei = &element_info[element];
3124 for (j = 0; j < ei->num_change_pages; j++)
3126 int trigger_element = ei->change_page[j].initial_trigger_element;
3128 if (trigger_element >= EL_PREV_CE_8 &&
3129 trigger_element <= EL_NEXT_CE_8)
3130 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3132 ei->change_page[j].trigger_element = trigger_element;
3136 // ---------- initialize run-time trigger player and element ----------------
3138 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3140 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3142 for (j = 0; j < ei->num_change_pages; j++)
3144 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3145 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3146 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3147 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3148 ei->change_page[j].actual_trigger_ce_value = 0;
3149 ei->change_page[j].actual_trigger_ce_score = 0;
3153 // ---------- initialize trigger events -------------------------------------
3155 // initialize trigger events information
3156 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3157 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3158 trigger_events[i][j] = FALSE;
3160 // add trigger events from element change event properties
3161 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3163 struct ElementInfo *ei = &element_info[i];
3165 for (j = 0; j < ei->num_change_pages; j++)
3167 if (!ei->change_page[j].can_change_or_has_action)
3170 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3172 int trigger_element = ei->change_page[j].trigger_element;
3174 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3176 if (ei->change_page[j].has_event[k])
3178 if (IS_GROUP_ELEMENT(trigger_element))
3180 struct ElementGroupInfo *group =
3181 element_info[trigger_element].group;
3183 for (l = 0; l < group->num_elements_resolved; l++)
3184 trigger_events[group->element_resolved[l]][k] = TRUE;
3186 else if (trigger_element == EL_ANY_ELEMENT)
3187 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3188 trigger_events[l][k] = TRUE;
3190 trigger_events[trigger_element][k] = TRUE;
3197 // ---------- initialize push delay -----------------------------------------
3199 // initialize push delay values to default
3200 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3202 if (!IS_CUSTOM_ELEMENT(i))
3204 // set default push delay values (corrected since version 3.0.7-1)
3205 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3207 element_info[i].push_delay_fixed = 2;
3208 element_info[i].push_delay_random = 8;
3212 element_info[i].push_delay_fixed = 8;
3213 element_info[i].push_delay_random = 8;
3218 // set push delay value for certain elements from pre-defined list
3219 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3221 int e = push_delay_list[i].element;
3223 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3224 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3227 // set push delay value for Supaplex elements for newer engine versions
3228 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3230 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3232 if (IS_SP_ELEMENT(i))
3234 // set SP push delay to just enough to push under a falling zonk
3235 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3237 element_info[i].push_delay_fixed = delay;
3238 element_info[i].push_delay_random = 0;
3243 // ---------- initialize move stepsize --------------------------------------
3245 // initialize move stepsize values to default
3246 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3247 if (!IS_CUSTOM_ELEMENT(i))
3248 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3250 // set move stepsize value for certain elements from pre-defined list
3251 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3253 int e = move_stepsize_list[i].element;
3255 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3257 // set move stepsize value for certain elements for older engine versions
3258 if (use_old_move_stepsize_for_magic_wall)
3260 if (e == EL_MAGIC_WALL_FILLING ||
3261 e == EL_MAGIC_WALL_EMPTYING ||
3262 e == EL_BD_MAGIC_WALL_FILLING ||
3263 e == EL_BD_MAGIC_WALL_EMPTYING)
3264 element_info[e].move_stepsize *= 2;
3268 // ---------- initialize collect score --------------------------------------
3270 // initialize collect score values for custom elements from initial value
3271 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3272 if (IS_CUSTOM_ELEMENT(i))
3273 element_info[i].collect_score = element_info[i].collect_score_initial;
3275 // ---------- initialize collect count --------------------------------------
3277 // initialize collect count values for non-custom elements
3278 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3279 if (!IS_CUSTOM_ELEMENT(i))
3280 element_info[i].collect_count_initial = 0;
3282 // add collect count values for all elements from pre-defined list
3283 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3284 element_info[collect_count_list[i].element].collect_count_initial =
3285 collect_count_list[i].count;
3287 // ---------- initialize access direction -----------------------------------
3289 // initialize access direction values to default (access from every side)
3290 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3291 if (!IS_CUSTOM_ELEMENT(i))
3292 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3294 // set access direction value for certain elements from pre-defined list
3295 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3296 element_info[access_direction_list[i].element].access_direction =
3297 access_direction_list[i].direction;
3299 // ---------- initialize explosion content ----------------------------------
3300 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3302 if (IS_CUSTOM_ELEMENT(i))
3305 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3307 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3309 element_info[i].content.e[x][y] =
3310 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3311 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3312 i == EL_PLAYER_3 ? EL_EMERALD :
3313 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3314 i == EL_MOLE ? EL_EMERALD_RED :
3315 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3316 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3317 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3318 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3319 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3320 i == EL_WALL_EMERALD ? EL_EMERALD :
3321 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3322 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3323 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3324 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3325 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3326 i == EL_WALL_PEARL ? EL_PEARL :
3327 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3332 // ---------- initialize recursion detection --------------------------------
3333 recursion_loop_depth = 0;
3334 recursion_loop_detected = FALSE;
3335 recursion_loop_element = EL_UNDEFINED;
3337 // ---------- initialize graphics engine ------------------------------------
3338 game.scroll_delay_value =
3339 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3340 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3341 !setup.forced_scroll_delay ? 0 :
3342 setup.scroll_delay ? setup.scroll_delay_value : 0);
3343 game.scroll_delay_value =
3344 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3346 // ---------- initialize game engine snapshots ------------------------------
3347 for (i = 0; i < MAX_PLAYERS; i++)
3348 game.snapshot.last_action[i] = 0;
3349 game.snapshot.changed_action = FALSE;
3350 game.snapshot.collected_item = FALSE;
3351 game.snapshot.mode =
3352 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3353 SNAPSHOT_MODE_EVERY_STEP :
3354 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3355 SNAPSHOT_MODE_EVERY_MOVE :
3356 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3357 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3358 game.snapshot.save_snapshot = FALSE;
3360 // ---------- initialize level time for Supaplex engine ---------------------
3361 // Supaplex levels with time limit currently unsupported -- should be added
3362 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3365 // ---------- initialize flags for handling game actions --------------------
3367 // set flags for game actions to default values
3368 game.use_key_actions = TRUE;
3369 game.use_mouse_actions = FALSE;
3371 // when using Mirror Magic game engine, handle mouse events only
3372 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3374 game.use_key_actions = FALSE;
3375 game.use_mouse_actions = TRUE;
3378 // check for custom elements with mouse click events
3379 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3381 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3383 int element = EL_CUSTOM_START + i;
3385 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3386 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3387 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3388 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3389 game.use_mouse_actions = TRUE;
3394 static int get_num_special_action(int element, int action_first,
3397 int num_special_action = 0;
3400 for (i = action_first; i <= action_last; i++)
3402 boolean found = FALSE;
3404 for (j = 0; j < NUM_DIRECTIONS; j++)
3405 if (el_act_dir2img(element, i, j) !=
3406 el_act_dir2img(element, ACTION_DEFAULT, j))
3410 num_special_action++;
3415 return num_special_action;
3419 // ============================================================================
3421 // ----------------------------------------------------------------------------
3422 // initialize and start new game
3423 // ============================================================================
3425 #if DEBUG_INIT_PLAYER
3426 static void DebugPrintPlayerStatus(char *message)
3433 printf("%s:\n", message);
3435 for (i = 0; i < MAX_PLAYERS; i++)
3437 struct PlayerInfo *player = &stored_player[i];
3439 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3443 player->connected_locally,
3444 player->connected_network,
3447 if (local_player == player)
3448 printf(" (local player)");
3457 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3458 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3459 int fade_mask = REDRAW_FIELD;
3461 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3462 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3463 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3464 int initial_move_dir = MV_DOWN;
3467 // required here to update video display before fading (FIX THIS)
3468 DrawMaskedBorder(REDRAW_DOOR_2);
3470 if (!game.restart_level)
3471 CloseDoor(DOOR_CLOSE_1);
3473 SetGameStatus(GAME_MODE_PLAYING);
3475 if (level_editor_test_game)
3476 FadeSkipNextFadeOut();
3478 FadeSetEnterScreen();
3481 fade_mask = REDRAW_ALL;
3483 FadeLevelSoundsAndMusic();
3485 ExpireSoundLoops(TRUE);
3489 if (level_editor_test_game)
3490 FadeSkipNextFadeIn();
3492 // needed if different viewport properties defined for playing
3493 ChangeViewportPropertiesIfNeeded();
3497 DrawCompleteVideoDisplay();
3499 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3502 InitGameControlValues();
3504 // initialize tape actions from game when recording tape
3507 tape.use_key_actions = game.use_key_actions;
3508 tape.use_mouse_actions = game.use_mouse_actions;
3511 // don't play tapes over network
3512 network_playing = (network.enabled && !tape.playing);
3514 for (i = 0; i < MAX_PLAYERS; i++)
3516 struct PlayerInfo *player = &stored_player[i];
3518 player->index_nr = i;
3519 player->index_bit = (1 << i);
3520 player->element_nr = EL_PLAYER_1 + i;
3522 player->present = FALSE;
3523 player->active = FALSE;
3524 player->mapped = FALSE;
3526 player->killed = FALSE;
3527 player->reanimated = FALSE;
3528 player->buried = FALSE;
3531 player->effective_action = 0;
3532 player->programmed_action = 0;
3533 player->snap_action = 0;
3535 player->mouse_action.lx = 0;
3536 player->mouse_action.ly = 0;
3537 player->mouse_action.button = 0;
3538 player->mouse_action.button_hint = 0;
3540 player->effective_mouse_action.lx = 0;
3541 player->effective_mouse_action.ly = 0;
3542 player->effective_mouse_action.button = 0;
3543 player->effective_mouse_action.button_hint = 0;
3545 for (j = 0; j < MAX_NUM_KEYS; j++)
3546 player->key[j] = FALSE;
3548 player->num_white_keys = 0;
3550 player->dynabomb_count = 0;
3551 player->dynabomb_size = 1;
3552 player->dynabombs_left = 0;
3553 player->dynabomb_xl = FALSE;
3555 player->MovDir = initial_move_dir;
3558 player->GfxDir = initial_move_dir;
3559 player->GfxAction = ACTION_DEFAULT;
3561 player->StepFrame = 0;
3563 player->initial_element = player->element_nr;
3564 player->artwork_element =
3565 (level.use_artwork_element[i] ? level.artwork_element[i] :
3566 player->element_nr);
3567 player->use_murphy = FALSE;
3569 player->block_last_field = FALSE; // initialized in InitPlayerField()
3570 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3572 player->gravity = level.initial_player_gravity[i];
3574 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3576 player->actual_frame_counter = 0;
3578 player->step_counter = 0;
3580 player->last_move_dir = initial_move_dir;
3582 player->is_active = FALSE;
3584 player->is_waiting = FALSE;
3585 player->is_moving = FALSE;
3586 player->is_auto_moving = FALSE;
3587 player->is_digging = FALSE;
3588 player->is_snapping = FALSE;
3589 player->is_collecting = FALSE;
3590 player->is_pushing = FALSE;
3591 player->is_switching = FALSE;
3592 player->is_dropping = FALSE;
3593 player->is_dropping_pressed = FALSE;
3595 player->is_bored = FALSE;
3596 player->is_sleeping = FALSE;
3598 player->was_waiting = TRUE;
3599 player->was_moving = FALSE;
3600 player->was_snapping = FALSE;
3601 player->was_dropping = FALSE;
3603 player->force_dropping = FALSE;
3605 player->frame_counter_bored = -1;
3606 player->frame_counter_sleeping = -1;
3608 player->anim_delay_counter = 0;
3609 player->post_delay_counter = 0;
3611 player->dir_waiting = initial_move_dir;
3612 player->action_waiting = ACTION_DEFAULT;
3613 player->last_action_waiting = ACTION_DEFAULT;
3614 player->special_action_bored = ACTION_DEFAULT;
3615 player->special_action_sleeping = ACTION_DEFAULT;
3617 player->switch_x = -1;
3618 player->switch_y = -1;
3620 player->drop_x = -1;
3621 player->drop_y = -1;
3623 player->show_envelope = 0;
3625 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3627 player->push_delay = -1; // initialized when pushing starts
3628 player->push_delay_value = game.initial_push_delay_value;
3630 player->drop_delay = 0;
3631 player->drop_pressed_delay = 0;
3633 player->last_jx = -1;
3634 player->last_jy = -1;
3638 player->shield_normal_time_left = 0;
3639 player->shield_deadly_time_left = 0;
3641 player->inventory_infinite_element = EL_UNDEFINED;
3642 player->inventory_size = 0;
3644 if (level.use_initial_inventory[i])
3646 for (j = 0; j < level.initial_inventory_size[i]; j++)
3648 int element = level.initial_inventory_content[i][j];
3649 int collect_count = element_info[element].collect_count_initial;
3652 if (!IS_CUSTOM_ELEMENT(element))
3655 if (collect_count == 0)
3656 player->inventory_infinite_element = element;
3658 for (k = 0; k < collect_count; k++)
3659 if (player->inventory_size < MAX_INVENTORY_SIZE)
3660 player->inventory_element[player->inventory_size++] = element;
3664 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3665 SnapField(player, 0, 0);
3667 map_player_action[i] = i;
3670 network_player_action_received = FALSE;
3672 // initial null action
3673 if (network_playing)
3674 SendToServer_MovePlayer(MV_NONE);
3679 TimeLeft = level.time;
3682 ScreenMovDir = MV_NONE;
3686 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3688 game.robot_wheel_x = -1;
3689 game.robot_wheel_y = -1;
3694 game.all_players_gone = FALSE;
3696 game.LevelSolved = FALSE;
3697 game.GameOver = FALSE;
3699 game.GamePlayed = !tape.playing;
3701 game.LevelSolved_GameWon = FALSE;
3702 game.LevelSolved_GameEnd = FALSE;
3703 game.LevelSolved_SaveTape = FALSE;
3704 game.LevelSolved_SaveScore = FALSE;
3706 game.LevelSolved_CountingTime = 0;
3707 game.LevelSolved_CountingScore = 0;
3708 game.LevelSolved_CountingHealth = 0;
3710 game.panel.active = TRUE;
3712 game.no_time_limit = (level.time == 0);
3714 game.yamyam_content_nr = 0;
3715 game.robot_wheel_active = FALSE;
3716 game.magic_wall_active = FALSE;
3717 game.magic_wall_time_left = 0;
3718 game.light_time_left = 0;
3719 game.timegate_time_left = 0;
3720 game.switchgate_pos = 0;
3721 game.wind_direction = level.wind_direction_initial;
3724 game.score_final = 0;
3726 game.health = MAX_HEALTH;
3727 game.health_final = MAX_HEALTH;
3729 game.gems_still_needed = level.gems_needed;
3730 game.sokoban_fields_still_needed = 0;
3731 game.sokoban_objects_still_needed = 0;
3732 game.lights_still_needed = 0;
3733 game.players_still_needed = 0;
3734 game.friends_still_needed = 0;
3736 game.lenses_time_left = 0;
3737 game.magnify_time_left = 0;
3739 game.ball_active = level.ball_active_initial;
3740 game.ball_content_nr = 0;
3742 game.explosions_delayed = TRUE;
3744 game.envelope_active = FALSE;
3746 for (i = 0; i < NUM_BELTS; i++)
3748 game.belt_dir[i] = MV_NONE;
3749 game.belt_dir_nr[i] = 3; // not moving, next moving left
3752 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3753 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3755 #if DEBUG_INIT_PLAYER
3756 DebugPrintPlayerStatus("Player status at level initialization");
3759 SCAN_PLAYFIELD(x, y)
3761 Feld[x][y] = Last[x][y] = level.field[x][y];
3762 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3763 ChangeDelay[x][y] = 0;
3764 ChangePage[x][y] = -1;
3765 CustomValue[x][y] = 0; // initialized in InitField()
3766 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3768 WasJustMoving[x][y] = 0;
3769 WasJustFalling[x][y] = 0;
3770 CheckCollision[x][y] = 0;
3771 CheckImpact[x][y] = 0;
3773 Pushed[x][y] = FALSE;
3775 ChangeCount[x][y] = 0;
3776 ChangeEvent[x][y] = -1;
3778 ExplodePhase[x][y] = 0;
3779 ExplodeDelay[x][y] = 0;
3780 ExplodeField[x][y] = EX_TYPE_NONE;
3782 RunnerVisit[x][y] = 0;
3783 PlayerVisit[x][y] = 0;
3786 GfxRandom[x][y] = INIT_GFX_RANDOM();
3787 GfxElement[x][y] = EL_UNDEFINED;
3788 GfxAction[x][y] = ACTION_DEFAULT;
3789 GfxDir[x][y] = MV_NONE;
3790 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3793 SCAN_PLAYFIELD(x, y)
3795 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3797 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3799 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3802 InitField(x, y, TRUE);
3804 ResetGfxAnimation(x, y);
3809 for (i = 0; i < MAX_PLAYERS; i++)
3811 struct PlayerInfo *player = &stored_player[i];
3813 // set number of special actions for bored and sleeping animation
3814 player->num_special_action_bored =
3815 get_num_special_action(player->artwork_element,
3816 ACTION_BORING_1, ACTION_BORING_LAST);
3817 player->num_special_action_sleeping =
3818 get_num_special_action(player->artwork_element,
3819 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3822 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3823 emulate_sb ? EMU_SOKOBAN :
3824 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3826 // initialize type of slippery elements
3827 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3829 if (!IS_CUSTOM_ELEMENT(i))
3831 // default: elements slip down either to the left or right randomly
3832 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3834 // SP style elements prefer to slip down on the left side
3835 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3836 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3838 // BD style elements prefer to slip down on the left side
3839 if (game.emulation == EMU_BOULDERDASH)
3840 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3844 // initialize explosion and ignition delay
3845 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3847 if (!IS_CUSTOM_ELEMENT(i))
3850 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3851 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3852 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3853 int last_phase = (num_phase + 1) * delay;
3854 int half_phase = (num_phase / 2) * delay;
3856 element_info[i].explosion_delay = last_phase - 1;
3857 element_info[i].ignition_delay = half_phase;
3859 if (i == EL_BLACK_ORB)
3860 element_info[i].ignition_delay = 1;
3864 // correct non-moving belts to start moving left
3865 for (i = 0; i < NUM_BELTS; i++)
3866 if (game.belt_dir[i] == MV_NONE)
3867 game.belt_dir_nr[i] = 3; // not moving, next moving left
3869 #if USE_NEW_PLAYER_ASSIGNMENTS
3870 // use preferred player also in local single-player mode
3871 if (!network.enabled && !game.team_mode)
3873 int new_index_nr = setup.network_player_nr;
3875 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3877 for (i = 0; i < MAX_PLAYERS; i++)
3878 stored_player[i].connected_locally = FALSE;
3880 stored_player[new_index_nr].connected_locally = TRUE;
3884 for (i = 0; i < MAX_PLAYERS; i++)
3886 stored_player[i].connected = FALSE;
3888 // in network game mode, the local player might not be the first player
3889 if (stored_player[i].connected_locally)
3890 local_player = &stored_player[i];
3893 if (!network.enabled)
3894 local_player->connected = TRUE;
3898 for (i = 0; i < MAX_PLAYERS; i++)
3899 stored_player[i].connected = tape.player_participates[i];
3901 else if (network.enabled)
3903 // add team mode players connected over the network (needed for correct
3904 // assignment of player figures from level to locally playing players)
3906 for (i = 0; i < MAX_PLAYERS; i++)
3907 if (stored_player[i].connected_network)
3908 stored_player[i].connected = TRUE;
3910 else if (game.team_mode)
3912 // try to guess locally connected team mode players (needed for correct
3913 // assignment of player figures from level to locally playing players)
3915 for (i = 0; i < MAX_PLAYERS; i++)
3916 if (setup.input[i].use_joystick ||
3917 setup.input[i].key.left != KSYM_UNDEFINED)
3918 stored_player[i].connected = TRUE;
3921 #if DEBUG_INIT_PLAYER
3922 DebugPrintPlayerStatus("Player status after level initialization");
3925 #if DEBUG_INIT_PLAYER
3927 printf("Reassigning players ...\n");
3930 // check if any connected player was not found in playfield
3931 for (i = 0; i < MAX_PLAYERS; i++)
3933 struct PlayerInfo *player = &stored_player[i];
3935 if (player->connected && !player->present)
3937 struct PlayerInfo *field_player = NULL;
3939 #if DEBUG_INIT_PLAYER
3941 printf("- looking for field player for player %d ...\n", i + 1);
3944 // assign first free player found that is present in the playfield
3946 // first try: look for unmapped playfield player that is not connected
3947 for (j = 0; j < MAX_PLAYERS; j++)
3948 if (field_player == NULL &&
3949 stored_player[j].present &&
3950 !stored_player[j].mapped &&
3951 !stored_player[j].connected)
3952 field_player = &stored_player[j];
3954 // second try: look for *any* unmapped playfield player
3955 for (j = 0; j < MAX_PLAYERS; j++)
3956 if (field_player == NULL &&
3957 stored_player[j].present &&
3958 !stored_player[j].mapped)
3959 field_player = &stored_player[j];
3961 if (field_player != NULL)
3963 int jx = field_player->jx, jy = field_player->jy;
3965 #if DEBUG_INIT_PLAYER
3967 printf("- found player %d\n", field_player->index_nr + 1);
3970 player->present = FALSE;
3971 player->active = FALSE;
3973 field_player->present = TRUE;
3974 field_player->active = TRUE;
3977 player->initial_element = field_player->initial_element;
3978 player->artwork_element = field_player->artwork_element;
3980 player->block_last_field = field_player->block_last_field;
3981 player->block_delay_adjustment = field_player->block_delay_adjustment;
3984 StorePlayer[jx][jy] = field_player->element_nr;
3986 field_player->jx = field_player->last_jx = jx;
3987 field_player->jy = field_player->last_jy = jy;
3989 if (local_player == player)
3990 local_player = field_player;
3992 map_player_action[field_player->index_nr] = i;
3994 field_player->mapped = TRUE;
3996 #if DEBUG_INIT_PLAYER
3998 printf("- map_player_action[%d] == %d\n",
3999 field_player->index_nr + 1, i + 1);
4004 if (player->connected && player->present)
4005 player->mapped = TRUE;
4008 #if DEBUG_INIT_PLAYER
4009 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4014 // check if any connected player was not found in playfield
4015 for (i = 0; i < MAX_PLAYERS; i++)
4017 struct PlayerInfo *player = &stored_player[i];
4019 if (player->connected && !player->present)
4021 for (j = 0; j < MAX_PLAYERS; j++)
4023 struct PlayerInfo *field_player = &stored_player[j];
4024 int jx = field_player->jx, jy = field_player->jy;
4026 // assign first free player found that is present in the playfield
4027 if (field_player->present && !field_player->connected)
4029 player->present = TRUE;
4030 player->active = TRUE;
4032 field_player->present = FALSE;
4033 field_player->active = FALSE;
4035 player->initial_element = field_player->initial_element;
4036 player->artwork_element = field_player->artwork_element;
4038 player->block_last_field = field_player->block_last_field;
4039 player->block_delay_adjustment = field_player->block_delay_adjustment;
4041 StorePlayer[jx][jy] = player->element_nr;
4043 player->jx = player->last_jx = jx;
4044 player->jy = player->last_jy = jy;
4054 printf("::: local_player->present == %d\n", local_player->present);
4057 // set focus to local player for network games, else to all players
4058 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4059 game.centered_player_nr_next = game.centered_player_nr;
4060 game.set_centered_player = FALSE;
4061 game.set_centered_player_wrap = FALSE;
4063 if (network_playing && tape.recording)
4065 // store client dependent player focus when recording network games
4066 tape.centered_player_nr_next = game.centered_player_nr_next;
4067 tape.set_centered_player = TRUE;
4072 // when playing a tape, eliminate all players who do not participate
4074 #if USE_NEW_PLAYER_ASSIGNMENTS
4076 if (!game.team_mode)
4078 for (i = 0; i < MAX_PLAYERS; i++)
4080 if (stored_player[i].active &&
4081 !tape.player_participates[map_player_action[i]])
4083 struct PlayerInfo *player = &stored_player[i];
4084 int jx = player->jx, jy = player->jy;
4086 #if DEBUG_INIT_PLAYER
4088 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4091 player->active = FALSE;
4092 StorePlayer[jx][jy] = 0;
4093 Feld[jx][jy] = EL_EMPTY;
4100 for (i = 0; i < MAX_PLAYERS; i++)
4102 if (stored_player[i].active &&
4103 !tape.player_participates[i])
4105 struct PlayerInfo *player = &stored_player[i];
4106 int jx = player->jx, jy = player->jy;
4108 player->active = FALSE;
4109 StorePlayer[jx][jy] = 0;
4110 Feld[jx][jy] = EL_EMPTY;
4115 else if (!network.enabled && !game.team_mode) // && !tape.playing
4117 // when in single player mode, eliminate all but the local player
4119 for (i = 0; i < MAX_PLAYERS; i++)
4121 struct PlayerInfo *player = &stored_player[i];
4123 if (player->active && player != local_player)
4125 int jx = player->jx, jy = player->jy;
4127 player->active = FALSE;
4128 player->present = FALSE;
4130 StorePlayer[jx][jy] = 0;
4131 Feld[jx][jy] = EL_EMPTY;
4136 for (i = 0; i < MAX_PLAYERS; i++)
4137 if (stored_player[i].active)
4138 game.players_still_needed++;
4140 if (level.solved_by_one_player)
4141 game.players_still_needed = 1;
4143 // when recording the game, store which players take part in the game
4146 #if USE_NEW_PLAYER_ASSIGNMENTS
4147 for (i = 0; i < MAX_PLAYERS; i++)
4148 if (stored_player[i].connected)
4149 tape.player_participates[i] = TRUE;
4151 for (i = 0; i < MAX_PLAYERS; i++)
4152 if (stored_player[i].active)
4153 tape.player_participates[i] = TRUE;
4157 #if DEBUG_INIT_PLAYER
4158 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4161 if (BorderElement == EL_EMPTY)
4164 SBX_Right = lev_fieldx - SCR_FIELDX;
4166 SBY_Lower = lev_fieldy - SCR_FIELDY;
4171 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4173 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4176 if (full_lev_fieldx <= SCR_FIELDX)
4177 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4178 if (full_lev_fieldy <= SCR_FIELDY)
4179 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4181 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4183 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4186 // if local player not found, look for custom element that might create
4187 // the player (make some assumptions about the right custom element)
4188 if (!local_player->present)
4190 int start_x = 0, start_y = 0;
4191 int found_rating = 0;
4192 int found_element = EL_UNDEFINED;
4193 int player_nr = local_player->index_nr;
4195 SCAN_PLAYFIELD(x, y)
4197 int element = Feld[x][y];
4202 if (level.use_start_element[player_nr] &&
4203 level.start_element[player_nr] == element &&
4210 found_element = element;
4213 if (!IS_CUSTOM_ELEMENT(element))
4216 if (CAN_CHANGE(element))
4218 for (i = 0; i < element_info[element].num_change_pages; i++)
4220 // check for player created from custom element as single target
4221 content = element_info[element].change_page[i].target_element;
4222 is_player = ELEM_IS_PLAYER(content);
4224 if (is_player && (found_rating < 3 ||
4225 (found_rating == 3 && element < found_element)))
4231 found_element = element;
4236 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4238 // check for player created from custom element as explosion content
4239 content = element_info[element].content.e[xx][yy];
4240 is_player = ELEM_IS_PLAYER(content);
4242 if (is_player && (found_rating < 2 ||
4243 (found_rating == 2 && element < found_element)))
4245 start_x = x + xx - 1;
4246 start_y = y + yy - 1;
4249 found_element = element;
4252 if (!CAN_CHANGE(element))
4255 for (i = 0; i < element_info[element].num_change_pages; i++)
4257 // check for player created from custom element as extended target
4259 element_info[element].change_page[i].target_content.e[xx][yy];
4261 is_player = ELEM_IS_PLAYER(content);
4263 if (is_player && (found_rating < 1 ||
4264 (found_rating == 1 && element < found_element)))
4266 start_x = x + xx - 1;
4267 start_y = y + yy - 1;
4270 found_element = element;
4276 scroll_x = SCROLL_POSITION_X(start_x);
4277 scroll_y = SCROLL_POSITION_Y(start_y);
4281 scroll_x = SCROLL_POSITION_X(local_player->jx);
4282 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4285 // !!! FIX THIS (START) !!!
4286 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4288 InitGameEngine_EM();
4290 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4292 InitGameEngine_SP();
4294 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4296 InitGameEngine_MM();
4300 DrawLevel(REDRAW_FIELD);
4303 // after drawing the level, correct some elements
4304 if (game.timegate_time_left == 0)
4305 CloseAllOpenTimegates();
4308 // blit playfield from scroll buffer to normal back buffer for fading in
4309 BlitScreenToBitmap(backbuffer);
4310 // !!! FIX THIS (END) !!!
4312 DrawMaskedBorder(fade_mask);
4317 // full screen redraw is required at this point in the following cases:
4318 // - special editor door undrawn when game was started from level editor
4319 // - drawing area (playfield) was changed and has to be removed completely
4320 redraw_mask = REDRAW_ALL;
4324 if (!game.restart_level)
4326 // copy default game door content to main double buffer
4328 // !!! CHECK AGAIN !!!
4329 SetPanelBackground();
4330 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4331 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4334 SetPanelBackground();
4335 SetDrawBackgroundMask(REDRAW_DOOR_1);
4337 UpdateAndDisplayGameControlValues();
4339 if (!game.restart_level)
4345 CreateGameButtons();
4350 // copy actual game door content to door double buffer for OpenDoor()
4351 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4353 OpenDoor(DOOR_OPEN_ALL);
4355 KeyboardAutoRepeatOffUnlessAutoplay();
4357 #if DEBUG_INIT_PLAYER
4358 DebugPrintPlayerStatus("Player status (final)");
4367 if (!game.restart_level && !tape.playing)
4369 LevelStats_incPlayed(level_nr);
4371 SaveLevelSetup_SeriesInfo();
4374 game.restart_level = FALSE;
4375 game.restart_game_message = NULL;
4376 game.request_active = FALSE;
4378 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4379 InitGameActions_MM();
4381 SaveEngineSnapshotToListInitial();
4383 if (!game.restart_level)
4385 PlaySound(SND_GAME_STARTING);
4387 if (setup.sound_music)
4392 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4393 int actual_player_x, int actual_player_y)
4395 // this is used for non-R'n'D game engines to update certain engine values
4397 // needed to determine if sounds are played within the visible screen area
4398 scroll_x = actual_scroll_x;
4399 scroll_y = actual_scroll_y;
4401 // needed to get player position for "follow finger" playing input method
4402 local_player->jx = actual_player_x;
4403 local_player->jy = actual_player_y;
4406 void InitMovDir(int x, int y)
4408 int i, element = Feld[x][y];
4409 static int xy[4][2] =
4416 static int direction[3][4] =
4418 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4419 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4420 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4429 Feld[x][y] = EL_BUG;
4430 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4433 case EL_SPACESHIP_RIGHT:
4434 case EL_SPACESHIP_UP:
4435 case EL_SPACESHIP_LEFT:
4436 case EL_SPACESHIP_DOWN:
4437 Feld[x][y] = EL_SPACESHIP;
4438 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4441 case EL_BD_BUTTERFLY_RIGHT:
4442 case EL_BD_BUTTERFLY_UP:
4443 case EL_BD_BUTTERFLY_LEFT:
4444 case EL_BD_BUTTERFLY_DOWN:
4445 Feld[x][y] = EL_BD_BUTTERFLY;
4446 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4449 case EL_BD_FIREFLY_RIGHT:
4450 case EL_BD_FIREFLY_UP:
4451 case EL_BD_FIREFLY_LEFT:
4452 case EL_BD_FIREFLY_DOWN:
4453 Feld[x][y] = EL_BD_FIREFLY;
4454 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4457 case EL_PACMAN_RIGHT:
4459 case EL_PACMAN_LEFT:
4460 case EL_PACMAN_DOWN:
4461 Feld[x][y] = EL_PACMAN;
4462 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4465 case EL_YAMYAM_LEFT:
4466 case EL_YAMYAM_RIGHT:
4468 case EL_YAMYAM_DOWN:
4469 Feld[x][y] = EL_YAMYAM;
4470 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4473 case EL_SP_SNIKSNAK:
4474 MovDir[x][y] = MV_UP;
4477 case EL_SP_ELECTRON:
4478 MovDir[x][y] = MV_LEFT;
4485 Feld[x][y] = EL_MOLE;
4486 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4490 if (IS_CUSTOM_ELEMENT(element))
4492 struct ElementInfo *ei = &element_info[element];
4493 int move_direction_initial = ei->move_direction_initial;
4494 int move_pattern = ei->move_pattern;
4496 if (move_direction_initial == MV_START_PREVIOUS)
4498 if (MovDir[x][y] != MV_NONE)
4501 move_direction_initial = MV_START_AUTOMATIC;
4504 if (move_direction_initial == MV_START_RANDOM)
4505 MovDir[x][y] = 1 << RND(4);
4506 else if (move_direction_initial & MV_ANY_DIRECTION)
4507 MovDir[x][y] = move_direction_initial;
4508 else if (move_pattern == MV_ALL_DIRECTIONS ||
4509 move_pattern == MV_TURNING_LEFT ||
4510 move_pattern == MV_TURNING_RIGHT ||
4511 move_pattern == MV_TURNING_LEFT_RIGHT ||
4512 move_pattern == MV_TURNING_RIGHT_LEFT ||
4513 move_pattern == MV_TURNING_RANDOM)
4514 MovDir[x][y] = 1 << RND(4);
4515 else if (move_pattern == MV_HORIZONTAL)
4516 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4517 else if (move_pattern == MV_VERTICAL)
4518 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4519 else if (move_pattern & MV_ANY_DIRECTION)
4520 MovDir[x][y] = element_info[element].move_pattern;
4521 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4522 move_pattern == MV_ALONG_RIGHT_SIDE)
4524 // use random direction as default start direction
4525 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4526 MovDir[x][y] = 1 << RND(4);
4528 for (i = 0; i < NUM_DIRECTIONS; i++)
4530 int x1 = x + xy[i][0];
4531 int y1 = y + xy[i][1];
4533 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4535 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4536 MovDir[x][y] = direction[0][i];
4538 MovDir[x][y] = direction[1][i];
4547 MovDir[x][y] = 1 << RND(4);
4549 if (element != EL_BUG &&
4550 element != EL_SPACESHIP &&
4551 element != EL_BD_BUTTERFLY &&
4552 element != EL_BD_FIREFLY)
4555 for (i = 0; i < NUM_DIRECTIONS; i++)
4557 int x1 = x + xy[i][0];
4558 int y1 = y + xy[i][1];
4560 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4562 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4564 MovDir[x][y] = direction[0][i];
4567 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4568 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4570 MovDir[x][y] = direction[1][i];
4579 GfxDir[x][y] = MovDir[x][y];
4582 void InitAmoebaNr(int x, int y)
4585 int group_nr = AmoebeNachbarNr(x, y);
4589 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4591 if (AmoebaCnt[i] == 0)
4599 AmoebaNr[x][y] = group_nr;
4600 AmoebaCnt[group_nr]++;
4601 AmoebaCnt2[group_nr]++;
4604 static void LevelSolved(void)
4606 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4607 game.players_still_needed > 0)
4610 game.LevelSolved = TRUE;
4611 game.GameOver = TRUE;
4613 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4614 game_em.lev->score :
4615 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4618 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4619 MM_HEALTH(game_mm.laser_overload_value) :
4622 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4623 game.LevelSolved_CountingScore = game.score_final;
4624 game.LevelSolved_CountingHealth = game.health_final;
4629 static int time_count_steps;
4630 static int time, time_final;
4631 static int score, score_final;
4632 static int health, health_final;
4633 static int game_over_delay_1 = 0;
4634 static int game_over_delay_2 = 0;
4635 static int game_over_delay_3 = 0;
4636 int game_over_delay_value_1 = 50;
4637 int game_over_delay_value_2 = 25;
4638 int game_over_delay_value_3 = 50;
4640 if (!game.LevelSolved_GameWon)
4644 // do not start end game actions before the player stops moving (to exit)
4645 if (local_player->active && local_player->MovPos)
4648 game.LevelSolved_GameWon = TRUE;
4649 game.LevelSolved_SaveTape = tape.recording;
4650 game.LevelSolved_SaveScore = !tape.playing;
4654 LevelStats_incSolved(level_nr);
4656 SaveLevelSetup_SeriesInfo();
4659 if (tape.auto_play) // tape might already be stopped here
4660 tape.auto_play_level_solved = TRUE;
4664 game_over_delay_1 = 0;
4665 game_over_delay_2 = 0;
4666 game_over_delay_3 = game_over_delay_value_3;
4668 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4669 score = score_final = game.score_final;
4670 health = health_final = game.health_final;
4672 if (level.score[SC_TIME_BONUS] > 0)
4677 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4679 else if (game.no_time_limit && TimePlayed < 999)
4682 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4685 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4687 game_over_delay_1 = game_over_delay_value_1;
4689 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4692 score_final += health * level.score[SC_TIME_BONUS];
4694 game_over_delay_2 = game_over_delay_value_2;
4697 game.score_final = score_final;
4698 game.health_final = health_final;
4701 if (level_editor_test_game)
4704 score = score_final;
4706 game.LevelSolved_CountingTime = time;
4707 game.LevelSolved_CountingScore = score;
4709 game_panel_controls[GAME_PANEL_TIME].value = time;
4710 game_panel_controls[GAME_PANEL_SCORE].value = score;
4712 DisplayGameControlValues();
4715 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4717 // check if last player has left the level
4718 if (game.exit_x >= 0 &&
4721 int x = game.exit_x;
4722 int y = game.exit_y;
4723 int element = Feld[x][y];
4725 // close exit door after last player
4726 if ((game.all_players_gone &&
4727 (element == EL_EXIT_OPEN ||
4728 element == EL_SP_EXIT_OPEN ||
4729 element == EL_STEEL_EXIT_OPEN)) ||
4730 element == EL_EM_EXIT_OPEN ||
4731 element == EL_EM_STEEL_EXIT_OPEN)
4735 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4736 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4737 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4738 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4739 EL_EM_STEEL_EXIT_CLOSING);
4741 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4744 // player disappears
4745 DrawLevelField(x, y);
4748 for (i = 0; i < MAX_PLAYERS; i++)
4750 struct PlayerInfo *player = &stored_player[i];
4752 if (player->present)
4754 RemovePlayer(player);
4756 // player disappears
4757 DrawLevelField(player->jx, player->jy);
4762 PlaySound(SND_GAME_WINNING);
4765 if (game_over_delay_1 > 0)
4767 game_over_delay_1--;
4772 if (time != time_final)
4774 int time_to_go = ABS(time_final - time);
4775 int time_count_dir = (time < time_final ? +1 : -1);
4777 if (time_to_go < time_count_steps)
4778 time_count_steps = 1;
4780 time += time_count_steps * time_count_dir;
4781 score += time_count_steps * level.score[SC_TIME_BONUS];
4783 game.LevelSolved_CountingTime = time;
4784 game.LevelSolved_CountingScore = score;
4786 game_panel_controls[GAME_PANEL_TIME].value = time;
4787 game_panel_controls[GAME_PANEL_SCORE].value = score;
4789 DisplayGameControlValues();
4791 if (time == time_final)
4792 StopSound(SND_GAME_LEVELTIME_BONUS);
4793 else if (setup.sound_loops)
4794 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4796 PlaySound(SND_GAME_LEVELTIME_BONUS);
4801 if (game_over_delay_2 > 0)
4803 game_over_delay_2--;
4808 if (health != health_final)
4810 int health_count_dir = (health < health_final ? +1 : -1);
4812 health += health_count_dir;
4813 score += level.score[SC_TIME_BONUS];
4815 game.LevelSolved_CountingHealth = health;
4816 game.LevelSolved_CountingScore = score;
4818 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4819 game_panel_controls[GAME_PANEL_SCORE].value = score;
4821 DisplayGameControlValues();
4823 if (health == health_final)
4824 StopSound(SND_GAME_LEVELTIME_BONUS);
4825 else if (setup.sound_loops)
4826 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4828 PlaySound(SND_GAME_LEVELTIME_BONUS);
4833 game.panel.active = FALSE;
4835 if (game_over_delay_3 > 0)
4837 game_over_delay_3--;
4847 // used instead of "level_nr" (needed for network games)
4848 int last_level_nr = levelset.level_nr;
4851 game.LevelSolved_GameEnd = TRUE;
4853 if (game.LevelSolved_SaveTape)
4855 // make sure that request dialog to save tape does not open door again
4856 if (!global.use_envelope_request)
4857 CloseDoor(DOOR_CLOSE_1);
4859 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4862 // if no tape is to be saved, close both doors simultaneously
4863 CloseDoor(DOOR_CLOSE_ALL);
4865 if (level_editor_test_game)
4867 SetGameStatus(GAME_MODE_MAIN);
4874 if (!game.LevelSolved_SaveScore)
4876 SetGameStatus(GAME_MODE_MAIN);
4883 if (level_nr == leveldir_current->handicap_level)
4885 leveldir_current->handicap_level++;
4887 SaveLevelSetup_SeriesInfo();
4890 if (setup.increment_levels &&
4891 level_nr < leveldir_current->last_level &&
4894 level_nr++; // advance to next level
4895 TapeErase(); // start with empty tape
4897 if (setup.auto_play_next_level)
4899 LoadLevel(level_nr);
4901 SaveLevelSetup_SeriesInfo();
4905 hi_pos = NewHiScore(last_level_nr);
4907 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4909 SetGameStatus(GAME_MODE_SCORES);
4911 DrawHallOfFame(last_level_nr, hi_pos);
4913 else if (setup.auto_play_next_level && setup.increment_levels &&
4914 last_level_nr < leveldir_current->last_level &&
4917 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4921 SetGameStatus(GAME_MODE_MAIN);
4927 int NewHiScore(int level_nr)
4931 boolean one_score_entry_per_name = !program.many_scores_per_name;
4933 LoadScore(level_nr);
4935 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4936 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4939 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4941 if (game.score_final > highscore[k].Score)
4943 // player has made it to the hall of fame
4945 if (k < MAX_SCORE_ENTRIES - 1)
4947 int m = MAX_SCORE_ENTRIES - 1;
4949 if (one_score_entry_per_name)
4951 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4952 if (strEqual(setup.player_name, highscore[l].Name))
4955 if (m == k) // player's new highscore overwrites his old one
4959 for (l = m; l > k; l--)
4961 strcpy(highscore[l].Name, highscore[l - 1].Name);
4962 highscore[l].Score = highscore[l - 1].Score;
4968 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4969 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4970 highscore[k].Score = game.score_final;
4975 else if (one_score_entry_per_name &&
4976 !strncmp(setup.player_name, highscore[k].Name,
4977 MAX_PLAYER_NAME_LEN))
4978 break; // player already there with a higher score
4982 SaveScore(level_nr);
4987 static int getElementMoveStepsizeExt(int x, int y, int direction)
4989 int element = Feld[x][y];
4990 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4991 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4992 int horiz_move = (dx != 0);
4993 int sign = (horiz_move ? dx : dy);
4994 int step = sign * element_info[element].move_stepsize;
4996 // special values for move stepsize for spring and things on conveyor belt
4999 if (CAN_FALL(element) &&
5000 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
5001 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5002 else if (element == EL_SPRING)
5003 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5009 static int getElementMoveStepsize(int x, int y)
5011 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5014 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5016 if (player->GfxAction != action || player->GfxDir != dir)
5018 player->GfxAction = action;
5019 player->GfxDir = dir;
5021 player->StepFrame = 0;
5025 static void ResetGfxFrame(int x, int y)
5027 // profiling showed that "autotest" spends 10~20% of its time in this function
5028 if (DrawingDeactivatedField())
5031 int element = Feld[x][y];
5032 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5034 if (graphic_info[graphic].anim_global_sync)
5035 GfxFrame[x][y] = FrameCounter;
5036 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5037 GfxFrame[x][y] = CustomValue[x][y];
5038 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5039 GfxFrame[x][y] = element_info[element].collect_score;
5040 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5041 GfxFrame[x][y] = ChangeDelay[x][y];
5044 static void ResetGfxAnimation(int x, int y)
5046 GfxAction[x][y] = ACTION_DEFAULT;
5047 GfxDir[x][y] = MovDir[x][y];
5050 ResetGfxFrame(x, y);
5053 static void ResetRandomAnimationValue(int x, int y)
5055 GfxRandom[x][y] = INIT_GFX_RANDOM();
5058 static void InitMovingField(int x, int y, int direction)
5060 int element = Feld[x][y];
5061 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5062 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5065 boolean is_moving_before, is_moving_after;
5067 // check if element was/is moving or being moved before/after mode change
5068 is_moving_before = (WasJustMoving[x][y] != 0);
5069 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5071 // reset animation only for moving elements which change direction of moving
5072 // or which just started or stopped moving
5073 // (else CEs with property "can move" / "not moving" are reset each frame)
5074 if (is_moving_before != is_moving_after ||
5075 direction != MovDir[x][y])
5076 ResetGfxAnimation(x, y);
5078 MovDir[x][y] = direction;
5079 GfxDir[x][y] = direction;
5081 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5082 direction == MV_DOWN && CAN_FALL(element) ?
5083 ACTION_FALLING : ACTION_MOVING);
5085 // this is needed for CEs with property "can move" / "not moving"
5087 if (is_moving_after)
5089 if (Feld[newx][newy] == EL_EMPTY)
5090 Feld[newx][newy] = EL_BLOCKED;
5092 MovDir[newx][newy] = MovDir[x][y];
5094 CustomValue[newx][newy] = CustomValue[x][y];
5096 GfxFrame[newx][newy] = GfxFrame[x][y];
5097 GfxRandom[newx][newy] = GfxRandom[x][y];
5098 GfxAction[newx][newy] = GfxAction[x][y];
5099 GfxDir[newx][newy] = GfxDir[x][y];
5103 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5105 int direction = MovDir[x][y];
5106 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5107 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5113 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5115 int oldx = x, oldy = y;
5116 int direction = MovDir[x][y];
5118 if (direction == MV_LEFT)
5120 else if (direction == MV_RIGHT)
5122 else if (direction == MV_UP)
5124 else if (direction == MV_DOWN)
5127 *comes_from_x = oldx;
5128 *comes_from_y = oldy;
5131 static int MovingOrBlocked2Element(int x, int y)
5133 int element = Feld[x][y];
5135 if (element == EL_BLOCKED)
5139 Blocked2Moving(x, y, &oldx, &oldy);
5140 return Feld[oldx][oldy];
5146 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5148 // like MovingOrBlocked2Element(), but if element is moving
5149 // and (x,y) is the field the moving element is just leaving,
5150 // return EL_BLOCKED instead of the element value
5151 int element = Feld[x][y];
5153 if (IS_MOVING(x, y))
5155 if (element == EL_BLOCKED)
5159 Blocked2Moving(x, y, &oldx, &oldy);
5160 return Feld[oldx][oldy];
5169 static void RemoveField(int x, int y)
5171 Feld[x][y] = EL_EMPTY;
5177 CustomValue[x][y] = 0;
5180 ChangeDelay[x][y] = 0;
5181 ChangePage[x][y] = -1;
5182 Pushed[x][y] = FALSE;
5184 GfxElement[x][y] = EL_UNDEFINED;
5185 GfxAction[x][y] = ACTION_DEFAULT;
5186 GfxDir[x][y] = MV_NONE;
5189 static void RemoveMovingField(int x, int y)
5191 int oldx = x, oldy = y, newx = x, newy = y;
5192 int element = Feld[x][y];
5193 int next_element = EL_UNDEFINED;
5195 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5198 if (IS_MOVING(x, y))
5200 Moving2Blocked(x, y, &newx, &newy);
5202 if (Feld[newx][newy] != EL_BLOCKED)
5204 // element is moving, but target field is not free (blocked), but
5205 // already occupied by something different (example: acid pool);
5206 // in this case, only remove the moving field, but not the target
5208 RemoveField(oldx, oldy);
5210 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5212 TEST_DrawLevelField(oldx, oldy);
5217 else if (element == EL_BLOCKED)
5219 Blocked2Moving(x, y, &oldx, &oldy);
5220 if (!IS_MOVING(oldx, oldy))
5224 if (element == EL_BLOCKED &&
5225 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5226 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5227 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5228 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5229 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5230 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5231 next_element = get_next_element(Feld[oldx][oldy]);
5233 RemoveField(oldx, oldy);
5234 RemoveField(newx, newy);
5236 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5238 if (next_element != EL_UNDEFINED)
5239 Feld[oldx][oldy] = next_element;
5241 TEST_DrawLevelField(oldx, oldy);
5242 TEST_DrawLevelField(newx, newy);
5245 void DrawDynamite(int x, int y)
5247 int sx = SCREENX(x), sy = SCREENY(y);
5248 int graphic = el2img(Feld[x][y]);
5251 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5254 if (IS_WALKABLE_INSIDE(Back[x][y]))
5258 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5259 else if (Store[x][y])
5260 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5262 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5264 if (Back[x][y] || Store[x][y])
5265 DrawGraphicThruMask(sx, sy, graphic, frame);
5267 DrawGraphic(sx, sy, graphic, frame);
5270 static void CheckDynamite(int x, int y)
5272 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5276 if (MovDelay[x][y] != 0)
5279 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5285 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5290 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5292 boolean num_checked_players = 0;
5295 for (i = 0; i < MAX_PLAYERS; i++)
5297 if (stored_player[i].active)
5299 int sx = stored_player[i].jx;
5300 int sy = stored_player[i].jy;
5302 if (num_checked_players == 0)
5309 *sx1 = MIN(*sx1, sx);
5310 *sy1 = MIN(*sy1, sy);
5311 *sx2 = MAX(*sx2, sx);
5312 *sy2 = MAX(*sy2, sy);
5315 num_checked_players++;
5320 static boolean checkIfAllPlayersFitToScreen_RND(void)
5322 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5324 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5326 return (sx2 - sx1 < SCR_FIELDX &&
5327 sy2 - sy1 < SCR_FIELDY);
5330 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5332 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5334 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5336 *sx = (sx1 + sx2) / 2;
5337 *sy = (sy1 + sy2) / 2;
5340 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5341 boolean center_screen, boolean quick_relocation)
5343 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5344 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5345 boolean no_delay = (tape.warp_forward);
5346 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5347 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5348 int new_scroll_x, new_scroll_y;
5350 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5352 // case 1: quick relocation inside visible screen (without scrolling)
5359 if (!level.shifted_relocation || center_screen)
5361 // relocation _with_ centering of screen
5363 new_scroll_x = SCROLL_POSITION_X(x);
5364 new_scroll_y = SCROLL_POSITION_Y(y);
5368 // relocation _without_ centering of screen
5370 int center_scroll_x = SCROLL_POSITION_X(old_x);
5371 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5372 int offset_x = x + (scroll_x - center_scroll_x);
5373 int offset_y = y + (scroll_y - center_scroll_y);
5375 // for new screen position, apply previous offset to center position
5376 new_scroll_x = SCROLL_POSITION_X(offset_x);
5377 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5380 if (quick_relocation)
5382 // case 2: quick relocation (redraw without visible scrolling)
5384 scroll_x = new_scroll_x;
5385 scroll_y = new_scroll_y;
5392 // case 3: visible relocation (with scrolling to new position)
5394 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5396 SetVideoFrameDelay(wait_delay_value);
5398 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5400 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5401 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5403 if (dx == 0 && dy == 0) // no scrolling needed at all
5409 // set values for horizontal/vertical screen scrolling (half tile size)
5410 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5411 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5412 int pos_x = dx * TILEX / 2;
5413 int pos_y = dy * TILEY / 2;
5414 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5415 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5417 ScrollLevel(dx, dy);
5420 // scroll in two steps of half tile size to make things smoother
5421 BlitScreenToBitmapExt_RND(window, fx, fy);
5423 // scroll second step to align at full tile size
5424 BlitScreenToBitmap(window);
5430 SetVideoFrameDelay(frame_delay_value_old);
5433 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5435 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5436 int player_nr = GET_PLAYER_NR(el_player);
5437 struct PlayerInfo *player = &stored_player[player_nr];
5438 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5439 boolean no_delay = (tape.warp_forward);
5440 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5441 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5442 int old_jx = player->jx;
5443 int old_jy = player->jy;
5444 int old_element = Feld[old_jx][old_jy];
5445 int element = Feld[jx][jy];
5446 boolean player_relocated = (old_jx != jx || old_jy != jy);
5448 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5449 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5450 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5451 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5452 int leave_side_horiz = move_dir_horiz;
5453 int leave_side_vert = move_dir_vert;
5454 int enter_side = enter_side_horiz | enter_side_vert;
5455 int leave_side = leave_side_horiz | leave_side_vert;
5457 if (player->buried) // do not reanimate dead player
5460 if (!player_relocated) // no need to relocate the player
5463 if (IS_PLAYER(jx, jy)) // player already placed at new position
5465 RemoveField(jx, jy); // temporarily remove newly placed player
5466 DrawLevelField(jx, jy);
5469 if (player->present)
5471 while (player->MovPos)
5473 ScrollPlayer(player, SCROLL_GO_ON);
5474 ScrollScreen(NULL, SCROLL_GO_ON);
5476 AdvanceFrameAndPlayerCounters(player->index_nr);
5480 BackToFront_WithFrameDelay(wait_delay_value);
5483 DrawPlayer(player); // needed here only to cleanup last field
5484 DrawLevelField(player->jx, player->jy); // remove player graphic
5486 player->is_moving = FALSE;
5489 if (IS_CUSTOM_ELEMENT(old_element))
5490 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5492 player->index_bit, leave_side);
5494 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5496 player->index_bit, leave_side);
5498 Feld[jx][jy] = el_player;
5499 InitPlayerField(jx, jy, el_player, TRUE);
5501 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5502 possible that the relocation target field did not contain a player element,
5503 but a walkable element, to which the new player was relocated -- in this
5504 case, restore that (already initialized!) element on the player field */
5505 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5507 Feld[jx][jy] = element; // restore previously existing element
5510 // only visually relocate centered player
5511 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5512 FALSE, level.instant_relocation);
5514 TestIfPlayerTouchesBadThing(jx, jy);
5515 TestIfPlayerTouchesCustomElement(jx, jy);
5517 if (IS_CUSTOM_ELEMENT(element))
5518 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5519 player->index_bit, enter_side);
5521 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5522 player->index_bit, enter_side);
5524 if (player->is_switching)
5526 /* ensure that relocation while still switching an element does not cause
5527 a new element to be treated as also switched directly after relocation
5528 (this is important for teleporter switches that teleport the player to
5529 a place where another teleporter switch is in the same direction, which
5530 would then incorrectly be treated as immediately switched before the
5531 direction key that caused the switch was released) */
5533 player->switch_x += jx - old_jx;
5534 player->switch_y += jy - old_jy;
5538 static void Explode(int ex, int ey, int phase, int mode)
5544 // !!! eliminate this variable !!!
5545 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5547 if (game.explosions_delayed)
5549 ExplodeField[ex][ey] = mode;
5553 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5555 int center_element = Feld[ex][ey];
5556 int artwork_element, explosion_element; // set these values later
5558 // remove things displayed in background while burning dynamite
5559 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5562 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5564 // put moving element to center field (and let it explode there)
5565 center_element = MovingOrBlocked2Element(ex, ey);
5566 RemoveMovingField(ex, ey);
5567 Feld[ex][ey] = center_element;
5570 // now "center_element" is finally determined -- set related values now
5571 artwork_element = center_element; // for custom player artwork
5572 explosion_element = center_element; // for custom player artwork
5574 if (IS_PLAYER(ex, ey))
5576 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5578 artwork_element = stored_player[player_nr].artwork_element;
5580 if (level.use_explosion_element[player_nr])
5582 explosion_element = level.explosion_element[player_nr];
5583 artwork_element = explosion_element;
5587 if (mode == EX_TYPE_NORMAL ||
5588 mode == EX_TYPE_CENTER ||
5589 mode == EX_TYPE_CROSS)
5590 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5592 last_phase = element_info[explosion_element].explosion_delay + 1;
5594 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5596 int xx = x - ex + 1;
5597 int yy = y - ey + 1;
5600 if (!IN_LEV_FIELD(x, y) ||
5601 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5602 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5605 element = Feld[x][y];
5607 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5609 element = MovingOrBlocked2Element(x, y);
5611 if (!IS_EXPLOSION_PROOF(element))
5612 RemoveMovingField(x, y);
5615 // indestructible elements can only explode in center (but not flames)
5616 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5617 mode == EX_TYPE_BORDER)) ||
5618 element == EL_FLAMES)
5621 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5622 behaviour, for example when touching a yamyam that explodes to rocks
5623 with active deadly shield, a rock is created under the player !!! */
5624 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5626 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5627 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5628 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5630 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5633 if (IS_ACTIVE_BOMB(element))
5635 // re-activate things under the bomb like gate or penguin
5636 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5643 // save walkable background elements while explosion on same tile
5644 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5645 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5646 Back[x][y] = element;
5648 // ignite explodable elements reached by other explosion
5649 if (element == EL_EXPLOSION)
5650 element = Store2[x][y];
5652 if (AmoebaNr[x][y] &&
5653 (element == EL_AMOEBA_FULL ||
5654 element == EL_BD_AMOEBA ||
5655 element == EL_AMOEBA_GROWING))
5657 AmoebaCnt[AmoebaNr[x][y]]--;
5658 AmoebaCnt2[AmoebaNr[x][y]]--;
5663 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5665 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5667 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5669 if (PLAYERINFO(ex, ey)->use_murphy)
5670 Store[x][y] = EL_EMPTY;
5673 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5674 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5675 else if (ELEM_IS_PLAYER(center_element))
5676 Store[x][y] = EL_EMPTY;
5677 else if (center_element == EL_YAMYAM)
5678 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5679 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5680 Store[x][y] = element_info[center_element].content.e[xx][yy];
5682 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5683 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5684 // otherwise) -- FIX THIS !!!
5685 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5686 Store[x][y] = element_info[element].content.e[1][1];
5688 else if (!CAN_EXPLODE(element))
5689 Store[x][y] = element_info[element].content.e[1][1];
5692 Store[x][y] = EL_EMPTY;
5694 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5695 center_element == EL_AMOEBA_TO_DIAMOND)
5696 Store2[x][y] = element;
5698 Feld[x][y] = EL_EXPLOSION;
5699 GfxElement[x][y] = artwork_element;
5701 ExplodePhase[x][y] = 1;
5702 ExplodeDelay[x][y] = last_phase;
5707 if (center_element == EL_YAMYAM)
5708 game.yamyam_content_nr =
5709 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5721 GfxFrame[x][y] = 0; // restart explosion animation
5723 last_phase = ExplodeDelay[x][y];
5725 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5727 // this can happen if the player leaves an explosion just in time
5728 if (GfxElement[x][y] == EL_UNDEFINED)
5729 GfxElement[x][y] = EL_EMPTY;
5731 border_element = Store2[x][y];
5732 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5733 border_element = StorePlayer[x][y];
5735 if (phase == element_info[border_element].ignition_delay ||
5736 phase == last_phase)
5738 boolean border_explosion = FALSE;
5740 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5741 !PLAYER_EXPLOSION_PROTECTED(x, y))
5743 KillPlayerUnlessExplosionProtected(x, y);
5744 border_explosion = TRUE;
5746 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5748 Feld[x][y] = Store2[x][y];
5751 border_explosion = TRUE;
5753 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5755 AmoebeUmwandeln(x, y);
5757 border_explosion = TRUE;
5760 // if an element just explodes due to another explosion (chain-reaction),
5761 // do not immediately end the new explosion when it was the last frame of
5762 // the explosion (as it would be done in the following "if"-statement!)
5763 if (border_explosion && phase == last_phase)
5767 if (phase == last_phase)
5771 element = Feld[x][y] = Store[x][y];
5772 Store[x][y] = Store2[x][y] = 0;
5773 GfxElement[x][y] = EL_UNDEFINED;
5775 // player can escape from explosions and might therefore be still alive
5776 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5777 element <= EL_PLAYER_IS_EXPLODING_4)
5779 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5780 int explosion_element = EL_PLAYER_1 + player_nr;
5781 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5782 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5784 if (level.use_explosion_element[player_nr])
5785 explosion_element = level.explosion_element[player_nr];
5787 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5788 element_info[explosion_element].content.e[xx][yy]);
5791 // restore probably existing indestructible background element
5792 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5793 element = Feld[x][y] = Back[x][y];
5796 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5797 GfxDir[x][y] = MV_NONE;
5798 ChangeDelay[x][y] = 0;
5799 ChangePage[x][y] = -1;
5801 CustomValue[x][y] = 0;
5803 InitField_WithBug2(x, y, FALSE);
5805 TEST_DrawLevelField(x, y);
5807 TestIfElementTouchesCustomElement(x, y);
5809 if (GFX_CRUMBLED(element))
5810 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5812 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5813 StorePlayer[x][y] = 0;
5815 if (ELEM_IS_PLAYER(element))
5816 RelocatePlayer(x, y, element);
5818 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5820 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5821 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5824 TEST_DrawLevelFieldCrumbled(x, y);
5826 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5828 DrawLevelElement(x, y, Back[x][y]);
5829 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5831 else if (IS_WALKABLE_UNDER(Back[x][y]))
5833 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5834 DrawLevelElementThruMask(x, y, Back[x][y]);
5836 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5837 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5841 static void DynaExplode(int ex, int ey)
5844 int dynabomb_element = Feld[ex][ey];
5845 int dynabomb_size = 1;
5846 boolean dynabomb_xl = FALSE;
5847 struct PlayerInfo *player;
5848 static int xy[4][2] =
5856 if (IS_ACTIVE_BOMB(dynabomb_element))
5858 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5859 dynabomb_size = player->dynabomb_size;
5860 dynabomb_xl = player->dynabomb_xl;
5861 player->dynabombs_left++;
5864 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5866 for (i = 0; i < NUM_DIRECTIONS; i++)
5868 for (j = 1; j <= dynabomb_size; j++)
5870 int x = ex + j * xy[i][0];
5871 int y = ey + j * xy[i][1];
5874 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5877 element = Feld[x][y];
5879 // do not restart explosions of fields with active bombs
5880 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5883 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5885 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5886 !IS_DIGGABLE(element) && !dynabomb_xl)
5892 void Bang(int x, int y)
5894 int element = MovingOrBlocked2Element(x, y);
5895 int explosion_type = EX_TYPE_NORMAL;
5897 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5899 struct PlayerInfo *player = PLAYERINFO(x, y);
5901 element = Feld[x][y] = player->initial_element;
5903 if (level.use_explosion_element[player->index_nr])
5905 int explosion_element = level.explosion_element[player->index_nr];
5907 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5908 explosion_type = EX_TYPE_CROSS;
5909 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5910 explosion_type = EX_TYPE_CENTER;
5918 case EL_BD_BUTTERFLY:
5921 case EL_DARK_YAMYAM:
5925 RaiseScoreElement(element);
5928 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5929 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5930 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5931 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5932 case EL_DYNABOMB_INCREASE_NUMBER:
5933 case EL_DYNABOMB_INCREASE_SIZE:
5934 case EL_DYNABOMB_INCREASE_POWER:
5935 explosion_type = EX_TYPE_DYNA;
5938 case EL_DC_LANDMINE:
5939 explosion_type = EX_TYPE_CENTER;
5944 case EL_LAMP_ACTIVE:
5945 case EL_AMOEBA_TO_DIAMOND:
5946 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5947 explosion_type = EX_TYPE_CENTER;
5951 if (element_info[element].explosion_type == EXPLODES_CROSS)
5952 explosion_type = EX_TYPE_CROSS;
5953 else if (element_info[element].explosion_type == EXPLODES_1X1)
5954 explosion_type = EX_TYPE_CENTER;
5958 if (explosion_type == EX_TYPE_DYNA)
5961 Explode(x, y, EX_PHASE_START, explosion_type);
5963 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5966 static void SplashAcid(int x, int y)
5968 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5969 (!IN_LEV_FIELD(x - 1, y - 2) ||
5970 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5971 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5973 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5974 (!IN_LEV_FIELD(x + 1, y - 2) ||
5975 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5976 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5978 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5981 static void InitBeltMovement(void)
5983 static int belt_base_element[4] =
5985 EL_CONVEYOR_BELT_1_LEFT,
5986 EL_CONVEYOR_BELT_2_LEFT,
5987 EL_CONVEYOR_BELT_3_LEFT,
5988 EL_CONVEYOR_BELT_4_LEFT
5990 static int belt_base_active_element[4] =
5992 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5993 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5994 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5995 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6000 // set frame order for belt animation graphic according to belt direction
6001 for (i = 0; i < NUM_BELTS; i++)
6005 for (j = 0; j < NUM_BELT_PARTS; j++)
6007 int element = belt_base_active_element[belt_nr] + j;
6008 int graphic_1 = el2img(element);
6009 int graphic_2 = el2panelimg(element);
6011 if (game.belt_dir[i] == MV_LEFT)
6013 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6014 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6018 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6019 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6024 SCAN_PLAYFIELD(x, y)
6026 int element = Feld[x][y];
6028 for (i = 0; i < NUM_BELTS; i++)
6030 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6032 int e_belt_nr = getBeltNrFromBeltElement(element);
6035 if (e_belt_nr == belt_nr)
6037 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
6039 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
6046 static void ToggleBeltSwitch(int x, int y)
6048 static int belt_base_element[4] =
6050 EL_CONVEYOR_BELT_1_LEFT,
6051 EL_CONVEYOR_BELT_2_LEFT,
6052 EL_CONVEYOR_BELT_3_LEFT,
6053 EL_CONVEYOR_BELT_4_LEFT
6055 static int belt_base_active_element[4] =
6057 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6058 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6059 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6060 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6062 static int belt_base_switch_element[4] =
6064 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6065 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6066 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6067 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6069 static int belt_move_dir[4] =
6077 int element = Feld[x][y];
6078 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6079 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6080 int belt_dir = belt_move_dir[belt_dir_nr];
6083 if (!IS_BELT_SWITCH(element))
6086 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6087 game.belt_dir[belt_nr] = belt_dir;
6089 if (belt_dir_nr == 3)
6092 // set frame order for belt animation graphic according to belt direction
6093 for (i = 0; i < NUM_BELT_PARTS; i++)
6095 int element = belt_base_active_element[belt_nr] + i;
6096 int graphic_1 = el2img(element);
6097 int graphic_2 = el2panelimg(element);
6099 if (belt_dir == MV_LEFT)
6101 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6102 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6106 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6107 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6111 SCAN_PLAYFIELD(xx, yy)
6113 int element = Feld[xx][yy];
6115 if (IS_BELT_SWITCH(element))
6117 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6119 if (e_belt_nr == belt_nr)
6121 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6122 TEST_DrawLevelField(xx, yy);
6125 else if (IS_BELT(element) && belt_dir != MV_NONE)
6127 int e_belt_nr = getBeltNrFromBeltElement(element);
6129 if (e_belt_nr == belt_nr)
6131 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6133 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6134 TEST_DrawLevelField(xx, yy);
6137 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6139 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6141 if (e_belt_nr == belt_nr)
6143 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6145 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6146 TEST_DrawLevelField(xx, yy);
6152 static void ToggleSwitchgateSwitch(int x, int y)
6156 game.switchgate_pos = !game.switchgate_pos;
6158 SCAN_PLAYFIELD(xx, yy)
6160 int element = Feld[xx][yy];
6162 if (element == EL_SWITCHGATE_SWITCH_UP)
6164 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6165 TEST_DrawLevelField(xx, yy);
6167 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6169 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6170 TEST_DrawLevelField(xx, yy);
6172 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6174 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6175 TEST_DrawLevelField(xx, yy);
6177 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6179 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6180 TEST_DrawLevelField(xx, yy);
6182 else if (element == EL_SWITCHGATE_OPEN ||
6183 element == EL_SWITCHGATE_OPENING)
6185 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6187 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6189 else if (element == EL_SWITCHGATE_CLOSED ||
6190 element == EL_SWITCHGATE_CLOSING)
6192 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6194 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6199 static int getInvisibleActiveFromInvisibleElement(int element)
6201 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6202 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6203 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6207 static int getInvisibleFromInvisibleActiveElement(int element)
6209 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6210 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6211 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6215 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6219 SCAN_PLAYFIELD(x, y)
6221 int element = Feld[x][y];
6223 if (element == EL_LIGHT_SWITCH &&
6224 game.light_time_left > 0)
6226 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6227 TEST_DrawLevelField(x, y);
6229 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6230 game.light_time_left == 0)
6232 Feld[x][y] = EL_LIGHT_SWITCH;
6233 TEST_DrawLevelField(x, y);
6235 else if (element == EL_EMC_DRIPPER &&
6236 game.light_time_left > 0)
6238 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6239 TEST_DrawLevelField(x, y);
6241 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6242 game.light_time_left == 0)
6244 Feld[x][y] = EL_EMC_DRIPPER;
6245 TEST_DrawLevelField(x, y);
6247 else if (element == EL_INVISIBLE_STEELWALL ||
6248 element == EL_INVISIBLE_WALL ||
6249 element == EL_INVISIBLE_SAND)
6251 if (game.light_time_left > 0)
6252 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6254 TEST_DrawLevelField(x, y);
6256 // uncrumble neighbour fields, if needed
6257 if (element == EL_INVISIBLE_SAND)
6258 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6260 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6261 element == EL_INVISIBLE_WALL_ACTIVE ||
6262 element == EL_INVISIBLE_SAND_ACTIVE)
6264 if (game.light_time_left == 0)
6265 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6267 TEST_DrawLevelField(x, y);
6269 // re-crumble neighbour fields, if needed
6270 if (element == EL_INVISIBLE_SAND)
6271 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6276 static void RedrawAllInvisibleElementsForLenses(void)
6280 SCAN_PLAYFIELD(x, y)
6282 int element = Feld[x][y];
6284 if (element == EL_EMC_DRIPPER &&
6285 game.lenses_time_left > 0)
6287 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6288 TEST_DrawLevelField(x, y);
6290 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6291 game.lenses_time_left == 0)
6293 Feld[x][y] = EL_EMC_DRIPPER;
6294 TEST_DrawLevelField(x, y);
6296 else if (element == EL_INVISIBLE_STEELWALL ||
6297 element == EL_INVISIBLE_WALL ||
6298 element == EL_INVISIBLE_SAND)
6300 if (game.lenses_time_left > 0)
6301 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6303 TEST_DrawLevelField(x, y);
6305 // uncrumble neighbour fields, if needed
6306 if (element == EL_INVISIBLE_SAND)
6307 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6309 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6310 element == EL_INVISIBLE_WALL_ACTIVE ||
6311 element == EL_INVISIBLE_SAND_ACTIVE)
6313 if (game.lenses_time_left == 0)
6314 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6316 TEST_DrawLevelField(x, y);
6318 // re-crumble neighbour fields, if needed
6319 if (element == EL_INVISIBLE_SAND)
6320 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6325 static void RedrawAllInvisibleElementsForMagnifier(void)
6329 SCAN_PLAYFIELD(x, y)
6331 int element = Feld[x][y];
6333 if (element == EL_EMC_FAKE_GRASS &&
6334 game.magnify_time_left > 0)
6336 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6337 TEST_DrawLevelField(x, y);
6339 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6340 game.magnify_time_left == 0)
6342 Feld[x][y] = EL_EMC_FAKE_GRASS;
6343 TEST_DrawLevelField(x, y);
6345 else if (IS_GATE_GRAY(element) &&
6346 game.magnify_time_left > 0)
6348 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6349 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6350 IS_EM_GATE_GRAY(element) ?
6351 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6352 IS_EMC_GATE_GRAY(element) ?
6353 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6354 IS_DC_GATE_GRAY(element) ?
6355 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6357 TEST_DrawLevelField(x, y);
6359 else if (IS_GATE_GRAY_ACTIVE(element) &&
6360 game.magnify_time_left == 0)
6362 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6363 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6364 IS_EM_GATE_GRAY_ACTIVE(element) ?
6365 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6366 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6367 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6368 IS_DC_GATE_GRAY_ACTIVE(element) ?
6369 EL_DC_GATE_WHITE_GRAY :
6371 TEST_DrawLevelField(x, y);
6376 static void ToggleLightSwitch(int x, int y)
6378 int element = Feld[x][y];
6380 game.light_time_left =
6381 (element == EL_LIGHT_SWITCH ?
6382 level.time_light * FRAMES_PER_SECOND : 0);
6384 RedrawAllLightSwitchesAndInvisibleElements();
6387 static void ActivateTimegateSwitch(int x, int y)
6391 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6393 SCAN_PLAYFIELD(xx, yy)
6395 int element = Feld[xx][yy];
6397 if (element == EL_TIMEGATE_CLOSED ||
6398 element == EL_TIMEGATE_CLOSING)
6400 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6401 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6405 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6407 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6408 TEST_DrawLevelField(xx, yy);
6414 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6415 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6418 static void Impact(int x, int y)
6420 boolean last_line = (y == lev_fieldy - 1);
6421 boolean object_hit = FALSE;
6422 boolean impact = (last_line || object_hit);
6423 int element = Feld[x][y];
6424 int smashed = EL_STEELWALL;
6426 if (!last_line) // check if element below was hit
6428 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6431 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6432 MovDir[x][y + 1] != MV_DOWN ||
6433 MovPos[x][y + 1] <= TILEY / 2));
6435 // do not smash moving elements that left the smashed field in time
6436 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6437 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6440 #if USE_QUICKSAND_IMPACT_BUGFIX
6441 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6443 RemoveMovingField(x, y + 1);
6444 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6445 Feld[x][y + 2] = EL_ROCK;
6446 TEST_DrawLevelField(x, y + 2);
6451 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6453 RemoveMovingField(x, y + 1);
6454 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6455 Feld[x][y + 2] = EL_ROCK;
6456 TEST_DrawLevelField(x, y + 2);
6463 smashed = MovingOrBlocked2Element(x, y + 1);
6465 impact = (last_line || object_hit);
6468 if (!last_line && smashed == EL_ACID) // element falls into acid
6470 SplashAcid(x, y + 1);
6474 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6475 // only reset graphic animation if graphic really changes after impact
6477 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6479 ResetGfxAnimation(x, y);
6480 TEST_DrawLevelField(x, y);
6483 if (impact && CAN_EXPLODE_IMPACT(element))
6488 else if (impact && element == EL_PEARL &&
6489 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6491 ResetGfxAnimation(x, y);
6493 Feld[x][y] = EL_PEARL_BREAKING;
6494 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6497 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6499 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6504 if (impact && element == EL_AMOEBA_DROP)
6506 if (object_hit && IS_PLAYER(x, y + 1))
6507 KillPlayerUnlessEnemyProtected(x, y + 1);
6508 else if (object_hit && smashed == EL_PENGUIN)
6512 Feld[x][y] = EL_AMOEBA_GROWING;
6513 Store[x][y] = EL_AMOEBA_WET;
6515 ResetRandomAnimationValue(x, y);
6520 if (object_hit) // check which object was hit
6522 if ((CAN_PASS_MAGIC_WALL(element) &&
6523 (smashed == EL_MAGIC_WALL ||
6524 smashed == EL_BD_MAGIC_WALL)) ||
6525 (CAN_PASS_DC_MAGIC_WALL(element) &&
6526 smashed == EL_DC_MAGIC_WALL))
6529 int activated_magic_wall =
6530 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6531 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6532 EL_DC_MAGIC_WALL_ACTIVE);
6534 // activate magic wall / mill
6535 SCAN_PLAYFIELD(xx, yy)
6537 if (Feld[xx][yy] == smashed)
6538 Feld[xx][yy] = activated_magic_wall;
6541 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6542 game.magic_wall_active = TRUE;
6544 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6545 SND_MAGIC_WALL_ACTIVATING :
6546 smashed == EL_BD_MAGIC_WALL ?
6547 SND_BD_MAGIC_WALL_ACTIVATING :
6548 SND_DC_MAGIC_WALL_ACTIVATING));
6551 if (IS_PLAYER(x, y + 1))
6553 if (CAN_SMASH_PLAYER(element))
6555 KillPlayerUnlessEnemyProtected(x, y + 1);
6559 else if (smashed == EL_PENGUIN)
6561 if (CAN_SMASH_PLAYER(element))
6567 else if (element == EL_BD_DIAMOND)
6569 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6575 else if (((element == EL_SP_INFOTRON ||
6576 element == EL_SP_ZONK) &&
6577 (smashed == EL_SP_SNIKSNAK ||
6578 smashed == EL_SP_ELECTRON ||
6579 smashed == EL_SP_DISK_ORANGE)) ||
6580 (element == EL_SP_INFOTRON &&
6581 smashed == EL_SP_DISK_YELLOW))
6586 else if (CAN_SMASH_EVERYTHING(element))
6588 if (IS_CLASSIC_ENEMY(smashed) ||
6589 CAN_EXPLODE_SMASHED(smashed))
6594 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6596 if (smashed == EL_LAMP ||
6597 smashed == EL_LAMP_ACTIVE)
6602 else if (smashed == EL_NUT)
6604 Feld[x][y + 1] = EL_NUT_BREAKING;
6605 PlayLevelSound(x, y, SND_NUT_BREAKING);
6606 RaiseScoreElement(EL_NUT);
6609 else if (smashed == EL_PEARL)
6611 ResetGfxAnimation(x, y);
6613 Feld[x][y + 1] = EL_PEARL_BREAKING;
6614 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6617 else if (smashed == EL_DIAMOND)
6619 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6620 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6623 else if (IS_BELT_SWITCH(smashed))
6625 ToggleBeltSwitch(x, y + 1);
6627 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6628 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6629 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6630 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6632 ToggleSwitchgateSwitch(x, y + 1);
6634 else if (smashed == EL_LIGHT_SWITCH ||
6635 smashed == EL_LIGHT_SWITCH_ACTIVE)
6637 ToggleLightSwitch(x, y + 1);
6641 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6643 CheckElementChangeBySide(x, y + 1, smashed, element,
6644 CE_SWITCHED, CH_SIDE_TOP);
6645 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6651 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6656 // play sound of magic wall / mill
6658 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6659 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6660 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6662 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6663 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6664 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6665 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6666 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6667 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6672 // play sound of object that hits the ground
6673 if (last_line || object_hit)
6674 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6677 static void TurnRoundExt(int x, int y)
6689 { 0, 0 }, { 0, 0 }, { 0, 0 },
6694 int left, right, back;
6698 { MV_DOWN, MV_UP, MV_RIGHT },
6699 { MV_UP, MV_DOWN, MV_LEFT },
6701 { MV_LEFT, MV_RIGHT, MV_DOWN },
6705 { MV_RIGHT, MV_LEFT, MV_UP }
6708 int element = Feld[x][y];
6709 int move_pattern = element_info[element].move_pattern;
6711 int old_move_dir = MovDir[x][y];
6712 int left_dir = turn[old_move_dir].left;
6713 int right_dir = turn[old_move_dir].right;
6714 int back_dir = turn[old_move_dir].back;
6716 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6717 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6718 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6719 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6721 int left_x = x + left_dx, left_y = y + left_dy;
6722 int right_x = x + right_dx, right_y = y + right_dy;
6723 int move_x = x + move_dx, move_y = y + move_dy;
6727 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6729 TestIfBadThingTouchesOtherBadThing(x, y);
6731 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6732 MovDir[x][y] = right_dir;
6733 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6734 MovDir[x][y] = left_dir;
6736 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6738 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6741 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6743 TestIfBadThingTouchesOtherBadThing(x, y);
6745 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6746 MovDir[x][y] = left_dir;
6747 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6748 MovDir[x][y] = right_dir;
6750 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6752 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6755 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6757 TestIfBadThingTouchesOtherBadThing(x, y);
6759 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6760 MovDir[x][y] = left_dir;
6761 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6762 MovDir[x][y] = right_dir;
6764 if (MovDir[x][y] != old_move_dir)
6767 else if (element == EL_YAMYAM)
6769 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6770 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6772 if (can_turn_left && can_turn_right)
6773 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6774 else if (can_turn_left)
6775 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6776 else if (can_turn_right)
6777 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6779 MovDir[x][y] = back_dir;
6781 MovDelay[x][y] = 16 + 16 * RND(3);
6783 else if (element == EL_DARK_YAMYAM)
6785 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6787 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6790 if (can_turn_left && can_turn_right)
6791 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6792 else if (can_turn_left)
6793 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6794 else if (can_turn_right)
6795 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6797 MovDir[x][y] = back_dir;
6799 MovDelay[x][y] = 16 + 16 * RND(3);
6801 else if (element == EL_PACMAN)
6803 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6804 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6806 if (can_turn_left && can_turn_right)
6807 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6808 else if (can_turn_left)
6809 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6810 else if (can_turn_right)
6811 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6813 MovDir[x][y] = back_dir;
6815 MovDelay[x][y] = 6 + RND(40);
6817 else if (element == EL_PIG)
6819 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6820 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6821 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6822 boolean should_turn_left, should_turn_right, should_move_on;
6824 int rnd = RND(rnd_value);
6826 should_turn_left = (can_turn_left &&
6828 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6829 y + back_dy + left_dy)));
6830 should_turn_right = (can_turn_right &&
6832 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6833 y + back_dy + right_dy)));
6834 should_move_on = (can_move_on &&
6837 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6838 y + move_dy + left_dy) ||
6839 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6840 y + move_dy + right_dy)));
6842 if (should_turn_left || should_turn_right || should_move_on)
6844 if (should_turn_left && should_turn_right && should_move_on)
6845 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6846 rnd < 2 * rnd_value / 3 ? right_dir :
6848 else if (should_turn_left && should_turn_right)
6849 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6850 else if (should_turn_left && should_move_on)
6851 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6852 else if (should_turn_right && should_move_on)
6853 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6854 else if (should_turn_left)
6855 MovDir[x][y] = left_dir;
6856 else if (should_turn_right)
6857 MovDir[x][y] = right_dir;
6858 else if (should_move_on)
6859 MovDir[x][y] = old_move_dir;
6861 else if (can_move_on && rnd > rnd_value / 8)
6862 MovDir[x][y] = old_move_dir;
6863 else if (can_turn_left && can_turn_right)
6864 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6865 else if (can_turn_left && rnd > rnd_value / 8)
6866 MovDir[x][y] = left_dir;
6867 else if (can_turn_right && rnd > rnd_value/8)
6868 MovDir[x][y] = right_dir;
6870 MovDir[x][y] = back_dir;
6872 xx = x + move_xy[MovDir[x][y]].dx;
6873 yy = y + move_xy[MovDir[x][y]].dy;
6875 if (!IN_LEV_FIELD(xx, yy) ||
6876 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6877 MovDir[x][y] = old_move_dir;
6881 else if (element == EL_DRAGON)
6883 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6884 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6885 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6887 int rnd = RND(rnd_value);
6889 if (can_move_on && rnd > rnd_value / 8)
6890 MovDir[x][y] = old_move_dir;
6891 else if (can_turn_left && can_turn_right)
6892 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6893 else if (can_turn_left && rnd > rnd_value / 8)
6894 MovDir[x][y] = left_dir;
6895 else if (can_turn_right && rnd > rnd_value / 8)
6896 MovDir[x][y] = right_dir;
6898 MovDir[x][y] = back_dir;
6900 xx = x + move_xy[MovDir[x][y]].dx;
6901 yy = y + move_xy[MovDir[x][y]].dy;
6903 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6904 MovDir[x][y] = old_move_dir;
6908 else if (element == EL_MOLE)
6910 boolean can_move_on =
6911 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6912 IS_AMOEBOID(Feld[move_x][move_y]) ||
6913 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6916 boolean can_turn_left =
6917 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6918 IS_AMOEBOID(Feld[left_x][left_y])));
6920 boolean can_turn_right =
6921 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6922 IS_AMOEBOID(Feld[right_x][right_y])));
6924 if (can_turn_left && can_turn_right)
6925 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6926 else if (can_turn_left)
6927 MovDir[x][y] = left_dir;
6929 MovDir[x][y] = right_dir;
6932 if (MovDir[x][y] != old_move_dir)
6935 else if (element == EL_BALLOON)
6937 MovDir[x][y] = game.wind_direction;
6940 else if (element == EL_SPRING)
6942 if (MovDir[x][y] & MV_HORIZONTAL)
6944 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6945 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6947 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6948 ResetGfxAnimation(move_x, move_y);
6949 TEST_DrawLevelField(move_x, move_y);
6951 MovDir[x][y] = back_dir;
6953 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6954 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6955 MovDir[x][y] = MV_NONE;
6960 else if (element == EL_ROBOT ||
6961 element == EL_SATELLITE ||
6962 element == EL_PENGUIN ||
6963 element == EL_EMC_ANDROID)
6965 int attr_x = -1, attr_y = -1;
6967 if (game.all_players_gone)
6969 attr_x = game.exit_x;
6970 attr_y = game.exit_y;
6976 for (i = 0; i < MAX_PLAYERS; i++)
6978 struct PlayerInfo *player = &stored_player[i];
6979 int jx = player->jx, jy = player->jy;
6981 if (!player->active)
6985 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6993 if (element == EL_ROBOT &&
6994 game.robot_wheel_x >= 0 &&
6995 game.robot_wheel_y >= 0 &&
6996 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6997 game.engine_version < VERSION_IDENT(3,1,0,0)))
6999 attr_x = game.robot_wheel_x;
7000 attr_y = game.robot_wheel_y;
7003 if (element == EL_PENGUIN)
7006 static int xy[4][2] =
7014 for (i = 0; i < NUM_DIRECTIONS; i++)
7016 int ex = x + xy[i][0];
7017 int ey = y + xy[i][1];
7019 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
7020 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
7021 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
7022 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7031 MovDir[x][y] = MV_NONE;
7033 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7034 else if (attr_x > x)
7035 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7037 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7038 else if (attr_y > y)
7039 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7041 if (element == EL_ROBOT)
7045 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7046 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7047 Moving2Blocked(x, y, &newx, &newy);
7049 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7050 MovDelay[x][y] = 8 + 8 * !RND(3);
7052 MovDelay[x][y] = 16;
7054 else if (element == EL_PENGUIN)
7060 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7062 boolean first_horiz = RND(2);
7063 int new_move_dir = MovDir[x][y];
7066 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7067 Moving2Blocked(x, y, &newx, &newy);
7069 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7073 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7074 Moving2Blocked(x, y, &newx, &newy);
7076 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7079 MovDir[x][y] = old_move_dir;
7083 else if (element == EL_SATELLITE)
7089 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7091 boolean first_horiz = RND(2);
7092 int new_move_dir = MovDir[x][y];
7095 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7096 Moving2Blocked(x, y, &newx, &newy);
7098 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7102 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7103 Moving2Blocked(x, y, &newx, &newy);
7105 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7108 MovDir[x][y] = old_move_dir;
7112 else if (element == EL_EMC_ANDROID)
7114 static int check_pos[16] =
7116 -1, // 0 => (invalid)
7119 -1, // 3 => (invalid)
7121 0, // 5 => MV_LEFT | MV_UP
7122 2, // 6 => MV_RIGHT | MV_UP
7123 -1, // 7 => (invalid)
7125 6, // 9 => MV_LEFT | MV_DOWN
7126 4, // 10 => MV_RIGHT | MV_DOWN
7127 -1, // 11 => (invalid)
7128 -1, // 12 => (invalid)
7129 -1, // 13 => (invalid)
7130 -1, // 14 => (invalid)
7131 -1, // 15 => (invalid)
7139 { -1, -1, MV_LEFT | MV_UP },
7141 { +1, -1, MV_RIGHT | MV_UP },
7142 { +1, 0, MV_RIGHT },
7143 { +1, +1, MV_RIGHT | MV_DOWN },
7145 { -1, +1, MV_LEFT | MV_DOWN },
7148 int start_pos, check_order;
7149 boolean can_clone = FALSE;
7152 // check if there is any free field around current position
7153 for (i = 0; i < 8; i++)
7155 int newx = x + check_xy[i].dx;
7156 int newy = y + check_xy[i].dy;
7158 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7166 if (can_clone) // randomly find an element to clone
7170 start_pos = check_pos[RND(8)];
7171 check_order = (RND(2) ? -1 : +1);
7173 for (i = 0; i < 8; i++)
7175 int pos_raw = start_pos + i * check_order;
7176 int pos = (pos_raw + 8) % 8;
7177 int newx = x + check_xy[pos].dx;
7178 int newy = y + check_xy[pos].dy;
7180 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7182 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7183 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7185 Store[x][y] = Feld[newx][newy];
7194 if (can_clone) // randomly find a direction to move
7198 start_pos = check_pos[RND(8)];
7199 check_order = (RND(2) ? -1 : +1);
7201 for (i = 0; i < 8; i++)
7203 int pos_raw = start_pos + i * check_order;
7204 int pos = (pos_raw + 8) % 8;
7205 int newx = x + check_xy[pos].dx;
7206 int newy = y + check_xy[pos].dy;
7207 int new_move_dir = check_xy[pos].dir;
7209 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7211 MovDir[x][y] = new_move_dir;
7212 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7221 if (can_clone) // cloning and moving successful
7224 // cannot clone -- try to move towards player
7226 start_pos = check_pos[MovDir[x][y] & 0x0f];
7227 check_order = (RND(2) ? -1 : +1);
7229 for (i = 0; i < 3; i++)
7231 // first check start_pos, then previous/next or (next/previous) pos
7232 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7233 int pos = (pos_raw + 8) % 8;
7234 int newx = x + check_xy[pos].dx;
7235 int newy = y + check_xy[pos].dy;
7236 int new_move_dir = check_xy[pos].dir;
7238 if (IS_PLAYER(newx, newy))
7241 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7243 MovDir[x][y] = new_move_dir;
7244 MovDelay[x][y] = level.android_move_time * 8 + 1;
7251 else if (move_pattern == MV_TURNING_LEFT ||
7252 move_pattern == MV_TURNING_RIGHT ||
7253 move_pattern == MV_TURNING_LEFT_RIGHT ||
7254 move_pattern == MV_TURNING_RIGHT_LEFT ||
7255 move_pattern == MV_TURNING_RANDOM ||
7256 move_pattern == MV_ALL_DIRECTIONS)
7258 boolean can_turn_left =
7259 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7260 boolean can_turn_right =
7261 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7263 if (element_info[element].move_stepsize == 0) // "not moving"
7266 if (move_pattern == MV_TURNING_LEFT)
7267 MovDir[x][y] = left_dir;
7268 else if (move_pattern == MV_TURNING_RIGHT)
7269 MovDir[x][y] = right_dir;
7270 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7271 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7272 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7273 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7274 else if (move_pattern == MV_TURNING_RANDOM)
7275 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7276 can_turn_right && !can_turn_left ? right_dir :
7277 RND(2) ? left_dir : right_dir);
7278 else if (can_turn_left && can_turn_right)
7279 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7280 else if (can_turn_left)
7281 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7282 else if (can_turn_right)
7283 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7285 MovDir[x][y] = back_dir;
7287 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7289 else if (move_pattern == MV_HORIZONTAL ||
7290 move_pattern == MV_VERTICAL)
7292 if (move_pattern & old_move_dir)
7293 MovDir[x][y] = back_dir;
7294 else if (move_pattern == MV_HORIZONTAL)
7295 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7296 else if (move_pattern == MV_VERTICAL)
7297 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7299 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7301 else if (move_pattern & MV_ANY_DIRECTION)
7303 MovDir[x][y] = move_pattern;
7304 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7306 else if (move_pattern & MV_WIND_DIRECTION)
7308 MovDir[x][y] = game.wind_direction;
7309 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7311 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7313 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7314 MovDir[x][y] = left_dir;
7315 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7316 MovDir[x][y] = right_dir;
7318 if (MovDir[x][y] != old_move_dir)
7319 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7321 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7323 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7324 MovDir[x][y] = right_dir;
7325 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7326 MovDir[x][y] = left_dir;
7328 if (MovDir[x][y] != old_move_dir)
7329 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7331 else if (move_pattern == MV_TOWARDS_PLAYER ||
7332 move_pattern == MV_AWAY_FROM_PLAYER)
7334 int attr_x = -1, attr_y = -1;
7336 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7338 if (game.all_players_gone)
7340 attr_x = game.exit_x;
7341 attr_y = game.exit_y;
7347 for (i = 0; i < MAX_PLAYERS; i++)
7349 struct PlayerInfo *player = &stored_player[i];
7350 int jx = player->jx, jy = player->jy;
7352 if (!player->active)
7356 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7364 MovDir[x][y] = MV_NONE;
7366 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7367 else if (attr_x > x)
7368 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7370 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7371 else if (attr_y > y)
7372 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7374 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7376 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7378 boolean first_horiz = RND(2);
7379 int new_move_dir = MovDir[x][y];
7381 if (element_info[element].move_stepsize == 0) // "not moving"
7383 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7384 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7390 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7391 Moving2Blocked(x, y, &newx, &newy);
7393 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7397 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7398 Moving2Blocked(x, y, &newx, &newy);
7400 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7403 MovDir[x][y] = old_move_dir;
7406 else if (move_pattern == MV_WHEN_PUSHED ||
7407 move_pattern == MV_WHEN_DROPPED)
7409 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7410 MovDir[x][y] = MV_NONE;
7414 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7416 static int test_xy[7][2] =
7426 static int test_dir[7] =
7436 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7437 int move_preference = -1000000; // start with very low preference
7438 int new_move_dir = MV_NONE;
7439 int start_test = RND(4);
7442 for (i = 0; i < NUM_DIRECTIONS; i++)
7444 int move_dir = test_dir[start_test + i];
7445 int move_dir_preference;
7447 xx = x + test_xy[start_test + i][0];
7448 yy = y + test_xy[start_test + i][1];
7450 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7451 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7453 new_move_dir = move_dir;
7458 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7461 move_dir_preference = -1 * RunnerVisit[xx][yy];
7462 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7463 move_dir_preference = PlayerVisit[xx][yy];
7465 if (move_dir_preference > move_preference)
7467 // prefer field that has not been visited for the longest time
7468 move_preference = move_dir_preference;
7469 new_move_dir = move_dir;
7471 else if (move_dir_preference == move_preference &&
7472 move_dir == old_move_dir)
7474 // prefer last direction when all directions are preferred equally
7475 move_preference = move_dir_preference;
7476 new_move_dir = move_dir;
7480 MovDir[x][y] = new_move_dir;
7481 if (old_move_dir != new_move_dir)
7482 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7486 static void TurnRound(int x, int y)
7488 int direction = MovDir[x][y];
7492 GfxDir[x][y] = MovDir[x][y];
7494 if (direction != MovDir[x][y])
7498 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7500 ResetGfxFrame(x, y);
7503 static boolean JustBeingPushed(int x, int y)
7507 for (i = 0; i < MAX_PLAYERS; i++)
7509 struct PlayerInfo *player = &stored_player[i];
7511 if (player->active && player->is_pushing && player->MovPos)
7513 int next_jx = player->jx + (player->jx - player->last_jx);
7514 int next_jy = player->jy + (player->jy - player->last_jy);
7516 if (x == next_jx && y == next_jy)
7524 static void StartMoving(int x, int y)
7526 boolean started_moving = FALSE; // some elements can fall _and_ move
7527 int element = Feld[x][y];
7532 if (MovDelay[x][y] == 0)
7533 GfxAction[x][y] = ACTION_DEFAULT;
7535 if (CAN_FALL(element) && y < lev_fieldy - 1)
7537 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7538 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7539 if (JustBeingPushed(x, y))
7542 if (element == EL_QUICKSAND_FULL)
7544 if (IS_FREE(x, y + 1))
7546 InitMovingField(x, y, MV_DOWN);
7547 started_moving = TRUE;
7549 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7550 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7551 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7552 Store[x][y] = EL_ROCK;
7554 Store[x][y] = EL_ROCK;
7557 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7559 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7561 if (!MovDelay[x][y])
7563 MovDelay[x][y] = TILEY + 1;
7565 ResetGfxAnimation(x, y);
7566 ResetGfxAnimation(x, y + 1);
7571 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7572 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7579 Feld[x][y] = EL_QUICKSAND_EMPTY;
7580 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7581 Store[x][y + 1] = Store[x][y];
7584 PlayLevelSoundAction(x, y, ACTION_FILLING);
7586 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7588 if (!MovDelay[x][y])
7590 MovDelay[x][y] = TILEY + 1;
7592 ResetGfxAnimation(x, y);
7593 ResetGfxAnimation(x, y + 1);
7598 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7599 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7606 Feld[x][y] = EL_QUICKSAND_EMPTY;
7607 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7608 Store[x][y + 1] = Store[x][y];
7611 PlayLevelSoundAction(x, y, ACTION_FILLING);
7614 else if (element == EL_QUICKSAND_FAST_FULL)
7616 if (IS_FREE(x, y + 1))
7618 InitMovingField(x, y, MV_DOWN);
7619 started_moving = TRUE;
7621 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7622 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7623 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7624 Store[x][y] = EL_ROCK;
7626 Store[x][y] = EL_ROCK;
7629 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7631 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7633 if (!MovDelay[x][y])
7635 MovDelay[x][y] = TILEY + 1;
7637 ResetGfxAnimation(x, y);
7638 ResetGfxAnimation(x, y + 1);
7643 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7644 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7651 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7652 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7653 Store[x][y + 1] = Store[x][y];
7656 PlayLevelSoundAction(x, y, ACTION_FILLING);
7658 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7660 if (!MovDelay[x][y])
7662 MovDelay[x][y] = TILEY + 1;
7664 ResetGfxAnimation(x, y);
7665 ResetGfxAnimation(x, y + 1);
7670 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7671 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7678 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7679 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7680 Store[x][y + 1] = Store[x][y];
7683 PlayLevelSoundAction(x, y, ACTION_FILLING);
7686 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7687 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7689 InitMovingField(x, y, MV_DOWN);
7690 started_moving = TRUE;
7692 Feld[x][y] = EL_QUICKSAND_FILLING;
7693 Store[x][y] = element;
7695 PlayLevelSoundAction(x, y, ACTION_FILLING);
7697 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7698 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7700 InitMovingField(x, y, MV_DOWN);
7701 started_moving = TRUE;
7703 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7704 Store[x][y] = element;
7706 PlayLevelSoundAction(x, y, ACTION_FILLING);
7708 else if (element == EL_MAGIC_WALL_FULL)
7710 if (IS_FREE(x, y + 1))
7712 InitMovingField(x, y, MV_DOWN);
7713 started_moving = TRUE;
7715 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7716 Store[x][y] = EL_CHANGED(Store[x][y]);
7718 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7720 if (!MovDelay[x][y])
7721 MovDelay[x][y] = TILEY / 4 + 1;
7730 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7731 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7732 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7736 else if (element == EL_BD_MAGIC_WALL_FULL)
7738 if (IS_FREE(x, y + 1))
7740 InitMovingField(x, y, MV_DOWN);
7741 started_moving = TRUE;
7743 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7744 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7746 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7748 if (!MovDelay[x][y])
7749 MovDelay[x][y] = TILEY / 4 + 1;
7758 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7759 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7760 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7764 else if (element == EL_DC_MAGIC_WALL_FULL)
7766 if (IS_FREE(x, y + 1))
7768 InitMovingField(x, y, MV_DOWN);
7769 started_moving = TRUE;
7771 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7772 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7774 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7776 if (!MovDelay[x][y])
7777 MovDelay[x][y] = TILEY / 4 + 1;
7786 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7787 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7788 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7792 else if ((CAN_PASS_MAGIC_WALL(element) &&
7793 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7794 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7795 (CAN_PASS_DC_MAGIC_WALL(element) &&
7796 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7799 InitMovingField(x, y, MV_DOWN);
7800 started_moving = TRUE;
7803 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7804 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7805 EL_DC_MAGIC_WALL_FILLING);
7806 Store[x][y] = element;
7808 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7810 SplashAcid(x, y + 1);
7812 InitMovingField(x, y, MV_DOWN);
7813 started_moving = TRUE;
7815 Store[x][y] = EL_ACID;
7818 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7819 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7820 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7821 CAN_FALL(element) && WasJustFalling[x][y] &&
7822 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7824 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7825 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7826 (Feld[x][y + 1] == EL_BLOCKED)))
7828 /* this is needed for a special case not covered by calling "Impact()"
7829 from "ContinueMoving()": if an element moves to a tile directly below
7830 another element which was just falling on that tile (which was empty
7831 in the previous frame), the falling element above would just stop
7832 instead of smashing the element below (in previous version, the above
7833 element was just checked for "moving" instead of "falling", resulting
7834 in incorrect smashes caused by horizontal movement of the above
7835 element; also, the case of the player being the element to smash was
7836 simply not covered here... :-/ ) */
7838 CheckCollision[x][y] = 0;
7839 CheckImpact[x][y] = 0;
7843 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7845 if (MovDir[x][y] == MV_NONE)
7847 InitMovingField(x, y, MV_DOWN);
7848 started_moving = TRUE;
7851 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7853 if (WasJustFalling[x][y]) // prevent animation from being restarted
7854 MovDir[x][y] = MV_DOWN;
7856 InitMovingField(x, y, MV_DOWN);
7857 started_moving = TRUE;
7859 else if (element == EL_AMOEBA_DROP)
7861 Feld[x][y] = EL_AMOEBA_GROWING;
7862 Store[x][y] = EL_AMOEBA_WET;
7864 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7865 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7866 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7867 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7869 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7870 (IS_FREE(x - 1, y + 1) ||
7871 Feld[x - 1][y + 1] == EL_ACID));
7872 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7873 (IS_FREE(x + 1, y + 1) ||
7874 Feld[x + 1][y + 1] == EL_ACID));
7875 boolean can_fall_any = (can_fall_left || can_fall_right);
7876 boolean can_fall_both = (can_fall_left && can_fall_right);
7877 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7879 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7881 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7882 can_fall_right = FALSE;
7883 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7884 can_fall_left = FALSE;
7885 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7886 can_fall_right = FALSE;
7887 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7888 can_fall_left = FALSE;
7890 can_fall_any = (can_fall_left || can_fall_right);
7891 can_fall_both = FALSE;
7896 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7897 can_fall_right = FALSE; // slip down on left side
7899 can_fall_left = !(can_fall_right = RND(2));
7901 can_fall_both = FALSE;
7906 // if not determined otherwise, prefer left side for slipping down
7907 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7908 started_moving = TRUE;
7911 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7913 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7914 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7915 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7916 int belt_dir = game.belt_dir[belt_nr];
7918 if ((belt_dir == MV_LEFT && left_is_free) ||
7919 (belt_dir == MV_RIGHT && right_is_free))
7921 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7923 InitMovingField(x, y, belt_dir);
7924 started_moving = TRUE;
7926 Pushed[x][y] = TRUE;
7927 Pushed[nextx][y] = TRUE;
7929 GfxAction[x][y] = ACTION_DEFAULT;
7933 MovDir[x][y] = 0; // if element was moving, stop it
7938 // not "else if" because of elements that can fall and move (EL_SPRING)
7939 if (CAN_MOVE(element) && !started_moving)
7941 int move_pattern = element_info[element].move_pattern;
7944 Moving2Blocked(x, y, &newx, &newy);
7946 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7949 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7950 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7952 WasJustMoving[x][y] = 0;
7953 CheckCollision[x][y] = 0;
7955 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7957 if (Feld[x][y] != element) // element has changed
7961 if (!MovDelay[x][y]) // start new movement phase
7963 // all objects that can change their move direction after each step
7964 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7966 if (element != EL_YAMYAM &&
7967 element != EL_DARK_YAMYAM &&
7968 element != EL_PACMAN &&
7969 !(move_pattern & MV_ANY_DIRECTION) &&
7970 move_pattern != MV_TURNING_LEFT &&
7971 move_pattern != MV_TURNING_RIGHT &&
7972 move_pattern != MV_TURNING_LEFT_RIGHT &&
7973 move_pattern != MV_TURNING_RIGHT_LEFT &&
7974 move_pattern != MV_TURNING_RANDOM)
7978 if (MovDelay[x][y] && (element == EL_BUG ||
7979 element == EL_SPACESHIP ||
7980 element == EL_SP_SNIKSNAK ||
7981 element == EL_SP_ELECTRON ||
7982 element == EL_MOLE))
7983 TEST_DrawLevelField(x, y);
7987 if (MovDelay[x][y]) // wait some time before next movement
7991 if (element == EL_ROBOT ||
7992 element == EL_YAMYAM ||
7993 element == EL_DARK_YAMYAM)
7995 DrawLevelElementAnimationIfNeeded(x, y, element);
7996 PlayLevelSoundAction(x, y, ACTION_WAITING);
7998 else if (element == EL_SP_ELECTRON)
7999 DrawLevelElementAnimationIfNeeded(x, y, element);
8000 else if (element == EL_DRAGON)
8003 int dir = MovDir[x][y];
8004 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8005 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8006 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8007 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8008 dir == MV_UP ? IMG_FLAMES_1_UP :
8009 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8010 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8012 GfxAction[x][y] = ACTION_ATTACKING;
8014 if (IS_PLAYER(x, y))
8015 DrawPlayerField(x, y);
8017 TEST_DrawLevelField(x, y);
8019 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8021 for (i = 1; i <= 3; i++)
8023 int xx = x + i * dx;
8024 int yy = y + i * dy;
8025 int sx = SCREENX(xx);
8026 int sy = SCREENY(yy);
8027 int flame_graphic = graphic + (i - 1);
8029 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
8034 int flamed = MovingOrBlocked2Element(xx, yy);
8036 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8039 RemoveMovingField(xx, yy);
8041 ChangeDelay[xx][yy] = 0;
8043 Feld[xx][yy] = EL_FLAMES;
8045 if (IN_SCR_FIELD(sx, sy))
8047 TEST_DrawLevelFieldCrumbled(xx, yy);
8048 DrawGraphic(sx, sy, flame_graphic, frame);
8053 if (Feld[xx][yy] == EL_FLAMES)
8054 Feld[xx][yy] = EL_EMPTY;
8055 TEST_DrawLevelField(xx, yy);
8060 if (MovDelay[x][y]) // element still has to wait some time
8062 PlayLevelSoundAction(x, y, ACTION_WAITING);
8068 // now make next step
8070 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8072 if (DONT_COLLIDE_WITH(element) &&
8073 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8074 !PLAYER_ENEMY_PROTECTED(newx, newy))
8076 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8081 else if (CAN_MOVE_INTO_ACID(element) &&
8082 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8083 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8084 (MovDir[x][y] == MV_DOWN ||
8085 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8087 SplashAcid(newx, newy);
8088 Store[x][y] = EL_ACID;
8090 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8092 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8093 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8094 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8095 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8098 TEST_DrawLevelField(x, y);
8100 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8101 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8102 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8104 game.friends_still_needed--;
8105 if (!game.friends_still_needed &&
8107 game.all_players_gone)
8112 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8114 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8115 TEST_DrawLevelField(newx, newy);
8117 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8119 else if (!IS_FREE(newx, newy))
8121 GfxAction[x][y] = ACTION_WAITING;
8123 if (IS_PLAYER(x, y))
8124 DrawPlayerField(x, y);
8126 TEST_DrawLevelField(x, y);
8131 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8133 if (IS_FOOD_PIG(Feld[newx][newy]))
8135 if (IS_MOVING(newx, newy))
8136 RemoveMovingField(newx, newy);
8139 Feld[newx][newy] = EL_EMPTY;
8140 TEST_DrawLevelField(newx, newy);
8143 PlayLevelSound(x, y, SND_PIG_DIGGING);
8145 else if (!IS_FREE(newx, newy))
8147 if (IS_PLAYER(x, y))
8148 DrawPlayerField(x, y);
8150 TEST_DrawLevelField(x, y);
8155 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8157 if (Store[x][y] != EL_EMPTY)
8159 boolean can_clone = FALSE;
8162 // check if element to clone is still there
8163 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8165 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8173 // cannot clone or target field not free anymore -- do not clone
8174 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8175 Store[x][y] = EL_EMPTY;
8178 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8180 if (IS_MV_DIAGONAL(MovDir[x][y]))
8182 int diagonal_move_dir = MovDir[x][y];
8183 int stored = Store[x][y];
8184 int change_delay = 8;
8187 // android is moving diagonally
8189 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8191 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8192 GfxElement[x][y] = EL_EMC_ANDROID;
8193 GfxAction[x][y] = ACTION_SHRINKING;
8194 GfxDir[x][y] = diagonal_move_dir;
8195 ChangeDelay[x][y] = change_delay;
8197 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8200 DrawLevelGraphicAnimation(x, y, graphic);
8201 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8203 if (Feld[newx][newy] == EL_ACID)
8205 SplashAcid(newx, newy);
8210 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8212 Store[newx][newy] = EL_EMC_ANDROID;
8213 GfxElement[newx][newy] = EL_EMC_ANDROID;
8214 GfxAction[newx][newy] = ACTION_GROWING;
8215 GfxDir[newx][newy] = diagonal_move_dir;
8216 ChangeDelay[newx][newy] = change_delay;
8218 graphic = el_act_dir2img(GfxElement[newx][newy],
8219 GfxAction[newx][newy], GfxDir[newx][newy]);
8221 DrawLevelGraphicAnimation(newx, newy, graphic);
8222 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8228 Feld[newx][newy] = EL_EMPTY;
8229 TEST_DrawLevelField(newx, newy);
8231 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8234 else if (!IS_FREE(newx, newy))
8239 else if (IS_CUSTOM_ELEMENT(element) &&
8240 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8242 if (!DigFieldByCE(newx, newy, element))
8245 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8247 RunnerVisit[x][y] = FrameCounter;
8248 PlayerVisit[x][y] /= 8; // expire player visit path
8251 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8253 if (!IS_FREE(newx, newy))
8255 if (IS_PLAYER(x, y))
8256 DrawPlayerField(x, y);
8258 TEST_DrawLevelField(x, y);
8264 boolean wanna_flame = !RND(10);
8265 int dx = newx - x, dy = newy - y;
8266 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8267 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8268 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8269 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8270 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8271 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8274 IS_CLASSIC_ENEMY(element1) ||
8275 IS_CLASSIC_ENEMY(element2)) &&
8276 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8277 element1 != EL_FLAMES && element2 != EL_FLAMES)
8279 ResetGfxAnimation(x, y);
8280 GfxAction[x][y] = ACTION_ATTACKING;
8282 if (IS_PLAYER(x, y))
8283 DrawPlayerField(x, y);
8285 TEST_DrawLevelField(x, y);
8287 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8289 MovDelay[x][y] = 50;
8291 Feld[newx][newy] = EL_FLAMES;
8292 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8293 Feld[newx1][newy1] = EL_FLAMES;
8294 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8295 Feld[newx2][newy2] = EL_FLAMES;
8301 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8302 Feld[newx][newy] == EL_DIAMOND)
8304 if (IS_MOVING(newx, newy))
8305 RemoveMovingField(newx, newy);
8308 Feld[newx][newy] = EL_EMPTY;
8309 TEST_DrawLevelField(newx, newy);
8312 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8314 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8315 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8317 if (AmoebaNr[newx][newy])
8319 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8320 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8321 Feld[newx][newy] == EL_BD_AMOEBA)
8322 AmoebaCnt[AmoebaNr[newx][newy]]--;
8325 if (IS_MOVING(newx, newy))
8327 RemoveMovingField(newx, newy);
8331 Feld[newx][newy] = EL_EMPTY;
8332 TEST_DrawLevelField(newx, newy);
8335 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8337 else if ((element == EL_PACMAN || element == EL_MOLE)
8338 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8340 if (AmoebaNr[newx][newy])
8342 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8343 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8344 Feld[newx][newy] == EL_BD_AMOEBA)
8345 AmoebaCnt[AmoebaNr[newx][newy]]--;
8348 if (element == EL_MOLE)
8350 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8351 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8353 ResetGfxAnimation(x, y);
8354 GfxAction[x][y] = ACTION_DIGGING;
8355 TEST_DrawLevelField(x, y);
8357 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8359 return; // wait for shrinking amoeba
8361 else // element == EL_PACMAN
8363 Feld[newx][newy] = EL_EMPTY;
8364 TEST_DrawLevelField(newx, newy);
8365 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8368 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8369 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8370 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8372 // wait for shrinking amoeba to completely disappear
8375 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8377 // object was running against a wall
8381 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8382 DrawLevelElementAnimation(x, y, element);
8384 if (DONT_TOUCH(element))
8385 TestIfBadThingTouchesPlayer(x, y);
8390 InitMovingField(x, y, MovDir[x][y]);
8392 PlayLevelSoundAction(x, y, ACTION_MOVING);
8396 ContinueMoving(x, y);
8399 void ContinueMoving(int x, int y)
8401 int element = Feld[x][y];
8402 struct ElementInfo *ei = &element_info[element];
8403 int direction = MovDir[x][y];
8404 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8405 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8406 int newx = x + dx, newy = y + dy;
8407 int stored = Store[x][y];
8408 int stored_new = Store[newx][newy];
8409 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8410 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8411 boolean last_line = (newy == lev_fieldy - 1);
8413 MovPos[x][y] += getElementMoveStepsize(x, y);
8415 if (pushed_by_player) // special case: moving object pushed by player
8416 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8418 if (ABS(MovPos[x][y]) < TILEX)
8420 TEST_DrawLevelField(x, y);
8422 return; // element is still moving
8425 // element reached destination field
8427 Feld[x][y] = EL_EMPTY;
8428 Feld[newx][newy] = element;
8429 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8431 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8433 element = Feld[newx][newy] = EL_ACID;
8435 else if (element == EL_MOLE)
8437 Feld[x][y] = EL_SAND;
8439 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8441 else if (element == EL_QUICKSAND_FILLING)
8443 element = Feld[newx][newy] = get_next_element(element);
8444 Store[newx][newy] = Store[x][y];
8446 else if (element == EL_QUICKSAND_EMPTYING)
8448 Feld[x][y] = get_next_element(element);
8449 element = Feld[newx][newy] = Store[x][y];
8451 else if (element == EL_QUICKSAND_FAST_FILLING)
8453 element = Feld[newx][newy] = get_next_element(element);
8454 Store[newx][newy] = Store[x][y];
8456 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8458 Feld[x][y] = get_next_element(element);
8459 element = Feld[newx][newy] = Store[x][y];
8461 else if (element == EL_MAGIC_WALL_FILLING)
8463 element = Feld[newx][newy] = get_next_element(element);
8464 if (!game.magic_wall_active)
8465 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8466 Store[newx][newy] = Store[x][y];
8468 else if (element == EL_MAGIC_WALL_EMPTYING)
8470 Feld[x][y] = get_next_element(element);
8471 if (!game.magic_wall_active)
8472 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8473 element = Feld[newx][newy] = Store[x][y];
8475 InitField(newx, newy, FALSE);
8477 else if (element == EL_BD_MAGIC_WALL_FILLING)
8479 element = Feld[newx][newy] = get_next_element(element);
8480 if (!game.magic_wall_active)
8481 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8482 Store[newx][newy] = Store[x][y];
8484 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8486 Feld[x][y] = get_next_element(element);
8487 if (!game.magic_wall_active)
8488 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8489 element = Feld[newx][newy] = Store[x][y];
8491 InitField(newx, newy, FALSE);
8493 else if (element == EL_DC_MAGIC_WALL_FILLING)
8495 element = Feld[newx][newy] = get_next_element(element);
8496 if (!game.magic_wall_active)
8497 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8498 Store[newx][newy] = Store[x][y];
8500 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8502 Feld[x][y] = get_next_element(element);
8503 if (!game.magic_wall_active)
8504 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8505 element = Feld[newx][newy] = Store[x][y];
8507 InitField(newx, newy, FALSE);
8509 else if (element == EL_AMOEBA_DROPPING)
8511 Feld[x][y] = get_next_element(element);
8512 element = Feld[newx][newy] = Store[x][y];
8514 else if (element == EL_SOKOBAN_OBJECT)
8517 Feld[x][y] = Back[x][y];
8519 if (Back[newx][newy])
8520 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8522 Back[x][y] = Back[newx][newy] = 0;
8525 Store[x][y] = EL_EMPTY;
8530 MovDelay[newx][newy] = 0;
8532 if (CAN_CHANGE_OR_HAS_ACTION(element))
8534 // copy element change control values to new field
8535 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8536 ChangePage[newx][newy] = ChangePage[x][y];
8537 ChangeCount[newx][newy] = ChangeCount[x][y];
8538 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8541 CustomValue[newx][newy] = CustomValue[x][y];
8543 ChangeDelay[x][y] = 0;
8544 ChangePage[x][y] = -1;
8545 ChangeCount[x][y] = 0;
8546 ChangeEvent[x][y] = -1;
8548 CustomValue[x][y] = 0;
8550 // copy animation control values to new field
8551 GfxFrame[newx][newy] = GfxFrame[x][y];
8552 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8553 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8554 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8556 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8558 // some elements can leave other elements behind after moving
8559 if (ei->move_leave_element != EL_EMPTY &&
8560 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8561 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8563 int move_leave_element = ei->move_leave_element;
8565 // this makes it possible to leave the removed element again
8566 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8567 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8569 Feld[x][y] = move_leave_element;
8571 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8572 MovDir[x][y] = direction;
8574 InitField(x, y, FALSE);
8576 if (GFX_CRUMBLED(Feld[x][y]))
8577 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8579 if (ELEM_IS_PLAYER(move_leave_element))
8580 RelocatePlayer(x, y, move_leave_element);
8583 // do this after checking for left-behind element
8584 ResetGfxAnimation(x, y); // reset animation values for old field
8586 if (!CAN_MOVE(element) ||
8587 (CAN_FALL(element) && direction == MV_DOWN &&
8588 (element == EL_SPRING ||
8589 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8590 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8591 GfxDir[x][y] = MovDir[newx][newy] = 0;
8593 TEST_DrawLevelField(x, y);
8594 TEST_DrawLevelField(newx, newy);
8596 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8598 // prevent pushed element from moving on in pushed direction
8599 if (pushed_by_player && CAN_MOVE(element) &&
8600 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8601 !(element_info[element].move_pattern & direction))
8602 TurnRound(newx, newy);
8604 // prevent elements on conveyor belt from moving on in last direction
8605 if (pushed_by_conveyor && CAN_FALL(element) &&
8606 direction & MV_HORIZONTAL)
8607 MovDir[newx][newy] = 0;
8609 if (!pushed_by_player)
8611 int nextx = newx + dx, nexty = newy + dy;
8612 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8614 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8616 if (CAN_FALL(element) && direction == MV_DOWN)
8617 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8619 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8620 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8622 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8623 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8626 if (DONT_TOUCH(element)) // object may be nasty to player or others
8628 TestIfBadThingTouchesPlayer(newx, newy);
8629 TestIfBadThingTouchesFriend(newx, newy);
8631 if (!IS_CUSTOM_ELEMENT(element))
8632 TestIfBadThingTouchesOtherBadThing(newx, newy);
8634 else if (element == EL_PENGUIN)
8635 TestIfFriendTouchesBadThing(newx, newy);
8637 if (DONT_GET_HIT_BY(element))
8639 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8642 // give the player one last chance (one more frame) to move away
8643 if (CAN_FALL(element) && direction == MV_DOWN &&
8644 (last_line || (!IS_FREE(x, newy + 1) &&
8645 (!IS_PLAYER(x, newy + 1) ||
8646 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8649 if (pushed_by_player && !game.use_change_when_pushing_bug)
8651 int push_side = MV_DIR_OPPOSITE(direction);
8652 struct PlayerInfo *player = PLAYERINFO(x, y);
8654 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8655 player->index_bit, push_side);
8656 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8657 player->index_bit, push_side);
8660 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8661 MovDelay[newx][newy] = 1;
8663 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8665 TestIfElementTouchesCustomElement(x, y); // empty or new element
8666 TestIfElementHitsCustomElement(newx, newy, direction);
8667 TestIfPlayerTouchesCustomElement(newx, newy);
8668 TestIfElementTouchesCustomElement(newx, newy);
8670 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8671 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8672 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8673 MV_DIR_OPPOSITE(direction));
8676 int AmoebeNachbarNr(int ax, int ay)
8679 int element = Feld[ax][ay];
8681 static int xy[4][2] =
8689 for (i = 0; i < NUM_DIRECTIONS; i++)
8691 int x = ax + xy[i][0];
8692 int y = ay + xy[i][1];
8694 if (!IN_LEV_FIELD(x, y))
8697 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8698 group_nr = AmoebaNr[x][y];
8704 static void AmoebenVereinigen(int ax, int ay)
8706 int i, x, y, xx, yy;
8707 int new_group_nr = AmoebaNr[ax][ay];
8708 static int xy[4][2] =
8716 if (new_group_nr == 0)
8719 for (i = 0; i < NUM_DIRECTIONS; i++)
8724 if (!IN_LEV_FIELD(x, y))
8727 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8728 Feld[x][y] == EL_BD_AMOEBA ||
8729 Feld[x][y] == EL_AMOEBA_DEAD) &&
8730 AmoebaNr[x][y] != new_group_nr)
8732 int old_group_nr = AmoebaNr[x][y];
8734 if (old_group_nr == 0)
8737 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8738 AmoebaCnt[old_group_nr] = 0;
8739 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8740 AmoebaCnt2[old_group_nr] = 0;
8742 SCAN_PLAYFIELD(xx, yy)
8744 if (AmoebaNr[xx][yy] == old_group_nr)
8745 AmoebaNr[xx][yy] = new_group_nr;
8751 void AmoebeUmwandeln(int ax, int ay)
8755 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8757 int group_nr = AmoebaNr[ax][ay];
8762 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8763 printf("AmoebeUmwandeln(): This should never happen!\n");
8768 SCAN_PLAYFIELD(x, y)
8770 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8773 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8777 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8778 SND_AMOEBA_TURNING_TO_GEM :
8779 SND_AMOEBA_TURNING_TO_ROCK));
8784 static int xy[4][2] =
8792 for (i = 0; i < NUM_DIRECTIONS; i++)
8797 if (!IN_LEV_FIELD(x, y))
8800 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8802 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8803 SND_AMOEBA_TURNING_TO_GEM :
8804 SND_AMOEBA_TURNING_TO_ROCK));
8811 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8814 int group_nr = AmoebaNr[ax][ay];
8815 boolean done = FALSE;
8820 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8821 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8826 SCAN_PLAYFIELD(x, y)
8828 if (AmoebaNr[x][y] == group_nr &&
8829 (Feld[x][y] == EL_AMOEBA_DEAD ||
8830 Feld[x][y] == EL_BD_AMOEBA ||
8831 Feld[x][y] == EL_AMOEBA_GROWING))
8834 Feld[x][y] = new_element;
8835 InitField(x, y, FALSE);
8836 TEST_DrawLevelField(x, y);
8842 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8843 SND_BD_AMOEBA_TURNING_TO_ROCK :
8844 SND_BD_AMOEBA_TURNING_TO_GEM));
8847 static void AmoebeWaechst(int x, int y)
8849 static unsigned int sound_delay = 0;
8850 static unsigned int sound_delay_value = 0;
8852 if (!MovDelay[x][y]) // start new growing cycle
8856 if (DelayReached(&sound_delay, sound_delay_value))
8858 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8859 sound_delay_value = 30;
8863 if (MovDelay[x][y]) // wait some time before growing bigger
8866 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8868 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8869 6 - MovDelay[x][y]);
8871 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8874 if (!MovDelay[x][y])
8876 Feld[x][y] = Store[x][y];
8878 TEST_DrawLevelField(x, y);
8883 static void AmoebaDisappearing(int x, int y)
8885 static unsigned int sound_delay = 0;
8886 static unsigned int sound_delay_value = 0;
8888 if (!MovDelay[x][y]) // start new shrinking cycle
8892 if (DelayReached(&sound_delay, sound_delay_value))
8893 sound_delay_value = 30;
8896 if (MovDelay[x][y]) // wait some time before shrinking
8899 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8901 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8902 6 - MovDelay[x][y]);
8904 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8907 if (!MovDelay[x][y])
8909 Feld[x][y] = EL_EMPTY;
8910 TEST_DrawLevelField(x, y);
8912 // don't let mole enter this field in this cycle;
8913 // (give priority to objects falling to this field from above)
8919 static void AmoebeAbleger(int ax, int ay)
8922 int element = Feld[ax][ay];
8923 int graphic = el2img(element);
8924 int newax = ax, neway = ay;
8925 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8926 static int xy[4][2] =
8934 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8936 Feld[ax][ay] = EL_AMOEBA_DEAD;
8937 TEST_DrawLevelField(ax, ay);
8941 if (IS_ANIMATED(graphic))
8942 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8944 if (!MovDelay[ax][ay]) // start making new amoeba field
8945 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8947 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8950 if (MovDelay[ax][ay])
8954 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8957 int x = ax + xy[start][0];
8958 int y = ay + xy[start][1];
8960 if (!IN_LEV_FIELD(x, y))
8963 if (IS_FREE(x, y) ||
8964 CAN_GROW_INTO(Feld[x][y]) ||
8965 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8966 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8972 if (newax == ax && neway == ay)
8975 else // normal or "filled" (BD style) amoeba
8978 boolean waiting_for_player = FALSE;
8980 for (i = 0; i < NUM_DIRECTIONS; i++)
8982 int j = (start + i) % 4;
8983 int x = ax + xy[j][0];
8984 int y = ay + xy[j][1];
8986 if (!IN_LEV_FIELD(x, y))
8989 if (IS_FREE(x, y) ||
8990 CAN_GROW_INTO(Feld[x][y]) ||
8991 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8992 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8998 else if (IS_PLAYER(x, y))
8999 waiting_for_player = TRUE;
9002 if (newax == ax && neway == ay) // amoeba cannot grow
9004 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9006 Feld[ax][ay] = EL_AMOEBA_DEAD;
9007 TEST_DrawLevelField(ax, ay);
9008 AmoebaCnt[AmoebaNr[ax][ay]]--;
9010 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9012 if (element == EL_AMOEBA_FULL)
9013 AmoebeUmwandeln(ax, ay);
9014 else if (element == EL_BD_AMOEBA)
9015 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
9020 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9022 // amoeba gets larger by growing in some direction
9024 int new_group_nr = AmoebaNr[ax][ay];
9027 if (new_group_nr == 0)
9029 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
9030 printf("AmoebeAbleger(): This should never happen!\n");
9035 AmoebaNr[newax][neway] = new_group_nr;
9036 AmoebaCnt[new_group_nr]++;
9037 AmoebaCnt2[new_group_nr]++;
9039 // if amoeba touches other amoeba(s) after growing, unify them
9040 AmoebenVereinigen(newax, neway);
9042 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9044 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
9050 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9051 (neway == lev_fieldy - 1 && newax != ax))
9053 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9054 Store[newax][neway] = element;
9056 else if (neway == ay || element == EL_EMC_DRIPPER)
9058 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9060 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9064 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9065 Feld[ax][ay] = EL_AMOEBA_DROPPING;
9066 Store[ax][ay] = EL_AMOEBA_DROP;
9067 ContinueMoving(ax, ay);
9071 TEST_DrawLevelField(newax, neway);
9074 static void Life(int ax, int ay)
9078 int element = Feld[ax][ay];
9079 int graphic = el2img(element);
9080 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9082 boolean changed = FALSE;
9084 if (IS_ANIMATED(graphic))
9085 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9090 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9091 MovDelay[ax][ay] = life_time;
9093 if (MovDelay[ax][ay]) // wait some time before next cycle
9096 if (MovDelay[ax][ay])
9100 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9102 int xx = ax+x1, yy = ay+y1;
9103 int old_element = Feld[xx][yy];
9104 int num_neighbours = 0;
9106 if (!IN_LEV_FIELD(xx, yy))
9109 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9111 int x = xx+x2, y = yy+y2;
9113 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9116 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9117 boolean is_neighbour = FALSE;
9119 if (level.use_life_bugs)
9121 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9122 (IS_FREE(x, y) && Stop[x][y]));
9125 (Last[x][y] == element || is_player_cell);
9131 boolean is_free = FALSE;
9133 if (level.use_life_bugs)
9134 is_free = (IS_FREE(xx, yy));
9136 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9138 if (xx == ax && yy == ay) // field in the middle
9140 if (num_neighbours < life_parameter[0] ||
9141 num_neighbours > life_parameter[1])
9143 Feld[xx][yy] = EL_EMPTY;
9144 if (Feld[xx][yy] != old_element)
9145 TEST_DrawLevelField(xx, yy);
9146 Stop[xx][yy] = TRUE;
9150 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9151 { // free border field
9152 if (num_neighbours >= life_parameter[2] &&
9153 num_neighbours <= life_parameter[3])
9155 Feld[xx][yy] = element;
9156 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9157 if (Feld[xx][yy] != old_element)
9158 TEST_DrawLevelField(xx, yy);
9159 Stop[xx][yy] = TRUE;
9166 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9167 SND_GAME_OF_LIFE_GROWING);
9170 static void InitRobotWheel(int x, int y)
9172 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9175 static void RunRobotWheel(int x, int y)
9177 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9180 static void StopRobotWheel(int x, int y)
9182 if (game.robot_wheel_x == x &&
9183 game.robot_wheel_y == y)
9185 game.robot_wheel_x = -1;
9186 game.robot_wheel_y = -1;
9187 game.robot_wheel_active = FALSE;
9191 static void InitTimegateWheel(int x, int y)
9193 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9196 static void RunTimegateWheel(int x, int y)
9198 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9201 static void InitMagicBallDelay(int x, int y)
9203 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9206 static void ActivateMagicBall(int bx, int by)
9210 if (level.ball_random)
9212 int pos_border = RND(8); // select one of the eight border elements
9213 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9214 int xx = pos_content % 3;
9215 int yy = pos_content / 3;
9220 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9221 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9225 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9227 int xx = x - bx + 1;
9228 int yy = y - by + 1;
9230 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9231 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9235 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9238 static void CheckExit(int x, int y)
9240 if (game.gems_still_needed > 0 ||
9241 game.sokoban_fields_still_needed > 0 ||
9242 game.sokoban_objects_still_needed > 0 ||
9243 game.lights_still_needed > 0)
9245 int element = Feld[x][y];
9246 int graphic = el2img(element);
9248 if (IS_ANIMATED(graphic))
9249 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9254 // do not re-open exit door closed after last player
9255 if (game.all_players_gone)
9258 Feld[x][y] = EL_EXIT_OPENING;
9260 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9263 static void CheckExitEM(int x, int y)
9265 if (game.gems_still_needed > 0 ||
9266 game.sokoban_fields_still_needed > 0 ||
9267 game.sokoban_objects_still_needed > 0 ||
9268 game.lights_still_needed > 0)
9270 int element = Feld[x][y];
9271 int graphic = el2img(element);
9273 if (IS_ANIMATED(graphic))
9274 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9279 // do not re-open exit door closed after last player
9280 if (game.all_players_gone)
9283 Feld[x][y] = EL_EM_EXIT_OPENING;
9285 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9288 static void CheckExitSteel(int x, int y)
9290 if (game.gems_still_needed > 0 ||
9291 game.sokoban_fields_still_needed > 0 ||
9292 game.sokoban_objects_still_needed > 0 ||
9293 game.lights_still_needed > 0)
9295 int element = Feld[x][y];
9296 int graphic = el2img(element);
9298 if (IS_ANIMATED(graphic))
9299 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9304 // do not re-open exit door closed after last player
9305 if (game.all_players_gone)
9308 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9310 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9313 static void CheckExitSteelEM(int x, int y)
9315 if (game.gems_still_needed > 0 ||
9316 game.sokoban_fields_still_needed > 0 ||
9317 game.sokoban_objects_still_needed > 0 ||
9318 game.lights_still_needed > 0)
9320 int element = Feld[x][y];
9321 int graphic = el2img(element);
9323 if (IS_ANIMATED(graphic))
9324 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9329 // do not re-open exit door closed after last player
9330 if (game.all_players_gone)
9333 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9335 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9338 static void CheckExitSP(int x, int y)
9340 if (game.gems_still_needed > 0)
9342 int element = Feld[x][y];
9343 int graphic = el2img(element);
9345 if (IS_ANIMATED(graphic))
9346 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9351 // do not re-open exit door closed after last player
9352 if (game.all_players_gone)
9355 Feld[x][y] = EL_SP_EXIT_OPENING;
9357 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9360 static void CloseAllOpenTimegates(void)
9364 SCAN_PLAYFIELD(x, y)
9366 int element = Feld[x][y];
9368 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9370 Feld[x][y] = EL_TIMEGATE_CLOSING;
9372 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9377 static void DrawTwinkleOnField(int x, int y)
9379 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9382 if (Feld[x][y] == EL_BD_DIAMOND)
9385 if (MovDelay[x][y] == 0) // next animation frame
9386 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9388 if (MovDelay[x][y] != 0) // wait some time before next frame
9392 DrawLevelElementAnimation(x, y, Feld[x][y]);
9394 if (MovDelay[x][y] != 0)
9396 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9397 10 - MovDelay[x][y]);
9399 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9404 static void MauerWaechst(int x, int y)
9408 if (!MovDelay[x][y]) // next animation frame
9409 MovDelay[x][y] = 3 * delay;
9411 if (MovDelay[x][y]) // wait some time before next frame
9415 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9417 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9418 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9420 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9423 if (!MovDelay[x][y])
9425 if (MovDir[x][y] == MV_LEFT)
9427 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9428 TEST_DrawLevelField(x - 1, y);
9430 else if (MovDir[x][y] == MV_RIGHT)
9432 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9433 TEST_DrawLevelField(x + 1, y);
9435 else if (MovDir[x][y] == MV_UP)
9437 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9438 TEST_DrawLevelField(x, y - 1);
9442 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9443 TEST_DrawLevelField(x, y + 1);
9446 Feld[x][y] = Store[x][y];
9448 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9449 TEST_DrawLevelField(x, y);
9454 static void MauerAbleger(int ax, int ay)
9456 int element = Feld[ax][ay];
9457 int graphic = el2img(element);
9458 boolean oben_frei = FALSE, unten_frei = FALSE;
9459 boolean links_frei = FALSE, rechts_frei = FALSE;
9460 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9461 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9462 boolean new_wall = FALSE;
9464 if (IS_ANIMATED(graphic))
9465 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9467 if (!MovDelay[ax][ay]) // start building new wall
9468 MovDelay[ax][ay] = 6;
9470 if (MovDelay[ax][ay]) // wait some time before building new wall
9473 if (MovDelay[ax][ay])
9477 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9479 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9481 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9483 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9486 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9487 element == EL_EXPANDABLE_WALL_ANY)
9491 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9492 Store[ax][ay-1] = element;
9493 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9494 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9495 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9496 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9501 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9502 Store[ax][ay+1] = element;
9503 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9504 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9505 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9506 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9511 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9512 element == EL_EXPANDABLE_WALL_ANY ||
9513 element == EL_EXPANDABLE_WALL ||
9514 element == EL_BD_EXPANDABLE_WALL)
9518 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9519 Store[ax-1][ay] = element;
9520 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9521 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9522 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9523 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9529 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9530 Store[ax+1][ay] = element;
9531 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9532 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9533 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9534 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9539 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9540 TEST_DrawLevelField(ax, ay);
9542 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9544 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9545 unten_massiv = TRUE;
9546 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9547 links_massiv = TRUE;
9548 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9549 rechts_massiv = TRUE;
9551 if (((oben_massiv && unten_massiv) ||
9552 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9553 element == EL_EXPANDABLE_WALL) &&
9554 ((links_massiv && rechts_massiv) ||
9555 element == EL_EXPANDABLE_WALL_VERTICAL))
9556 Feld[ax][ay] = EL_WALL;
9559 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9562 static void MauerAblegerStahl(int ax, int ay)
9564 int element = Feld[ax][ay];
9565 int graphic = el2img(element);
9566 boolean oben_frei = FALSE, unten_frei = FALSE;
9567 boolean links_frei = FALSE, rechts_frei = FALSE;
9568 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9569 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9570 boolean new_wall = FALSE;
9572 if (IS_ANIMATED(graphic))
9573 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9575 if (!MovDelay[ax][ay]) // start building new wall
9576 MovDelay[ax][ay] = 6;
9578 if (MovDelay[ax][ay]) // wait some time before building new wall
9581 if (MovDelay[ax][ay])
9585 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9587 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9589 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9591 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9594 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9595 element == EL_EXPANDABLE_STEELWALL_ANY)
9599 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9600 Store[ax][ay-1] = element;
9601 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9602 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9603 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9604 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9609 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9610 Store[ax][ay+1] = element;
9611 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9612 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9613 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9614 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9619 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9620 element == EL_EXPANDABLE_STEELWALL_ANY)
9624 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9625 Store[ax-1][ay] = element;
9626 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9627 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9628 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9629 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9635 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9636 Store[ax+1][ay] = element;
9637 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9638 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9639 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9640 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9645 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9647 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9648 unten_massiv = TRUE;
9649 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9650 links_massiv = TRUE;
9651 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9652 rechts_massiv = TRUE;
9654 if (((oben_massiv && unten_massiv) ||
9655 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9656 ((links_massiv && rechts_massiv) ||
9657 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9658 Feld[ax][ay] = EL_STEELWALL;
9661 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9664 static void CheckForDragon(int x, int y)
9667 boolean dragon_found = FALSE;
9668 static int xy[4][2] =
9676 for (i = 0; i < NUM_DIRECTIONS; i++)
9678 for (j = 0; j < 4; j++)
9680 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9682 if (IN_LEV_FIELD(xx, yy) &&
9683 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9685 if (Feld[xx][yy] == EL_DRAGON)
9686 dragon_found = TRUE;
9695 for (i = 0; i < NUM_DIRECTIONS; i++)
9697 for (j = 0; j < 3; j++)
9699 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9701 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9703 Feld[xx][yy] = EL_EMPTY;
9704 TEST_DrawLevelField(xx, yy);
9713 static void InitBuggyBase(int x, int y)
9715 int element = Feld[x][y];
9716 int activating_delay = FRAMES_PER_SECOND / 4;
9719 (element == EL_SP_BUGGY_BASE ?
9720 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9721 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9723 element == EL_SP_BUGGY_BASE_ACTIVE ?
9724 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9727 static void WarnBuggyBase(int x, int y)
9730 static int xy[4][2] =
9738 for (i = 0; i < NUM_DIRECTIONS; i++)
9740 int xx = x + xy[i][0];
9741 int yy = y + xy[i][1];
9743 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9745 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9752 static void InitTrap(int x, int y)
9754 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9757 static void ActivateTrap(int x, int y)
9759 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9762 static void ChangeActiveTrap(int x, int y)
9764 int graphic = IMG_TRAP_ACTIVE;
9766 // if new animation frame was drawn, correct crumbled sand border
9767 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9768 TEST_DrawLevelFieldCrumbled(x, y);
9771 static int getSpecialActionElement(int element, int number, int base_element)
9773 return (element != EL_EMPTY ? element :
9774 number != -1 ? base_element + number - 1 :
9778 static int getModifiedActionNumber(int value_old, int operator, int operand,
9779 int value_min, int value_max)
9781 int value_new = (operator == CA_MODE_SET ? operand :
9782 operator == CA_MODE_ADD ? value_old + operand :
9783 operator == CA_MODE_SUBTRACT ? value_old - operand :
9784 operator == CA_MODE_MULTIPLY ? value_old * operand :
9785 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9786 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9789 return (value_new < value_min ? value_min :
9790 value_new > value_max ? value_max :
9794 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9796 struct ElementInfo *ei = &element_info[element];
9797 struct ElementChangeInfo *change = &ei->change_page[page];
9798 int target_element = change->target_element;
9799 int action_type = change->action_type;
9800 int action_mode = change->action_mode;
9801 int action_arg = change->action_arg;
9802 int action_element = change->action_element;
9805 if (!change->has_action)
9808 // ---------- determine action paramater values -----------------------------
9810 int level_time_value =
9811 (level.time > 0 ? TimeLeft :
9814 int action_arg_element_raw =
9815 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9816 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9817 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9818 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9819 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9820 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9821 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9823 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9825 int action_arg_direction =
9826 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9827 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9828 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9829 change->actual_trigger_side :
9830 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9831 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9834 int action_arg_number_min =
9835 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9838 int action_arg_number_max =
9839 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9840 action_type == CA_SET_LEVEL_GEMS ? 999 :
9841 action_type == CA_SET_LEVEL_TIME ? 9999 :
9842 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9843 action_type == CA_SET_CE_VALUE ? 9999 :
9844 action_type == CA_SET_CE_SCORE ? 9999 :
9847 int action_arg_number_reset =
9848 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9849 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9850 action_type == CA_SET_LEVEL_TIME ? level.time :
9851 action_type == CA_SET_LEVEL_SCORE ? 0 :
9852 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9853 action_type == CA_SET_CE_SCORE ? 0 :
9856 int action_arg_number =
9857 (action_arg <= CA_ARG_MAX ? action_arg :
9858 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9859 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9860 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9861 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9862 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9863 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9864 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9865 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9866 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9867 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9868 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9869 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9870 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9871 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9872 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9873 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9874 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9875 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9876 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9877 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9878 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9881 int action_arg_number_old =
9882 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9883 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9884 action_type == CA_SET_LEVEL_SCORE ? game.score :
9885 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9886 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9889 int action_arg_number_new =
9890 getModifiedActionNumber(action_arg_number_old,
9891 action_mode, action_arg_number,
9892 action_arg_number_min, action_arg_number_max);
9894 int trigger_player_bits =
9895 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9896 change->actual_trigger_player_bits : change->trigger_player);
9898 int action_arg_player_bits =
9899 (action_arg >= CA_ARG_PLAYER_1 &&
9900 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9901 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9902 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9905 // ---------- execute action -----------------------------------------------
9907 switch (action_type)
9914 // ---------- level actions ----------------------------------------------
9916 case CA_RESTART_LEVEL:
9918 game.restart_level = TRUE;
9923 case CA_SHOW_ENVELOPE:
9925 int element = getSpecialActionElement(action_arg_element,
9926 action_arg_number, EL_ENVELOPE_1);
9928 if (IS_ENVELOPE(element))
9929 local_player->show_envelope = element;
9934 case CA_SET_LEVEL_TIME:
9936 if (level.time > 0) // only modify limited time value
9938 TimeLeft = action_arg_number_new;
9940 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9942 DisplayGameControlValues();
9944 if (!TimeLeft && setup.time_limit)
9945 for (i = 0; i < MAX_PLAYERS; i++)
9946 KillPlayer(&stored_player[i]);
9952 case CA_SET_LEVEL_SCORE:
9954 game.score = action_arg_number_new;
9956 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9958 DisplayGameControlValues();
9963 case CA_SET_LEVEL_GEMS:
9965 game.gems_still_needed = action_arg_number_new;
9967 game.snapshot.collected_item = TRUE;
9969 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9971 DisplayGameControlValues();
9976 case CA_SET_LEVEL_WIND:
9978 game.wind_direction = action_arg_direction;
9983 case CA_SET_LEVEL_RANDOM_SEED:
9985 // ensure that setting a new random seed while playing is predictable
9986 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9991 // ---------- player actions ---------------------------------------------
9993 case CA_MOVE_PLAYER:
9994 case CA_MOVE_PLAYER_NEW:
9996 // automatically move to the next field in specified direction
9997 for (i = 0; i < MAX_PLAYERS; i++)
9998 if (trigger_player_bits & (1 << i))
9999 if (action_type == CA_MOVE_PLAYER ||
10000 stored_player[i].MovPos == 0)
10001 stored_player[i].programmed_action = action_arg_direction;
10006 case CA_EXIT_PLAYER:
10008 for (i = 0; i < MAX_PLAYERS; i++)
10009 if (action_arg_player_bits & (1 << i))
10010 ExitPlayer(&stored_player[i]);
10012 if (game.players_still_needed == 0)
10018 case CA_KILL_PLAYER:
10020 for (i = 0; i < MAX_PLAYERS; i++)
10021 if (action_arg_player_bits & (1 << i))
10022 KillPlayer(&stored_player[i]);
10027 case CA_SET_PLAYER_KEYS:
10029 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10030 int element = getSpecialActionElement(action_arg_element,
10031 action_arg_number, EL_KEY_1);
10033 if (IS_KEY(element))
10035 for (i = 0; i < MAX_PLAYERS; i++)
10037 if (trigger_player_bits & (1 << i))
10039 stored_player[i].key[KEY_NR(element)] = key_state;
10041 DrawGameDoorValues();
10049 case CA_SET_PLAYER_SPEED:
10051 for (i = 0; i < MAX_PLAYERS; i++)
10053 if (trigger_player_bits & (1 << i))
10055 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10057 if (action_arg == CA_ARG_SPEED_FASTER &&
10058 stored_player[i].cannot_move)
10060 action_arg_number = STEPSIZE_VERY_SLOW;
10062 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10063 action_arg == CA_ARG_SPEED_FASTER)
10065 action_arg_number = 2;
10066 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10069 else if (action_arg == CA_ARG_NUMBER_RESET)
10071 action_arg_number = level.initial_player_stepsize[i];
10075 getModifiedActionNumber(move_stepsize,
10078 action_arg_number_min,
10079 action_arg_number_max);
10081 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10088 case CA_SET_PLAYER_SHIELD:
10090 for (i = 0; i < MAX_PLAYERS; i++)
10092 if (trigger_player_bits & (1 << i))
10094 if (action_arg == CA_ARG_SHIELD_OFF)
10096 stored_player[i].shield_normal_time_left = 0;
10097 stored_player[i].shield_deadly_time_left = 0;
10099 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10101 stored_player[i].shield_normal_time_left = 999999;
10103 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10105 stored_player[i].shield_normal_time_left = 999999;
10106 stored_player[i].shield_deadly_time_left = 999999;
10114 case CA_SET_PLAYER_GRAVITY:
10116 for (i = 0; i < MAX_PLAYERS; i++)
10118 if (trigger_player_bits & (1 << i))
10120 stored_player[i].gravity =
10121 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10122 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10123 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10124 stored_player[i].gravity);
10131 case CA_SET_PLAYER_ARTWORK:
10133 for (i = 0; i < MAX_PLAYERS; i++)
10135 if (trigger_player_bits & (1 << i))
10137 int artwork_element = action_arg_element;
10139 if (action_arg == CA_ARG_ELEMENT_RESET)
10141 (level.use_artwork_element[i] ? level.artwork_element[i] :
10142 stored_player[i].element_nr);
10144 if (stored_player[i].artwork_element != artwork_element)
10145 stored_player[i].Frame = 0;
10147 stored_player[i].artwork_element = artwork_element;
10149 SetPlayerWaiting(&stored_player[i], FALSE);
10151 // set number of special actions for bored and sleeping animation
10152 stored_player[i].num_special_action_bored =
10153 get_num_special_action(artwork_element,
10154 ACTION_BORING_1, ACTION_BORING_LAST);
10155 stored_player[i].num_special_action_sleeping =
10156 get_num_special_action(artwork_element,
10157 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10164 case CA_SET_PLAYER_INVENTORY:
10166 for (i = 0; i < MAX_PLAYERS; i++)
10168 struct PlayerInfo *player = &stored_player[i];
10171 if (trigger_player_bits & (1 << i))
10173 int inventory_element = action_arg_element;
10175 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10176 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10177 action_arg == CA_ARG_ELEMENT_ACTION)
10179 int element = inventory_element;
10180 int collect_count = element_info[element].collect_count_initial;
10182 if (!IS_CUSTOM_ELEMENT(element))
10185 if (collect_count == 0)
10186 player->inventory_infinite_element = element;
10188 for (k = 0; k < collect_count; k++)
10189 if (player->inventory_size < MAX_INVENTORY_SIZE)
10190 player->inventory_element[player->inventory_size++] =
10193 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10194 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10195 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10197 if (player->inventory_infinite_element != EL_UNDEFINED &&
10198 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10199 action_arg_element_raw))
10200 player->inventory_infinite_element = EL_UNDEFINED;
10202 for (k = 0, j = 0; j < player->inventory_size; j++)
10204 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10205 action_arg_element_raw))
10206 player->inventory_element[k++] = player->inventory_element[j];
10209 player->inventory_size = k;
10211 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10213 if (player->inventory_size > 0)
10215 for (j = 0; j < player->inventory_size - 1; j++)
10216 player->inventory_element[j] = player->inventory_element[j + 1];
10218 player->inventory_size--;
10221 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10223 if (player->inventory_size > 0)
10224 player->inventory_size--;
10226 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10228 player->inventory_infinite_element = EL_UNDEFINED;
10229 player->inventory_size = 0;
10231 else if (action_arg == CA_ARG_INVENTORY_RESET)
10233 player->inventory_infinite_element = EL_UNDEFINED;
10234 player->inventory_size = 0;
10236 if (level.use_initial_inventory[i])
10238 for (j = 0; j < level.initial_inventory_size[i]; j++)
10240 int element = level.initial_inventory_content[i][j];
10241 int collect_count = element_info[element].collect_count_initial;
10243 if (!IS_CUSTOM_ELEMENT(element))
10246 if (collect_count == 0)
10247 player->inventory_infinite_element = element;
10249 for (k = 0; k < collect_count; k++)
10250 if (player->inventory_size < MAX_INVENTORY_SIZE)
10251 player->inventory_element[player->inventory_size++] =
10262 // ---------- CE actions -------------------------------------------------
10264 case CA_SET_CE_VALUE:
10266 int last_ce_value = CustomValue[x][y];
10268 CustomValue[x][y] = action_arg_number_new;
10270 if (CustomValue[x][y] != last_ce_value)
10272 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10273 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10275 if (CustomValue[x][y] == 0)
10277 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10278 ChangeCount[x][y] = 0; // allow at least one more change
10280 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10281 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10288 case CA_SET_CE_SCORE:
10290 int last_ce_score = ei->collect_score;
10292 ei->collect_score = action_arg_number_new;
10294 if (ei->collect_score != last_ce_score)
10296 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10297 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10299 if (ei->collect_score == 0)
10303 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10304 ChangeCount[x][y] = 0; // allow at least one more change
10306 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10307 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10310 This is a very special case that seems to be a mixture between
10311 CheckElementChange() and CheckTriggeredElementChange(): while
10312 the first one only affects single elements that are triggered
10313 directly, the second one affects multiple elements in the playfield
10314 that are triggered indirectly by another element. This is a third
10315 case: Changing the CE score always affects multiple identical CEs,
10316 so every affected CE must be checked, not only the single CE for
10317 which the CE score was changed in the first place (as every instance
10318 of that CE shares the same CE score, and therefore also can change)!
10320 SCAN_PLAYFIELD(xx, yy)
10322 if (Feld[xx][yy] == element)
10323 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10324 CE_SCORE_GETS_ZERO);
10332 case CA_SET_CE_ARTWORK:
10334 int artwork_element = action_arg_element;
10335 boolean reset_frame = FALSE;
10338 if (action_arg == CA_ARG_ELEMENT_RESET)
10339 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10342 if (ei->gfx_element != artwork_element)
10343 reset_frame = TRUE;
10345 ei->gfx_element = artwork_element;
10347 SCAN_PLAYFIELD(xx, yy)
10349 if (Feld[xx][yy] == element)
10353 ResetGfxAnimation(xx, yy);
10354 ResetRandomAnimationValue(xx, yy);
10357 TEST_DrawLevelField(xx, yy);
10364 // ---------- engine actions ---------------------------------------------
10366 case CA_SET_ENGINE_SCAN_MODE:
10368 InitPlayfieldScanMode(action_arg);
10378 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10380 int old_element = Feld[x][y];
10381 int new_element = GetElementFromGroupElement(element);
10382 int previous_move_direction = MovDir[x][y];
10383 int last_ce_value = CustomValue[x][y];
10384 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10385 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10386 boolean add_player_onto_element = (new_element_is_player &&
10387 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10388 IS_WALKABLE(old_element));
10390 if (!add_player_onto_element)
10392 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10393 RemoveMovingField(x, y);
10397 Feld[x][y] = new_element;
10399 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10400 MovDir[x][y] = previous_move_direction;
10402 if (element_info[new_element].use_last_ce_value)
10403 CustomValue[x][y] = last_ce_value;
10405 InitField_WithBug1(x, y, FALSE);
10407 new_element = Feld[x][y]; // element may have changed
10409 ResetGfxAnimation(x, y);
10410 ResetRandomAnimationValue(x, y);
10412 TEST_DrawLevelField(x, y);
10414 if (GFX_CRUMBLED(new_element))
10415 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10418 // check if element under the player changes from accessible to unaccessible
10419 // (needed for special case of dropping element which then changes)
10420 // (must be checked after creating new element for walkable group elements)
10421 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10422 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10429 // "ChangeCount" not set yet to allow "entered by player" change one time
10430 if (new_element_is_player)
10431 RelocatePlayer(x, y, new_element);
10434 ChangeCount[x][y]++; // count number of changes in the same frame
10436 TestIfBadThingTouchesPlayer(x, y);
10437 TestIfPlayerTouchesCustomElement(x, y);
10438 TestIfElementTouchesCustomElement(x, y);
10441 static void CreateField(int x, int y, int element)
10443 CreateFieldExt(x, y, element, FALSE);
10446 static void CreateElementFromChange(int x, int y, int element)
10448 element = GET_VALID_RUNTIME_ELEMENT(element);
10450 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10452 int old_element = Feld[x][y];
10454 // prevent changed element from moving in same engine frame
10455 // unless both old and new element can either fall or move
10456 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10457 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10461 CreateFieldExt(x, y, element, TRUE);
10464 static boolean ChangeElement(int x, int y, int element, int page)
10466 struct ElementInfo *ei = &element_info[element];
10467 struct ElementChangeInfo *change = &ei->change_page[page];
10468 int ce_value = CustomValue[x][y];
10469 int ce_score = ei->collect_score;
10470 int target_element;
10471 int old_element = Feld[x][y];
10473 // always use default change event to prevent running into a loop
10474 if (ChangeEvent[x][y] == -1)
10475 ChangeEvent[x][y] = CE_DELAY;
10477 if (ChangeEvent[x][y] == CE_DELAY)
10479 // reset actual trigger element, trigger player and action element
10480 change->actual_trigger_element = EL_EMPTY;
10481 change->actual_trigger_player = EL_EMPTY;
10482 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10483 change->actual_trigger_side = CH_SIDE_NONE;
10484 change->actual_trigger_ce_value = 0;
10485 change->actual_trigger_ce_score = 0;
10488 // do not change elements more than a specified maximum number of changes
10489 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10492 ChangeCount[x][y]++; // count number of changes in the same frame
10494 if (change->explode)
10501 if (change->use_target_content)
10503 boolean complete_replace = TRUE;
10504 boolean can_replace[3][3];
10507 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10510 boolean is_walkable;
10511 boolean is_diggable;
10512 boolean is_collectible;
10513 boolean is_removable;
10514 boolean is_destructible;
10515 int ex = x + xx - 1;
10516 int ey = y + yy - 1;
10517 int content_element = change->target_content.e[xx][yy];
10520 can_replace[xx][yy] = TRUE;
10522 if (ex == x && ey == y) // do not check changing element itself
10525 if (content_element == EL_EMPTY_SPACE)
10527 can_replace[xx][yy] = FALSE; // do not replace border with space
10532 if (!IN_LEV_FIELD(ex, ey))
10534 can_replace[xx][yy] = FALSE;
10535 complete_replace = FALSE;
10542 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10543 e = MovingOrBlocked2Element(ex, ey);
10545 is_empty = (IS_FREE(ex, ey) ||
10546 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10548 is_walkable = (is_empty || IS_WALKABLE(e));
10549 is_diggable = (is_empty || IS_DIGGABLE(e));
10550 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10551 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10552 is_removable = (is_diggable || is_collectible);
10554 can_replace[xx][yy] =
10555 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10556 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10557 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10558 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10559 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10560 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10561 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10563 if (!can_replace[xx][yy])
10564 complete_replace = FALSE;
10567 if (!change->only_if_complete || complete_replace)
10569 boolean something_has_changed = FALSE;
10571 if (change->only_if_complete && change->use_random_replace &&
10572 RND(100) < change->random_percentage)
10575 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10577 int ex = x + xx - 1;
10578 int ey = y + yy - 1;
10579 int content_element;
10581 if (can_replace[xx][yy] && (!change->use_random_replace ||
10582 RND(100) < change->random_percentage))
10584 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10585 RemoveMovingField(ex, ey);
10587 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10589 content_element = change->target_content.e[xx][yy];
10590 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10591 ce_value, ce_score);
10593 CreateElementFromChange(ex, ey, target_element);
10595 something_has_changed = TRUE;
10597 // for symmetry reasons, freeze newly created border elements
10598 if (ex != x || ey != y)
10599 Stop[ex][ey] = TRUE; // no more moving in this frame
10603 if (something_has_changed)
10605 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10606 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10612 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10613 ce_value, ce_score);
10615 if (element == EL_DIAGONAL_GROWING ||
10616 element == EL_DIAGONAL_SHRINKING)
10618 target_element = Store[x][y];
10620 Store[x][y] = EL_EMPTY;
10623 CreateElementFromChange(x, y, target_element);
10625 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10626 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10629 // this uses direct change before indirect change
10630 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10635 static void HandleElementChange(int x, int y, int page)
10637 int element = MovingOrBlocked2Element(x, y);
10638 struct ElementInfo *ei = &element_info[element];
10639 struct ElementChangeInfo *change = &ei->change_page[page];
10640 boolean handle_action_before_change = FALSE;
10643 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10644 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10647 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10648 x, y, element, element_info[element].token_name);
10649 printf("HandleElementChange(): This should never happen!\n");
10654 // this can happen with classic bombs on walkable, changing elements
10655 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10660 if (ChangeDelay[x][y] == 0) // initialize element change
10662 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10664 if (change->can_change)
10666 // !!! not clear why graphic animation should be reset at all here !!!
10667 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10668 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10671 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10673 When using an animation frame delay of 1 (this only happens with
10674 "sp_zonk.moving.left/right" in the classic graphics), the default
10675 (non-moving) animation shows wrong animation frames (while the
10676 moving animation, like "sp_zonk.moving.left/right", is correct,
10677 so this graphical bug never shows up with the classic graphics).
10678 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10679 be drawn instead of the correct frames 0,1,2,3. This is caused by
10680 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10681 an element change: First when the change delay ("ChangeDelay[][]")
10682 counter has reached zero after decrementing, then a second time in
10683 the next frame (after "GfxFrame[][]" was already incremented) when
10684 "ChangeDelay[][]" is reset to the initial delay value again.
10686 This causes frame 0 to be drawn twice, while the last frame won't
10687 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10689 As some animations may already be cleverly designed around this bug
10690 (at least the "Snake Bite" snake tail animation does this), it cannot
10691 simply be fixed here without breaking such existing animations.
10692 Unfortunately, it cannot easily be detected if a graphics set was
10693 designed "before" or "after" the bug was fixed. As a workaround,
10694 a new graphics set option "game.graphics_engine_version" was added
10695 to be able to specify the game's major release version for which the
10696 graphics set was designed, which can then be used to decide if the
10697 bugfix should be used (version 4 and above) or not (version 3 or
10698 below, or if no version was specified at all, as with old sets).
10700 (The wrong/fixed animation frames can be tested with the test level set
10701 "test_gfxframe" and level "000", which contains a specially prepared
10702 custom element at level position (x/y) == (11/9) which uses the zonk
10703 animation mentioned above. Using "game.graphics_engine_version: 4"
10704 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10705 This can also be seen from the debug output for this test element.)
10708 // when a custom element is about to change (for example by change delay),
10709 // do not reset graphic animation when the custom element is moving
10710 if (game.graphics_engine_version < 4 &&
10713 ResetGfxAnimation(x, y);
10714 ResetRandomAnimationValue(x, y);
10717 if (change->pre_change_function)
10718 change->pre_change_function(x, y);
10722 ChangeDelay[x][y]--;
10724 if (ChangeDelay[x][y] != 0) // continue element change
10726 if (change->can_change)
10728 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10730 if (IS_ANIMATED(graphic))
10731 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10733 if (change->change_function)
10734 change->change_function(x, y);
10737 else // finish element change
10739 if (ChangePage[x][y] != -1) // remember page from delayed change
10741 page = ChangePage[x][y];
10742 ChangePage[x][y] = -1;
10744 change = &ei->change_page[page];
10747 if (IS_MOVING(x, y)) // never change a running system ;-)
10749 ChangeDelay[x][y] = 1; // try change after next move step
10750 ChangePage[x][y] = page; // remember page to use for change
10755 // special case: set new level random seed before changing element
10756 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10757 handle_action_before_change = TRUE;
10759 if (change->has_action && handle_action_before_change)
10760 ExecuteCustomElementAction(x, y, element, page);
10762 if (change->can_change)
10764 if (ChangeElement(x, y, element, page))
10766 if (change->post_change_function)
10767 change->post_change_function(x, y);
10771 if (change->has_action && !handle_action_before_change)
10772 ExecuteCustomElementAction(x, y, element, page);
10776 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10777 int trigger_element,
10779 int trigger_player,
10783 boolean change_done_any = FALSE;
10784 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10787 if (!(trigger_events[trigger_element][trigger_event]))
10790 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10792 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10794 int element = EL_CUSTOM_START + i;
10795 boolean change_done = FALSE;
10798 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10799 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10802 for (p = 0; p < element_info[element].num_change_pages; p++)
10804 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10806 if (change->can_change_or_has_action &&
10807 change->has_event[trigger_event] &&
10808 change->trigger_side & trigger_side &&
10809 change->trigger_player & trigger_player &&
10810 change->trigger_page & trigger_page_bits &&
10811 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10813 change->actual_trigger_element = trigger_element;
10814 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10815 change->actual_trigger_player_bits = trigger_player;
10816 change->actual_trigger_side = trigger_side;
10817 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10818 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10820 if ((change->can_change && !change_done) || change->has_action)
10824 SCAN_PLAYFIELD(x, y)
10826 if (Feld[x][y] == element)
10828 if (change->can_change && !change_done)
10830 // if element already changed in this frame, not only prevent
10831 // another element change (checked in ChangeElement()), but
10832 // also prevent additional element actions for this element
10834 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10835 !level.use_action_after_change_bug)
10838 ChangeDelay[x][y] = 1;
10839 ChangeEvent[x][y] = trigger_event;
10841 HandleElementChange(x, y, p);
10843 else if (change->has_action)
10845 // if element already changed in this frame, not only prevent
10846 // another element change (checked in ChangeElement()), but
10847 // also prevent additional element actions for this element
10849 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10850 !level.use_action_after_change_bug)
10853 ExecuteCustomElementAction(x, y, element, p);
10854 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10859 if (change->can_change)
10861 change_done = TRUE;
10862 change_done_any = TRUE;
10869 RECURSION_LOOP_DETECTION_END();
10871 return change_done_any;
10874 static boolean CheckElementChangeExt(int x, int y,
10876 int trigger_element,
10878 int trigger_player,
10881 boolean change_done = FALSE;
10884 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10885 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10888 if (Feld[x][y] == EL_BLOCKED)
10890 Blocked2Moving(x, y, &x, &y);
10891 element = Feld[x][y];
10894 // check if element has already changed or is about to change after moving
10895 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10896 Feld[x][y] != element) ||
10898 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10899 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10900 ChangePage[x][y] != -1)))
10903 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10905 for (p = 0; p < element_info[element].num_change_pages; p++)
10907 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10909 /* check trigger element for all events where the element that is checked
10910 for changing interacts with a directly adjacent element -- this is
10911 different to element changes that affect other elements to change on the
10912 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10913 boolean check_trigger_element =
10914 (trigger_event == CE_TOUCHING_X ||
10915 trigger_event == CE_HITTING_X ||
10916 trigger_event == CE_HIT_BY_X ||
10917 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10919 if (change->can_change_or_has_action &&
10920 change->has_event[trigger_event] &&
10921 change->trigger_side & trigger_side &&
10922 change->trigger_player & trigger_player &&
10923 (!check_trigger_element ||
10924 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10926 change->actual_trigger_element = trigger_element;
10927 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10928 change->actual_trigger_player_bits = trigger_player;
10929 change->actual_trigger_side = trigger_side;
10930 change->actual_trigger_ce_value = CustomValue[x][y];
10931 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10933 // special case: trigger element not at (x,y) position for some events
10934 if (check_trigger_element)
10946 { 0, 0 }, { 0, 0 }, { 0, 0 },
10950 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10951 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10953 change->actual_trigger_ce_value = CustomValue[xx][yy];
10954 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10957 if (change->can_change && !change_done)
10959 ChangeDelay[x][y] = 1;
10960 ChangeEvent[x][y] = trigger_event;
10962 HandleElementChange(x, y, p);
10964 change_done = TRUE;
10966 else if (change->has_action)
10968 ExecuteCustomElementAction(x, y, element, p);
10969 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10974 RECURSION_LOOP_DETECTION_END();
10976 return change_done;
10979 static void PlayPlayerSound(struct PlayerInfo *player)
10981 int jx = player->jx, jy = player->jy;
10982 int sound_element = player->artwork_element;
10983 int last_action = player->last_action_waiting;
10984 int action = player->action_waiting;
10986 if (player->is_waiting)
10988 if (action != last_action)
10989 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10991 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10995 if (action != last_action)
10996 StopSound(element_info[sound_element].sound[last_action]);
10998 if (last_action == ACTION_SLEEPING)
10999 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11003 static void PlayAllPlayersSound(void)
11007 for (i = 0; i < MAX_PLAYERS; i++)
11008 if (stored_player[i].active)
11009 PlayPlayerSound(&stored_player[i]);
11012 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11014 boolean last_waiting = player->is_waiting;
11015 int move_dir = player->MovDir;
11017 player->dir_waiting = move_dir;
11018 player->last_action_waiting = player->action_waiting;
11022 if (!last_waiting) // not waiting -> waiting
11024 player->is_waiting = TRUE;
11026 player->frame_counter_bored =
11028 game.player_boring_delay_fixed +
11029 GetSimpleRandom(game.player_boring_delay_random);
11030 player->frame_counter_sleeping =
11032 game.player_sleeping_delay_fixed +
11033 GetSimpleRandom(game.player_sleeping_delay_random);
11035 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11038 if (game.player_sleeping_delay_fixed +
11039 game.player_sleeping_delay_random > 0 &&
11040 player->anim_delay_counter == 0 &&
11041 player->post_delay_counter == 0 &&
11042 FrameCounter >= player->frame_counter_sleeping)
11043 player->is_sleeping = TRUE;
11044 else if (game.player_boring_delay_fixed +
11045 game.player_boring_delay_random > 0 &&
11046 FrameCounter >= player->frame_counter_bored)
11047 player->is_bored = TRUE;
11049 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11050 player->is_bored ? ACTION_BORING :
11053 if (player->is_sleeping && player->use_murphy)
11055 // special case for sleeping Murphy when leaning against non-free tile
11057 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11058 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
11059 !IS_MOVING(player->jx - 1, player->jy)))
11060 move_dir = MV_LEFT;
11061 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11062 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
11063 !IS_MOVING(player->jx + 1, player->jy)))
11064 move_dir = MV_RIGHT;
11066 player->is_sleeping = FALSE;
11068 player->dir_waiting = move_dir;
11071 if (player->is_sleeping)
11073 if (player->num_special_action_sleeping > 0)
11075 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11077 int last_special_action = player->special_action_sleeping;
11078 int num_special_action = player->num_special_action_sleeping;
11079 int special_action =
11080 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11081 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11082 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11083 last_special_action + 1 : ACTION_SLEEPING);
11084 int special_graphic =
11085 el_act_dir2img(player->artwork_element, special_action, move_dir);
11087 player->anim_delay_counter =
11088 graphic_info[special_graphic].anim_delay_fixed +
11089 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11090 player->post_delay_counter =
11091 graphic_info[special_graphic].post_delay_fixed +
11092 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11094 player->special_action_sleeping = special_action;
11097 if (player->anim_delay_counter > 0)
11099 player->action_waiting = player->special_action_sleeping;
11100 player->anim_delay_counter--;
11102 else if (player->post_delay_counter > 0)
11104 player->post_delay_counter--;
11108 else if (player->is_bored)
11110 if (player->num_special_action_bored > 0)
11112 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11114 int special_action =
11115 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11116 int special_graphic =
11117 el_act_dir2img(player->artwork_element, special_action, move_dir);
11119 player->anim_delay_counter =
11120 graphic_info[special_graphic].anim_delay_fixed +
11121 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11122 player->post_delay_counter =
11123 graphic_info[special_graphic].post_delay_fixed +
11124 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11126 player->special_action_bored = special_action;
11129 if (player->anim_delay_counter > 0)
11131 player->action_waiting = player->special_action_bored;
11132 player->anim_delay_counter--;
11134 else if (player->post_delay_counter > 0)
11136 player->post_delay_counter--;
11141 else if (last_waiting) // waiting -> not waiting
11143 player->is_waiting = FALSE;
11144 player->is_bored = FALSE;
11145 player->is_sleeping = FALSE;
11147 player->frame_counter_bored = -1;
11148 player->frame_counter_sleeping = -1;
11150 player->anim_delay_counter = 0;
11151 player->post_delay_counter = 0;
11153 player->dir_waiting = player->MovDir;
11154 player->action_waiting = ACTION_DEFAULT;
11156 player->special_action_bored = ACTION_DEFAULT;
11157 player->special_action_sleeping = ACTION_DEFAULT;
11161 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11163 if ((!player->is_moving && player->was_moving) ||
11164 (player->MovPos == 0 && player->was_moving) ||
11165 (player->is_snapping && !player->was_snapping) ||
11166 (player->is_dropping && !player->was_dropping))
11168 if (!CheckSaveEngineSnapshotToList())
11171 player->was_moving = FALSE;
11172 player->was_snapping = TRUE;
11173 player->was_dropping = TRUE;
11177 if (player->is_moving)
11178 player->was_moving = TRUE;
11180 if (!player->is_snapping)
11181 player->was_snapping = FALSE;
11183 if (!player->is_dropping)
11184 player->was_dropping = FALSE;
11188 static void CheckSingleStepMode(struct PlayerInfo *player)
11190 if (tape.single_step && tape.recording && !tape.pausing)
11192 /* as it is called "single step mode", just return to pause mode when the
11193 player stopped moving after one tile (or never starts moving at all) */
11194 if (!player->is_moving &&
11195 !player->is_pushing &&
11196 !player->is_dropping_pressed)
11197 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11200 CheckSaveEngineSnapshot(player);
11203 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11205 int left = player_action & JOY_LEFT;
11206 int right = player_action & JOY_RIGHT;
11207 int up = player_action & JOY_UP;
11208 int down = player_action & JOY_DOWN;
11209 int button1 = player_action & JOY_BUTTON_1;
11210 int button2 = player_action & JOY_BUTTON_2;
11211 int dx = (left ? -1 : right ? 1 : 0);
11212 int dy = (up ? -1 : down ? 1 : 0);
11214 if (!player->active || tape.pausing)
11220 SnapField(player, dx, dy);
11224 DropElement(player);
11226 MovePlayer(player, dx, dy);
11229 CheckSingleStepMode(player);
11231 SetPlayerWaiting(player, FALSE);
11233 return player_action;
11237 // no actions for this player (no input at player's configured device)
11239 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11240 SnapField(player, 0, 0);
11241 CheckGravityMovementWhenNotMoving(player);
11243 if (player->MovPos == 0)
11244 SetPlayerWaiting(player, TRUE);
11246 if (player->MovPos == 0) // needed for tape.playing
11247 player->is_moving = FALSE;
11249 player->is_dropping = FALSE;
11250 player->is_dropping_pressed = FALSE;
11251 player->drop_pressed_delay = 0;
11253 CheckSingleStepMode(player);
11259 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11262 if (!tape.use_mouse_actions)
11265 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11266 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11267 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11270 static void SetTapeActionFromMouseAction(byte *tape_action,
11271 struct MouseActionInfo *mouse_action)
11273 if (!tape.use_mouse_actions)
11276 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11277 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11278 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11281 static void CheckLevelSolved(void)
11283 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11285 if (game_em.level_solved &&
11286 !game_em.game_over) // game won
11290 game_em.game_over = TRUE;
11292 game.all_players_gone = TRUE;
11295 if (game_em.game_over) // game lost
11296 game.all_players_gone = TRUE;
11298 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11300 if (game_sp.level_solved &&
11301 !game_sp.game_over) // game won
11305 game_sp.game_over = TRUE;
11307 game.all_players_gone = TRUE;
11310 if (game_sp.game_over) // game lost
11311 game.all_players_gone = TRUE;
11313 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11315 if (game_mm.level_solved &&
11316 !game_mm.game_over) // game won
11320 game_mm.game_over = TRUE;
11322 game.all_players_gone = TRUE;
11325 if (game_mm.game_over) // game lost
11326 game.all_players_gone = TRUE;
11330 static void CheckLevelTime(void)
11334 if (TimeFrames >= FRAMES_PER_SECOND)
11339 for (i = 0; i < MAX_PLAYERS; i++)
11341 struct PlayerInfo *player = &stored_player[i];
11343 if (SHIELD_ON(player))
11345 player->shield_normal_time_left--;
11347 if (player->shield_deadly_time_left > 0)
11348 player->shield_deadly_time_left--;
11352 if (!game.LevelSolved && !level.use_step_counter)
11360 if (TimeLeft <= 10 && setup.time_limit)
11361 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11363 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11364 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11366 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11368 if (!TimeLeft && setup.time_limit)
11370 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11371 game_em.lev->killed_out_of_time = TRUE;
11373 for (i = 0; i < MAX_PLAYERS; i++)
11374 KillPlayer(&stored_player[i]);
11377 else if (game.no_time_limit && !game.all_players_gone)
11379 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11382 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11385 if (tape.recording || tape.playing)
11386 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11389 if (tape.recording || tape.playing)
11390 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11392 UpdateAndDisplayGameControlValues();
11395 void AdvanceFrameAndPlayerCounters(int player_nr)
11399 // advance frame counters (global frame counter and time frame counter)
11403 // advance player counters (counters for move delay, move animation etc.)
11404 for (i = 0; i < MAX_PLAYERS; i++)
11406 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11407 int move_delay_value = stored_player[i].move_delay_value;
11408 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11410 if (!advance_player_counters) // not all players may be affected
11413 if (move_frames == 0) // less than one move per game frame
11415 int stepsize = TILEX / move_delay_value;
11416 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11417 int count = (stored_player[i].is_moving ?
11418 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11420 if (count % delay == 0)
11424 stored_player[i].Frame += move_frames;
11426 if (stored_player[i].MovPos != 0)
11427 stored_player[i].StepFrame += move_frames;
11429 if (stored_player[i].move_delay > 0)
11430 stored_player[i].move_delay--;
11432 // due to bugs in previous versions, counter must count up, not down
11433 if (stored_player[i].push_delay != -1)
11434 stored_player[i].push_delay++;
11436 if (stored_player[i].drop_delay > 0)
11437 stored_player[i].drop_delay--;
11439 if (stored_player[i].is_dropping_pressed)
11440 stored_player[i].drop_pressed_delay++;
11444 void StartGameActions(boolean init_network_game, boolean record_tape,
11447 unsigned int new_random_seed = InitRND(random_seed);
11450 TapeStartRecording(new_random_seed);
11452 if (init_network_game)
11454 SendToServer_LevelFile();
11455 SendToServer_StartPlaying();
11463 static void GameActionsExt(void)
11466 static unsigned int game_frame_delay = 0;
11468 unsigned int game_frame_delay_value;
11469 byte *recorded_player_action;
11470 byte summarized_player_action = 0;
11471 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11474 // detect endless loops, caused by custom element programming
11475 if (recursion_loop_detected && recursion_loop_depth == 0)
11477 char *message = getStringCat3("Internal Error! Element ",
11478 EL_NAME(recursion_loop_element),
11479 " caused endless loop! Quit the game?");
11481 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11482 EL_NAME(recursion_loop_element));
11484 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11486 recursion_loop_detected = FALSE; // if game should be continued
11493 if (game.restart_level)
11494 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11496 CheckLevelSolved();
11498 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11501 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11504 if (game_status != GAME_MODE_PLAYING) // status might have changed
11507 game_frame_delay_value =
11508 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11510 if (tape.playing && tape.warp_forward && !tape.pausing)
11511 game_frame_delay_value = 0;
11513 SetVideoFrameDelay(game_frame_delay_value);
11515 // (de)activate virtual buttons depending on current game status
11516 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11518 if (game.all_players_gone) // if no players there to be controlled anymore
11519 SetOverlayActive(FALSE);
11520 else if (!tape.playing) // if game continues after tape stopped playing
11521 SetOverlayActive(TRUE);
11526 // ---------- main game synchronization point ----------
11528 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11530 printf("::: skip == %d\n", skip);
11533 // ---------- main game synchronization point ----------
11535 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11539 if (network_playing && !network_player_action_received)
11541 // try to get network player actions in time
11543 // last chance to get network player actions without main loop delay
11544 HandleNetworking();
11546 // game was quit by network peer
11547 if (game_status != GAME_MODE_PLAYING)
11550 // check if network player actions still missing and game still running
11551 if (!network_player_action_received && !checkGameEnded())
11552 return; // failed to get network player actions in time
11554 // do not yet reset "network_player_action_received" (for tape.pausing)
11560 // at this point we know that we really continue executing the game
11562 network_player_action_received = FALSE;
11564 // when playing tape, read previously recorded player input from tape data
11565 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11567 local_player->effective_mouse_action = local_player->mouse_action;
11569 if (recorded_player_action != NULL)
11570 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11571 recorded_player_action);
11573 // TapePlayAction() may return NULL when toggling to "pause before death"
11577 if (tape.set_centered_player)
11579 game.centered_player_nr_next = tape.centered_player_nr_next;
11580 game.set_centered_player = TRUE;
11583 for (i = 0; i < MAX_PLAYERS; i++)
11585 summarized_player_action |= stored_player[i].action;
11587 if (!network_playing && (game.team_mode || tape.playing))
11588 stored_player[i].effective_action = stored_player[i].action;
11591 if (network_playing && !checkGameEnded())
11592 SendToServer_MovePlayer(summarized_player_action);
11594 // summarize all actions at local players mapped input device position
11595 // (this allows using different input devices in single player mode)
11596 if (!network.enabled && !game.team_mode)
11597 stored_player[map_player_action[local_player->index_nr]].effective_action =
11598 summarized_player_action;
11600 // summarize all actions at centered player in local team mode
11601 if (tape.recording &&
11602 setup.team_mode && !network.enabled &&
11603 setup.input_on_focus &&
11604 game.centered_player_nr != -1)
11606 for (i = 0; i < MAX_PLAYERS; i++)
11607 stored_player[map_player_action[i]].effective_action =
11608 (i == game.centered_player_nr ? summarized_player_action : 0);
11611 if (recorded_player_action != NULL)
11612 for (i = 0; i < MAX_PLAYERS; i++)
11613 stored_player[i].effective_action = recorded_player_action[i];
11615 for (i = 0; i < MAX_PLAYERS; i++)
11617 tape_action[i] = stored_player[i].effective_action;
11619 /* (this may happen in the RND game engine if a player was not present on
11620 the playfield on level start, but appeared later from a custom element */
11621 if (setup.team_mode &&
11624 !tape.player_participates[i])
11625 tape.player_participates[i] = TRUE;
11628 SetTapeActionFromMouseAction(tape_action,
11629 &local_player->effective_mouse_action);
11631 // only record actions from input devices, but not programmed actions
11632 if (tape.recording)
11633 TapeRecordAction(tape_action);
11635 // remember if game was played (especially after tape stopped playing)
11636 if (!tape.playing && summarized_player_action)
11637 game.GamePlayed = TRUE;
11639 #if USE_NEW_PLAYER_ASSIGNMENTS
11640 // !!! also map player actions in single player mode !!!
11641 // if (game.team_mode)
11644 byte mapped_action[MAX_PLAYERS];
11646 #if DEBUG_PLAYER_ACTIONS
11648 for (i = 0; i < MAX_PLAYERS; i++)
11649 printf(" %d, ", stored_player[i].effective_action);
11652 for (i = 0; i < MAX_PLAYERS; i++)
11653 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11655 for (i = 0; i < MAX_PLAYERS; i++)
11656 stored_player[i].effective_action = mapped_action[i];
11658 #if DEBUG_PLAYER_ACTIONS
11660 for (i = 0; i < MAX_PLAYERS; i++)
11661 printf(" %d, ", stored_player[i].effective_action);
11665 #if DEBUG_PLAYER_ACTIONS
11669 for (i = 0; i < MAX_PLAYERS; i++)
11670 printf(" %d, ", stored_player[i].effective_action);
11676 for (i = 0; i < MAX_PLAYERS; i++)
11678 // allow engine snapshot in case of changed movement attempt
11679 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11680 (stored_player[i].effective_action & KEY_MOTION))
11681 game.snapshot.changed_action = TRUE;
11683 // allow engine snapshot in case of snapping/dropping attempt
11684 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11685 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11686 game.snapshot.changed_action = TRUE;
11688 game.snapshot.last_action[i] = stored_player[i].effective_action;
11691 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11693 GameActions_EM_Main();
11695 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11697 GameActions_SP_Main();
11699 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11701 GameActions_MM_Main();
11705 GameActions_RND_Main();
11708 BlitScreenToBitmap(backbuffer);
11710 CheckLevelSolved();
11713 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11715 if (global.show_frames_per_second)
11717 static unsigned int fps_counter = 0;
11718 static int fps_frames = 0;
11719 unsigned int fps_delay_ms = Counter() - fps_counter;
11723 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11725 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11728 fps_counter = Counter();
11730 // always draw FPS to screen after FPS value was updated
11731 redraw_mask |= REDRAW_FPS;
11734 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11735 if (GetDrawDeactivationMask() == REDRAW_NONE)
11736 redraw_mask |= REDRAW_FPS;
11740 static void GameActions_CheckSaveEngineSnapshot(void)
11742 if (!game.snapshot.save_snapshot)
11745 // clear flag for saving snapshot _before_ saving snapshot
11746 game.snapshot.save_snapshot = FALSE;
11748 SaveEngineSnapshotToList();
11751 void GameActions(void)
11755 GameActions_CheckSaveEngineSnapshot();
11758 void GameActions_EM_Main(void)
11760 byte effective_action[MAX_PLAYERS];
11761 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11764 for (i = 0; i < MAX_PLAYERS; i++)
11765 effective_action[i] = stored_player[i].effective_action;
11767 GameActions_EM(effective_action, warp_mode);
11770 void GameActions_SP_Main(void)
11772 byte effective_action[MAX_PLAYERS];
11773 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11776 for (i = 0; i < MAX_PLAYERS; i++)
11777 effective_action[i] = stored_player[i].effective_action;
11779 GameActions_SP(effective_action, warp_mode);
11781 for (i = 0; i < MAX_PLAYERS; i++)
11783 if (stored_player[i].force_dropping)
11784 stored_player[i].action |= KEY_BUTTON_DROP;
11786 stored_player[i].force_dropping = FALSE;
11790 void GameActions_MM_Main(void)
11792 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11794 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11797 void GameActions_RND_Main(void)
11802 void GameActions_RND(void)
11804 static struct MouseActionInfo mouse_action_last = { 0 };
11805 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11806 int magic_wall_x = 0, magic_wall_y = 0;
11807 int i, x, y, element, graphic, last_gfx_frame;
11809 InitPlayfieldScanModeVars();
11811 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11813 SCAN_PLAYFIELD(x, y)
11815 ChangeCount[x][y] = 0;
11816 ChangeEvent[x][y] = -1;
11820 if (game.set_centered_player)
11822 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11824 // switching to "all players" only possible if all players fit to screen
11825 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11827 game.centered_player_nr_next = game.centered_player_nr;
11828 game.set_centered_player = FALSE;
11831 // do not switch focus to non-existing (or non-active) player
11832 if (game.centered_player_nr_next >= 0 &&
11833 !stored_player[game.centered_player_nr_next].active)
11835 game.centered_player_nr_next = game.centered_player_nr;
11836 game.set_centered_player = FALSE;
11840 if (game.set_centered_player &&
11841 ScreenMovPos == 0) // screen currently aligned at tile position
11845 if (game.centered_player_nr_next == -1)
11847 setScreenCenteredToAllPlayers(&sx, &sy);
11851 sx = stored_player[game.centered_player_nr_next].jx;
11852 sy = stored_player[game.centered_player_nr_next].jy;
11855 game.centered_player_nr = game.centered_player_nr_next;
11856 game.set_centered_player = FALSE;
11858 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11859 DrawGameDoorValues();
11862 for (i = 0; i < MAX_PLAYERS; i++)
11864 int actual_player_action = stored_player[i].effective_action;
11867 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11868 - rnd_equinox_tetrachloride 048
11869 - rnd_equinox_tetrachloride_ii 096
11870 - rnd_emanuel_schmieg 002
11871 - doctor_sloan_ww 001, 020
11873 if (stored_player[i].MovPos == 0)
11874 CheckGravityMovement(&stored_player[i]);
11877 // overwrite programmed action with tape action
11878 if (stored_player[i].programmed_action)
11879 actual_player_action = stored_player[i].programmed_action;
11881 PlayerActions(&stored_player[i], actual_player_action);
11883 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11886 ScrollScreen(NULL, SCROLL_GO_ON);
11888 /* for backwards compatibility, the following code emulates a fixed bug that
11889 occured when pushing elements (causing elements that just made their last
11890 pushing step to already (if possible) make their first falling step in the
11891 same game frame, which is bad); this code is also needed to use the famous
11892 "spring push bug" which is used in older levels and might be wanted to be
11893 used also in newer levels, but in this case the buggy pushing code is only
11894 affecting the "spring" element and no other elements */
11896 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11898 for (i = 0; i < MAX_PLAYERS; i++)
11900 struct PlayerInfo *player = &stored_player[i];
11901 int x = player->jx;
11902 int y = player->jy;
11904 if (player->active && player->is_pushing && player->is_moving &&
11906 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11907 Feld[x][y] == EL_SPRING))
11909 ContinueMoving(x, y);
11911 // continue moving after pushing (this is actually a bug)
11912 if (!IS_MOVING(x, y))
11913 Stop[x][y] = FALSE;
11918 SCAN_PLAYFIELD(x, y)
11920 Last[x][y] = Feld[x][y];
11922 ChangeCount[x][y] = 0;
11923 ChangeEvent[x][y] = -1;
11925 // this must be handled before main playfield loop
11926 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11929 if (MovDelay[x][y] <= 0)
11933 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11936 if (MovDelay[x][y] <= 0)
11939 TEST_DrawLevelField(x, y);
11941 TestIfElementTouchesCustomElement(x, y); // for empty space
11946 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11948 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11949 printf("GameActions(): This should never happen!\n");
11951 ChangePage[x][y] = -1;
11955 Stop[x][y] = FALSE;
11956 if (WasJustMoving[x][y] > 0)
11957 WasJustMoving[x][y]--;
11958 if (WasJustFalling[x][y] > 0)
11959 WasJustFalling[x][y]--;
11960 if (CheckCollision[x][y] > 0)
11961 CheckCollision[x][y]--;
11962 if (CheckImpact[x][y] > 0)
11963 CheckImpact[x][y]--;
11967 /* reset finished pushing action (not done in ContinueMoving() to allow
11968 continuous pushing animation for elements with zero push delay) */
11969 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11971 ResetGfxAnimation(x, y);
11972 TEST_DrawLevelField(x, y);
11976 if (IS_BLOCKED(x, y))
11980 Blocked2Moving(x, y, &oldx, &oldy);
11981 if (!IS_MOVING(oldx, oldy))
11983 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11984 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11985 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11986 printf("GameActions(): This should never happen!\n");
11992 if (mouse_action.button)
11994 int new_button = (mouse_action.button && mouse_action_last.button == 0);
11996 x = mouse_action.lx;
11997 y = mouse_action.ly;
11998 element = Feld[x][y];
12002 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12003 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12006 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12007 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12010 SCAN_PLAYFIELD(x, y)
12012 element = Feld[x][y];
12013 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12014 last_gfx_frame = GfxFrame[x][y];
12016 ResetGfxFrame(x, y);
12018 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12019 DrawLevelGraphicAnimation(x, y, graphic);
12021 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12022 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12023 ResetRandomAnimationValue(x, y);
12025 SetRandomAnimationValue(x, y);
12027 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12029 if (IS_INACTIVE(element))
12031 if (IS_ANIMATED(graphic))
12032 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12037 // this may take place after moving, so 'element' may have changed
12038 if (IS_CHANGING(x, y) &&
12039 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12041 int page = element_info[element].event_page_nr[CE_DELAY];
12043 HandleElementChange(x, y, page);
12045 element = Feld[x][y];
12046 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12049 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12053 element = Feld[x][y];
12054 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12056 if (IS_ANIMATED(graphic) &&
12057 !IS_MOVING(x, y) &&
12059 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12061 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12062 TEST_DrawTwinkleOnField(x, y);
12064 else if (element == EL_ACID)
12067 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12069 else if ((element == EL_EXIT_OPEN ||
12070 element == EL_EM_EXIT_OPEN ||
12071 element == EL_SP_EXIT_OPEN ||
12072 element == EL_STEEL_EXIT_OPEN ||
12073 element == EL_EM_STEEL_EXIT_OPEN ||
12074 element == EL_SP_TERMINAL ||
12075 element == EL_SP_TERMINAL_ACTIVE ||
12076 element == EL_EXTRA_TIME ||
12077 element == EL_SHIELD_NORMAL ||
12078 element == EL_SHIELD_DEADLY) &&
12079 IS_ANIMATED(graphic))
12080 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12081 else if (IS_MOVING(x, y))
12082 ContinueMoving(x, y);
12083 else if (IS_ACTIVE_BOMB(element))
12084 CheckDynamite(x, y);
12085 else if (element == EL_AMOEBA_GROWING)
12086 AmoebeWaechst(x, y);
12087 else if (element == EL_AMOEBA_SHRINKING)
12088 AmoebaDisappearing(x, y);
12090 #if !USE_NEW_AMOEBA_CODE
12091 else if (IS_AMOEBALIVE(element))
12092 AmoebeAbleger(x, y);
12095 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12097 else if (element == EL_EXIT_CLOSED)
12099 else if (element == EL_EM_EXIT_CLOSED)
12101 else if (element == EL_STEEL_EXIT_CLOSED)
12102 CheckExitSteel(x, y);
12103 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12104 CheckExitSteelEM(x, y);
12105 else if (element == EL_SP_EXIT_CLOSED)
12107 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12108 element == EL_EXPANDABLE_STEELWALL_GROWING)
12109 MauerWaechst(x, y);
12110 else if (element == EL_EXPANDABLE_WALL ||
12111 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12112 element == EL_EXPANDABLE_WALL_VERTICAL ||
12113 element == EL_EXPANDABLE_WALL_ANY ||
12114 element == EL_BD_EXPANDABLE_WALL)
12115 MauerAbleger(x, y);
12116 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12117 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12118 element == EL_EXPANDABLE_STEELWALL_ANY)
12119 MauerAblegerStahl(x, y);
12120 else if (element == EL_FLAMES)
12121 CheckForDragon(x, y);
12122 else if (element == EL_EXPLOSION)
12123 ; // drawing of correct explosion animation is handled separately
12124 else if (element == EL_ELEMENT_SNAPPING ||
12125 element == EL_DIAGONAL_SHRINKING ||
12126 element == EL_DIAGONAL_GROWING)
12128 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12130 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12132 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12133 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12135 if (IS_BELT_ACTIVE(element))
12136 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12138 if (game.magic_wall_active)
12140 int jx = local_player->jx, jy = local_player->jy;
12142 // play the element sound at the position nearest to the player
12143 if ((element == EL_MAGIC_WALL_FULL ||
12144 element == EL_MAGIC_WALL_ACTIVE ||
12145 element == EL_MAGIC_WALL_EMPTYING ||
12146 element == EL_BD_MAGIC_WALL_FULL ||
12147 element == EL_BD_MAGIC_WALL_ACTIVE ||
12148 element == EL_BD_MAGIC_WALL_EMPTYING ||
12149 element == EL_DC_MAGIC_WALL_FULL ||
12150 element == EL_DC_MAGIC_WALL_ACTIVE ||
12151 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12152 ABS(x - jx) + ABS(y - jy) <
12153 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12161 #if USE_NEW_AMOEBA_CODE
12162 // new experimental amoeba growth stuff
12163 if (!(FrameCounter % 8))
12165 static unsigned int random = 1684108901;
12167 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12169 x = RND(lev_fieldx);
12170 y = RND(lev_fieldy);
12171 element = Feld[x][y];
12173 if (!IS_PLAYER(x,y) &&
12174 (element == EL_EMPTY ||
12175 CAN_GROW_INTO(element) ||
12176 element == EL_QUICKSAND_EMPTY ||
12177 element == EL_QUICKSAND_FAST_EMPTY ||
12178 element == EL_ACID_SPLASH_LEFT ||
12179 element == EL_ACID_SPLASH_RIGHT))
12181 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12182 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12183 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12184 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12185 Feld[x][y] = EL_AMOEBA_DROP;
12188 random = random * 129 + 1;
12193 game.explosions_delayed = FALSE;
12195 SCAN_PLAYFIELD(x, y)
12197 element = Feld[x][y];
12199 if (ExplodeField[x][y])
12200 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12201 else if (element == EL_EXPLOSION)
12202 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12204 ExplodeField[x][y] = EX_TYPE_NONE;
12207 game.explosions_delayed = TRUE;
12209 if (game.magic_wall_active)
12211 if (!(game.magic_wall_time_left % 4))
12213 int element = Feld[magic_wall_x][magic_wall_y];
12215 if (element == EL_BD_MAGIC_WALL_FULL ||
12216 element == EL_BD_MAGIC_WALL_ACTIVE ||
12217 element == EL_BD_MAGIC_WALL_EMPTYING)
12218 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12219 else if (element == EL_DC_MAGIC_WALL_FULL ||
12220 element == EL_DC_MAGIC_WALL_ACTIVE ||
12221 element == EL_DC_MAGIC_WALL_EMPTYING)
12222 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12224 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12227 if (game.magic_wall_time_left > 0)
12229 game.magic_wall_time_left--;
12231 if (!game.magic_wall_time_left)
12233 SCAN_PLAYFIELD(x, y)
12235 element = Feld[x][y];
12237 if (element == EL_MAGIC_WALL_ACTIVE ||
12238 element == EL_MAGIC_WALL_FULL)
12240 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12241 TEST_DrawLevelField(x, y);
12243 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12244 element == EL_BD_MAGIC_WALL_FULL)
12246 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12247 TEST_DrawLevelField(x, y);
12249 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12250 element == EL_DC_MAGIC_WALL_FULL)
12252 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12253 TEST_DrawLevelField(x, y);
12257 game.magic_wall_active = FALSE;
12262 if (game.light_time_left > 0)
12264 game.light_time_left--;
12266 if (game.light_time_left == 0)
12267 RedrawAllLightSwitchesAndInvisibleElements();
12270 if (game.timegate_time_left > 0)
12272 game.timegate_time_left--;
12274 if (game.timegate_time_left == 0)
12275 CloseAllOpenTimegates();
12278 if (game.lenses_time_left > 0)
12280 game.lenses_time_left--;
12282 if (game.lenses_time_left == 0)
12283 RedrawAllInvisibleElementsForLenses();
12286 if (game.magnify_time_left > 0)
12288 game.magnify_time_left--;
12290 if (game.magnify_time_left == 0)
12291 RedrawAllInvisibleElementsForMagnifier();
12294 for (i = 0; i < MAX_PLAYERS; i++)
12296 struct PlayerInfo *player = &stored_player[i];
12298 if (SHIELD_ON(player))
12300 if (player->shield_deadly_time_left)
12301 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12302 else if (player->shield_normal_time_left)
12303 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12307 #if USE_DELAYED_GFX_REDRAW
12308 SCAN_PLAYFIELD(x, y)
12310 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12312 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12313 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12315 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12316 DrawLevelField(x, y);
12318 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12319 DrawLevelFieldCrumbled(x, y);
12321 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12322 DrawLevelFieldCrumbledNeighbours(x, y);
12324 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12325 DrawTwinkleOnField(x, y);
12328 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12333 PlayAllPlayersSound();
12335 for (i = 0; i < MAX_PLAYERS; i++)
12337 struct PlayerInfo *player = &stored_player[i];
12339 if (player->show_envelope != 0 && (!player->active ||
12340 player->MovPos == 0))
12342 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12344 player->show_envelope = 0;
12348 // use random number generator in every frame to make it less predictable
12349 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12352 mouse_action_last = mouse_action;
12355 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12357 int min_x = x, min_y = y, max_x = x, max_y = y;
12360 for (i = 0; i < MAX_PLAYERS; i++)
12362 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12364 if (!stored_player[i].active || &stored_player[i] == player)
12367 min_x = MIN(min_x, jx);
12368 min_y = MIN(min_y, jy);
12369 max_x = MAX(max_x, jx);
12370 max_y = MAX(max_y, jy);
12373 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12376 static boolean AllPlayersInVisibleScreen(void)
12380 for (i = 0; i < MAX_PLAYERS; i++)
12382 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12384 if (!stored_player[i].active)
12387 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12394 void ScrollLevel(int dx, int dy)
12396 int scroll_offset = 2 * TILEX_VAR;
12399 BlitBitmap(drawto_field, drawto_field,
12400 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12401 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12402 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12403 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12404 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12405 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12409 x = (dx == 1 ? BX1 : BX2);
12410 for (y = BY1; y <= BY2; y++)
12411 DrawScreenField(x, y);
12416 y = (dy == 1 ? BY1 : BY2);
12417 for (x = BX1; x <= BX2; x++)
12418 DrawScreenField(x, y);
12421 redraw_mask |= REDRAW_FIELD;
12424 static boolean canFallDown(struct PlayerInfo *player)
12426 int jx = player->jx, jy = player->jy;
12428 return (IN_LEV_FIELD(jx, jy + 1) &&
12429 (IS_FREE(jx, jy + 1) ||
12430 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12431 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12432 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12435 static boolean canPassField(int x, int y, int move_dir)
12437 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12438 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12439 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12440 int nextx = x + dx;
12441 int nexty = y + dy;
12442 int element = Feld[x][y];
12444 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12445 !CAN_MOVE(element) &&
12446 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12447 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12448 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12451 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12453 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12454 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12455 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12459 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12460 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12461 (IS_DIGGABLE(Feld[newx][newy]) ||
12462 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12463 canPassField(newx, newy, move_dir)));
12466 static void CheckGravityMovement(struct PlayerInfo *player)
12468 if (player->gravity && !player->programmed_action)
12470 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12471 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12472 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12473 int jx = player->jx, jy = player->jy;
12474 boolean player_is_moving_to_valid_field =
12475 (!player_is_snapping &&
12476 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12477 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12478 boolean player_can_fall_down = canFallDown(player);
12480 if (player_can_fall_down &&
12481 !player_is_moving_to_valid_field)
12482 player->programmed_action = MV_DOWN;
12486 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12488 return CheckGravityMovement(player);
12490 if (player->gravity && !player->programmed_action)
12492 int jx = player->jx, jy = player->jy;
12493 boolean field_under_player_is_free =
12494 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12495 boolean player_is_standing_on_valid_field =
12496 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12497 (IS_WALKABLE(Feld[jx][jy]) &&
12498 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12500 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12501 player->programmed_action = MV_DOWN;
12506 MovePlayerOneStep()
12507 -----------------------------------------------------------------------------
12508 dx, dy: direction (non-diagonal) to try to move the player to
12509 real_dx, real_dy: direction as read from input device (can be diagonal)
12512 boolean MovePlayerOneStep(struct PlayerInfo *player,
12513 int dx, int dy, int real_dx, int real_dy)
12515 int jx = player->jx, jy = player->jy;
12516 int new_jx = jx + dx, new_jy = jy + dy;
12518 boolean player_can_move = !player->cannot_move;
12520 if (!player->active || (!dx && !dy))
12521 return MP_NO_ACTION;
12523 player->MovDir = (dx < 0 ? MV_LEFT :
12524 dx > 0 ? MV_RIGHT :
12526 dy > 0 ? MV_DOWN : MV_NONE);
12528 if (!IN_LEV_FIELD(new_jx, new_jy))
12529 return MP_NO_ACTION;
12531 if (!player_can_move)
12533 if (player->MovPos == 0)
12535 player->is_moving = FALSE;
12536 player->is_digging = FALSE;
12537 player->is_collecting = FALSE;
12538 player->is_snapping = FALSE;
12539 player->is_pushing = FALSE;
12543 if (!network.enabled && game.centered_player_nr == -1 &&
12544 !AllPlayersInSight(player, new_jx, new_jy))
12545 return MP_NO_ACTION;
12547 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12548 if (can_move != MP_MOVING)
12551 // check if DigField() has caused relocation of the player
12552 if (player->jx != jx || player->jy != jy)
12553 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12555 StorePlayer[jx][jy] = 0;
12556 player->last_jx = jx;
12557 player->last_jy = jy;
12558 player->jx = new_jx;
12559 player->jy = new_jy;
12560 StorePlayer[new_jx][new_jy] = player->element_nr;
12562 if (player->move_delay_value_next != -1)
12564 player->move_delay_value = player->move_delay_value_next;
12565 player->move_delay_value_next = -1;
12569 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12571 player->step_counter++;
12573 PlayerVisit[jx][jy] = FrameCounter;
12575 player->is_moving = TRUE;
12578 // should better be called in MovePlayer(), but this breaks some tapes
12579 ScrollPlayer(player, SCROLL_INIT);
12585 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12587 int jx = player->jx, jy = player->jy;
12588 int old_jx = jx, old_jy = jy;
12589 int moved = MP_NO_ACTION;
12591 if (!player->active)
12596 if (player->MovPos == 0)
12598 player->is_moving = FALSE;
12599 player->is_digging = FALSE;
12600 player->is_collecting = FALSE;
12601 player->is_snapping = FALSE;
12602 player->is_pushing = FALSE;
12608 if (player->move_delay > 0)
12611 player->move_delay = -1; // set to "uninitialized" value
12613 // store if player is automatically moved to next field
12614 player->is_auto_moving = (player->programmed_action != MV_NONE);
12616 // remove the last programmed player action
12617 player->programmed_action = 0;
12619 if (player->MovPos)
12621 // should only happen if pre-1.2 tape recordings are played
12622 // this is only for backward compatibility
12624 int original_move_delay_value = player->move_delay_value;
12627 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12631 // scroll remaining steps with finest movement resolution
12632 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12634 while (player->MovPos)
12636 ScrollPlayer(player, SCROLL_GO_ON);
12637 ScrollScreen(NULL, SCROLL_GO_ON);
12639 AdvanceFrameAndPlayerCounters(player->index_nr);
12642 BackToFront_WithFrameDelay(0);
12645 player->move_delay_value = original_move_delay_value;
12648 player->is_active = FALSE;
12650 if (player->last_move_dir & MV_HORIZONTAL)
12652 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12653 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12657 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12658 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12661 if (!moved && !player->is_active)
12663 player->is_moving = FALSE;
12664 player->is_digging = FALSE;
12665 player->is_collecting = FALSE;
12666 player->is_snapping = FALSE;
12667 player->is_pushing = FALSE;
12673 if (moved & MP_MOVING && !ScreenMovPos &&
12674 (player->index_nr == game.centered_player_nr ||
12675 game.centered_player_nr == -1))
12677 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12679 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12681 // actual player has left the screen -- scroll in that direction
12682 if (jx != old_jx) // player has moved horizontally
12683 scroll_x += (jx - old_jx);
12684 else // player has moved vertically
12685 scroll_y += (jy - old_jy);
12689 int offset_raw = game.scroll_delay_value;
12691 if (jx != old_jx) // player has moved horizontally
12693 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12694 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12695 int new_scroll_x = jx - MIDPOSX + offset_x;
12697 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12698 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12699 scroll_x = new_scroll_x;
12701 // don't scroll over playfield boundaries
12702 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12704 // don't scroll more than one field at a time
12705 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12707 // don't scroll against the player's moving direction
12708 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12709 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12710 scroll_x = old_scroll_x;
12712 else // player has moved vertically
12714 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12715 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12716 int new_scroll_y = jy - MIDPOSY + offset_y;
12718 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12719 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12720 scroll_y = new_scroll_y;
12722 // don't scroll over playfield boundaries
12723 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12725 // don't scroll more than one field at a time
12726 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12728 // don't scroll against the player's moving direction
12729 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12730 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12731 scroll_y = old_scroll_y;
12735 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12737 if (!network.enabled && game.centered_player_nr == -1 &&
12738 !AllPlayersInVisibleScreen())
12740 scroll_x = old_scroll_x;
12741 scroll_y = old_scroll_y;
12745 ScrollScreen(player, SCROLL_INIT);
12746 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12751 player->StepFrame = 0;
12753 if (moved & MP_MOVING)
12755 if (old_jx != jx && old_jy == jy)
12756 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12757 else if (old_jx == jx && old_jy != jy)
12758 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12760 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12762 player->last_move_dir = player->MovDir;
12763 player->is_moving = TRUE;
12764 player->is_snapping = FALSE;
12765 player->is_switching = FALSE;
12766 player->is_dropping = FALSE;
12767 player->is_dropping_pressed = FALSE;
12768 player->drop_pressed_delay = 0;
12771 // should better be called here than above, but this breaks some tapes
12772 ScrollPlayer(player, SCROLL_INIT);
12777 CheckGravityMovementWhenNotMoving(player);
12779 player->is_moving = FALSE;
12781 /* at this point, the player is allowed to move, but cannot move right now
12782 (e.g. because of something blocking the way) -- ensure that the player
12783 is also allowed to move in the next frame (in old versions before 3.1.1,
12784 the player was forced to wait again for eight frames before next try) */
12786 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12787 player->move_delay = 0; // allow direct movement in the next frame
12790 if (player->move_delay == -1) // not yet initialized by DigField()
12791 player->move_delay = player->move_delay_value;
12793 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12795 TestIfPlayerTouchesBadThing(jx, jy);
12796 TestIfPlayerTouchesCustomElement(jx, jy);
12799 if (!player->active)
12800 RemovePlayer(player);
12805 void ScrollPlayer(struct PlayerInfo *player, int mode)
12807 int jx = player->jx, jy = player->jy;
12808 int last_jx = player->last_jx, last_jy = player->last_jy;
12809 int move_stepsize = TILEX / player->move_delay_value;
12811 if (!player->active)
12814 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12817 if (mode == SCROLL_INIT)
12819 player->actual_frame_counter = FrameCounter;
12820 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12822 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12823 Feld[last_jx][last_jy] == EL_EMPTY)
12825 int last_field_block_delay = 0; // start with no blocking at all
12826 int block_delay_adjustment = player->block_delay_adjustment;
12828 // if player blocks last field, add delay for exactly one move
12829 if (player->block_last_field)
12831 last_field_block_delay += player->move_delay_value;
12833 // when blocking enabled, prevent moving up despite gravity
12834 if (player->gravity && player->MovDir == MV_UP)
12835 block_delay_adjustment = -1;
12838 // add block delay adjustment (also possible when not blocking)
12839 last_field_block_delay += block_delay_adjustment;
12841 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12842 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12845 if (player->MovPos != 0) // player has not yet reached destination
12848 else if (!FrameReached(&player->actual_frame_counter, 1))
12851 if (player->MovPos != 0)
12853 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12854 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12856 // before DrawPlayer() to draw correct player graphic for this case
12857 if (player->MovPos == 0)
12858 CheckGravityMovement(player);
12861 if (player->MovPos == 0) // player reached destination field
12863 if (player->move_delay_reset_counter > 0)
12865 player->move_delay_reset_counter--;
12867 if (player->move_delay_reset_counter == 0)
12869 // continue with normal speed after quickly moving through gate
12870 HALVE_PLAYER_SPEED(player);
12872 // be able to make the next move without delay
12873 player->move_delay = 0;
12877 player->last_jx = jx;
12878 player->last_jy = jy;
12880 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12881 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12882 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12883 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12884 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12885 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12886 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12887 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12889 ExitPlayer(player);
12891 if (game.players_still_needed == 0 &&
12892 (game.friends_still_needed == 0 ||
12893 IS_SP_ELEMENT(Feld[jx][jy])))
12897 // this breaks one level: "machine", level 000
12899 int move_direction = player->MovDir;
12900 int enter_side = MV_DIR_OPPOSITE(move_direction);
12901 int leave_side = move_direction;
12902 int old_jx = last_jx;
12903 int old_jy = last_jy;
12904 int old_element = Feld[old_jx][old_jy];
12905 int new_element = Feld[jx][jy];
12907 if (IS_CUSTOM_ELEMENT(old_element))
12908 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12910 player->index_bit, leave_side);
12912 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12913 CE_PLAYER_LEAVES_X,
12914 player->index_bit, leave_side);
12916 if (IS_CUSTOM_ELEMENT(new_element))
12917 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12918 player->index_bit, enter_side);
12920 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12921 CE_PLAYER_ENTERS_X,
12922 player->index_bit, enter_side);
12924 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12925 CE_MOVE_OF_X, move_direction);
12928 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12930 TestIfPlayerTouchesBadThing(jx, jy);
12931 TestIfPlayerTouchesCustomElement(jx, jy);
12933 /* needed because pushed element has not yet reached its destination,
12934 so it would trigger a change event at its previous field location */
12935 if (!player->is_pushing)
12936 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12938 if (!player->active)
12939 RemovePlayer(player);
12942 if (!game.LevelSolved && level.use_step_counter)
12952 if (TimeLeft <= 10 && setup.time_limit)
12953 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12955 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12957 DisplayGameControlValues();
12959 if (!TimeLeft && setup.time_limit)
12960 for (i = 0; i < MAX_PLAYERS; i++)
12961 KillPlayer(&stored_player[i]);
12963 else if (game.no_time_limit && !game.all_players_gone)
12965 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12967 DisplayGameControlValues();
12971 if (tape.single_step && tape.recording && !tape.pausing &&
12972 !player->programmed_action)
12973 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12975 if (!player->programmed_action)
12976 CheckSaveEngineSnapshot(player);
12980 void ScrollScreen(struct PlayerInfo *player, int mode)
12982 static unsigned int screen_frame_counter = 0;
12984 if (mode == SCROLL_INIT)
12986 // set scrolling step size according to actual player's moving speed
12987 ScrollStepSize = TILEX / player->move_delay_value;
12989 screen_frame_counter = FrameCounter;
12990 ScreenMovDir = player->MovDir;
12991 ScreenMovPos = player->MovPos;
12992 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12995 else if (!FrameReached(&screen_frame_counter, 1))
13000 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13001 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13002 redraw_mask |= REDRAW_FIELD;
13005 ScreenMovDir = MV_NONE;
13008 void TestIfPlayerTouchesCustomElement(int x, int y)
13010 static int xy[4][2] =
13017 static int trigger_sides[4][2] =
13019 // center side border side
13020 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13021 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13022 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13023 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13025 static int touch_dir[4] =
13027 MV_LEFT | MV_RIGHT,
13032 int center_element = Feld[x][y]; // should always be non-moving!
13035 for (i = 0; i < NUM_DIRECTIONS; i++)
13037 int xx = x + xy[i][0];
13038 int yy = y + xy[i][1];
13039 int center_side = trigger_sides[i][0];
13040 int border_side = trigger_sides[i][1];
13041 int border_element;
13043 if (!IN_LEV_FIELD(xx, yy))
13046 if (IS_PLAYER(x, y)) // player found at center element
13048 struct PlayerInfo *player = PLAYERINFO(x, y);
13050 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13051 border_element = Feld[xx][yy]; // may be moving!
13052 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13053 border_element = Feld[xx][yy];
13054 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13055 border_element = MovingOrBlocked2Element(xx, yy);
13057 continue; // center and border element do not touch
13059 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13060 player->index_bit, border_side);
13061 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13062 CE_PLAYER_TOUCHES_X,
13063 player->index_bit, border_side);
13066 /* use player element that is initially defined in the level playfield,
13067 not the player element that corresponds to the runtime player number
13068 (example: a level that contains EL_PLAYER_3 as the only player would
13069 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13070 int player_element = PLAYERINFO(x, y)->initial_element;
13072 CheckElementChangeBySide(xx, yy, border_element, player_element,
13073 CE_TOUCHING_X, border_side);
13076 else if (IS_PLAYER(xx, yy)) // player found at border element
13078 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13080 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13082 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13083 continue; // center and border element do not touch
13086 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13087 player->index_bit, center_side);
13088 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13089 CE_PLAYER_TOUCHES_X,
13090 player->index_bit, center_side);
13093 /* use player element that is initially defined in the level playfield,
13094 not the player element that corresponds to the runtime player number
13095 (example: a level that contains EL_PLAYER_3 as the only player would
13096 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13097 int player_element = PLAYERINFO(xx, yy)->initial_element;
13099 CheckElementChangeBySide(x, y, center_element, player_element,
13100 CE_TOUCHING_X, center_side);
13108 void TestIfElementTouchesCustomElement(int x, int y)
13110 static int xy[4][2] =
13117 static int trigger_sides[4][2] =
13119 // center side border side
13120 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13121 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13122 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13123 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13125 static int touch_dir[4] =
13127 MV_LEFT | MV_RIGHT,
13132 boolean change_center_element = FALSE;
13133 int center_element = Feld[x][y]; // should always be non-moving!
13134 int border_element_old[NUM_DIRECTIONS];
13137 for (i = 0; i < NUM_DIRECTIONS; i++)
13139 int xx = x + xy[i][0];
13140 int yy = y + xy[i][1];
13141 int border_element;
13143 border_element_old[i] = -1;
13145 if (!IN_LEV_FIELD(xx, yy))
13148 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13149 border_element = Feld[xx][yy]; // may be moving!
13150 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13151 border_element = Feld[xx][yy];
13152 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13153 border_element = MovingOrBlocked2Element(xx, yy);
13155 continue; // center and border element do not touch
13157 border_element_old[i] = border_element;
13160 for (i = 0; i < NUM_DIRECTIONS; i++)
13162 int xx = x + xy[i][0];
13163 int yy = y + xy[i][1];
13164 int center_side = trigger_sides[i][0];
13165 int border_element = border_element_old[i];
13167 if (border_element == -1)
13170 // check for change of border element
13171 CheckElementChangeBySide(xx, yy, border_element, center_element,
13172 CE_TOUCHING_X, center_side);
13174 // (center element cannot be player, so we dont have to check this here)
13177 for (i = 0; i < NUM_DIRECTIONS; i++)
13179 int xx = x + xy[i][0];
13180 int yy = y + xy[i][1];
13181 int border_side = trigger_sides[i][1];
13182 int border_element = border_element_old[i];
13184 if (border_element == -1)
13187 // check for change of center element (but change it only once)
13188 if (!change_center_element)
13189 change_center_element =
13190 CheckElementChangeBySide(x, y, center_element, border_element,
13191 CE_TOUCHING_X, border_side);
13193 if (IS_PLAYER(xx, yy))
13195 /* use player element that is initially defined in the level playfield,
13196 not the player element that corresponds to the runtime player number
13197 (example: a level that contains EL_PLAYER_3 as the only player would
13198 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13199 int player_element = PLAYERINFO(xx, yy)->initial_element;
13201 CheckElementChangeBySide(x, y, center_element, player_element,
13202 CE_TOUCHING_X, border_side);
13207 void TestIfElementHitsCustomElement(int x, int y, int direction)
13209 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13210 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13211 int hitx = x + dx, hity = y + dy;
13212 int hitting_element = Feld[x][y];
13213 int touched_element;
13215 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13218 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13219 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13221 if (IN_LEV_FIELD(hitx, hity))
13223 int opposite_direction = MV_DIR_OPPOSITE(direction);
13224 int hitting_side = direction;
13225 int touched_side = opposite_direction;
13226 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13227 MovDir[hitx][hity] != direction ||
13228 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13234 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13235 CE_HITTING_X, touched_side);
13237 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13238 CE_HIT_BY_X, hitting_side);
13240 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13241 CE_HIT_BY_SOMETHING, opposite_direction);
13243 if (IS_PLAYER(hitx, hity))
13245 /* use player element that is initially defined in the level playfield,
13246 not the player element that corresponds to the runtime player number
13247 (example: a level that contains EL_PLAYER_3 as the only player would
13248 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13249 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13251 CheckElementChangeBySide(x, y, hitting_element, player_element,
13252 CE_HITTING_X, touched_side);
13257 // "hitting something" is also true when hitting the playfield border
13258 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13259 CE_HITTING_SOMETHING, direction);
13262 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13264 int i, kill_x = -1, kill_y = -1;
13266 int bad_element = -1;
13267 static int test_xy[4][2] =
13274 static int test_dir[4] =
13282 for (i = 0; i < NUM_DIRECTIONS; i++)
13284 int test_x, test_y, test_move_dir, test_element;
13286 test_x = good_x + test_xy[i][0];
13287 test_y = good_y + test_xy[i][1];
13289 if (!IN_LEV_FIELD(test_x, test_y))
13293 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13295 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13297 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13298 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13300 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13301 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13305 bad_element = test_element;
13311 if (kill_x != -1 || kill_y != -1)
13313 if (IS_PLAYER(good_x, good_y))
13315 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13317 if (player->shield_deadly_time_left > 0 &&
13318 !IS_INDESTRUCTIBLE(bad_element))
13319 Bang(kill_x, kill_y);
13320 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13321 KillPlayer(player);
13324 Bang(good_x, good_y);
13328 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13330 int i, kill_x = -1, kill_y = -1;
13331 int bad_element = Feld[bad_x][bad_y];
13332 static int test_xy[4][2] =
13339 static int touch_dir[4] =
13341 MV_LEFT | MV_RIGHT,
13346 static int test_dir[4] =
13354 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13357 for (i = 0; i < NUM_DIRECTIONS; i++)
13359 int test_x, test_y, test_move_dir, test_element;
13361 test_x = bad_x + test_xy[i][0];
13362 test_y = bad_y + test_xy[i][1];
13364 if (!IN_LEV_FIELD(test_x, test_y))
13368 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13370 test_element = Feld[test_x][test_y];
13372 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13373 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13375 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13376 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13378 // good thing is player or penguin that does not move away
13379 if (IS_PLAYER(test_x, test_y))
13381 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13383 if (bad_element == EL_ROBOT && player->is_moving)
13384 continue; // robot does not kill player if he is moving
13386 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13388 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13389 continue; // center and border element do not touch
13397 else if (test_element == EL_PENGUIN)
13407 if (kill_x != -1 || kill_y != -1)
13409 if (IS_PLAYER(kill_x, kill_y))
13411 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13413 if (player->shield_deadly_time_left > 0 &&
13414 !IS_INDESTRUCTIBLE(bad_element))
13415 Bang(bad_x, bad_y);
13416 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13417 KillPlayer(player);
13420 Bang(kill_x, kill_y);
13424 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13426 int bad_element = Feld[bad_x][bad_y];
13427 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13428 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13429 int test_x = bad_x + dx, test_y = bad_y + dy;
13430 int test_move_dir, test_element;
13431 int kill_x = -1, kill_y = -1;
13433 if (!IN_LEV_FIELD(test_x, test_y))
13437 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13439 test_element = Feld[test_x][test_y];
13441 if (test_move_dir != bad_move_dir)
13443 // good thing can be player or penguin that does not move away
13444 if (IS_PLAYER(test_x, test_y))
13446 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13448 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13449 player as being hit when he is moving towards the bad thing, because
13450 the "get hit by" condition would be lost after the player stops) */
13451 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13452 return; // player moves away from bad thing
13457 else if (test_element == EL_PENGUIN)
13464 if (kill_x != -1 || kill_y != -1)
13466 if (IS_PLAYER(kill_x, kill_y))
13468 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13470 if (player->shield_deadly_time_left > 0 &&
13471 !IS_INDESTRUCTIBLE(bad_element))
13472 Bang(bad_x, bad_y);
13473 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13474 KillPlayer(player);
13477 Bang(kill_x, kill_y);
13481 void TestIfPlayerTouchesBadThing(int x, int y)
13483 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13486 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13488 TestIfGoodThingHitsBadThing(x, y, move_dir);
13491 void TestIfBadThingTouchesPlayer(int x, int y)
13493 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13496 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13498 TestIfBadThingHitsGoodThing(x, y, move_dir);
13501 void TestIfFriendTouchesBadThing(int x, int y)
13503 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13506 void TestIfBadThingTouchesFriend(int x, int y)
13508 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13511 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13513 int i, kill_x = bad_x, kill_y = bad_y;
13514 static int xy[4][2] =
13522 for (i = 0; i < NUM_DIRECTIONS; i++)
13526 x = bad_x + xy[i][0];
13527 y = bad_y + xy[i][1];
13528 if (!IN_LEV_FIELD(x, y))
13531 element = Feld[x][y];
13532 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13533 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13541 if (kill_x != bad_x || kill_y != bad_y)
13542 Bang(bad_x, bad_y);
13545 void KillPlayer(struct PlayerInfo *player)
13547 int jx = player->jx, jy = player->jy;
13549 if (!player->active)
13553 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13554 player->killed, player->active, player->reanimated);
13557 /* the following code was introduced to prevent an infinite loop when calling
13559 -> CheckTriggeredElementChangeExt()
13560 -> ExecuteCustomElementAction()
13562 -> (infinitely repeating the above sequence of function calls)
13563 which occurs when killing the player while having a CE with the setting
13564 "kill player X when explosion of <player X>"; the solution using a new
13565 field "player->killed" was chosen for backwards compatibility, although
13566 clever use of the fields "player->active" etc. would probably also work */
13568 if (player->killed)
13572 player->killed = TRUE;
13574 // remove accessible field at the player's position
13575 Feld[jx][jy] = EL_EMPTY;
13577 // deactivate shield (else Bang()/Explode() would not work right)
13578 player->shield_normal_time_left = 0;
13579 player->shield_deadly_time_left = 0;
13582 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13583 player->killed, player->active, player->reanimated);
13589 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13590 player->killed, player->active, player->reanimated);
13593 if (player->reanimated) // killed player may have been reanimated
13594 player->killed = player->reanimated = FALSE;
13596 BuryPlayer(player);
13599 static void KillPlayerUnlessEnemyProtected(int x, int y)
13601 if (!PLAYER_ENEMY_PROTECTED(x, y))
13602 KillPlayer(PLAYERINFO(x, y));
13605 static void KillPlayerUnlessExplosionProtected(int x, int y)
13607 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13608 KillPlayer(PLAYERINFO(x, y));
13611 void BuryPlayer(struct PlayerInfo *player)
13613 int jx = player->jx, jy = player->jy;
13615 if (!player->active)
13618 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13619 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13621 RemovePlayer(player);
13623 player->buried = TRUE;
13625 if (game.all_players_gone)
13626 game.GameOver = TRUE;
13629 void RemovePlayer(struct PlayerInfo *player)
13631 int jx = player->jx, jy = player->jy;
13632 int i, found = FALSE;
13634 player->present = FALSE;
13635 player->active = FALSE;
13637 // required for some CE actions (even if the player is not active anymore)
13638 player->MovPos = 0;
13640 if (!ExplodeField[jx][jy])
13641 StorePlayer[jx][jy] = 0;
13643 if (player->is_moving)
13644 TEST_DrawLevelField(player->last_jx, player->last_jy);
13646 for (i = 0; i < MAX_PLAYERS; i++)
13647 if (stored_player[i].active)
13652 game.all_players_gone = TRUE;
13653 game.GameOver = TRUE;
13656 game.exit_x = game.robot_wheel_x = jx;
13657 game.exit_y = game.robot_wheel_y = jy;
13660 void ExitPlayer(struct PlayerInfo *player)
13662 DrawPlayer(player); // needed here only to cleanup last field
13663 RemovePlayer(player);
13665 if (game.players_still_needed > 0)
13666 game.players_still_needed--;
13669 static void setFieldForSnapping(int x, int y, int element, int direction)
13671 struct ElementInfo *ei = &element_info[element];
13672 int direction_bit = MV_DIR_TO_BIT(direction);
13673 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13674 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13675 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13677 Feld[x][y] = EL_ELEMENT_SNAPPING;
13678 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13680 ResetGfxAnimation(x, y);
13682 GfxElement[x][y] = element;
13683 GfxAction[x][y] = action;
13684 GfxDir[x][y] = direction;
13685 GfxFrame[x][y] = -1;
13689 =============================================================================
13690 checkDiagonalPushing()
13691 -----------------------------------------------------------------------------
13692 check if diagonal input device direction results in pushing of object
13693 (by checking if the alternative direction is walkable, diggable, ...)
13694 =============================================================================
13697 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13698 int x, int y, int real_dx, int real_dy)
13700 int jx, jy, dx, dy, xx, yy;
13702 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13705 // diagonal direction: check alternative direction
13710 xx = jx + (dx == 0 ? real_dx : 0);
13711 yy = jy + (dy == 0 ? real_dy : 0);
13713 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13717 =============================================================================
13719 -----------------------------------------------------------------------------
13720 x, y: field next to player (non-diagonal) to try to dig to
13721 real_dx, real_dy: direction as read from input device (can be diagonal)
13722 =============================================================================
13725 static int DigField(struct PlayerInfo *player,
13726 int oldx, int oldy, int x, int y,
13727 int real_dx, int real_dy, int mode)
13729 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13730 boolean player_was_pushing = player->is_pushing;
13731 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13732 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13733 int jx = oldx, jy = oldy;
13734 int dx = x - jx, dy = y - jy;
13735 int nextx = x + dx, nexty = y + dy;
13736 int move_direction = (dx == -1 ? MV_LEFT :
13737 dx == +1 ? MV_RIGHT :
13739 dy == +1 ? MV_DOWN : MV_NONE);
13740 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13741 int dig_side = MV_DIR_OPPOSITE(move_direction);
13742 int old_element = Feld[jx][jy];
13743 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13746 if (is_player) // function can also be called by EL_PENGUIN
13748 if (player->MovPos == 0)
13750 player->is_digging = FALSE;
13751 player->is_collecting = FALSE;
13754 if (player->MovPos == 0) // last pushing move finished
13755 player->is_pushing = FALSE;
13757 if (mode == DF_NO_PUSH) // player just stopped pushing
13759 player->is_switching = FALSE;
13760 player->push_delay = -1;
13762 return MP_NO_ACTION;
13766 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13767 old_element = Back[jx][jy];
13769 // in case of element dropped at player position, check background
13770 else if (Back[jx][jy] != EL_EMPTY &&
13771 game.engine_version >= VERSION_IDENT(2,2,0,0))
13772 old_element = Back[jx][jy];
13774 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13775 return MP_NO_ACTION; // field has no opening in this direction
13777 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13778 return MP_NO_ACTION; // field has no opening in this direction
13780 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13784 Feld[jx][jy] = player->artwork_element;
13785 InitMovingField(jx, jy, MV_DOWN);
13786 Store[jx][jy] = EL_ACID;
13787 ContinueMoving(jx, jy);
13788 BuryPlayer(player);
13790 return MP_DONT_RUN_INTO;
13793 if (player_can_move && DONT_RUN_INTO(element))
13795 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13797 return MP_DONT_RUN_INTO;
13800 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13801 return MP_NO_ACTION;
13803 collect_count = element_info[element].collect_count_initial;
13805 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13806 return MP_NO_ACTION;
13808 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13809 player_can_move = player_can_move_or_snap;
13811 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13812 game.engine_version >= VERSION_IDENT(2,2,0,0))
13814 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13815 player->index_bit, dig_side);
13816 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13817 player->index_bit, dig_side);
13819 if (element == EL_DC_LANDMINE)
13822 if (Feld[x][y] != element) // field changed by snapping
13825 return MP_NO_ACTION;
13828 if (player->gravity && is_player && !player->is_auto_moving &&
13829 canFallDown(player) && move_direction != MV_DOWN &&
13830 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13831 return MP_NO_ACTION; // player cannot walk here due to gravity
13833 if (player_can_move &&
13834 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13836 int sound_element = SND_ELEMENT(element);
13837 int sound_action = ACTION_WALKING;
13839 if (IS_RND_GATE(element))
13841 if (!player->key[RND_GATE_NR(element)])
13842 return MP_NO_ACTION;
13844 else if (IS_RND_GATE_GRAY(element))
13846 if (!player->key[RND_GATE_GRAY_NR(element)])
13847 return MP_NO_ACTION;
13849 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13851 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13852 return MP_NO_ACTION;
13854 else if (element == EL_EXIT_OPEN ||
13855 element == EL_EM_EXIT_OPEN ||
13856 element == EL_EM_EXIT_OPENING ||
13857 element == EL_STEEL_EXIT_OPEN ||
13858 element == EL_EM_STEEL_EXIT_OPEN ||
13859 element == EL_EM_STEEL_EXIT_OPENING ||
13860 element == EL_SP_EXIT_OPEN ||
13861 element == EL_SP_EXIT_OPENING)
13863 sound_action = ACTION_PASSING; // player is passing exit
13865 else if (element == EL_EMPTY)
13867 sound_action = ACTION_MOVING; // nothing to walk on
13870 // play sound from background or player, whatever is available
13871 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13872 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13874 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13876 else if (player_can_move &&
13877 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13879 if (!ACCESS_FROM(element, opposite_direction))
13880 return MP_NO_ACTION; // field not accessible from this direction
13882 if (CAN_MOVE(element)) // only fixed elements can be passed!
13883 return MP_NO_ACTION;
13885 if (IS_EM_GATE(element))
13887 if (!player->key[EM_GATE_NR(element)])
13888 return MP_NO_ACTION;
13890 else if (IS_EM_GATE_GRAY(element))
13892 if (!player->key[EM_GATE_GRAY_NR(element)])
13893 return MP_NO_ACTION;
13895 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13897 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13898 return MP_NO_ACTION;
13900 else if (IS_EMC_GATE(element))
13902 if (!player->key[EMC_GATE_NR(element)])
13903 return MP_NO_ACTION;
13905 else if (IS_EMC_GATE_GRAY(element))
13907 if (!player->key[EMC_GATE_GRAY_NR(element)])
13908 return MP_NO_ACTION;
13910 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13912 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13913 return MP_NO_ACTION;
13915 else if (element == EL_DC_GATE_WHITE ||
13916 element == EL_DC_GATE_WHITE_GRAY ||
13917 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13919 if (player->num_white_keys == 0)
13920 return MP_NO_ACTION;
13922 player->num_white_keys--;
13924 else if (IS_SP_PORT(element))
13926 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13927 element == EL_SP_GRAVITY_PORT_RIGHT ||
13928 element == EL_SP_GRAVITY_PORT_UP ||
13929 element == EL_SP_GRAVITY_PORT_DOWN)
13930 player->gravity = !player->gravity;
13931 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13932 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13933 element == EL_SP_GRAVITY_ON_PORT_UP ||
13934 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13935 player->gravity = TRUE;
13936 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13937 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13938 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13939 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13940 player->gravity = FALSE;
13943 // automatically move to the next field with double speed
13944 player->programmed_action = move_direction;
13946 if (player->move_delay_reset_counter == 0)
13948 player->move_delay_reset_counter = 2; // two double speed steps
13950 DOUBLE_PLAYER_SPEED(player);
13953 PlayLevelSoundAction(x, y, ACTION_PASSING);
13955 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13959 if (mode != DF_SNAP)
13961 GfxElement[x][y] = GFX_ELEMENT(element);
13962 player->is_digging = TRUE;
13965 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13967 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13968 player->index_bit, dig_side);
13970 if (mode == DF_SNAP)
13972 if (level.block_snap_field)
13973 setFieldForSnapping(x, y, element, move_direction);
13975 TestIfElementTouchesCustomElement(x, y); // for empty space
13977 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13978 player->index_bit, dig_side);
13981 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13985 if (is_player && mode != DF_SNAP)
13987 GfxElement[x][y] = element;
13988 player->is_collecting = TRUE;
13991 if (element == EL_SPEED_PILL)
13993 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13995 else if (element == EL_EXTRA_TIME && level.time > 0)
13997 TimeLeft += level.extra_time;
13999 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14001 DisplayGameControlValues();
14003 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14005 player->shield_normal_time_left += level.shield_normal_time;
14006 if (element == EL_SHIELD_DEADLY)
14007 player->shield_deadly_time_left += level.shield_deadly_time;
14009 else if (element == EL_DYNAMITE ||
14010 element == EL_EM_DYNAMITE ||
14011 element == EL_SP_DISK_RED)
14013 if (player->inventory_size < MAX_INVENTORY_SIZE)
14014 player->inventory_element[player->inventory_size++] = element;
14016 DrawGameDoorValues();
14018 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14020 player->dynabomb_count++;
14021 player->dynabombs_left++;
14023 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14025 player->dynabomb_size++;
14027 else if (element == EL_DYNABOMB_INCREASE_POWER)
14029 player->dynabomb_xl = TRUE;
14031 else if (IS_KEY(element))
14033 player->key[KEY_NR(element)] = TRUE;
14035 DrawGameDoorValues();
14037 else if (element == EL_DC_KEY_WHITE)
14039 player->num_white_keys++;
14041 // display white keys?
14042 // DrawGameDoorValues();
14044 else if (IS_ENVELOPE(element))
14046 player->show_envelope = element;
14048 else if (element == EL_EMC_LENSES)
14050 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14052 RedrawAllInvisibleElementsForLenses();
14054 else if (element == EL_EMC_MAGNIFIER)
14056 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14058 RedrawAllInvisibleElementsForMagnifier();
14060 else if (IS_DROPPABLE(element) ||
14061 IS_THROWABLE(element)) // can be collected and dropped
14065 if (collect_count == 0)
14066 player->inventory_infinite_element = element;
14068 for (i = 0; i < collect_count; i++)
14069 if (player->inventory_size < MAX_INVENTORY_SIZE)
14070 player->inventory_element[player->inventory_size++] = element;
14072 DrawGameDoorValues();
14074 else if (collect_count > 0)
14076 game.gems_still_needed -= collect_count;
14077 if (game.gems_still_needed < 0)
14078 game.gems_still_needed = 0;
14080 game.snapshot.collected_item = TRUE;
14082 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14084 DisplayGameControlValues();
14087 RaiseScoreElement(element);
14088 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14091 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14092 player->index_bit, dig_side);
14094 if (mode == DF_SNAP)
14096 if (level.block_snap_field)
14097 setFieldForSnapping(x, y, element, move_direction);
14099 TestIfElementTouchesCustomElement(x, y); // for empty space
14101 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14102 player->index_bit, dig_side);
14105 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14107 if (mode == DF_SNAP && element != EL_BD_ROCK)
14108 return MP_NO_ACTION;
14110 if (CAN_FALL(element) && dy)
14111 return MP_NO_ACTION;
14113 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14114 !(element == EL_SPRING && level.use_spring_bug))
14115 return MP_NO_ACTION;
14117 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14118 ((move_direction & MV_VERTICAL &&
14119 ((element_info[element].move_pattern & MV_LEFT &&
14120 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14121 (element_info[element].move_pattern & MV_RIGHT &&
14122 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14123 (move_direction & MV_HORIZONTAL &&
14124 ((element_info[element].move_pattern & MV_UP &&
14125 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14126 (element_info[element].move_pattern & MV_DOWN &&
14127 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14128 return MP_NO_ACTION;
14130 // do not push elements already moving away faster than player
14131 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14132 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14133 return MP_NO_ACTION;
14135 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14137 if (player->push_delay_value == -1 || !player_was_pushing)
14138 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14140 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14142 if (player->push_delay_value == -1)
14143 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14145 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14147 if (!player->is_pushing)
14148 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14151 player->is_pushing = TRUE;
14152 player->is_active = TRUE;
14154 if (!(IN_LEV_FIELD(nextx, nexty) &&
14155 (IS_FREE(nextx, nexty) ||
14156 (IS_SB_ELEMENT(element) &&
14157 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14158 (IS_CUSTOM_ELEMENT(element) &&
14159 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14160 return MP_NO_ACTION;
14162 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14163 return MP_NO_ACTION;
14165 if (player->push_delay == -1) // new pushing; restart delay
14166 player->push_delay = 0;
14168 if (player->push_delay < player->push_delay_value &&
14169 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14170 element != EL_SPRING && element != EL_BALLOON)
14172 // make sure that there is no move delay before next try to push
14173 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14174 player->move_delay = 0;
14176 return MP_NO_ACTION;
14179 if (IS_CUSTOM_ELEMENT(element) &&
14180 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14182 if (!DigFieldByCE(nextx, nexty, element))
14183 return MP_NO_ACTION;
14186 if (IS_SB_ELEMENT(element))
14188 boolean sokoban_task_solved = FALSE;
14190 if (element == EL_SOKOBAN_FIELD_FULL)
14192 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14194 IncrementSokobanFieldsNeeded();
14195 IncrementSokobanObjectsNeeded();
14198 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14200 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14202 DecrementSokobanFieldsNeeded();
14203 DecrementSokobanObjectsNeeded();
14205 // sokoban object was pushed from empty field to sokoban field
14206 if (Back[x][y] == EL_EMPTY)
14207 sokoban_task_solved = TRUE;
14210 Feld[x][y] = EL_SOKOBAN_OBJECT;
14212 if (Back[x][y] == Back[nextx][nexty])
14213 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14214 else if (Back[x][y] != 0)
14215 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14218 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14221 if (sokoban_task_solved &&
14222 game.sokoban_fields_still_needed == 0 &&
14223 game.sokoban_objects_still_needed == 0 &&
14224 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14226 game.players_still_needed = 0;
14230 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14234 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14236 InitMovingField(x, y, move_direction);
14237 GfxAction[x][y] = ACTION_PUSHING;
14239 if (mode == DF_SNAP)
14240 ContinueMoving(x, y);
14242 MovPos[x][y] = (dx != 0 ? dx : dy);
14244 Pushed[x][y] = TRUE;
14245 Pushed[nextx][nexty] = TRUE;
14247 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14248 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14250 player->push_delay_value = -1; // get new value later
14252 // check for element change _after_ element has been pushed
14253 if (game.use_change_when_pushing_bug)
14255 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14256 player->index_bit, dig_side);
14257 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14258 player->index_bit, dig_side);
14261 else if (IS_SWITCHABLE(element))
14263 if (PLAYER_SWITCHING(player, x, y))
14265 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14266 player->index_bit, dig_side);
14271 player->is_switching = TRUE;
14272 player->switch_x = x;
14273 player->switch_y = y;
14275 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14277 if (element == EL_ROBOT_WHEEL)
14279 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14281 game.robot_wheel_x = x;
14282 game.robot_wheel_y = y;
14283 game.robot_wheel_active = TRUE;
14285 TEST_DrawLevelField(x, y);
14287 else if (element == EL_SP_TERMINAL)
14291 SCAN_PLAYFIELD(xx, yy)
14293 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14297 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14299 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14301 ResetGfxAnimation(xx, yy);
14302 TEST_DrawLevelField(xx, yy);
14306 else if (IS_BELT_SWITCH(element))
14308 ToggleBeltSwitch(x, y);
14310 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14311 element == EL_SWITCHGATE_SWITCH_DOWN ||
14312 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14313 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14315 ToggleSwitchgateSwitch(x, y);
14317 else if (element == EL_LIGHT_SWITCH ||
14318 element == EL_LIGHT_SWITCH_ACTIVE)
14320 ToggleLightSwitch(x, y);
14322 else if (element == EL_TIMEGATE_SWITCH ||
14323 element == EL_DC_TIMEGATE_SWITCH)
14325 ActivateTimegateSwitch(x, y);
14327 else if (element == EL_BALLOON_SWITCH_LEFT ||
14328 element == EL_BALLOON_SWITCH_RIGHT ||
14329 element == EL_BALLOON_SWITCH_UP ||
14330 element == EL_BALLOON_SWITCH_DOWN ||
14331 element == EL_BALLOON_SWITCH_NONE ||
14332 element == EL_BALLOON_SWITCH_ANY)
14334 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14335 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14336 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14337 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14338 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14341 else if (element == EL_LAMP)
14343 Feld[x][y] = EL_LAMP_ACTIVE;
14344 game.lights_still_needed--;
14346 ResetGfxAnimation(x, y);
14347 TEST_DrawLevelField(x, y);
14349 else if (element == EL_TIME_ORB_FULL)
14351 Feld[x][y] = EL_TIME_ORB_EMPTY;
14353 if (level.time > 0 || level.use_time_orb_bug)
14355 TimeLeft += level.time_orb_time;
14356 game.no_time_limit = FALSE;
14358 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14360 DisplayGameControlValues();
14363 ResetGfxAnimation(x, y);
14364 TEST_DrawLevelField(x, y);
14366 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14367 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14371 game.ball_active = !game.ball_active;
14373 SCAN_PLAYFIELD(xx, yy)
14375 int e = Feld[xx][yy];
14377 if (game.ball_active)
14379 if (e == EL_EMC_MAGIC_BALL)
14380 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14381 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14382 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14386 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14387 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14388 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14389 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14394 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14395 player->index_bit, dig_side);
14397 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14398 player->index_bit, dig_side);
14400 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14401 player->index_bit, dig_side);
14407 if (!PLAYER_SWITCHING(player, x, y))
14409 player->is_switching = TRUE;
14410 player->switch_x = x;
14411 player->switch_y = y;
14413 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14414 player->index_bit, dig_side);
14415 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14416 player->index_bit, dig_side);
14418 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14419 player->index_bit, dig_side);
14420 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14421 player->index_bit, dig_side);
14424 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14425 player->index_bit, dig_side);
14426 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14427 player->index_bit, dig_side);
14429 return MP_NO_ACTION;
14432 player->push_delay = -1;
14434 if (is_player) // function can also be called by EL_PENGUIN
14436 if (Feld[x][y] != element) // really digged/collected something
14438 player->is_collecting = !player->is_digging;
14439 player->is_active = TRUE;
14446 static boolean DigFieldByCE(int x, int y, int digging_element)
14448 int element = Feld[x][y];
14450 if (!IS_FREE(x, y))
14452 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14453 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14456 // no element can dig solid indestructible elements
14457 if (IS_INDESTRUCTIBLE(element) &&
14458 !IS_DIGGABLE(element) &&
14459 !IS_COLLECTIBLE(element))
14462 if (AmoebaNr[x][y] &&
14463 (element == EL_AMOEBA_FULL ||
14464 element == EL_BD_AMOEBA ||
14465 element == EL_AMOEBA_GROWING))
14467 AmoebaCnt[AmoebaNr[x][y]]--;
14468 AmoebaCnt2[AmoebaNr[x][y]]--;
14471 if (IS_MOVING(x, y))
14472 RemoveMovingField(x, y);
14476 TEST_DrawLevelField(x, y);
14479 // if digged element was about to explode, prevent the explosion
14480 ExplodeField[x][y] = EX_TYPE_NONE;
14482 PlayLevelSoundAction(x, y, action);
14485 Store[x][y] = EL_EMPTY;
14487 // this makes it possible to leave the removed element again
14488 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14489 Store[x][y] = element;
14494 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14496 int jx = player->jx, jy = player->jy;
14497 int x = jx + dx, y = jy + dy;
14498 int snap_direction = (dx == -1 ? MV_LEFT :
14499 dx == +1 ? MV_RIGHT :
14501 dy == +1 ? MV_DOWN : MV_NONE);
14502 boolean can_continue_snapping = (level.continuous_snapping &&
14503 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14505 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14508 if (!player->active || !IN_LEV_FIELD(x, y))
14516 if (player->MovPos == 0)
14517 player->is_pushing = FALSE;
14519 player->is_snapping = FALSE;
14521 if (player->MovPos == 0)
14523 player->is_moving = FALSE;
14524 player->is_digging = FALSE;
14525 player->is_collecting = FALSE;
14531 // prevent snapping with already pressed snap key when not allowed
14532 if (player->is_snapping && !can_continue_snapping)
14535 player->MovDir = snap_direction;
14537 if (player->MovPos == 0)
14539 player->is_moving = FALSE;
14540 player->is_digging = FALSE;
14541 player->is_collecting = FALSE;
14544 player->is_dropping = FALSE;
14545 player->is_dropping_pressed = FALSE;
14546 player->drop_pressed_delay = 0;
14548 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14551 player->is_snapping = TRUE;
14552 player->is_active = TRUE;
14554 if (player->MovPos == 0)
14556 player->is_moving = FALSE;
14557 player->is_digging = FALSE;
14558 player->is_collecting = FALSE;
14561 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14562 TEST_DrawLevelField(player->last_jx, player->last_jy);
14564 TEST_DrawLevelField(x, y);
14569 static boolean DropElement(struct PlayerInfo *player)
14571 int old_element, new_element;
14572 int dropx = player->jx, dropy = player->jy;
14573 int drop_direction = player->MovDir;
14574 int drop_side = drop_direction;
14575 int drop_element = get_next_dropped_element(player);
14577 /* do not drop an element on top of another element; when holding drop key
14578 pressed without moving, dropped element must move away before the next
14579 element can be dropped (this is especially important if the next element
14580 is dynamite, which can be placed on background for historical reasons) */
14581 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14584 if (IS_THROWABLE(drop_element))
14586 dropx += GET_DX_FROM_DIR(drop_direction);
14587 dropy += GET_DY_FROM_DIR(drop_direction);
14589 if (!IN_LEV_FIELD(dropx, dropy))
14593 old_element = Feld[dropx][dropy]; // old element at dropping position
14594 new_element = drop_element; // default: no change when dropping
14596 // check if player is active, not moving and ready to drop
14597 if (!player->active || player->MovPos || player->drop_delay > 0)
14600 // check if player has anything that can be dropped
14601 if (new_element == EL_UNDEFINED)
14604 // only set if player has anything that can be dropped
14605 player->is_dropping_pressed = TRUE;
14607 // check if drop key was pressed long enough for EM style dynamite
14608 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14611 // check if anything can be dropped at the current position
14612 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14615 // collected custom elements can only be dropped on empty fields
14616 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14619 if (old_element != EL_EMPTY)
14620 Back[dropx][dropy] = old_element; // store old element on this field
14622 ResetGfxAnimation(dropx, dropy);
14623 ResetRandomAnimationValue(dropx, dropy);
14625 if (player->inventory_size > 0 ||
14626 player->inventory_infinite_element != EL_UNDEFINED)
14628 if (player->inventory_size > 0)
14630 player->inventory_size--;
14632 DrawGameDoorValues();
14634 if (new_element == EL_DYNAMITE)
14635 new_element = EL_DYNAMITE_ACTIVE;
14636 else if (new_element == EL_EM_DYNAMITE)
14637 new_element = EL_EM_DYNAMITE_ACTIVE;
14638 else if (new_element == EL_SP_DISK_RED)
14639 new_element = EL_SP_DISK_RED_ACTIVE;
14642 Feld[dropx][dropy] = new_element;
14644 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14645 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14646 el2img(Feld[dropx][dropy]), 0);
14648 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14650 // needed if previous element just changed to "empty" in the last frame
14651 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14653 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14654 player->index_bit, drop_side);
14655 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14657 player->index_bit, drop_side);
14659 TestIfElementTouchesCustomElement(dropx, dropy);
14661 else // player is dropping a dyna bomb
14663 player->dynabombs_left--;
14665 Feld[dropx][dropy] = new_element;
14667 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14668 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14669 el2img(Feld[dropx][dropy]), 0);
14671 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14674 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14675 InitField_WithBug1(dropx, dropy, FALSE);
14677 new_element = Feld[dropx][dropy]; // element might have changed
14679 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14680 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14682 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14683 MovDir[dropx][dropy] = drop_direction;
14685 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14687 // do not cause impact style collision by dropping elements that can fall
14688 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14691 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14692 player->is_dropping = TRUE;
14694 player->drop_pressed_delay = 0;
14695 player->is_dropping_pressed = FALSE;
14697 player->drop_x = dropx;
14698 player->drop_y = dropy;
14703 // ----------------------------------------------------------------------------
14704 // game sound playing functions
14705 // ----------------------------------------------------------------------------
14707 static int *loop_sound_frame = NULL;
14708 static int *loop_sound_volume = NULL;
14710 void InitPlayLevelSound(void)
14712 int num_sounds = getSoundListSize();
14714 checked_free(loop_sound_frame);
14715 checked_free(loop_sound_volume);
14717 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14718 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14721 static void PlayLevelSound(int x, int y, int nr)
14723 int sx = SCREENX(x), sy = SCREENY(y);
14724 int volume, stereo_position;
14725 int max_distance = 8;
14726 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14728 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14729 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14732 if (!IN_LEV_FIELD(x, y) ||
14733 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14734 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14737 volume = SOUND_MAX_VOLUME;
14739 if (!IN_SCR_FIELD(sx, sy))
14741 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14742 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14744 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14747 stereo_position = (SOUND_MAX_LEFT +
14748 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14749 (SCR_FIELDX + 2 * max_distance));
14751 if (IS_LOOP_SOUND(nr))
14753 /* This assures that quieter loop sounds do not overwrite louder ones,
14754 while restarting sound volume comparison with each new game frame. */
14756 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14759 loop_sound_volume[nr] = volume;
14760 loop_sound_frame[nr] = FrameCounter;
14763 PlaySoundExt(nr, volume, stereo_position, type);
14766 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14768 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14769 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14770 y < LEVELY(BY1) ? LEVELY(BY1) :
14771 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14775 static void PlayLevelSoundAction(int x, int y, int action)
14777 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14780 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14782 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14784 if (sound_effect != SND_UNDEFINED)
14785 PlayLevelSound(x, y, sound_effect);
14788 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14791 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14793 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14794 PlayLevelSound(x, y, sound_effect);
14797 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14799 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14801 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14802 PlayLevelSound(x, y, sound_effect);
14805 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14807 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14809 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14810 StopSound(sound_effect);
14813 static int getLevelMusicNr(void)
14815 if (levelset.music[level_nr] != MUS_UNDEFINED)
14816 return levelset.music[level_nr]; // from config file
14818 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14821 static void FadeLevelSounds(void)
14826 static void FadeLevelMusic(void)
14828 int music_nr = getLevelMusicNr();
14829 char *curr_music = getCurrentlyPlayingMusicFilename();
14830 char *next_music = getMusicInfoEntryFilename(music_nr);
14832 if (!strEqual(curr_music, next_music))
14836 void FadeLevelSoundsAndMusic(void)
14842 static void PlayLevelMusic(void)
14844 int music_nr = getLevelMusicNr();
14845 char *curr_music = getCurrentlyPlayingMusicFilename();
14846 char *next_music = getMusicInfoEntryFilename(music_nr);
14848 if (!strEqual(curr_music, next_music))
14849 PlayMusicLoop(music_nr);
14852 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14854 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14856 int x = xx - offset;
14857 int y = yy - offset;
14862 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14866 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14870 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14874 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14878 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14882 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14886 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14889 case SOUND_android_clone:
14890 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14893 case SOUND_android_move:
14894 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14898 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14902 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14906 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14909 case SOUND_eater_eat:
14910 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14914 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14917 case SOUND_collect:
14918 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14921 case SOUND_diamond:
14922 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14926 // !!! CHECK THIS !!!
14928 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14930 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14934 case SOUND_wonderfall:
14935 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14939 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14943 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14947 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14951 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14955 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14959 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14963 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14967 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14970 case SOUND_exit_open:
14971 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14974 case SOUND_exit_leave:
14975 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14978 case SOUND_dynamite:
14979 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14983 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14987 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14991 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14995 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14999 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15003 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15007 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15012 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15014 int element = map_element_SP_to_RND(element_sp);
15015 int action = map_action_SP_to_RND(action_sp);
15016 int offset = (setup.sp_show_border_elements ? 0 : 1);
15017 int x = xx - offset;
15018 int y = yy - offset;
15020 PlayLevelSoundElementAction(x, y, element, action);
15023 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15025 int element = map_element_MM_to_RND(element_mm);
15026 int action = map_action_MM_to_RND(action_mm);
15028 int x = xx - offset;
15029 int y = yy - offset;
15031 if (!IS_MM_ELEMENT(element))
15032 element = EL_MM_DEFAULT;
15034 PlayLevelSoundElementAction(x, y, element, action);
15037 void PlaySound_MM(int sound_mm)
15039 int sound = map_sound_MM_to_RND(sound_mm);
15041 if (sound == SND_UNDEFINED)
15047 void PlaySoundLoop_MM(int sound_mm)
15049 int sound = map_sound_MM_to_RND(sound_mm);
15051 if (sound == SND_UNDEFINED)
15054 PlaySoundLoop(sound);
15057 void StopSound_MM(int sound_mm)
15059 int sound = map_sound_MM_to_RND(sound_mm);
15061 if (sound == SND_UNDEFINED)
15067 void RaiseScore(int value)
15069 game.score += value;
15071 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15073 DisplayGameControlValues();
15076 void RaiseScoreElement(int element)
15081 case EL_BD_DIAMOND:
15082 case EL_EMERALD_YELLOW:
15083 case EL_EMERALD_RED:
15084 case EL_EMERALD_PURPLE:
15085 case EL_SP_INFOTRON:
15086 RaiseScore(level.score[SC_EMERALD]);
15089 RaiseScore(level.score[SC_DIAMOND]);
15092 RaiseScore(level.score[SC_CRYSTAL]);
15095 RaiseScore(level.score[SC_PEARL]);
15098 case EL_BD_BUTTERFLY:
15099 case EL_SP_ELECTRON:
15100 RaiseScore(level.score[SC_BUG]);
15103 case EL_BD_FIREFLY:
15104 case EL_SP_SNIKSNAK:
15105 RaiseScore(level.score[SC_SPACESHIP]);
15108 case EL_DARK_YAMYAM:
15109 RaiseScore(level.score[SC_YAMYAM]);
15112 RaiseScore(level.score[SC_ROBOT]);
15115 RaiseScore(level.score[SC_PACMAN]);
15118 RaiseScore(level.score[SC_NUT]);
15121 case EL_EM_DYNAMITE:
15122 case EL_SP_DISK_RED:
15123 case EL_DYNABOMB_INCREASE_NUMBER:
15124 case EL_DYNABOMB_INCREASE_SIZE:
15125 case EL_DYNABOMB_INCREASE_POWER:
15126 RaiseScore(level.score[SC_DYNAMITE]);
15128 case EL_SHIELD_NORMAL:
15129 case EL_SHIELD_DEADLY:
15130 RaiseScore(level.score[SC_SHIELD]);
15132 case EL_EXTRA_TIME:
15133 RaiseScore(level.extra_time_score);
15147 case EL_DC_KEY_WHITE:
15148 RaiseScore(level.score[SC_KEY]);
15151 RaiseScore(element_info[element].collect_score);
15156 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15158 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15160 // closing door required in case of envelope style request dialogs
15163 // prevent short reactivation of overlay buttons while closing door
15164 SetOverlayActive(FALSE);
15166 CloseDoor(DOOR_CLOSE_1);
15169 if (network.enabled)
15170 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15174 FadeSkipNextFadeIn();
15176 SetGameStatus(GAME_MODE_MAIN);
15181 else // continue playing the game
15183 if (tape.playing && tape.deactivate_display)
15184 TapeDeactivateDisplayOff(TRUE);
15186 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15188 if (tape.playing && tape.deactivate_display)
15189 TapeDeactivateDisplayOn();
15193 void RequestQuitGame(boolean ask_if_really_quit)
15195 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15196 boolean skip_request = game.all_players_gone || quick_quit;
15198 RequestQuitGameExt(skip_request, quick_quit,
15199 "Do you really want to quit the game?");
15202 void RequestRestartGame(char *message)
15204 game.restart_game_message = NULL;
15206 boolean has_started_game = hasStartedNetworkGame();
15207 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15209 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15211 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15215 SetGameStatus(GAME_MODE_MAIN);
15221 void CheckGameOver(void)
15223 static boolean last_game_over = FALSE;
15224 static int game_over_delay = 0;
15225 int game_over_delay_value = 50;
15226 boolean game_over = checkGameFailed();
15228 // do not handle game over if request dialog is already active
15229 if (game.request_active)
15232 // do not ask to play again if game was never actually played
15233 if (!game.GamePlayed)
15238 last_game_over = FALSE;
15239 game_over_delay = game_over_delay_value;
15244 if (game_over_delay > 0)
15251 if (last_game_over != game_over)
15252 game.restart_game_message = (hasStartedNetworkGame() ?
15253 "Game over! Play it again?" :
15256 last_game_over = game_over;
15259 boolean checkGameSolved(void)
15261 // set for all game engines if level was solved
15262 return game.LevelSolved_GameEnd;
15265 boolean checkGameFailed(void)
15267 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15268 return (game_em.game_over && !game_em.level_solved);
15269 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15270 return (game_sp.game_over && !game_sp.level_solved);
15271 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15272 return (game_mm.game_over && !game_mm.level_solved);
15273 else // GAME_ENGINE_TYPE_RND
15274 return (game.GameOver && !game.LevelSolved);
15277 boolean checkGameEnded(void)
15279 return (checkGameSolved() || checkGameFailed());
15283 // ----------------------------------------------------------------------------
15284 // random generator functions
15285 // ----------------------------------------------------------------------------
15287 unsigned int InitEngineRandom_RND(int seed)
15289 game.num_random_calls = 0;
15291 return InitEngineRandom(seed);
15294 unsigned int RND(int max)
15298 game.num_random_calls++;
15300 return GetEngineRandom(max);
15307 // ----------------------------------------------------------------------------
15308 // game engine snapshot handling functions
15309 // ----------------------------------------------------------------------------
15311 struct EngineSnapshotInfo
15313 // runtime values for custom element collect score
15314 int collect_score[NUM_CUSTOM_ELEMENTS];
15316 // runtime values for group element choice position
15317 int choice_pos[NUM_GROUP_ELEMENTS];
15319 // runtime values for belt position animations
15320 int belt_graphic[4][NUM_BELT_PARTS];
15321 int belt_anim_mode[4][NUM_BELT_PARTS];
15324 static struct EngineSnapshotInfo engine_snapshot_rnd;
15325 static char *snapshot_level_identifier = NULL;
15326 static int snapshot_level_nr = -1;
15328 static void SaveEngineSnapshotValues_RND(void)
15330 static int belt_base_active_element[4] =
15332 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15333 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15334 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15335 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15339 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15341 int element = EL_CUSTOM_START + i;
15343 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15346 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15348 int element = EL_GROUP_START + i;
15350 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15353 for (i = 0; i < 4; i++)
15355 for (j = 0; j < NUM_BELT_PARTS; j++)
15357 int element = belt_base_active_element[i] + j;
15358 int graphic = el2img(element);
15359 int anim_mode = graphic_info[graphic].anim_mode;
15361 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15362 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15367 static void LoadEngineSnapshotValues_RND(void)
15369 unsigned int num_random_calls = game.num_random_calls;
15372 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15374 int element = EL_CUSTOM_START + i;
15376 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15379 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15381 int element = EL_GROUP_START + i;
15383 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15386 for (i = 0; i < 4; i++)
15388 for (j = 0; j < NUM_BELT_PARTS; j++)
15390 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15391 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15393 graphic_info[graphic].anim_mode = anim_mode;
15397 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15399 InitRND(tape.random_seed);
15400 for (i = 0; i < num_random_calls; i++)
15404 if (game.num_random_calls != num_random_calls)
15406 Error(ERR_INFO, "number of random calls out of sync");
15407 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15408 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15409 Error(ERR_EXIT, "this should not happen -- please debug");
15413 void FreeEngineSnapshotSingle(void)
15415 FreeSnapshotSingle();
15417 setString(&snapshot_level_identifier, NULL);
15418 snapshot_level_nr = -1;
15421 void FreeEngineSnapshotList(void)
15423 FreeSnapshotList();
15426 static ListNode *SaveEngineSnapshotBuffers(void)
15428 ListNode *buffers = NULL;
15430 // copy some special values to a structure better suited for the snapshot
15432 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15433 SaveEngineSnapshotValues_RND();
15434 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15435 SaveEngineSnapshotValues_EM();
15436 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15437 SaveEngineSnapshotValues_SP(&buffers);
15438 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15439 SaveEngineSnapshotValues_MM(&buffers);
15441 // save values stored in special snapshot structure
15443 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15444 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15445 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15446 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15447 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15448 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15449 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15450 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15452 // save further RND engine values
15454 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15455 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15456 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15458 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15459 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15460 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15461 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15462 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15464 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15465 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15466 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15470 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15471 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15474 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15475 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15476 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15479 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15480 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15481 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15484 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15486 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15489 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15490 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15492 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15512 ListNode *node = engine_snapshot_list_rnd;
15515 while (node != NULL)
15517 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15522 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15528 void SaveEngineSnapshotSingle(void)
15530 ListNode *buffers = SaveEngineSnapshotBuffers();
15532 // finally save all snapshot buffers to single snapshot
15533 SaveSnapshotSingle(buffers);
15535 // save level identification information
15536 setString(&snapshot_level_identifier, leveldir_current->identifier);
15537 snapshot_level_nr = level_nr;
15540 boolean CheckSaveEngineSnapshotToList(void)
15542 boolean save_snapshot =
15543 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15544 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15545 game.snapshot.changed_action) ||
15546 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15547 game.snapshot.collected_item));
15549 game.snapshot.changed_action = FALSE;
15550 game.snapshot.collected_item = FALSE;
15551 game.snapshot.save_snapshot = save_snapshot;
15553 return save_snapshot;
15556 void SaveEngineSnapshotToList(void)
15558 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15562 ListNode *buffers = SaveEngineSnapshotBuffers();
15564 // finally save all snapshot buffers to snapshot list
15565 SaveSnapshotToList(buffers);
15568 void SaveEngineSnapshotToListInitial(void)
15570 FreeEngineSnapshotList();
15572 SaveEngineSnapshotToList();
15575 static void LoadEngineSnapshotValues(void)
15577 // restore special values from snapshot structure
15579 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15580 LoadEngineSnapshotValues_RND();
15581 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15582 LoadEngineSnapshotValues_EM();
15583 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15584 LoadEngineSnapshotValues_SP();
15585 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15586 LoadEngineSnapshotValues_MM();
15589 void LoadEngineSnapshotSingle(void)
15591 LoadSnapshotSingle();
15593 LoadEngineSnapshotValues();
15596 static void LoadEngineSnapshot_Undo(int steps)
15598 LoadSnapshotFromList_Older(steps);
15600 LoadEngineSnapshotValues();
15603 static void LoadEngineSnapshot_Redo(int steps)
15605 LoadSnapshotFromList_Newer(steps);
15607 LoadEngineSnapshotValues();
15610 boolean CheckEngineSnapshotSingle(void)
15612 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15613 snapshot_level_nr == level_nr);
15616 boolean CheckEngineSnapshotList(void)
15618 return CheckSnapshotList();
15622 // ---------- new game button stuff -------------------------------------------
15629 boolean *setup_value;
15630 boolean allowed_on_tape;
15631 boolean is_touch_button;
15633 } gamebutton_info[NUM_GAME_BUTTONS] =
15636 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15637 GAME_CTRL_ID_STOP, NULL,
15638 TRUE, FALSE, "stop game"
15641 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15642 GAME_CTRL_ID_PAUSE, NULL,
15643 TRUE, FALSE, "pause game"
15646 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15647 GAME_CTRL_ID_PLAY, NULL,
15648 TRUE, FALSE, "play game"
15651 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15652 GAME_CTRL_ID_UNDO, NULL,
15653 TRUE, FALSE, "undo step"
15656 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15657 GAME_CTRL_ID_REDO, NULL,
15658 TRUE, FALSE, "redo step"
15661 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15662 GAME_CTRL_ID_SAVE, NULL,
15663 TRUE, FALSE, "save game"
15666 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15667 GAME_CTRL_ID_PAUSE2, NULL,
15668 TRUE, FALSE, "pause game"
15671 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15672 GAME_CTRL_ID_LOAD, NULL,
15673 TRUE, FALSE, "load game"
15676 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15677 GAME_CTRL_ID_PANEL_STOP, NULL,
15678 FALSE, FALSE, "stop game"
15681 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15682 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15683 FALSE, FALSE, "pause game"
15686 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15687 GAME_CTRL_ID_PANEL_PLAY, NULL,
15688 FALSE, FALSE, "play game"
15691 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15692 GAME_CTRL_ID_TOUCH_STOP, NULL,
15693 FALSE, TRUE, "stop game"
15696 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15697 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15698 FALSE, TRUE, "pause game"
15701 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15702 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15703 TRUE, FALSE, "background music on/off"
15706 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15707 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15708 TRUE, FALSE, "sound loops on/off"
15711 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15712 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15713 TRUE, FALSE, "normal sounds on/off"
15716 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15717 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15718 FALSE, FALSE, "background music on/off"
15721 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15722 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15723 FALSE, FALSE, "sound loops on/off"
15726 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15727 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15728 FALSE, FALSE, "normal sounds on/off"
15732 void CreateGameButtons(void)
15736 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15738 int graphic = gamebutton_info[i].graphic;
15739 struct GraphicInfo *gfx = &graphic_info[graphic];
15740 struct XY *pos = gamebutton_info[i].pos;
15741 struct GadgetInfo *gi;
15744 unsigned int event_mask;
15745 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15746 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15747 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15748 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15749 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15750 int gd_x = gfx->src_x;
15751 int gd_y = gfx->src_y;
15752 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15753 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15754 int gd_xa = gfx->src_x + gfx->active_xoffset;
15755 int gd_ya = gfx->src_y + gfx->active_yoffset;
15756 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15757 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15758 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15759 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15762 if (gfx->bitmap == NULL)
15764 game_gadget[id] = NULL;
15769 if (id == GAME_CTRL_ID_STOP ||
15770 id == GAME_CTRL_ID_PANEL_STOP ||
15771 id == GAME_CTRL_ID_TOUCH_STOP ||
15772 id == GAME_CTRL_ID_PLAY ||
15773 id == GAME_CTRL_ID_PANEL_PLAY ||
15774 id == GAME_CTRL_ID_SAVE ||
15775 id == GAME_CTRL_ID_LOAD)
15777 button_type = GD_TYPE_NORMAL_BUTTON;
15779 event_mask = GD_EVENT_RELEASED;
15781 else if (id == GAME_CTRL_ID_UNDO ||
15782 id == GAME_CTRL_ID_REDO)
15784 button_type = GD_TYPE_NORMAL_BUTTON;
15786 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15790 button_type = GD_TYPE_CHECK_BUTTON;
15791 checked = (gamebutton_info[i].setup_value != NULL ?
15792 *gamebutton_info[i].setup_value : FALSE);
15793 event_mask = GD_EVENT_PRESSED;
15796 gi = CreateGadget(GDI_CUSTOM_ID, id,
15797 GDI_IMAGE_ID, graphic,
15798 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15801 GDI_WIDTH, gfx->width,
15802 GDI_HEIGHT, gfx->height,
15803 GDI_TYPE, button_type,
15804 GDI_STATE, GD_BUTTON_UNPRESSED,
15805 GDI_CHECKED, checked,
15806 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15807 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15808 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15809 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15810 GDI_DIRECT_DRAW, FALSE,
15811 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15812 GDI_EVENT_MASK, event_mask,
15813 GDI_CALLBACK_ACTION, HandleGameButtons,
15817 Error(ERR_EXIT, "cannot create gadget");
15819 game_gadget[id] = gi;
15823 void FreeGameButtons(void)
15827 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15828 FreeGadget(game_gadget[i]);
15831 static void UnmapGameButtonsAtSamePosition(int id)
15835 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15837 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15838 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15839 UnmapGadget(game_gadget[i]);
15842 static void UnmapGameButtonsAtSamePosition_All(void)
15844 if (setup.show_snapshot_buttons)
15846 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15847 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15848 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15852 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15853 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15854 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15856 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15857 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15858 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15862 static void MapGameButtonsAtSamePosition(int id)
15866 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15868 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15869 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15870 MapGadget(game_gadget[i]);
15872 UnmapGameButtonsAtSamePosition_All();
15875 void MapUndoRedoButtons(void)
15877 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15878 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15880 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15881 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15884 void UnmapUndoRedoButtons(void)
15886 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15887 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15889 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15890 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15893 void ModifyPauseButtons(void)
15897 GAME_CTRL_ID_PAUSE,
15898 GAME_CTRL_ID_PAUSE2,
15899 GAME_CTRL_ID_PANEL_PAUSE,
15900 GAME_CTRL_ID_TOUCH_PAUSE,
15905 for (i = 0; ids[i] > -1; i++)
15906 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15909 static void MapGameButtonsExt(boolean on_tape)
15913 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15914 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15915 i != GAME_CTRL_ID_UNDO &&
15916 i != GAME_CTRL_ID_REDO)
15917 MapGadget(game_gadget[i]);
15919 UnmapGameButtonsAtSamePosition_All();
15921 RedrawGameButtons();
15924 static void UnmapGameButtonsExt(boolean on_tape)
15928 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15929 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15930 UnmapGadget(game_gadget[i]);
15933 static void RedrawGameButtonsExt(boolean on_tape)
15937 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15938 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15939 RedrawGadget(game_gadget[i]);
15942 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15947 gi->checked = state;
15950 static void RedrawSoundButtonGadget(int id)
15952 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15953 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15954 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15955 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15956 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15957 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15960 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15961 RedrawGadget(game_gadget[id2]);
15964 void MapGameButtons(void)
15966 MapGameButtonsExt(FALSE);
15969 void UnmapGameButtons(void)
15971 UnmapGameButtonsExt(FALSE);
15974 void RedrawGameButtons(void)
15976 RedrawGameButtonsExt(FALSE);
15979 void MapGameButtonsOnTape(void)
15981 MapGameButtonsExt(TRUE);
15984 void UnmapGameButtonsOnTape(void)
15986 UnmapGameButtonsExt(TRUE);
15989 void RedrawGameButtonsOnTape(void)
15991 RedrawGameButtonsExt(TRUE);
15994 static void GameUndoRedoExt(void)
15996 ClearPlayerAction();
15998 tape.pausing = TRUE;
16001 UpdateAndDisplayGameControlValues();
16003 DrawCompleteVideoDisplay();
16004 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16005 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16006 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16011 static void GameUndo(int steps)
16013 if (!CheckEngineSnapshotList())
16016 LoadEngineSnapshot_Undo(steps);
16021 static void GameRedo(int steps)
16023 if (!CheckEngineSnapshotList())
16026 LoadEngineSnapshot_Redo(steps);
16031 static void HandleGameButtonsExt(int id, int button)
16033 static boolean game_undo_executed = FALSE;
16034 int steps = BUTTON_STEPSIZE(button);
16035 boolean handle_game_buttons =
16036 (game_status == GAME_MODE_PLAYING ||
16037 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16039 if (!handle_game_buttons)
16044 case GAME_CTRL_ID_STOP:
16045 case GAME_CTRL_ID_PANEL_STOP:
16046 case GAME_CTRL_ID_TOUCH_STOP:
16047 if (game_status == GAME_MODE_MAIN)
16053 RequestQuitGame(TRUE);
16057 case GAME_CTRL_ID_PAUSE:
16058 case GAME_CTRL_ID_PAUSE2:
16059 case GAME_CTRL_ID_PANEL_PAUSE:
16060 case GAME_CTRL_ID_TOUCH_PAUSE:
16061 if (network.enabled && game_status == GAME_MODE_PLAYING)
16064 SendToServer_ContinuePlaying();
16066 SendToServer_PausePlaying();
16069 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16071 game_undo_executed = FALSE;
16075 case GAME_CTRL_ID_PLAY:
16076 case GAME_CTRL_ID_PANEL_PLAY:
16077 if (game_status == GAME_MODE_MAIN)
16079 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16081 else if (tape.pausing)
16083 if (network.enabled)
16084 SendToServer_ContinuePlaying();
16086 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16090 case GAME_CTRL_ID_UNDO:
16091 // Important: When using "save snapshot when collecting an item" mode,
16092 // load last (current) snapshot for first "undo" after pressing "pause"
16093 // (else the last-but-one snapshot would be loaded, because the snapshot
16094 // pointer already points to the last snapshot when pressing "pause",
16095 // which is fine for "every step/move" mode, but not for "every collect")
16096 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16097 !game_undo_executed)
16100 game_undo_executed = TRUE;
16105 case GAME_CTRL_ID_REDO:
16109 case GAME_CTRL_ID_SAVE:
16113 case GAME_CTRL_ID_LOAD:
16117 case SOUND_CTRL_ID_MUSIC:
16118 case SOUND_CTRL_ID_PANEL_MUSIC:
16119 if (setup.sound_music)
16121 setup.sound_music = FALSE;
16125 else if (audio.music_available)
16127 setup.sound = setup.sound_music = TRUE;
16129 SetAudioMode(setup.sound);
16131 if (game_status == GAME_MODE_PLAYING)
16135 RedrawSoundButtonGadget(id);
16139 case SOUND_CTRL_ID_LOOPS:
16140 case SOUND_CTRL_ID_PANEL_LOOPS:
16141 if (setup.sound_loops)
16142 setup.sound_loops = FALSE;
16143 else if (audio.loops_available)
16145 setup.sound = setup.sound_loops = TRUE;
16147 SetAudioMode(setup.sound);
16150 RedrawSoundButtonGadget(id);
16154 case SOUND_CTRL_ID_SIMPLE:
16155 case SOUND_CTRL_ID_PANEL_SIMPLE:
16156 if (setup.sound_simple)
16157 setup.sound_simple = FALSE;
16158 else if (audio.sound_available)
16160 setup.sound = setup.sound_simple = TRUE;
16162 SetAudioMode(setup.sound);
16165 RedrawSoundButtonGadget(id);
16174 static void HandleGameButtons(struct GadgetInfo *gi)
16176 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16179 void HandleSoundButtonKeys(Key key)
16181 if (key == setup.shortcut.sound_simple)
16182 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16183 else if (key == setup.shortcut.sound_loops)
16184 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16185 else if (key == setup.shortcut.sound_music)
16186 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);