1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Feld[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Feld[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Feld[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
971 Feld[x][y] == EL_EM_EXIT_OPEN || \
972 Feld[x][y] == EL_STEEL_EXIT_OPEN || \
973 Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Feld[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \
986 Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define GAME_CTRL_ID_TOUCH_STOP 11
1020 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1021 #define SOUND_CTRL_ID_MUSIC 13
1022 #define SOUND_CTRL_ID_LOOPS 14
1023 #define SOUND_CTRL_ID_SIMPLE 15
1024 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1025 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1026 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1028 #define NUM_GAME_BUTTONS 19
1031 // forward declaration for internal use
1033 static void CreateField(int, int, int);
1035 static void ResetGfxAnimation(int, int);
1037 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1038 static void AdvanceFrameAndPlayerCounters(int);
1040 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1041 static boolean MovePlayer(struct PlayerInfo *, int, int);
1042 static void ScrollPlayer(struct PlayerInfo *, int);
1043 static void ScrollScreen(struct PlayerInfo *, int);
1045 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1046 static boolean DigFieldByCE(int, int, int);
1047 static boolean SnapField(struct PlayerInfo *, int, int);
1048 static boolean DropElement(struct PlayerInfo *);
1050 static void InitBeltMovement(void);
1051 static void CloseAllOpenTimegates(void);
1052 static void CheckGravityMovement(struct PlayerInfo *);
1053 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1054 static void KillPlayerUnlessEnemyProtected(int, int);
1055 static void KillPlayerUnlessExplosionProtected(int, int);
1057 static void TestIfPlayerTouchesCustomElement(int, int);
1058 static void TestIfElementTouchesCustomElement(int, int);
1059 static void TestIfElementHitsCustomElement(int, int, int);
1061 static void HandleElementChange(int, int, int);
1062 static void ExecuteCustomElementAction(int, int, int, int);
1063 static boolean ChangeElement(int, int, int, int);
1065 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1066 #define CheckTriggeredElementChange(x, y, e, ev) \
1067 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1068 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1070 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1071 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1072 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1073 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1075 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckElementChange(x, y, e, te, ev) \
1077 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1078 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1080 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1083 static void PlayLevelSound(int, int, int);
1084 static void PlayLevelSoundNearest(int, int, int);
1085 static void PlayLevelSoundAction(int, int, int);
1086 static void PlayLevelSoundElementAction(int, int, int, int);
1087 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1088 static void PlayLevelSoundActionIfLoop(int, int, int);
1089 static void StopLevelSoundActionIfLoop(int, int, int);
1090 static void PlayLevelMusic(void);
1091 static void FadeLevelSoundsAndMusic(void);
1093 static void HandleGameButtons(struct GadgetInfo *);
1095 int AmoebeNachbarNr(int, int);
1096 void AmoebeUmwandeln(int, int);
1097 void ContinueMoving(int, int);
1098 void Bang(int, int);
1099 void InitMovDir(int, int);
1100 void InitAmoebaNr(int, int);
1101 int NewHiScore(int);
1103 void TestIfGoodThingHitsBadThing(int, int, int);
1104 void TestIfBadThingHitsGoodThing(int, int, int);
1105 void TestIfPlayerTouchesBadThing(int, int);
1106 void TestIfPlayerRunsIntoBadThing(int, int, int);
1107 void TestIfBadThingTouchesPlayer(int, int);
1108 void TestIfBadThingRunsIntoPlayer(int, int, int);
1109 void TestIfFriendTouchesBadThing(int, int);
1110 void TestIfBadThingTouchesFriend(int, int);
1111 void TestIfBadThingTouchesOtherBadThing(int, int);
1112 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1114 void KillPlayer(struct PlayerInfo *);
1115 void BuryPlayer(struct PlayerInfo *);
1116 void RemovePlayer(struct PlayerInfo *);
1117 void ExitPlayer(struct PlayerInfo *);
1119 static int getInvisibleActiveFromInvisibleElement(int);
1120 static int getInvisibleFromInvisibleActiveElement(int);
1122 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Feld[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Feld[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Feld[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Feld[x][y];
1771 #if DEBUG_INIT_PLAYER
1774 printf("- player element %d activated", player->element_nr);
1775 printf(" (local player is %d and currently %s)\n",
1776 local_player->element_nr,
1777 local_player->active ? "active" : "not active");
1782 Feld[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Feld[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Feld[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1832 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Feld[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Feld[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Feld[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Feld[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Feld[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2151 Error(ERR_EXIT, "this should not happen -- please debug");
2154 // force update of game controls after initialization
2155 gpc->value = gpc->last_value = -1;
2156 gpc->frame = gpc->last_frame = -1;
2157 gpc->gfx_frame = -1;
2159 // determine panel value width for later calculation of alignment
2160 if (type == TYPE_INTEGER || type == TYPE_STRING)
2162 pos->width = pos->size * getFontWidth(pos->font);
2163 pos->height = getFontHeight(pos->font);
2165 else if (type == TYPE_ELEMENT)
2167 pos->width = pos->size;
2168 pos->height = pos->size;
2171 // fill structure for game panel draw order
2173 gpo->sort_priority = pos->sort_priority;
2176 // sort game panel controls according to sort_priority and control number
2177 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2178 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2181 static void UpdatePlayfieldElementCount(void)
2183 boolean use_element_count = FALSE;
2186 // first check if it is needed at all to calculate playfield element count
2187 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2188 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2189 use_element_count = TRUE;
2191 if (!use_element_count)
2194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2195 element_info[i].element_count = 0;
2197 SCAN_PLAYFIELD(x, y)
2199 element_info[Feld[x][y]].element_count++;
2202 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2203 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2204 if (IS_IN_GROUP(j, i))
2205 element_info[EL_GROUP_START + i].element_count +=
2206 element_info[j].element_count;
2209 static void UpdateGameControlValues(void)
2212 int time = (game.LevelSolved ?
2213 game.LevelSolved_CountingTime :
2214 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 game_sp.time_played :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 game_mm.energy_left :
2220 game.no_time_limit ? TimePlayed : TimeLeft);
2221 int score = (game.LevelSolved ?
2222 game.LevelSolved_CountingScore :
2223 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2224 game_em.lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->gems_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 game_sp.infotrons_still_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2235 game_mm.kettles_still_needed :
2236 game.gems_still_needed);
2237 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed > 0 ||
2243 game_mm.lights_still_needed > 0 :
2244 game.gems_still_needed > 0 ||
2245 game.sokoban_fields_still_needed > 0 ||
2246 game.sokoban_objects_still_needed > 0 ||
2247 game.lights_still_needed > 0);
2248 int health = (game.LevelSolved ?
2249 game.LevelSolved_CountingHealth :
2250 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2251 MM_HEALTH(game_mm.laser_overload_value) :
2254 UpdatePlayfieldElementCount();
2256 // update game panel control values
2258 // used instead of "level_nr" (for network games)
2259 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2260 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2262 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2263 for (i = 0; i < MAX_NUM_KEYS; i++)
2264 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2265 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2268 if (game.centered_player_nr == -1)
2270 for (i = 0; i < MAX_PLAYERS; i++)
2272 // only one player in Supaplex game engine
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2276 for (k = 0; k < MAX_NUM_KEYS; k++)
2278 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2280 if (game_em.ply[i]->keys & (1 << k))
2281 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2282 get_key_element_from_nr(k);
2284 else if (stored_player[i].key[k])
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2289 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2290 getPlayerInventorySize(i);
2292 if (stored_player[i].num_white_keys > 0)
2293 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2296 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2297 stored_player[i].num_white_keys;
2302 int player_nr = game.centered_player_nr;
2304 for (k = 0; k < MAX_NUM_KEYS; k++)
2306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2308 if (game_em.ply[player_nr]->keys & (1 << k))
2309 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2310 get_key_element_from_nr(k);
2312 else if (stored_player[player_nr].key[k])
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2317 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2318 getPlayerInventorySize(player_nr);
2320 if (stored_player[player_nr].num_white_keys > 0)
2321 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2324 stored_player[player_nr].num_white_keys;
2327 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2329 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, i);
2331 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, -i - 1);
2335 game_panel_controls[GAME_PANEL_SCORE].value = score;
2336 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2338 game_panel_controls[GAME_PANEL_TIME].value = time;
2340 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2341 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2342 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2344 if (level.time == 0)
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2349 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2350 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2352 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2354 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2355 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2357 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2358 local_player->shield_normal_time_left;
2359 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2360 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2362 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2363 local_player->shield_deadly_time_left;
2365 game_panel_controls[GAME_PANEL_EXIT].value =
2366 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2369 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2372 EL_EMC_MAGIC_BALL_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2375 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2377 game.light_time_left;
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2380 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2382 game.timegate_time_left;
2384 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2385 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2387 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2388 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2389 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2390 game.lenses_time_left;
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2393 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2395 game.magnify_time_left;
2397 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2398 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2399 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2400 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2401 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2402 EL_BALLOON_SWITCH_NONE);
2404 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2405 local_player->dynabomb_count;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2407 local_player->dynabomb_size;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2409 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2411 game_panel_controls[GAME_PANEL_PENGUINS].value =
2412 game.friends_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2415 game.sokoban_objects_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2417 game.sokoban_fields_still_needed;
2419 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2420 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2422 for (i = 0; i < NUM_BELTS; i++)
2424 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2425 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2426 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2427 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2428 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2432 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2434 game.magic_wall_time_left;
2436 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2437 local_player->gravity;
2439 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2440 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2442 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2443 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2444 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2445 game.panel.element[i].id : EL_UNDEFINED);
2447 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2448 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2449 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2450 element_info[game.panel.element_count[i].id].element_count : 0);
2452 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2453 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2454 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2455 element_info[game.panel.ce_score[i].id].collect_score : 0);
2457 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2458 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2459 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2460 element_info[game.panel.ce_score_element[i].id].collect_score :
2463 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2464 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2467 // update game panel control frames
2469 for (i = 0; game_panel_controls[i].nr != -1; i++)
2471 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2473 if (gpc->type == TYPE_ELEMENT)
2475 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2477 int last_anim_random_frame = gfx.anim_random_frame;
2478 int element = gpc->value;
2479 int graphic = el2panelimg(element);
2481 if (gpc->value != gpc->last_value)
2484 gpc->gfx_random = INIT_GFX_RANDOM();
2490 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2491 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2495 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2496 gfx.anim_random_frame = gpc->gfx_random;
2498 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2499 gpc->gfx_frame = element_info[element].collect_score;
2501 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2504 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2505 gfx.anim_random_frame = last_anim_random_frame;
2508 else if (gpc->type == TYPE_GRAPHIC)
2510 if (gpc->graphic != IMG_UNDEFINED)
2512 int last_anim_random_frame = gfx.anim_random_frame;
2513 int graphic = gpc->graphic;
2515 if (gpc->value != gpc->last_value)
2518 gpc->gfx_random = INIT_GFX_RANDOM();
2524 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2525 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2526 gpc->gfx_random = INIT_GFX_RANDOM();
2529 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2530 gfx.anim_random_frame = gpc->gfx_random;
2532 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2534 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2535 gfx.anim_random_frame = last_anim_random_frame;
2541 static void DisplayGameControlValues(void)
2543 boolean redraw_panel = FALSE;
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (PANEL_DEACTIVATED(gpc->pos))
2553 if (gpc->value == gpc->last_value &&
2554 gpc->frame == gpc->last_frame)
2557 redraw_panel = TRUE;
2563 // copy default game door content to main double buffer
2565 // !!! CHECK AGAIN !!!
2566 SetPanelBackground();
2567 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2568 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2570 // redraw game control buttons
2571 RedrawGameButtons();
2573 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2575 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2577 int nr = game_panel_order[i].nr;
2578 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2579 struct TextPosInfo *pos = gpc->pos;
2580 int type = gpc->type;
2581 int value = gpc->value;
2582 int frame = gpc->frame;
2583 int size = pos->size;
2584 int font = pos->font;
2585 boolean draw_masked = pos->draw_masked;
2586 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2588 if (PANEL_DEACTIVATED(pos))
2591 gpc->last_value = value;
2592 gpc->last_frame = frame;
2594 if (type == TYPE_INTEGER)
2596 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2597 nr == GAME_PANEL_TIME)
2599 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2601 if (use_dynamic_size) // use dynamic number of digits
2603 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2604 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2605 int size2 = size1 + 1;
2606 int font1 = pos->font;
2607 int font2 = pos->font_alt;
2609 size = (value < value_change ? size1 : size2);
2610 font = (value < value_change ? font1 : font2);
2614 // correct text size if "digits" is zero or less
2616 size = strlen(int2str(value, size));
2618 // dynamically correct text alignment
2619 pos->width = size * getFontWidth(font);
2621 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2622 int2str(value, size), font, mask_mode);
2624 else if (type == TYPE_ELEMENT)
2626 int element, graphic;
2630 int dst_x = PANEL_XPOS(pos);
2631 int dst_y = PANEL_YPOS(pos);
2633 if (value != EL_UNDEFINED && value != EL_EMPTY)
2636 graphic = el2panelimg(value);
2638 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2640 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2643 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2646 width = graphic_info[graphic].width * size / TILESIZE;
2647 height = graphic_info[graphic].height * size / TILESIZE;
2650 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2653 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2657 else if (type == TYPE_GRAPHIC)
2659 int graphic = gpc->graphic;
2660 int graphic_active = gpc->graphic_active;
2664 int dst_x = PANEL_XPOS(pos);
2665 int dst_y = PANEL_YPOS(pos);
2666 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2667 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2669 if (graphic != IMG_UNDEFINED && !skip)
2671 if (pos->style == STYLE_REVERSE)
2672 value = 100 - value;
2674 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2676 if (pos->direction & MV_HORIZONTAL)
2678 width = graphic_info[graphic_active].width * value / 100;
2679 height = graphic_info[graphic_active].height;
2681 if (pos->direction == MV_LEFT)
2683 src_x += graphic_info[graphic_active].width - width;
2684 dst_x += graphic_info[graphic_active].width - width;
2689 width = graphic_info[graphic_active].width;
2690 height = graphic_info[graphic_active].height * value / 100;
2692 if (pos->direction == MV_UP)
2694 src_y += graphic_info[graphic_active].height - height;
2695 dst_y += graphic_info[graphic_active].height - height;
2700 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2703 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2708 if (pos->direction & MV_HORIZONTAL)
2710 if (pos->direction == MV_RIGHT)
2717 dst_x = PANEL_XPOS(pos);
2720 width = graphic_info[graphic].width - width;
2724 if (pos->direction == MV_DOWN)
2731 dst_y = PANEL_YPOS(pos);
2734 height = graphic_info[graphic].height - height;
2738 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2741 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2745 else if (type == TYPE_STRING)
2747 boolean active = (value != 0);
2748 char *state_normal = "off";
2749 char *state_active = "on";
2750 char *state = (active ? state_active : state_normal);
2751 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2752 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2753 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2754 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2756 if (nr == GAME_PANEL_GRAVITY_STATE)
2758 int font1 = pos->font; // (used for normal state)
2759 int font2 = pos->font_alt; // (used for active state)
2761 font = (active ? font2 : font1);
2770 // don't truncate output if "chars" is zero or less
2773 // dynamically correct text alignment
2774 pos->width = size * getFontWidth(font);
2777 s_cut = getStringCopyN(s, size);
2779 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2780 s_cut, font, mask_mode);
2786 redraw_mask |= REDRAW_DOOR_1;
2789 SetGameStatus(GAME_MODE_PLAYING);
2792 void UpdateAndDisplayGameControlValues(void)
2794 if (tape.deactivate_display)
2797 UpdateGameControlValues();
2798 DisplayGameControlValues();
2802 static void UpdateGameDoorValues(void)
2804 UpdateGameControlValues();
2808 void DrawGameDoorValues(void)
2810 DisplayGameControlValues();
2814 // ============================================================================
2816 // ----------------------------------------------------------------------------
2817 // initialize game engine due to level / tape version number
2818 // ============================================================================
2820 static void InitGameEngine(void)
2822 int i, j, k, l, x, y;
2824 // set game engine from tape file when re-playing, else from level file
2825 game.engine_version = (tape.playing ? tape.engine_version :
2826 level.game_version);
2828 // set single or multi-player game mode (needed for re-playing tapes)
2829 game.team_mode = setup.team_mode;
2833 int num_players = 0;
2835 for (i = 0; i < MAX_PLAYERS; i++)
2836 if (tape.player_participates[i])
2839 // multi-player tapes contain input data for more than one player
2840 game.team_mode = (num_players > 1);
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 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4399 actual_player_x = correctLevelPosX_EM(actual_player_x);
4400 actual_player_y = correctLevelPosY_EM(actual_player_y);
4403 // needed to determine if sounds are played within the visible screen area
4404 scroll_x = actual_scroll_x;
4405 scroll_y = actual_scroll_y;
4407 // needed to get player position for "follow finger" playing input method
4408 local_player->jx = actual_player_x;
4409 local_player->jy = actual_player_y;
4412 void InitMovDir(int x, int y)
4414 int i, element = Feld[x][y];
4415 static int xy[4][2] =
4422 static int direction[3][4] =
4424 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4425 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4426 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4435 Feld[x][y] = EL_BUG;
4436 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4439 case EL_SPACESHIP_RIGHT:
4440 case EL_SPACESHIP_UP:
4441 case EL_SPACESHIP_LEFT:
4442 case EL_SPACESHIP_DOWN:
4443 Feld[x][y] = EL_SPACESHIP;
4444 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4447 case EL_BD_BUTTERFLY_RIGHT:
4448 case EL_BD_BUTTERFLY_UP:
4449 case EL_BD_BUTTERFLY_LEFT:
4450 case EL_BD_BUTTERFLY_DOWN:
4451 Feld[x][y] = EL_BD_BUTTERFLY;
4452 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4455 case EL_BD_FIREFLY_RIGHT:
4456 case EL_BD_FIREFLY_UP:
4457 case EL_BD_FIREFLY_LEFT:
4458 case EL_BD_FIREFLY_DOWN:
4459 Feld[x][y] = EL_BD_FIREFLY;
4460 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4463 case EL_PACMAN_RIGHT:
4465 case EL_PACMAN_LEFT:
4466 case EL_PACMAN_DOWN:
4467 Feld[x][y] = EL_PACMAN;
4468 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4471 case EL_YAMYAM_LEFT:
4472 case EL_YAMYAM_RIGHT:
4474 case EL_YAMYAM_DOWN:
4475 Feld[x][y] = EL_YAMYAM;
4476 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4479 case EL_SP_SNIKSNAK:
4480 MovDir[x][y] = MV_UP;
4483 case EL_SP_ELECTRON:
4484 MovDir[x][y] = MV_LEFT;
4491 Feld[x][y] = EL_MOLE;
4492 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4496 if (IS_CUSTOM_ELEMENT(element))
4498 struct ElementInfo *ei = &element_info[element];
4499 int move_direction_initial = ei->move_direction_initial;
4500 int move_pattern = ei->move_pattern;
4502 if (move_direction_initial == MV_START_PREVIOUS)
4504 if (MovDir[x][y] != MV_NONE)
4507 move_direction_initial = MV_START_AUTOMATIC;
4510 if (move_direction_initial == MV_START_RANDOM)
4511 MovDir[x][y] = 1 << RND(4);
4512 else if (move_direction_initial & MV_ANY_DIRECTION)
4513 MovDir[x][y] = move_direction_initial;
4514 else if (move_pattern == MV_ALL_DIRECTIONS ||
4515 move_pattern == MV_TURNING_LEFT ||
4516 move_pattern == MV_TURNING_RIGHT ||
4517 move_pattern == MV_TURNING_LEFT_RIGHT ||
4518 move_pattern == MV_TURNING_RIGHT_LEFT ||
4519 move_pattern == MV_TURNING_RANDOM)
4520 MovDir[x][y] = 1 << RND(4);
4521 else if (move_pattern == MV_HORIZONTAL)
4522 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4523 else if (move_pattern == MV_VERTICAL)
4524 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4525 else if (move_pattern & MV_ANY_DIRECTION)
4526 MovDir[x][y] = element_info[element].move_pattern;
4527 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4528 move_pattern == MV_ALONG_RIGHT_SIDE)
4530 // use random direction as default start direction
4531 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4532 MovDir[x][y] = 1 << RND(4);
4534 for (i = 0; i < NUM_DIRECTIONS; i++)
4536 int x1 = x + xy[i][0];
4537 int y1 = y + xy[i][1];
4539 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4541 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4542 MovDir[x][y] = direction[0][i];
4544 MovDir[x][y] = direction[1][i];
4553 MovDir[x][y] = 1 << RND(4);
4555 if (element != EL_BUG &&
4556 element != EL_SPACESHIP &&
4557 element != EL_BD_BUTTERFLY &&
4558 element != EL_BD_FIREFLY)
4561 for (i = 0; i < NUM_DIRECTIONS; i++)
4563 int x1 = x + xy[i][0];
4564 int y1 = y + xy[i][1];
4566 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4568 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4570 MovDir[x][y] = direction[0][i];
4573 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4574 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4576 MovDir[x][y] = direction[1][i];
4585 GfxDir[x][y] = MovDir[x][y];
4588 void InitAmoebaNr(int x, int y)
4591 int group_nr = AmoebeNachbarNr(x, y);
4595 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4597 if (AmoebaCnt[i] == 0)
4605 AmoebaNr[x][y] = group_nr;
4606 AmoebaCnt[group_nr]++;
4607 AmoebaCnt2[group_nr]++;
4610 static void LevelSolved(void)
4612 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4613 game.players_still_needed > 0)
4616 game.LevelSolved = TRUE;
4617 game.GameOver = TRUE;
4619 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4620 game_em.lev->score :
4621 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4624 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4625 MM_HEALTH(game_mm.laser_overload_value) :
4628 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4629 game.LevelSolved_CountingScore = game.score_final;
4630 game.LevelSolved_CountingHealth = game.health_final;
4635 static int time_count_steps;
4636 static int time, time_final;
4637 static int score, score_final;
4638 static int health, health_final;
4639 static int game_over_delay_1 = 0;
4640 static int game_over_delay_2 = 0;
4641 static int game_over_delay_3 = 0;
4642 int game_over_delay_value_1 = 50;
4643 int game_over_delay_value_2 = 25;
4644 int game_over_delay_value_3 = 50;
4646 if (!game.LevelSolved_GameWon)
4650 // do not start end game actions before the player stops moving (to exit)
4651 if (local_player->active && local_player->MovPos)
4654 game.LevelSolved_GameWon = TRUE;
4655 game.LevelSolved_SaveTape = tape.recording;
4656 game.LevelSolved_SaveScore = !tape.playing;
4660 LevelStats_incSolved(level_nr);
4662 SaveLevelSetup_SeriesInfo();
4665 if (tape.auto_play) // tape might already be stopped here
4666 tape.auto_play_level_solved = TRUE;
4670 game_over_delay_1 = 0;
4671 game_over_delay_2 = 0;
4672 game_over_delay_3 = game_over_delay_value_3;
4674 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4675 score = score_final = game.score_final;
4676 health = health_final = game.health_final;
4678 if (level.score[SC_TIME_BONUS] > 0)
4683 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4685 else if (game.no_time_limit && TimePlayed < 999)
4688 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4691 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4693 game_over_delay_1 = game_over_delay_value_1;
4695 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4698 score_final += health * level.score[SC_TIME_BONUS];
4700 game_over_delay_2 = game_over_delay_value_2;
4703 game.score_final = score_final;
4704 game.health_final = health_final;
4707 if (level_editor_test_game)
4710 score = score_final;
4712 game.LevelSolved_CountingTime = time;
4713 game.LevelSolved_CountingScore = score;
4715 game_panel_controls[GAME_PANEL_TIME].value = time;
4716 game_panel_controls[GAME_PANEL_SCORE].value = score;
4718 DisplayGameControlValues();
4721 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4723 // check if last player has left the level
4724 if (game.exit_x >= 0 &&
4727 int x = game.exit_x;
4728 int y = game.exit_y;
4729 int element = Feld[x][y];
4731 // close exit door after last player
4732 if ((game.all_players_gone &&
4733 (element == EL_EXIT_OPEN ||
4734 element == EL_SP_EXIT_OPEN ||
4735 element == EL_STEEL_EXIT_OPEN)) ||
4736 element == EL_EM_EXIT_OPEN ||
4737 element == EL_EM_STEEL_EXIT_OPEN)
4741 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4742 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4743 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4744 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4745 EL_EM_STEEL_EXIT_CLOSING);
4747 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4750 // player disappears
4751 DrawLevelField(x, y);
4754 for (i = 0; i < MAX_PLAYERS; i++)
4756 struct PlayerInfo *player = &stored_player[i];
4758 if (player->present)
4760 RemovePlayer(player);
4762 // player disappears
4763 DrawLevelField(player->jx, player->jy);
4768 PlaySound(SND_GAME_WINNING);
4771 if (game_over_delay_1 > 0)
4773 game_over_delay_1--;
4778 if (time != time_final)
4780 int time_to_go = ABS(time_final - time);
4781 int time_count_dir = (time < time_final ? +1 : -1);
4783 if (time_to_go < time_count_steps)
4784 time_count_steps = 1;
4786 time += time_count_steps * time_count_dir;
4787 score += time_count_steps * level.score[SC_TIME_BONUS];
4789 game.LevelSolved_CountingTime = time;
4790 game.LevelSolved_CountingScore = score;
4792 game_panel_controls[GAME_PANEL_TIME].value = time;
4793 game_panel_controls[GAME_PANEL_SCORE].value = score;
4795 DisplayGameControlValues();
4797 if (time == time_final)
4798 StopSound(SND_GAME_LEVELTIME_BONUS);
4799 else if (setup.sound_loops)
4800 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4802 PlaySound(SND_GAME_LEVELTIME_BONUS);
4807 if (game_over_delay_2 > 0)
4809 game_over_delay_2--;
4814 if (health != health_final)
4816 int health_count_dir = (health < health_final ? +1 : -1);
4818 health += health_count_dir;
4819 score += level.score[SC_TIME_BONUS];
4821 game.LevelSolved_CountingHealth = health;
4822 game.LevelSolved_CountingScore = score;
4824 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4825 game_panel_controls[GAME_PANEL_SCORE].value = score;
4827 DisplayGameControlValues();
4829 if (health == health_final)
4830 StopSound(SND_GAME_LEVELTIME_BONUS);
4831 else if (setup.sound_loops)
4832 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4834 PlaySound(SND_GAME_LEVELTIME_BONUS);
4839 game.panel.active = FALSE;
4841 if (game_over_delay_3 > 0)
4843 game_over_delay_3--;
4853 // used instead of "level_nr" (needed for network games)
4854 int last_level_nr = levelset.level_nr;
4857 game.LevelSolved_GameEnd = TRUE;
4859 if (game.LevelSolved_SaveTape)
4861 // make sure that request dialog to save tape does not open door again
4862 if (!global.use_envelope_request)
4863 CloseDoor(DOOR_CLOSE_1);
4865 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4868 // if no tape is to be saved, close both doors simultaneously
4869 CloseDoor(DOOR_CLOSE_ALL);
4871 if (level_editor_test_game)
4873 SetGameStatus(GAME_MODE_MAIN);
4880 if (!game.LevelSolved_SaveScore)
4882 SetGameStatus(GAME_MODE_MAIN);
4889 if (level_nr == leveldir_current->handicap_level)
4891 leveldir_current->handicap_level++;
4893 SaveLevelSetup_SeriesInfo();
4896 if (setup.increment_levels &&
4897 level_nr < leveldir_current->last_level &&
4900 level_nr++; // advance to next level
4901 TapeErase(); // start with empty tape
4903 if (setup.auto_play_next_level)
4905 LoadLevel(level_nr);
4907 SaveLevelSetup_SeriesInfo();
4911 hi_pos = NewHiScore(last_level_nr);
4913 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4915 SetGameStatus(GAME_MODE_SCORES);
4917 DrawHallOfFame(last_level_nr, hi_pos);
4919 else if (setup.auto_play_next_level && setup.increment_levels &&
4920 last_level_nr < leveldir_current->last_level &&
4923 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4927 SetGameStatus(GAME_MODE_MAIN);
4933 int NewHiScore(int level_nr)
4937 boolean one_score_entry_per_name = !program.many_scores_per_name;
4939 LoadScore(level_nr);
4941 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4942 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4945 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4947 if (game.score_final > highscore[k].Score)
4949 // player has made it to the hall of fame
4951 if (k < MAX_SCORE_ENTRIES - 1)
4953 int m = MAX_SCORE_ENTRIES - 1;
4955 if (one_score_entry_per_name)
4957 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4958 if (strEqual(setup.player_name, highscore[l].Name))
4961 if (m == k) // player's new highscore overwrites his old one
4965 for (l = m; l > k; l--)
4967 strcpy(highscore[l].Name, highscore[l - 1].Name);
4968 highscore[l].Score = highscore[l - 1].Score;
4974 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4975 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4976 highscore[k].Score = game.score_final;
4981 else if (one_score_entry_per_name &&
4982 !strncmp(setup.player_name, highscore[k].Name,
4983 MAX_PLAYER_NAME_LEN))
4984 break; // player already there with a higher score
4988 SaveScore(level_nr);
4993 static int getElementMoveStepsizeExt(int x, int y, int direction)
4995 int element = Feld[x][y];
4996 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4997 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4998 int horiz_move = (dx != 0);
4999 int sign = (horiz_move ? dx : dy);
5000 int step = sign * element_info[element].move_stepsize;
5002 // special values for move stepsize for spring and things on conveyor belt
5005 if (CAN_FALL(element) &&
5006 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
5007 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5008 else if (element == EL_SPRING)
5009 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5015 static int getElementMoveStepsize(int x, int y)
5017 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5020 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5022 if (player->GfxAction != action || player->GfxDir != dir)
5024 player->GfxAction = action;
5025 player->GfxDir = dir;
5027 player->StepFrame = 0;
5031 static void ResetGfxFrame(int x, int y)
5033 // profiling showed that "autotest" spends 10~20% of its time in this function
5034 if (DrawingDeactivatedField())
5037 int element = Feld[x][y];
5038 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5040 if (graphic_info[graphic].anim_global_sync)
5041 GfxFrame[x][y] = FrameCounter;
5042 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5043 GfxFrame[x][y] = CustomValue[x][y];
5044 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5045 GfxFrame[x][y] = element_info[element].collect_score;
5046 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5047 GfxFrame[x][y] = ChangeDelay[x][y];
5050 static void ResetGfxAnimation(int x, int y)
5052 GfxAction[x][y] = ACTION_DEFAULT;
5053 GfxDir[x][y] = MovDir[x][y];
5056 ResetGfxFrame(x, y);
5059 static void ResetRandomAnimationValue(int x, int y)
5061 GfxRandom[x][y] = INIT_GFX_RANDOM();
5064 static void InitMovingField(int x, int y, int direction)
5066 int element = Feld[x][y];
5067 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5068 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5071 boolean is_moving_before, is_moving_after;
5073 // check if element was/is moving or being moved before/after mode change
5074 is_moving_before = (WasJustMoving[x][y] != 0);
5075 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5077 // reset animation only for moving elements which change direction of moving
5078 // or which just started or stopped moving
5079 // (else CEs with property "can move" / "not moving" are reset each frame)
5080 if (is_moving_before != is_moving_after ||
5081 direction != MovDir[x][y])
5082 ResetGfxAnimation(x, y);
5084 MovDir[x][y] = direction;
5085 GfxDir[x][y] = direction;
5087 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5088 direction == MV_DOWN && CAN_FALL(element) ?
5089 ACTION_FALLING : ACTION_MOVING);
5091 // this is needed for CEs with property "can move" / "not moving"
5093 if (is_moving_after)
5095 if (Feld[newx][newy] == EL_EMPTY)
5096 Feld[newx][newy] = EL_BLOCKED;
5098 MovDir[newx][newy] = MovDir[x][y];
5100 CustomValue[newx][newy] = CustomValue[x][y];
5102 GfxFrame[newx][newy] = GfxFrame[x][y];
5103 GfxRandom[newx][newy] = GfxRandom[x][y];
5104 GfxAction[newx][newy] = GfxAction[x][y];
5105 GfxDir[newx][newy] = GfxDir[x][y];
5109 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5111 int direction = MovDir[x][y];
5112 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5113 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5119 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5121 int oldx = x, oldy = y;
5122 int direction = MovDir[x][y];
5124 if (direction == MV_LEFT)
5126 else if (direction == MV_RIGHT)
5128 else if (direction == MV_UP)
5130 else if (direction == MV_DOWN)
5133 *comes_from_x = oldx;
5134 *comes_from_y = oldy;
5137 static int MovingOrBlocked2Element(int x, int y)
5139 int element = Feld[x][y];
5141 if (element == EL_BLOCKED)
5145 Blocked2Moving(x, y, &oldx, &oldy);
5146 return Feld[oldx][oldy];
5152 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5154 // like MovingOrBlocked2Element(), but if element is moving
5155 // and (x,y) is the field the moving element is just leaving,
5156 // return EL_BLOCKED instead of the element value
5157 int element = Feld[x][y];
5159 if (IS_MOVING(x, y))
5161 if (element == EL_BLOCKED)
5165 Blocked2Moving(x, y, &oldx, &oldy);
5166 return Feld[oldx][oldy];
5175 static void RemoveField(int x, int y)
5177 Feld[x][y] = EL_EMPTY;
5183 CustomValue[x][y] = 0;
5186 ChangeDelay[x][y] = 0;
5187 ChangePage[x][y] = -1;
5188 Pushed[x][y] = FALSE;
5190 GfxElement[x][y] = EL_UNDEFINED;
5191 GfxAction[x][y] = ACTION_DEFAULT;
5192 GfxDir[x][y] = MV_NONE;
5195 static void RemoveMovingField(int x, int y)
5197 int oldx = x, oldy = y, newx = x, newy = y;
5198 int element = Feld[x][y];
5199 int next_element = EL_UNDEFINED;
5201 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5204 if (IS_MOVING(x, y))
5206 Moving2Blocked(x, y, &newx, &newy);
5208 if (Feld[newx][newy] != EL_BLOCKED)
5210 // element is moving, but target field is not free (blocked), but
5211 // already occupied by something different (example: acid pool);
5212 // in this case, only remove the moving field, but not the target
5214 RemoveField(oldx, oldy);
5216 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5218 TEST_DrawLevelField(oldx, oldy);
5223 else if (element == EL_BLOCKED)
5225 Blocked2Moving(x, y, &oldx, &oldy);
5226 if (!IS_MOVING(oldx, oldy))
5230 if (element == EL_BLOCKED &&
5231 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5232 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5233 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5234 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5235 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5236 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5237 next_element = get_next_element(Feld[oldx][oldy]);
5239 RemoveField(oldx, oldy);
5240 RemoveField(newx, newy);
5242 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5244 if (next_element != EL_UNDEFINED)
5245 Feld[oldx][oldy] = next_element;
5247 TEST_DrawLevelField(oldx, oldy);
5248 TEST_DrawLevelField(newx, newy);
5251 void DrawDynamite(int x, int y)
5253 int sx = SCREENX(x), sy = SCREENY(y);
5254 int graphic = el2img(Feld[x][y]);
5257 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5260 if (IS_WALKABLE_INSIDE(Back[x][y]))
5264 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5265 else if (Store[x][y])
5266 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5268 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5270 if (Back[x][y] || Store[x][y])
5271 DrawGraphicThruMask(sx, sy, graphic, frame);
5273 DrawGraphic(sx, sy, graphic, frame);
5276 static void CheckDynamite(int x, int y)
5278 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5282 if (MovDelay[x][y] != 0)
5285 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5291 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5296 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5298 boolean num_checked_players = 0;
5301 for (i = 0; i < MAX_PLAYERS; i++)
5303 if (stored_player[i].active)
5305 int sx = stored_player[i].jx;
5306 int sy = stored_player[i].jy;
5308 if (num_checked_players == 0)
5315 *sx1 = MIN(*sx1, sx);
5316 *sy1 = MIN(*sy1, sy);
5317 *sx2 = MAX(*sx2, sx);
5318 *sy2 = MAX(*sy2, sy);
5321 num_checked_players++;
5326 static boolean checkIfAllPlayersFitToScreen_RND(void)
5328 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5330 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5332 return (sx2 - sx1 < SCR_FIELDX &&
5333 sy2 - sy1 < SCR_FIELDY);
5336 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5338 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5340 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5342 *sx = (sx1 + sx2) / 2;
5343 *sy = (sy1 + sy2) / 2;
5346 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5347 boolean center_screen, boolean quick_relocation)
5349 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5350 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5351 boolean no_delay = (tape.warp_forward);
5352 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5353 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5354 int new_scroll_x, new_scroll_y;
5356 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5358 // case 1: quick relocation inside visible screen (without scrolling)
5365 if (!level.shifted_relocation || center_screen)
5367 // relocation _with_ centering of screen
5369 new_scroll_x = SCROLL_POSITION_X(x);
5370 new_scroll_y = SCROLL_POSITION_Y(y);
5374 // relocation _without_ centering of screen
5376 int center_scroll_x = SCROLL_POSITION_X(old_x);
5377 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5378 int offset_x = x + (scroll_x - center_scroll_x);
5379 int offset_y = y + (scroll_y - center_scroll_y);
5381 // for new screen position, apply previous offset to center position
5382 new_scroll_x = SCROLL_POSITION_X(offset_x);
5383 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5386 if (quick_relocation)
5388 // case 2: quick relocation (redraw without visible scrolling)
5390 scroll_x = new_scroll_x;
5391 scroll_y = new_scroll_y;
5398 // case 3: visible relocation (with scrolling to new position)
5400 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5402 SetVideoFrameDelay(wait_delay_value);
5404 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5406 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5407 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5409 if (dx == 0 && dy == 0) // no scrolling needed at all
5415 // set values for horizontal/vertical screen scrolling (half tile size)
5416 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5417 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5418 int pos_x = dx * TILEX / 2;
5419 int pos_y = dy * TILEY / 2;
5420 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5421 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5423 ScrollLevel(dx, dy);
5426 // scroll in two steps of half tile size to make things smoother
5427 BlitScreenToBitmapExt_RND(window, fx, fy);
5429 // scroll second step to align at full tile size
5430 BlitScreenToBitmap(window);
5436 SetVideoFrameDelay(frame_delay_value_old);
5439 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5441 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5442 int player_nr = GET_PLAYER_NR(el_player);
5443 struct PlayerInfo *player = &stored_player[player_nr];
5444 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5445 boolean no_delay = (tape.warp_forward);
5446 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5447 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5448 int old_jx = player->jx;
5449 int old_jy = player->jy;
5450 int old_element = Feld[old_jx][old_jy];
5451 int element = Feld[jx][jy];
5452 boolean player_relocated = (old_jx != jx || old_jy != jy);
5454 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5455 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5456 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5457 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5458 int leave_side_horiz = move_dir_horiz;
5459 int leave_side_vert = move_dir_vert;
5460 int enter_side = enter_side_horiz | enter_side_vert;
5461 int leave_side = leave_side_horiz | leave_side_vert;
5463 if (player->buried) // do not reanimate dead player
5466 if (!player_relocated) // no need to relocate the player
5469 if (IS_PLAYER(jx, jy)) // player already placed at new position
5471 RemoveField(jx, jy); // temporarily remove newly placed player
5472 DrawLevelField(jx, jy);
5475 if (player->present)
5477 while (player->MovPos)
5479 ScrollPlayer(player, SCROLL_GO_ON);
5480 ScrollScreen(NULL, SCROLL_GO_ON);
5482 AdvanceFrameAndPlayerCounters(player->index_nr);
5486 BackToFront_WithFrameDelay(wait_delay_value);
5489 DrawPlayer(player); // needed here only to cleanup last field
5490 DrawLevelField(player->jx, player->jy); // remove player graphic
5492 player->is_moving = FALSE;
5495 if (IS_CUSTOM_ELEMENT(old_element))
5496 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5498 player->index_bit, leave_side);
5500 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5502 player->index_bit, leave_side);
5504 Feld[jx][jy] = el_player;
5505 InitPlayerField(jx, jy, el_player, TRUE);
5507 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5508 possible that the relocation target field did not contain a player element,
5509 but a walkable element, to which the new player was relocated -- in this
5510 case, restore that (already initialized!) element on the player field */
5511 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5513 Feld[jx][jy] = element; // restore previously existing element
5516 // only visually relocate centered player
5517 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5518 FALSE, level.instant_relocation);
5520 TestIfPlayerTouchesBadThing(jx, jy);
5521 TestIfPlayerTouchesCustomElement(jx, jy);
5523 if (IS_CUSTOM_ELEMENT(element))
5524 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5525 player->index_bit, enter_side);
5527 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5528 player->index_bit, enter_side);
5530 if (player->is_switching)
5532 /* ensure that relocation while still switching an element does not cause
5533 a new element to be treated as also switched directly after relocation
5534 (this is important for teleporter switches that teleport the player to
5535 a place where another teleporter switch is in the same direction, which
5536 would then incorrectly be treated as immediately switched before the
5537 direction key that caused the switch was released) */
5539 player->switch_x += jx - old_jx;
5540 player->switch_y += jy - old_jy;
5544 static void Explode(int ex, int ey, int phase, int mode)
5550 // !!! eliminate this variable !!!
5551 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5553 if (game.explosions_delayed)
5555 ExplodeField[ex][ey] = mode;
5559 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5561 int center_element = Feld[ex][ey];
5562 int artwork_element, explosion_element; // set these values later
5564 // remove things displayed in background while burning dynamite
5565 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5568 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5570 // put moving element to center field (and let it explode there)
5571 center_element = MovingOrBlocked2Element(ex, ey);
5572 RemoveMovingField(ex, ey);
5573 Feld[ex][ey] = center_element;
5576 // now "center_element" is finally determined -- set related values now
5577 artwork_element = center_element; // for custom player artwork
5578 explosion_element = center_element; // for custom player artwork
5580 if (IS_PLAYER(ex, ey))
5582 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5584 artwork_element = stored_player[player_nr].artwork_element;
5586 if (level.use_explosion_element[player_nr])
5588 explosion_element = level.explosion_element[player_nr];
5589 artwork_element = explosion_element;
5593 if (mode == EX_TYPE_NORMAL ||
5594 mode == EX_TYPE_CENTER ||
5595 mode == EX_TYPE_CROSS)
5596 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5598 last_phase = element_info[explosion_element].explosion_delay + 1;
5600 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5602 int xx = x - ex + 1;
5603 int yy = y - ey + 1;
5606 if (!IN_LEV_FIELD(x, y) ||
5607 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5608 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5611 element = Feld[x][y];
5613 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5615 element = MovingOrBlocked2Element(x, y);
5617 if (!IS_EXPLOSION_PROOF(element))
5618 RemoveMovingField(x, y);
5621 // indestructible elements can only explode in center (but not flames)
5622 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5623 mode == EX_TYPE_BORDER)) ||
5624 element == EL_FLAMES)
5627 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5628 behaviour, for example when touching a yamyam that explodes to rocks
5629 with active deadly shield, a rock is created under the player !!! */
5630 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5632 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5633 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5634 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5636 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5639 if (IS_ACTIVE_BOMB(element))
5641 // re-activate things under the bomb like gate or penguin
5642 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5649 // save walkable background elements while explosion on same tile
5650 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5651 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5652 Back[x][y] = element;
5654 // ignite explodable elements reached by other explosion
5655 if (element == EL_EXPLOSION)
5656 element = Store2[x][y];
5658 if (AmoebaNr[x][y] &&
5659 (element == EL_AMOEBA_FULL ||
5660 element == EL_BD_AMOEBA ||
5661 element == EL_AMOEBA_GROWING))
5663 AmoebaCnt[AmoebaNr[x][y]]--;
5664 AmoebaCnt2[AmoebaNr[x][y]]--;
5669 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5671 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5673 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5675 if (PLAYERINFO(ex, ey)->use_murphy)
5676 Store[x][y] = EL_EMPTY;
5679 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5680 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5681 else if (ELEM_IS_PLAYER(center_element))
5682 Store[x][y] = EL_EMPTY;
5683 else if (center_element == EL_YAMYAM)
5684 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5685 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5686 Store[x][y] = element_info[center_element].content.e[xx][yy];
5688 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5689 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5690 // otherwise) -- FIX THIS !!!
5691 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5692 Store[x][y] = element_info[element].content.e[1][1];
5694 else if (!CAN_EXPLODE(element))
5695 Store[x][y] = element_info[element].content.e[1][1];
5698 Store[x][y] = EL_EMPTY;
5700 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5701 center_element == EL_AMOEBA_TO_DIAMOND)
5702 Store2[x][y] = element;
5704 Feld[x][y] = EL_EXPLOSION;
5705 GfxElement[x][y] = artwork_element;
5707 ExplodePhase[x][y] = 1;
5708 ExplodeDelay[x][y] = last_phase;
5713 if (center_element == EL_YAMYAM)
5714 game.yamyam_content_nr =
5715 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5727 GfxFrame[x][y] = 0; // restart explosion animation
5729 last_phase = ExplodeDelay[x][y];
5731 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5733 // this can happen if the player leaves an explosion just in time
5734 if (GfxElement[x][y] == EL_UNDEFINED)
5735 GfxElement[x][y] = EL_EMPTY;
5737 border_element = Store2[x][y];
5738 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5739 border_element = StorePlayer[x][y];
5741 if (phase == element_info[border_element].ignition_delay ||
5742 phase == last_phase)
5744 boolean border_explosion = FALSE;
5746 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5747 !PLAYER_EXPLOSION_PROTECTED(x, y))
5749 KillPlayerUnlessExplosionProtected(x, y);
5750 border_explosion = TRUE;
5752 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5754 Feld[x][y] = Store2[x][y];
5757 border_explosion = TRUE;
5759 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5761 AmoebeUmwandeln(x, y);
5763 border_explosion = TRUE;
5766 // if an element just explodes due to another explosion (chain-reaction),
5767 // do not immediately end the new explosion when it was the last frame of
5768 // the explosion (as it would be done in the following "if"-statement!)
5769 if (border_explosion && phase == last_phase)
5773 if (phase == last_phase)
5777 element = Feld[x][y] = Store[x][y];
5778 Store[x][y] = Store2[x][y] = 0;
5779 GfxElement[x][y] = EL_UNDEFINED;
5781 // player can escape from explosions and might therefore be still alive
5782 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5783 element <= EL_PLAYER_IS_EXPLODING_4)
5785 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5786 int explosion_element = EL_PLAYER_1 + player_nr;
5787 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5788 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5790 if (level.use_explosion_element[player_nr])
5791 explosion_element = level.explosion_element[player_nr];
5793 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5794 element_info[explosion_element].content.e[xx][yy]);
5797 // restore probably existing indestructible background element
5798 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5799 element = Feld[x][y] = Back[x][y];
5802 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5803 GfxDir[x][y] = MV_NONE;
5804 ChangeDelay[x][y] = 0;
5805 ChangePage[x][y] = -1;
5807 CustomValue[x][y] = 0;
5809 InitField_WithBug2(x, y, FALSE);
5811 TEST_DrawLevelField(x, y);
5813 TestIfElementTouchesCustomElement(x, y);
5815 if (GFX_CRUMBLED(element))
5816 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5818 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5819 StorePlayer[x][y] = 0;
5821 if (ELEM_IS_PLAYER(element))
5822 RelocatePlayer(x, y, element);
5824 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5826 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5827 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5830 TEST_DrawLevelFieldCrumbled(x, y);
5832 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5834 DrawLevelElement(x, y, Back[x][y]);
5835 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5837 else if (IS_WALKABLE_UNDER(Back[x][y]))
5839 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5840 DrawLevelElementThruMask(x, y, Back[x][y]);
5842 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5843 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5847 static void DynaExplode(int ex, int ey)
5850 int dynabomb_element = Feld[ex][ey];
5851 int dynabomb_size = 1;
5852 boolean dynabomb_xl = FALSE;
5853 struct PlayerInfo *player;
5854 static int xy[4][2] =
5862 if (IS_ACTIVE_BOMB(dynabomb_element))
5864 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5865 dynabomb_size = player->dynabomb_size;
5866 dynabomb_xl = player->dynabomb_xl;
5867 player->dynabombs_left++;
5870 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5872 for (i = 0; i < NUM_DIRECTIONS; i++)
5874 for (j = 1; j <= dynabomb_size; j++)
5876 int x = ex + j * xy[i][0];
5877 int y = ey + j * xy[i][1];
5880 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5883 element = Feld[x][y];
5885 // do not restart explosions of fields with active bombs
5886 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5889 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5891 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5892 !IS_DIGGABLE(element) && !dynabomb_xl)
5898 void Bang(int x, int y)
5900 int element = MovingOrBlocked2Element(x, y);
5901 int explosion_type = EX_TYPE_NORMAL;
5903 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5905 struct PlayerInfo *player = PLAYERINFO(x, y);
5907 element = Feld[x][y] = player->initial_element;
5909 if (level.use_explosion_element[player->index_nr])
5911 int explosion_element = level.explosion_element[player->index_nr];
5913 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5914 explosion_type = EX_TYPE_CROSS;
5915 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5916 explosion_type = EX_TYPE_CENTER;
5924 case EL_BD_BUTTERFLY:
5927 case EL_DARK_YAMYAM:
5931 RaiseScoreElement(element);
5934 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5935 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5936 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5937 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5938 case EL_DYNABOMB_INCREASE_NUMBER:
5939 case EL_DYNABOMB_INCREASE_SIZE:
5940 case EL_DYNABOMB_INCREASE_POWER:
5941 explosion_type = EX_TYPE_DYNA;
5944 case EL_DC_LANDMINE:
5945 explosion_type = EX_TYPE_CENTER;
5950 case EL_LAMP_ACTIVE:
5951 case EL_AMOEBA_TO_DIAMOND:
5952 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5953 explosion_type = EX_TYPE_CENTER;
5957 if (element_info[element].explosion_type == EXPLODES_CROSS)
5958 explosion_type = EX_TYPE_CROSS;
5959 else if (element_info[element].explosion_type == EXPLODES_1X1)
5960 explosion_type = EX_TYPE_CENTER;
5964 if (explosion_type == EX_TYPE_DYNA)
5967 Explode(x, y, EX_PHASE_START, explosion_type);
5969 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5972 static void SplashAcid(int x, int y)
5974 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5975 (!IN_LEV_FIELD(x - 1, y - 2) ||
5976 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5977 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5979 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5980 (!IN_LEV_FIELD(x + 1, y - 2) ||
5981 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5982 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5984 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5987 static void InitBeltMovement(void)
5989 static int belt_base_element[4] =
5991 EL_CONVEYOR_BELT_1_LEFT,
5992 EL_CONVEYOR_BELT_2_LEFT,
5993 EL_CONVEYOR_BELT_3_LEFT,
5994 EL_CONVEYOR_BELT_4_LEFT
5996 static int belt_base_active_element[4] =
5998 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5999 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6000 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6001 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6006 // set frame order for belt animation graphic according to belt direction
6007 for (i = 0; i < NUM_BELTS; i++)
6011 for (j = 0; j < NUM_BELT_PARTS; j++)
6013 int element = belt_base_active_element[belt_nr] + j;
6014 int graphic_1 = el2img(element);
6015 int graphic_2 = el2panelimg(element);
6017 if (game.belt_dir[i] == MV_LEFT)
6019 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6020 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6024 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6025 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6030 SCAN_PLAYFIELD(x, y)
6032 int element = Feld[x][y];
6034 for (i = 0; i < NUM_BELTS; i++)
6036 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6038 int e_belt_nr = getBeltNrFromBeltElement(element);
6041 if (e_belt_nr == belt_nr)
6043 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
6045 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
6052 static void ToggleBeltSwitch(int x, int y)
6054 static int belt_base_element[4] =
6056 EL_CONVEYOR_BELT_1_LEFT,
6057 EL_CONVEYOR_BELT_2_LEFT,
6058 EL_CONVEYOR_BELT_3_LEFT,
6059 EL_CONVEYOR_BELT_4_LEFT
6061 static int belt_base_active_element[4] =
6063 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6064 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6065 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6066 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6068 static int belt_base_switch_element[4] =
6070 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6071 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6072 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6073 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6075 static int belt_move_dir[4] =
6083 int element = Feld[x][y];
6084 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6085 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6086 int belt_dir = belt_move_dir[belt_dir_nr];
6089 if (!IS_BELT_SWITCH(element))
6092 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6093 game.belt_dir[belt_nr] = belt_dir;
6095 if (belt_dir_nr == 3)
6098 // set frame order for belt animation graphic according to belt direction
6099 for (i = 0; i < NUM_BELT_PARTS; i++)
6101 int element = belt_base_active_element[belt_nr] + i;
6102 int graphic_1 = el2img(element);
6103 int graphic_2 = el2panelimg(element);
6105 if (belt_dir == MV_LEFT)
6107 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6108 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6112 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6113 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6117 SCAN_PLAYFIELD(xx, yy)
6119 int element = Feld[xx][yy];
6121 if (IS_BELT_SWITCH(element))
6123 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6125 if (e_belt_nr == belt_nr)
6127 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6128 TEST_DrawLevelField(xx, yy);
6131 else if (IS_BELT(element) && belt_dir != MV_NONE)
6133 int e_belt_nr = getBeltNrFromBeltElement(element);
6135 if (e_belt_nr == belt_nr)
6137 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6139 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6140 TEST_DrawLevelField(xx, yy);
6143 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6145 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6147 if (e_belt_nr == belt_nr)
6149 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6151 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6152 TEST_DrawLevelField(xx, yy);
6158 static void ToggleSwitchgateSwitch(int x, int y)
6162 game.switchgate_pos = !game.switchgate_pos;
6164 SCAN_PLAYFIELD(xx, yy)
6166 int element = Feld[xx][yy];
6168 if (element == EL_SWITCHGATE_SWITCH_UP)
6170 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6171 TEST_DrawLevelField(xx, yy);
6173 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6175 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6176 TEST_DrawLevelField(xx, yy);
6178 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6180 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6181 TEST_DrawLevelField(xx, yy);
6183 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6185 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6186 TEST_DrawLevelField(xx, yy);
6188 else if (element == EL_SWITCHGATE_OPEN ||
6189 element == EL_SWITCHGATE_OPENING)
6191 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6193 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6195 else if (element == EL_SWITCHGATE_CLOSED ||
6196 element == EL_SWITCHGATE_CLOSING)
6198 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6200 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6205 static int getInvisibleActiveFromInvisibleElement(int element)
6207 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6208 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6209 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6213 static int getInvisibleFromInvisibleActiveElement(int element)
6215 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6216 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6217 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6221 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6225 SCAN_PLAYFIELD(x, y)
6227 int element = Feld[x][y];
6229 if (element == EL_LIGHT_SWITCH &&
6230 game.light_time_left > 0)
6232 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6233 TEST_DrawLevelField(x, y);
6235 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6236 game.light_time_left == 0)
6238 Feld[x][y] = EL_LIGHT_SWITCH;
6239 TEST_DrawLevelField(x, y);
6241 else if (element == EL_EMC_DRIPPER &&
6242 game.light_time_left > 0)
6244 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6245 TEST_DrawLevelField(x, y);
6247 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6248 game.light_time_left == 0)
6250 Feld[x][y] = EL_EMC_DRIPPER;
6251 TEST_DrawLevelField(x, y);
6253 else if (element == EL_INVISIBLE_STEELWALL ||
6254 element == EL_INVISIBLE_WALL ||
6255 element == EL_INVISIBLE_SAND)
6257 if (game.light_time_left > 0)
6258 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6260 TEST_DrawLevelField(x, y);
6262 // uncrumble neighbour fields, if needed
6263 if (element == EL_INVISIBLE_SAND)
6264 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6266 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6267 element == EL_INVISIBLE_WALL_ACTIVE ||
6268 element == EL_INVISIBLE_SAND_ACTIVE)
6270 if (game.light_time_left == 0)
6271 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6273 TEST_DrawLevelField(x, y);
6275 // re-crumble neighbour fields, if needed
6276 if (element == EL_INVISIBLE_SAND)
6277 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6282 static void RedrawAllInvisibleElementsForLenses(void)
6286 SCAN_PLAYFIELD(x, y)
6288 int element = Feld[x][y];
6290 if (element == EL_EMC_DRIPPER &&
6291 game.lenses_time_left > 0)
6293 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6294 TEST_DrawLevelField(x, y);
6296 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6297 game.lenses_time_left == 0)
6299 Feld[x][y] = EL_EMC_DRIPPER;
6300 TEST_DrawLevelField(x, y);
6302 else if (element == EL_INVISIBLE_STEELWALL ||
6303 element == EL_INVISIBLE_WALL ||
6304 element == EL_INVISIBLE_SAND)
6306 if (game.lenses_time_left > 0)
6307 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6309 TEST_DrawLevelField(x, y);
6311 // uncrumble neighbour fields, if needed
6312 if (element == EL_INVISIBLE_SAND)
6313 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6315 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6316 element == EL_INVISIBLE_WALL_ACTIVE ||
6317 element == EL_INVISIBLE_SAND_ACTIVE)
6319 if (game.lenses_time_left == 0)
6320 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6322 TEST_DrawLevelField(x, y);
6324 // re-crumble neighbour fields, if needed
6325 if (element == EL_INVISIBLE_SAND)
6326 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6331 static void RedrawAllInvisibleElementsForMagnifier(void)
6335 SCAN_PLAYFIELD(x, y)
6337 int element = Feld[x][y];
6339 if (element == EL_EMC_FAKE_GRASS &&
6340 game.magnify_time_left > 0)
6342 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6343 TEST_DrawLevelField(x, y);
6345 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6346 game.magnify_time_left == 0)
6348 Feld[x][y] = EL_EMC_FAKE_GRASS;
6349 TEST_DrawLevelField(x, y);
6351 else if (IS_GATE_GRAY(element) &&
6352 game.magnify_time_left > 0)
6354 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6355 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6356 IS_EM_GATE_GRAY(element) ?
6357 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6358 IS_EMC_GATE_GRAY(element) ?
6359 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6360 IS_DC_GATE_GRAY(element) ?
6361 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6363 TEST_DrawLevelField(x, y);
6365 else if (IS_GATE_GRAY_ACTIVE(element) &&
6366 game.magnify_time_left == 0)
6368 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6369 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6370 IS_EM_GATE_GRAY_ACTIVE(element) ?
6371 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6372 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6373 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6374 IS_DC_GATE_GRAY_ACTIVE(element) ?
6375 EL_DC_GATE_WHITE_GRAY :
6377 TEST_DrawLevelField(x, y);
6382 static void ToggleLightSwitch(int x, int y)
6384 int element = Feld[x][y];
6386 game.light_time_left =
6387 (element == EL_LIGHT_SWITCH ?
6388 level.time_light * FRAMES_PER_SECOND : 0);
6390 RedrawAllLightSwitchesAndInvisibleElements();
6393 static void ActivateTimegateSwitch(int x, int y)
6397 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6399 SCAN_PLAYFIELD(xx, yy)
6401 int element = Feld[xx][yy];
6403 if (element == EL_TIMEGATE_CLOSED ||
6404 element == EL_TIMEGATE_CLOSING)
6406 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6407 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6411 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6413 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6414 TEST_DrawLevelField(xx, yy);
6420 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6421 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6424 static void Impact(int x, int y)
6426 boolean last_line = (y == lev_fieldy - 1);
6427 boolean object_hit = FALSE;
6428 boolean impact = (last_line || object_hit);
6429 int element = Feld[x][y];
6430 int smashed = EL_STEELWALL;
6432 if (!last_line) // check if element below was hit
6434 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6437 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6438 MovDir[x][y + 1] != MV_DOWN ||
6439 MovPos[x][y + 1] <= TILEY / 2));
6441 // do not smash moving elements that left the smashed field in time
6442 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6443 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6446 #if USE_QUICKSAND_IMPACT_BUGFIX
6447 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6449 RemoveMovingField(x, y + 1);
6450 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6451 Feld[x][y + 2] = EL_ROCK;
6452 TEST_DrawLevelField(x, y + 2);
6457 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6459 RemoveMovingField(x, y + 1);
6460 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6461 Feld[x][y + 2] = EL_ROCK;
6462 TEST_DrawLevelField(x, y + 2);
6469 smashed = MovingOrBlocked2Element(x, y + 1);
6471 impact = (last_line || object_hit);
6474 if (!last_line && smashed == EL_ACID) // element falls into acid
6476 SplashAcid(x, y + 1);
6480 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6481 // only reset graphic animation if graphic really changes after impact
6483 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6485 ResetGfxAnimation(x, y);
6486 TEST_DrawLevelField(x, y);
6489 if (impact && CAN_EXPLODE_IMPACT(element))
6494 else if (impact && element == EL_PEARL &&
6495 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6497 ResetGfxAnimation(x, y);
6499 Feld[x][y] = EL_PEARL_BREAKING;
6500 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6503 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6505 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6510 if (impact && element == EL_AMOEBA_DROP)
6512 if (object_hit && IS_PLAYER(x, y + 1))
6513 KillPlayerUnlessEnemyProtected(x, y + 1);
6514 else if (object_hit && smashed == EL_PENGUIN)
6518 Feld[x][y] = EL_AMOEBA_GROWING;
6519 Store[x][y] = EL_AMOEBA_WET;
6521 ResetRandomAnimationValue(x, y);
6526 if (object_hit) // check which object was hit
6528 if ((CAN_PASS_MAGIC_WALL(element) &&
6529 (smashed == EL_MAGIC_WALL ||
6530 smashed == EL_BD_MAGIC_WALL)) ||
6531 (CAN_PASS_DC_MAGIC_WALL(element) &&
6532 smashed == EL_DC_MAGIC_WALL))
6535 int activated_magic_wall =
6536 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6537 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6538 EL_DC_MAGIC_WALL_ACTIVE);
6540 // activate magic wall / mill
6541 SCAN_PLAYFIELD(xx, yy)
6543 if (Feld[xx][yy] == smashed)
6544 Feld[xx][yy] = activated_magic_wall;
6547 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6548 game.magic_wall_active = TRUE;
6550 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6551 SND_MAGIC_WALL_ACTIVATING :
6552 smashed == EL_BD_MAGIC_WALL ?
6553 SND_BD_MAGIC_WALL_ACTIVATING :
6554 SND_DC_MAGIC_WALL_ACTIVATING));
6557 if (IS_PLAYER(x, y + 1))
6559 if (CAN_SMASH_PLAYER(element))
6561 KillPlayerUnlessEnemyProtected(x, y + 1);
6565 else if (smashed == EL_PENGUIN)
6567 if (CAN_SMASH_PLAYER(element))
6573 else if (element == EL_BD_DIAMOND)
6575 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6581 else if (((element == EL_SP_INFOTRON ||
6582 element == EL_SP_ZONK) &&
6583 (smashed == EL_SP_SNIKSNAK ||
6584 smashed == EL_SP_ELECTRON ||
6585 smashed == EL_SP_DISK_ORANGE)) ||
6586 (element == EL_SP_INFOTRON &&
6587 smashed == EL_SP_DISK_YELLOW))
6592 else if (CAN_SMASH_EVERYTHING(element))
6594 if (IS_CLASSIC_ENEMY(smashed) ||
6595 CAN_EXPLODE_SMASHED(smashed))
6600 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6602 if (smashed == EL_LAMP ||
6603 smashed == EL_LAMP_ACTIVE)
6608 else if (smashed == EL_NUT)
6610 Feld[x][y + 1] = EL_NUT_BREAKING;
6611 PlayLevelSound(x, y, SND_NUT_BREAKING);
6612 RaiseScoreElement(EL_NUT);
6615 else if (smashed == EL_PEARL)
6617 ResetGfxAnimation(x, y);
6619 Feld[x][y + 1] = EL_PEARL_BREAKING;
6620 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6623 else if (smashed == EL_DIAMOND)
6625 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6626 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6629 else if (IS_BELT_SWITCH(smashed))
6631 ToggleBeltSwitch(x, y + 1);
6633 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6634 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6635 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6636 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6638 ToggleSwitchgateSwitch(x, y + 1);
6640 else if (smashed == EL_LIGHT_SWITCH ||
6641 smashed == EL_LIGHT_SWITCH_ACTIVE)
6643 ToggleLightSwitch(x, y + 1);
6647 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6649 CheckElementChangeBySide(x, y + 1, smashed, element,
6650 CE_SWITCHED, CH_SIDE_TOP);
6651 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6657 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6662 // play sound of magic wall / mill
6664 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6665 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6666 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6668 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6669 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6670 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6671 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6672 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6673 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6678 // play sound of object that hits the ground
6679 if (last_line || object_hit)
6680 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6683 static void TurnRoundExt(int x, int y)
6695 { 0, 0 }, { 0, 0 }, { 0, 0 },
6700 int left, right, back;
6704 { MV_DOWN, MV_UP, MV_RIGHT },
6705 { MV_UP, MV_DOWN, MV_LEFT },
6707 { MV_LEFT, MV_RIGHT, MV_DOWN },
6711 { MV_RIGHT, MV_LEFT, MV_UP }
6714 int element = Feld[x][y];
6715 int move_pattern = element_info[element].move_pattern;
6717 int old_move_dir = MovDir[x][y];
6718 int left_dir = turn[old_move_dir].left;
6719 int right_dir = turn[old_move_dir].right;
6720 int back_dir = turn[old_move_dir].back;
6722 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6723 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6724 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6725 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6727 int left_x = x + left_dx, left_y = y + left_dy;
6728 int right_x = x + right_dx, right_y = y + right_dy;
6729 int move_x = x + move_dx, move_y = y + move_dy;
6733 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6735 TestIfBadThingTouchesOtherBadThing(x, y);
6737 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6738 MovDir[x][y] = right_dir;
6739 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6740 MovDir[x][y] = left_dir;
6742 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6744 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6747 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6749 TestIfBadThingTouchesOtherBadThing(x, y);
6751 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6752 MovDir[x][y] = left_dir;
6753 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6754 MovDir[x][y] = right_dir;
6756 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6758 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6761 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6763 TestIfBadThingTouchesOtherBadThing(x, y);
6765 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6766 MovDir[x][y] = left_dir;
6767 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6768 MovDir[x][y] = right_dir;
6770 if (MovDir[x][y] != old_move_dir)
6773 else if (element == EL_YAMYAM)
6775 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6776 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6778 if (can_turn_left && can_turn_right)
6779 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6780 else if (can_turn_left)
6781 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6782 else if (can_turn_right)
6783 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6785 MovDir[x][y] = back_dir;
6787 MovDelay[x][y] = 16 + 16 * RND(3);
6789 else if (element == EL_DARK_YAMYAM)
6791 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6793 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6796 if (can_turn_left && can_turn_right)
6797 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6798 else if (can_turn_left)
6799 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6800 else if (can_turn_right)
6801 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6803 MovDir[x][y] = back_dir;
6805 MovDelay[x][y] = 16 + 16 * RND(3);
6807 else if (element == EL_PACMAN)
6809 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6810 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6812 if (can_turn_left && can_turn_right)
6813 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6814 else if (can_turn_left)
6815 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6816 else if (can_turn_right)
6817 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6819 MovDir[x][y] = back_dir;
6821 MovDelay[x][y] = 6 + RND(40);
6823 else if (element == EL_PIG)
6825 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6826 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6827 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6828 boolean should_turn_left, should_turn_right, should_move_on;
6830 int rnd = RND(rnd_value);
6832 should_turn_left = (can_turn_left &&
6834 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6835 y + back_dy + left_dy)));
6836 should_turn_right = (can_turn_right &&
6838 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6839 y + back_dy + right_dy)));
6840 should_move_on = (can_move_on &&
6843 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6844 y + move_dy + left_dy) ||
6845 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6846 y + move_dy + right_dy)));
6848 if (should_turn_left || should_turn_right || should_move_on)
6850 if (should_turn_left && should_turn_right && should_move_on)
6851 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6852 rnd < 2 * rnd_value / 3 ? right_dir :
6854 else if (should_turn_left && should_turn_right)
6855 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6856 else if (should_turn_left && should_move_on)
6857 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6858 else if (should_turn_right && should_move_on)
6859 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6860 else if (should_turn_left)
6861 MovDir[x][y] = left_dir;
6862 else if (should_turn_right)
6863 MovDir[x][y] = right_dir;
6864 else if (should_move_on)
6865 MovDir[x][y] = old_move_dir;
6867 else if (can_move_on && rnd > rnd_value / 8)
6868 MovDir[x][y] = old_move_dir;
6869 else if (can_turn_left && can_turn_right)
6870 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6871 else if (can_turn_left && rnd > rnd_value / 8)
6872 MovDir[x][y] = left_dir;
6873 else if (can_turn_right && rnd > rnd_value/8)
6874 MovDir[x][y] = right_dir;
6876 MovDir[x][y] = back_dir;
6878 xx = x + move_xy[MovDir[x][y]].dx;
6879 yy = y + move_xy[MovDir[x][y]].dy;
6881 if (!IN_LEV_FIELD(xx, yy) ||
6882 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6883 MovDir[x][y] = old_move_dir;
6887 else if (element == EL_DRAGON)
6889 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6890 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6891 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6893 int rnd = RND(rnd_value);
6895 if (can_move_on && rnd > rnd_value / 8)
6896 MovDir[x][y] = old_move_dir;
6897 else if (can_turn_left && can_turn_right)
6898 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6899 else if (can_turn_left && rnd > rnd_value / 8)
6900 MovDir[x][y] = left_dir;
6901 else if (can_turn_right && rnd > rnd_value / 8)
6902 MovDir[x][y] = right_dir;
6904 MovDir[x][y] = back_dir;
6906 xx = x + move_xy[MovDir[x][y]].dx;
6907 yy = y + move_xy[MovDir[x][y]].dy;
6909 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6910 MovDir[x][y] = old_move_dir;
6914 else if (element == EL_MOLE)
6916 boolean can_move_on =
6917 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6918 IS_AMOEBOID(Feld[move_x][move_y]) ||
6919 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6922 boolean can_turn_left =
6923 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6924 IS_AMOEBOID(Feld[left_x][left_y])));
6926 boolean can_turn_right =
6927 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6928 IS_AMOEBOID(Feld[right_x][right_y])));
6930 if (can_turn_left && can_turn_right)
6931 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6932 else if (can_turn_left)
6933 MovDir[x][y] = left_dir;
6935 MovDir[x][y] = right_dir;
6938 if (MovDir[x][y] != old_move_dir)
6941 else if (element == EL_BALLOON)
6943 MovDir[x][y] = game.wind_direction;
6946 else if (element == EL_SPRING)
6948 if (MovDir[x][y] & MV_HORIZONTAL)
6950 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6951 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6953 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6954 ResetGfxAnimation(move_x, move_y);
6955 TEST_DrawLevelField(move_x, move_y);
6957 MovDir[x][y] = back_dir;
6959 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6960 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6961 MovDir[x][y] = MV_NONE;
6966 else if (element == EL_ROBOT ||
6967 element == EL_SATELLITE ||
6968 element == EL_PENGUIN ||
6969 element == EL_EMC_ANDROID)
6971 int attr_x = -1, attr_y = -1;
6973 if (game.all_players_gone)
6975 attr_x = game.exit_x;
6976 attr_y = game.exit_y;
6982 for (i = 0; i < MAX_PLAYERS; i++)
6984 struct PlayerInfo *player = &stored_player[i];
6985 int jx = player->jx, jy = player->jy;
6987 if (!player->active)
6991 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6999 if (element == EL_ROBOT &&
7000 game.robot_wheel_x >= 0 &&
7001 game.robot_wheel_y >= 0 &&
7002 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7003 game.engine_version < VERSION_IDENT(3,1,0,0)))
7005 attr_x = game.robot_wheel_x;
7006 attr_y = game.robot_wheel_y;
7009 if (element == EL_PENGUIN)
7012 static int xy[4][2] =
7020 for (i = 0; i < NUM_DIRECTIONS; i++)
7022 int ex = x + xy[i][0];
7023 int ey = y + xy[i][1];
7025 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
7026 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
7027 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
7028 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7037 MovDir[x][y] = MV_NONE;
7039 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7040 else if (attr_x > x)
7041 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7043 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7044 else if (attr_y > y)
7045 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7047 if (element == EL_ROBOT)
7051 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7052 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7053 Moving2Blocked(x, y, &newx, &newy);
7055 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7056 MovDelay[x][y] = 8 + 8 * !RND(3);
7058 MovDelay[x][y] = 16;
7060 else if (element == EL_PENGUIN)
7066 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7068 boolean first_horiz = RND(2);
7069 int new_move_dir = MovDir[x][y];
7072 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7073 Moving2Blocked(x, y, &newx, &newy);
7075 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7079 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7080 Moving2Blocked(x, y, &newx, &newy);
7082 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7085 MovDir[x][y] = old_move_dir;
7089 else if (element == EL_SATELLITE)
7095 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7097 boolean first_horiz = RND(2);
7098 int new_move_dir = MovDir[x][y];
7101 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7102 Moving2Blocked(x, y, &newx, &newy);
7104 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7108 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7109 Moving2Blocked(x, y, &newx, &newy);
7111 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7114 MovDir[x][y] = old_move_dir;
7118 else if (element == EL_EMC_ANDROID)
7120 static int check_pos[16] =
7122 -1, // 0 => (invalid)
7125 -1, // 3 => (invalid)
7127 0, // 5 => MV_LEFT | MV_UP
7128 2, // 6 => MV_RIGHT | MV_UP
7129 -1, // 7 => (invalid)
7131 6, // 9 => MV_LEFT | MV_DOWN
7132 4, // 10 => MV_RIGHT | MV_DOWN
7133 -1, // 11 => (invalid)
7134 -1, // 12 => (invalid)
7135 -1, // 13 => (invalid)
7136 -1, // 14 => (invalid)
7137 -1, // 15 => (invalid)
7145 { -1, -1, MV_LEFT | MV_UP },
7147 { +1, -1, MV_RIGHT | MV_UP },
7148 { +1, 0, MV_RIGHT },
7149 { +1, +1, MV_RIGHT | MV_DOWN },
7151 { -1, +1, MV_LEFT | MV_DOWN },
7154 int start_pos, check_order;
7155 boolean can_clone = FALSE;
7158 // check if there is any free field around current position
7159 for (i = 0; i < 8; i++)
7161 int newx = x + check_xy[i].dx;
7162 int newy = y + check_xy[i].dy;
7164 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7172 if (can_clone) // randomly find an element to clone
7176 start_pos = check_pos[RND(8)];
7177 check_order = (RND(2) ? -1 : +1);
7179 for (i = 0; i < 8; i++)
7181 int pos_raw = start_pos + i * check_order;
7182 int pos = (pos_raw + 8) % 8;
7183 int newx = x + check_xy[pos].dx;
7184 int newy = y + check_xy[pos].dy;
7186 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7188 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7189 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7191 Store[x][y] = Feld[newx][newy];
7200 if (can_clone) // randomly find a direction to move
7204 start_pos = check_pos[RND(8)];
7205 check_order = (RND(2) ? -1 : +1);
7207 for (i = 0; i < 8; i++)
7209 int pos_raw = start_pos + i * check_order;
7210 int pos = (pos_raw + 8) % 8;
7211 int newx = x + check_xy[pos].dx;
7212 int newy = y + check_xy[pos].dy;
7213 int new_move_dir = check_xy[pos].dir;
7215 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7217 MovDir[x][y] = new_move_dir;
7218 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7227 if (can_clone) // cloning and moving successful
7230 // cannot clone -- try to move towards player
7232 start_pos = check_pos[MovDir[x][y] & 0x0f];
7233 check_order = (RND(2) ? -1 : +1);
7235 for (i = 0; i < 3; i++)
7237 // first check start_pos, then previous/next or (next/previous) pos
7238 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7239 int pos = (pos_raw + 8) % 8;
7240 int newx = x + check_xy[pos].dx;
7241 int newy = y + check_xy[pos].dy;
7242 int new_move_dir = check_xy[pos].dir;
7244 if (IS_PLAYER(newx, newy))
7247 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7249 MovDir[x][y] = new_move_dir;
7250 MovDelay[x][y] = level.android_move_time * 8 + 1;
7257 else if (move_pattern == MV_TURNING_LEFT ||
7258 move_pattern == MV_TURNING_RIGHT ||
7259 move_pattern == MV_TURNING_LEFT_RIGHT ||
7260 move_pattern == MV_TURNING_RIGHT_LEFT ||
7261 move_pattern == MV_TURNING_RANDOM ||
7262 move_pattern == MV_ALL_DIRECTIONS)
7264 boolean can_turn_left =
7265 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7266 boolean can_turn_right =
7267 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7269 if (element_info[element].move_stepsize == 0) // "not moving"
7272 if (move_pattern == MV_TURNING_LEFT)
7273 MovDir[x][y] = left_dir;
7274 else if (move_pattern == MV_TURNING_RIGHT)
7275 MovDir[x][y] = right_dir;
7276 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7277 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7278 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7279 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7280 else if (move_pattern == MV_TURNING_RANDOM)
7281 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7282 can_turn_right && !can_turn_left ? right_dir :
7283 RND(2) ? left_dir : right_dir);
7284 else if (can_turn_left && can_turn_right)
7285 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7286 else if (can_turn_left)
7287 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7288 else if (can_turn_right)
7289 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7291 MovDir[x][y] = back_dir;
7293 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7295 else if (move_pattern == MV_HORIZONTAL ||
7296 move_pattern == MV_VERTICAL)
7298 if (move_pattern & old_move_dir)
7299 MovDir[x][y] = back_dir;
7300 else if (move_pattern == MV_HORIZONTAL)
7301 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7302 else if (move_pattern == MV_VERTICAL)
7303 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7305 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7307 else if (move_pattern & MV_ANY_DIRECTION)
7309 MovDir[x][y] = move_pattern;
7310 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7312 else if (move_pattern & MV_WIND_DIRECTION)
7314 MovDir[x][y] = game.wind_direction;
7315 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7317 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7319 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7320 MovDir[x][y] = left_dir;
7321 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7322 MovDir[x][y] = right_dir;
7324 if (MovDir[x][y] != old_move_dir)
7325 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7327 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7329 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7330 MovDir[x][y] = right_dir;
7331 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7332 MovDir[x][y] = left_dir;
7334 if (MovDir[x][y] != old_move_dir)
7335 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7337 else if (move_pattern == MV_TOWARDS_PLAYER ||
7338 move_pattern == MV_AWAY_FROM_PLAYER)
7340 int attr_x = -1, attr_y = -1;
7342 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7344 if (game.all_players_gone)
7346 attr_x = game.exit_x;
7347 attr_y = game.exit_y;
7353 for (i = 0; i < MAX_PLAYERS; i++)
7355 struct PlayerInfo *player = &stored_player[i];
7356 int jx = player->jx, jy = player->jy;
7358 if (!player->active)
7362 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7370 MovDir[x][y] = MV_NONE;
7372 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7373 else if (attr_x > x)
7374 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7376 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7377 else if (attr_y > y)
7378 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7380 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7382 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7384 boolean first_horiz = RND(2);
7385 int new_move_dir = MovDir[x][y];
7387 if (element_info[element].move_stepsize == 0) // "not moving"
7389 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7390 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7396 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7397 Moving2Blocked(x, y, &newx, &newy);
7399 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7403 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7404 Moving2Blocked(x, y, &newx, &newy);
7406 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7409 MovDir[x][y] = old_move_dir;
7412 else if (move_pattern == MV_WHEN_PUSHED ||
7413 move_pattern == MV_WHEN_DROPPED)
7415 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7416 MovDir[x][y] = MV_NONE;
7420 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7422 static int test_xy[7][2] =
7432 static int test_dir[7] =
7442 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7443 int move_preference = -1000000; // start with very low preference
7444 int new_move_dir = MV_NONE;
7445 int start_test = RND(4);
7448 for (i = 0; i < NUM_DIRECTIONS; i++)
7450 int move_dir = test_dir[start_test + i];
7451 int move_dir_preference;
7453 xx = x + test_xy[start_test + i][0];
7454 yy = y + test_xy[start_test + i][1];
7456 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7457 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7459 new_move_dir = move_dir;
7464 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7467 move_dir_preference = -1 * RunnerVisit[xx][yy];
7468 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7469 move_dir_preference = PlayerVisit[xx][yy];
7471 if (move_dir_preference > move_preference)
7473 // prefer field that has not been visited for the longest time
7474 move_preference = move_dir_preference;
7475 new_move_dir = move_dir;
7477 else if (move_dir_preference == move_preference &&
7478 move_dir == old_move_dir)
7480 // prefer last direction when all directions are preferred equally
7481 move_preference = move_dir_preference;
7482 new_move_dir = move_dir;
7486 MovDir[x][y] = new_move_dir;
7487 if (old_move_dir != new_move_dir)
7488 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7492 static void TurnRound(int x, int y)
7494 int direction = MovDir[x][y];
7498 GfxDir[x][y] = MovDir[x][y];
7500 if (direction != MovDir[x][y])
7504 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7506 ResetGfxFrame(x, y);
7509 static boolean JustBeingPushed(int x, int y)
7513 for (i = 0; i < MAX_PLAYERS; i++)
7515 struct PlayerInfo *player = &stored_player[i];
7517 if (player->active && player->is_pushing && player->MovPos)
7519 int next_jx = player->jx + (player->jx - player->last_jx);
7520 int next_jy = player->jy + (player->jy - player->last_jy);
7522 if (x == next_jx && y == next_jy)
7530 static void StartMoving(int x, int y)
7532 boolean started_moving = FALSE; // some elements can fall _and_ move
7533 int element = Feld[x][y];
7538 if (MovDelay[x][y] == 0)
7539 GfxAction[x][y] = ACTION_DEFAULT;
7541 if (CAN_FALL(element) && y < lev_fieldy - 1)
7543 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7544 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7545 if (JustBeingPushed(x, y))
7548 if (element == EL_QUICKSAND_FULL)
7550 if (IS_FREE(x, y + 1))
7552 InitMovingField(x, y, MV_DOWN);
7553 started_moving = TRUE;
7555 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7556 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7557 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7558 Store[x][y] = EL_ROCK;
7560 Store[x][y] = EL_ROCK;
7563 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7565 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7567 if (!MovDelay[x][y])
7569 MovDelay[x][y] = TILEY + 1;
7571 ResetGfxAnimation(x, y);
7572 ResetGfxAnimation(x, y + 1);
7577 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7578 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7585 Feld[x][y] = EL_QUICKSAND_EMPTY;
7586 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7587 Store[x][y + 1] = Store[x][y];
7590 PlayLevelSoundAction(x, y, ACTION_FILLING);
7592 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7594 if (!MovDelay[x][y])
7596 MovDelay[x][y] = TILEY + 1;
7598 ResetGfxAnimation(x, y);
7599 ResetGfxAnimation(x, y + 1);
7604 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7605 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7612 Feld[x][y] = EL_QUICKSAND_EMPTY;
7613 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7614 Store[x][y + 1] = Store[x][y];
7617 PlayLevelSoundAction(x, y, ACTION_FILLING);
7620 else if (element == EL_QUICKSAND_FAST_FULL)
7622 if (IS_FREE(x, y + 1))
7624 InitMovingField(x, y, MV_DOWN);
7625 started_moving = TRUE;
7627 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7628 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7629 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7630 Store[x][y] = EL_ROCK;
7632 Store[x][y] = EL_ROCK;
7635 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7637 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7639 if (!MovDelay[x][y])
7641 MovDelay[x][y] = TILEY + 1;
7643 ResetGfxAnimation(x, y);
7644 ResetGfxAnimation(x, y + 1);
7649 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7650 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7657 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7658 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7659 Store[x][y + 1] = Store[x][y];
7662 PlayLevelSoundAction(x, y, ACTION_FILLING);
7664 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7666 if (!MovDelay[x][y])
7668 MovDelay[x][y] = TILEY + 1;
7670 ResetGfxAnimation(x, y);
7671 ResetGfxAnimation(x, y + 1);
7676 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7677 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7684 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7685 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7686 Store[x][y + 1] = Store[x][y];
7689 PlayLevelSoundAction(x, y, ACTION_FILLING);
7692 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7693 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7695 InitMovingField(x, y, MV_DOWN);
7696 started_moving = TRUE;
7698 Feld[x][y] = EL_QUICKSAND_FILLING;
7699 Store[x][y] = element;
7701 PlayLevelSoundAction(x, y, ACTION_FILLING);
7703 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7704 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7706 InitMovingField(x, y, MV_DOWN);
7707 started_moving = TRUE;
7709 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7710 Store[x][y] = element;
7712 PlayLevelSoundAction(x, y, ACTION_FILLING);
7714 else if (element == EL_MAGIC_WALL_FULL)
7716 if (IS_FREE(x, y + 1))
7718 InitMovingField(x, y, MV_DOWN);
7719 started_moving = TRUE;
7721 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7722 Store[x][y] = EL_CHANGED(Store[x][y]);
7724 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7726 if (!MovDelay[x][y])
7727 MovDelay[x][y] = TILEY / 4 + 1;
7736 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7737 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7738 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7742 else if (element == EL_BD_MAGIC_WALL_FULL)
7744 if (IS_FREE(x, y + 1))
7746 InitMovingField(x, y, MV_DOWN);
7747 started_moving = TRUE;
7749 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7750 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7752 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7754 if (!MovDelay[x][y])
7755 MovDelay[x][y] = TILEY / 4 + 1;
7764 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7765 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7766 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7770 else if (element == EL_DC_MAGIC_WALL_FULL)
7772 if (IS_FREE(x, y + 1))
7774 InitMovingField(x, y, MV_DOWN);
7775 started_moving = TRUE;
7777 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7778 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7780 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7782 if (!MovDelay[x][y])
7783 MovDelay[x][y] = TILEY / 4 + 1;
7792 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7793 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7794 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7798 else if ((CAN_PASS_MAGIC_WALL(element) &&
7799 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7800 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7801 (CAN_PASS_DC_MAGIC_WALL(element) &&
7802 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7805 InitMovingField(x, y, MV_DOWN);
7806 started_moving = TRUE;
7809 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7810 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7811 EL_DC_MAGIC_WALL_FILLING);
7812 Store[x][y] = element;
7814 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7816 SplashAcid(x, y + 1);
7818 InitMovingField(x, y, MV_DOWN);
7819 started_moving = TRUE;
7821 Store[x][y] = EL_ACID;
7824 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7825 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7826 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7827 CAN_FALL(element) && WasJustFalling[x][y] &&
7828 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7830 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7831 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7832 (Feld[x][y + 1] == EL_BLOCKED)))
7834 /* this is needed for a special case not covered by calling "Impact()"
7835 from "ContinueMoving()": if an element moves to a tile directly below
7836 another element which was just falling on that tile (which was empty
7837 in the previous frame), the falling element above would just stop
7838 instead of smashing the element below (in previous version, the above
7839 element was just checked for "moving" instead of "falling", resulting
7840 in incorrect smashes caused by horizontal movement of the above
7841 element; also, the case of the player being the element to smash was
7842 simply not covered here... :-/ ) */
7844 CheckCollision[x][y] = 0;
7845 CheckImpact[x][y] = 0;
7849 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7851 if (MovDir[x][y] == MV_NONE)
7853 InitMovingField(x, y, MV_DOWN);
7854 started_moving = TRUE;
7857 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7859 if (WasJustFalling[x][y]) // prevent animation from being restarted
7860 MovDir[x][y] = MV_DOWN;
7862 InitMovingField(x, y, MV_DOWN);
7863 started_moving = TRUE;
7865 else if (element == EL_AMOEBA_DROP)
7867 Feld[x][y] = EL_AMOEBA_GROWING;
7868 Store[x][y] = EL_AMOEBA_WET;
7870 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7871 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7872 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7873 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7875 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7876 (IS_FREE(x - 1, y + 1) ||
7877 Feld[x - 1][y + 1] == EL_ACID));
7878 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7879 (IS_FREE(x + 1, y + 1) ||
7880 Feld[x + 1][y + 1] == EL_ACID));
7881 boolean can_fall_any = (can_fall_left || can_fall_right);
7882 boolean can_fall_both = (can_fall_left && can_fall_right);
7883 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7885 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7887 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7888 can_fall_right = FALSE;
7889 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7890 can_fall_left = FALSE;
7891 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7892 can_fall_right = FALSE;
7893 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7894 can_fall_left = FALSE;
7896 can_fall_any = (can_fall_left || can_fall_right);
7897 can_fall_both = FALSE;
7902 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7903 can_fall_right = FALSE; // slip down on left side
7905 can_fall_left = !(can_fall_right = RND(2));
7907 can_fall_both = FALSE;
7912 // if not determined otherwise, prefer left side for slipping down
7913 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7914 started_moving = TRUE;
7917 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7919 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7920 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7921 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7922 int belt_dir = game.belt_dir[belt_nr];
7924 if ((belt_dir == MV_LEFT && left_is_free) ||
7925 (belt_dir == MV_RIGHT && right_is_free))
7927 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7929 InitMovingField(x, y, belt_dir);
7930 started_moving = TRUE;
7932 Pushed[x][y] = TRUE;
7933 Pushed[nextx][y] = TRUE;
7935 GfxAction[x][y] = ACTION_DEFAULT;
7939 MovDir[x][y] = 0; // if element was moving, stop it
7944 // not "else if" because of elements that can fall and move (EL_SPRING)
7945 if (CAN_MOVE(element) && !started_moving)
7947 int move_pattern = element_info[element].move_pattern;
7950 Moving2Blocked(x, y, &newx, &newy);
7952 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7955 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7956 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7958 WasJustMoving[x][y] = 0;
7959 CheckCollision[x][y] = 0;
7961 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7963 if (Feld[x][y] != element) // element has changed
7967 if (!MovDelay[x][y]) // start new movement phase
7969 // all objects that can change their move direction after each step
7970 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7972 if (element != EL_YAMYAM &&
7973 element != EL_DARK_YAMYAM &&
7974 element != EL_PACMAN &&
7975 !(move_pattern & MV_ANY_DIRECTION) &&
7976 move_pattern != MV_TURNING_LEFT &&
7977 move_pattern != MV_TURNING_RIGHT &&
7978 move_pattern != MV_TURNING_LEFT_RIGHT &&
7979 move_pattern != MV_TURNING_RIGHT_LEFT &&
7980 move_pattern != MV_TURNING_RANDOM)
7984 if (MovDelay[x][y] && (element == EL_BUG ||
7985 element == EL_SPACESHIP ||
7986 element == EL_SP_SNIKSNAK ||
7987 element == EL_SP_ELECTRON ||
7988 element == EL_MOLE))
7989 TEST_DrawLevelField(x, y);
7993 if (MovDelay[x][y]) // wait some time before next movement
7997 if (element == EL_ROBOT ||
7998 element == EL_YAMYAM ||
7999 element == EL_DARK_YAMYAM)
8001 DrawLevelElementAnimationIfNeeded(x, y, element);
8002 PlayLevelSoundAction(x, y, ACTION_WAITING);
8004 else if (element == EL_SP_ELECTRON)
8005 DrawLevelElementAnimationIfNeeded(x, y, element);
8006 else if (element == EL_DRAGON)
8009 int dir = MovDir[x][y];
8010 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8011 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8012 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8013 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8014 dir == MV_UP ? IMG_FLAMES_1_UP :
8015 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8016 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8018 GfxAction[x][y] = ACTION_ATTACKING;
8020 if (IS_PLAYER(x, y))
8021 DrawPlayerField(x, y);
8023 TEST_DrawLevelField(x, y);
8025 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8027 for (i = 1; i <= 3; i++)
8029 int xx = x + i * dx;
8030 int yy = y + i * dy;
8031 int sx = SCREENX(xx);
8032 int sy = SCREENY(yy);
8033 int flame_graphic = graphic + (i - 1);
8035 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
8040 int flamed = MovingOrBlocked2Element(xx, yy);
8042 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8045 RemoveMovingField(xx, yy);
8047 ChangeDelay[xx][yy] = 0;
8049 Feld[xx][yy] = EL_FLAMES;
8051 if (IN_SCR_FIELD(sx, sy))
8053 TEST_DrawLevelFieldCrumbled(xx, yy);
8054 DrawGraphic(sx, sy, flame_graphic, frame);
8059 if (Feld[xx][yy] == EL_FLAMES)
8060 Feld[xx][yy] = EL_EMPTY;
8061 TEST_DrawLevelField(xx, yy);
8066 if (MovDelay[x][y]) // element still has to wait some time
8068 PlayLevelSoundAction(x, y, ACTION_WAITING);
8074 // now make next step
8076 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8078 if (DONT_COLLIDE_WITH(element) &&
8079 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8080 !PLAYER_ENEMY_PROTECTED(newx, newy))
8082 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8087 else if (CAN_MOVE_INTO_ACID(element) &&
8088 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8089 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8090 (MovDir[x][y] == MV_DOWN ||
8091 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8093 SplashAcid(newx, newy);
8094 Store[x][y] = EL_ACID;
8096 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8098 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8099 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8100 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8101 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8104 TEST_DrawLevelField(x, y);
8106 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8107 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8108 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8110 game.friends_still_needed--;
8111 if (!game.friends_still_needed &&
8113 game.all_players_gone)
8118 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8120 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8121 TEST_DrawLevelField(newx, newy);
8123 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8125 else if (!IS_FREE(newx, newy))
8127 GfxAction[x][y] = ACTION_WAITING;
8129 if (IS_PLAYER(x, y))
8130 DrawPlayerField(x, y);
8132 TEST_DrawLevelField(x, y);
8137 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8139 if (IS_FOOD_PIG(Feld[newx][newy]))
8141 if (IS_MOVING(newx, newy))
8142 RemoveMovingField(newx, newy);
8145 Feld[newx][newy] = EL_EMPTY;
8146 TEST_DrawLevelField(newx, newy);
8149 PlayLevelSound(x, y, SND_PIG_DIGGING);
8151 else if (!IS_FREE(newx, newy))
8153 if (IS_PLAYER(x, y))
8154 DrawPlayerField(x, y);
8156 TEST_DrawLevelField(x, y);
8161 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8163 if (Store[x][y] != EL_EMPTY)
8165 boolean can_clone = FALSE;
8168 // check if element to clone is still there
8169 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8171 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8179 // cannot clone or target field not free anymore -- do not clone
8180 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8181 Store[x][y] = EL_EMPTY;
8184 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8186 if (IS_MV_DIAGONAL(MovDir[x][y]))
8188 int diagonal_move_dir = MovDir[x][y];
8189 int stored = Store[x][y];
8190 int change_delay = 8;
8193 // android is moving diagonally
8195 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8197 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8198 GfxElement[x][y] = EL_EMC_ANDROID;
8199 GfxAction[x][y] = ACTION_SHRINKING;
8200 GfxDir[x][y] = diagonal_move_dir;
8201 ChangeDelay[x][y] = change_delay;
8203 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8206 DrawLevelGraphicAnimation(x, y, graphic);
8207 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8209 if (Feld[newx][newy] == EL_ACID)
8211 SplashAcid(newx, newy);
8216 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8218 Store[newx][newy] = EL_EMC_ANDROID;
8219 GfxElement[newx][newy] = EL_EMC_ANDROID;
8220 GfxAction[newx][newy] = ACTION_GROWING;
8221 GfxDir[newx][newy] = diagonal_move_dir;
8222 ChangeDelay[newx][newy] = change_delay;
8224 graphic = el_act_dir2img(GfxElement[newx][newy],
8225 GfxAction[newx][newy], GfxDir[newx][newy]);
8227 DrawLevelGraphicAnimation(newx, newy, graphic);
8228 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8234 Feld[newx][newy] = EL_EMPTY;
8235 TEST_DrawLevelField(newx, newy);
8237 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8240 else if (!IS_FREE(newx, newy))
8245 else if (IS_CUSTOM_ELEMENT(element) &&
8246 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8248 if (!DigFieldByCE(newx, newy, element))
8251 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8253 RunnerVisit[x][y] = FrameCounter;
8254 PlayerVisit[x][y] /= 8; // expire player visit path
8257 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8259 if (!IS_FREE(newx, newy))
8261 if (IS_PLAYER(x, y))
8262 DrawPlayerField(x, y);
8264 TEST_DrawLevelField(x, y);
8270 boolean wanna_flame = !RND(10);
8271 int dx = newx - x, dy = newy - y;
8272 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8273 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8274 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8275 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8276 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8277 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8280 IS_CLASSIC_ENEMY(element1) ||
8281 IS_CLASSIC_ENEMY(element2)) &&
8282 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8283 element1 != EL_FLAMES && element2 != EL_FLAMES)
8285 ResetGfxAnimation(x, y);
8286 GfxAction[x][y] = ACTION_ATTACKING;
8288 if (IS_PLAYER(x, y))
8289 DrawPlayerField(x, y);
8291 TEST_DrawLevelField(x, y);
8293 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8295 MovDelay[x][y] = 50;
8297 Feld[newx][newy] = EL_FLAMES;
8298 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8299 Feld[newx1][newy1] = EL_FLAMES;
8300 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8301 Feld[newx2][newy2] = EL_FLAMES;
8307 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8308 Feld[newx][newy] == EL_DIAMOND)
8310 if (IS_MOVING(newx, newy))
8311 RemoveMovingField(newx, newy);
8314 Feld[newx][newy] = EL_EMPTY;
8315 TEST_DrawLevelField(newx, newy);
8318 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8320 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8321 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8323 if (AmoebaNr[newx][newy])
8325 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8326 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8327 Feld[newx][newy] == EL_BD_AMOEBA)
8328 AmoebaCnt[AmoebaNr[newx][newy]]--;
8331 if (IS_MOVING(newx, newy))
8333 RemoveMovingField(newx, newy);
8337 Feld[newx][newy] = EL_EMPTY;
8338 TEST_DrawLevelField(newx, newy);
8341 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8343 else if ((element == EL_PACMAN || element == EL_MOLE)
8344 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8346 if (AmoebaNr[newx][newy])
8348 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8349 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8350 Feld[newx][newy] == EL_BD_AMOEBA)
8351 AmoebaCnt[AmoebaNr[newx][newy]]--;
8354 if (element == EL_MOLE)
8356 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8357 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8359 ResetGfxAnimation(x, y);
8360 GfxAction[x][y] = ACTION_DIGGING;
8361 TEST_DrawLevelField(x, y);
8363 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8365 return; // wait for shrinking amoeba
8367 else // element == EL_PACMAN
8369 Feld[newx][newy] = EL_EMPTY;
8370 TEST_DrawLevelField(newx, newy);
8371 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8374 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8375 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8376 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8378 // wait for shrinking amoeba to completely disappear
8381 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8383 // object was running against a wall
8387 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8388 DrawLevelElementAnimation(x, y, element);
8390 if (DONT_TOUCH(element))
8391 TestIfBadThingTouchesPlayer(x, y);
8396 InitMovingField(x, y, MovDir[x][y]);
8398 PlayLevelSoundAction(x, y, ACTION_MOVING);
8402 ContinueMoving(x, y);
8405 void ContinueMoving(int x, int y)
8407 int element = Feld[x][y];
8408 struct ElementInfo *ei = &element_info[element];
8409 int direction = MovDir[x][y];
8410 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8411 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8412 int newx = x + dx, newy = y + dy;
8413 int stored = Store[x][y];
8414 int stored_new = Store[newx][newy];
8415 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8416 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8417 boolean last_line = (newy == lev_fieldy - 1);
8419 MovPos[x][y] += getElementMoveStepsize(x, y);
8421 if (pushed_by_player) // special case: moving object pushed by player
8422 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8424 if (ABS(MovPos[x][y]) < TILEX)
8426 TEST_DrawLevelField(x, y);
8428 return; // element is still moving
8431 // element reached destination field
8433 Feld[x][y] = EL_EMPTY;
8434 Feld[newx][newy] = element;
8435 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8437 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8439 element = Feld[newx][newy] = EL_ACID;
8441 else if (element == EL_MOLE)
8443 Feld[x][y] = EL_SAND;
8445 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8447 else if (element == EL_QUICKSAND_FILLING)
8449 element = Feld[newx][newy] = get_next_element(element);
8450 Store[newx][newy] = Store[x][y];
8452 else if (element == EL_QUICKSAND_EMPTYING)
8454 Feld[x][y] = get_next_element(element);
8455 element = Feld[newx][newy] = Store[x][y];
8457 else if (element == EL_QUICKSAND_FAST_FILLING)
8459 element = Feld[newx][newy] = get_next_element(element);
8460 Store[newx][newy] = Store[x][y];
8462 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8464 Feld[x][y] = get_next_element(element);
8465 element = Feld[newx][newy] = Store[x][y];
8467 else if (element == EL_MAGIC_WALL_FILLING)
8469 element = Feld[newx][newy] = get_next_element(element);
8470 if (!game.magic_wall_active)
8471 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8472 Store[newx][newy] = Store[x][y];
8474 else if (element == EL_MAGIC_WALL_EMPTYING)
8476 Feld[x][y] = get_next_element(element);
8477 if (!game.magic_wall_active)
8478 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8479 element = Feld[newx][newy] = Store[x][y];
8481 InitField(newx, newy, FALSE);
8483 else if (element == EL_BD_MAGIC_WALL_FILLING)
8485 element = Feld[newx][newy] = get_next_element(element);
8486 if (!game.magic_wall_active)
8487 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8488 Store[newx][newy] = Store[x][y];
8490 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8492 Feld[x][y] = get_next_element(element);
8493 if (!game.magic_wall_active)
8494 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8495 element = Feld[newx][newy] = Store[x][y];
8497 InitField(newx, newy, FALSE);
8499 else if (element == EL_DC_MAGIC_WALL_FILLING)
8501 element = Feld[newx][newy] = get_next_element(element);
8502 if (!game.magic_wall_active)
8503 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8504 Store[newx][newy] = Store[x][y];
8506 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8508 Feld[x][y] = get_next_element(element);
8509 if (!game.magic_wall_active)
8510 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8511 element = Feld[newx][newy] = Store[x][y];
8513 InitField(newx, newy, FALSE);
8515 else if (element == EL_AMOEBA_DROPPING)
8517 Feld[x][y] = get_next_element(element);
8518 element = Feld[newx][newy] = Store[x][y];
8520 else if (element == EL_SOKOBAN_OBJECT)
8523 Feld[x][y] = Back[x][y];
8525 if (Back[newx][newy])
8526 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8528 Back[x][y] = Back[newx][newy] = 0;
8531 Store[x][y] = EL_EMPTY;
8536 MovDelay[newx][newy] = 0;
8538 if (CAN_CHANGE_OR_HAS_ACTION(element))
8540 // copy element change control values to new field
8541 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8542 ChangePage[newx][newy] = ChangePage[x][y];
8543 ChangeCount[newx][newy] = ChangeCount[x][y];
8544 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8547 CustomValue[newx][newy] = CustomValue[x][y];
8549 ChangeDelay[x][y] = 0;
8550 ChangePage[x][y] = -1;
8551 ChangeCount[x][y] = 0;
8552 ChangeEvent[x][y] = -1;
8554 CustomValue[x][y] = 0;
8556 // copy animation control values to new field
8557 GfxFrame[newx][newy] = GfxFrame[x][y];
8558 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8559 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8560 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8562 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8564 // some elements can leave other elements behind after moving
8565 if (ei->move_leave_element != EL_EMPTY &&
8566 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8567 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8569 int move_leave_element = ei->move_leave_element;
8571 // this makes it possible to leave the removed element again
8572 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8573 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8575 Feld[x][y] = move_leave_element;
8577 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8578 MovDir[x][y] = direction;
8580 InitField(x, y, FALSE);
8582 if (GFX_CRUMBLED(Feld[x][y]))
8583 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8585 if (ELEM_IS_PLAYER(move_leave_element))
8586 RelocatePlayer(x, y, move_leave_element);
8589 // do this after checking for left-behind element
8590 ResetGfxAnimation(x, y); // reset animation values for old field
8592 if (!CAN_MOVE(element) ||
8593 (CAN_FALL(element) && direction == MV_DOWN &&
8594 (element == EL_SPRING ||
8595 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8596 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8597 GfxDir[x][y] = MovDir[newx][newy] = 0;
8599 TEST_DrawLevelField(x, y);
8600 TEST_DrawLevelField(newx, newy);
8602 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8604 // prevent pushed element from moving on in pushed direction
8605 if (pushed_by_player && CAN_MOVE(element) &&
8606 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8607 !(element_info[element].move_pattern & direction))
8608 TurnRound(newx, newy);
8610 // prevent elements on conveyor belt from moving on in last direction
8611 if (pushed_by_conveyor && CAN_FALL(element) &&
8612 direction & MV_HORIZONTAL)
8613 MovDir[newx][newy] = 0;
8615 if (!pushed_by_player)
8617 int nextx = newx + dx, nexty = newy + dy;
8618 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8620 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8622 if (CAN_FALL(element) && direction == MV_DOWN)
8623 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8625 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8626 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8628 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8629 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8632 if (DONT_TOUCH(element)) // object may be nasty to player or others
8634 TestIfBadThingTouchesPlayer(newx, newy);
8635 TestIfBadThingTouchesFriend(newx, newy);
8637 if (!IS_CUSTOM_ELEMENT(element))
8638 TestIfBadThingTouchesOtherBadThing(newx, newy);
8640 else if (element == EL_PENGUIN)
8641 TestIfFriendTouchesBadThing(newx, newy);
8643 if (DONT_GET_HIT_BY(element))
8645 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8648 // give the player one last chance (one more frame) to move away
8649 if (CAN_FALL(element) && direction == MV_DOWN &&
8650 (last_line || (!IS_FREE(x, newy + 1) &&
8651 (!IS_PLAYER(x, newy + 1) ||
8652 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8655 if (pushed_by_player && !game.use_change_when_pushing_bug)
8657 int push_side = MV_DIR_OPPOSITE(direction);
8658 struct PlayerInfo *player = PLAYERINFO(x, y);
8660 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8661 player->index_bit, push_side);
8662 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8663 player->index_bit, push_side);
8666 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8667 MovDelay[newx][newy] = 1;
8669 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8671 TestIfElementTouchesCustomElement(x, y); // empty or new element
8672 TestIfElementHitsCustomElement(newx, newy, direction);
8673 TestIfPlayerTouchesCustomElement(newx, newy);
8674 TestIfElementTouchesCustomElement(newx, newy);
8676 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8677 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8678 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8679 MV_DIR_OPPOSITE(direction));
8682 int AmoebeNachbarNr(int ax, int ay)
8685 int element = Feld[ax][ay];
8687 static int xy[4][2] =
8695 for (i = 0; i < NUM_DIRECTIONS; i++)
8697 int x = ax + xy[i][0];
8698 int y = ay + xy[i][1];
8700 if (!IN_LEV_FIELD(x, y))
8703 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8704 group_nr = AmoebaNr[x][y];
8710 static void AmoebenVereinigen(int ax, int ay)
8712 int i, x, y, xx, yy;
8713 int new_group_nr = AmoebaNr[ax][ay];
8714 static int xy[4][2] =
8722 if (new_group_nr == 0)
8725 for (i = 0; i < NUM_DIRECTIONS; i++)
8730 if (!IN_LEV_FIELD(x, y))
8733 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8734 Feld[x][y] == EL_BD_AMOEBA ||
8735 Feld[x][y] == EL_AMOEBA_DEAD) &&
8736 AmoebaNr[x][y] != new_group_nr)
8738 int old_group_nr = AmoebaNr[x][y];
8740 if (old_group_nr == 0)
8743 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8744 AmoebaCnt[old_group_nr] = 0;
8745 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8746 AmoebaCnt2[old_group_nr] = 0;
8748 SCAN_PLAYFIELD(xx, yy)
8750 if (AmoebaNr[xx][yy] == old_group_nr)
8751 AmoebaNr[xx][yy] = new_group_nr;
8757 void AmoebeUmwandeln(int ax, int ay)
8761 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8763 int group_nr = AmoebaNr[ax][ay];
8768 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8769 printf("AmoebeUmwandeln(): This should never happen!\n");
8774 SCAN_PLAYFIELD(x, y)
8776 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8779 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8783 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8784 SND_AMOEBA_TURNING_TO_GEM :
8785 SND_AMOEBA_TURNING_TO_ROCK));
8790 static int xy[4][2] =
8798 for (i = 0; i < NUM_DIRECTIONS; i++)
8803 if (!IN_LEV_FIELD(x, y))
8806 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8808 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8809 SND_AMOEBA_TURNING_TO_GEM :
8810 SND_AMOEBA_TURNING_TO_ROCK));
8817 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8820 int group_nr = AmoebaNr[ax][ay];
8821 boolean done = FALSE;
8826 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8827 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8832 SCAN_PLAYFIELD(x, y)
8834 if (AmoebaNr[x][y] == group_nr &&
8835 (Feld[x][y] == EL_AMOEBA_DEAD ||
8836 Feld[x][y] == EL_BD_AMOEBA ||
8837 Feld[x][y] == EL_AMOEBA_GROWING))
8840 Feld[x][y] = new_element;
8841 InitField(x, y, FALSE);
8842 TEST_DrawLevelField(x, y);
8848 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8849 SND_BD_AMOEBA_TURNING_TO_ROCK :
8850 SND_BD_AMOEBA_TURNING_TO_GEM));
8853 static void AmoebeWaechst(int x, int y)
8855 static unsigned int sound_delay = 0;
8856 static unsigned int sound_delay_value = 0;
8858 if (!MovDelay[x][y]) // start new growing cycle
8862 if (DelayReached(&sound_delay, sound_delay_value))
8864 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8865 sound_delay_value = 30;
8869 if (MovDelay[x][y]) // wait some time before growing bigger
8872 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8874 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8875 6 - MovDelay[x][y]);
8877 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8880 if (!MovDelay[x][y])
8882 Feld[x][y] = Store[x][y];
8884 TEST_DrawLevelField(x, y);
8889 static void AmoebaDisappearing(int x, int y)
8891 static unsigned int sound_delay = 0;
8892 static unsigned int sound_delay_value = 0;
8894 if (!MovDelay[x][y]) // start new shrinking cycle
8898 if (DelayReached(&sound_delay, sound_delay_value))
8899 sound_delay_value = 30;
8902 if (MovDelay[x][y]) // wait some time before shrinking
8905 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8907 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8908 6 - MovDelay[x][y]);
8910 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8913 if (!MovDelay[x][y])
8915 Feld[x][y] = EL_EMPTY;
8916 TEST_DrawLevelField(x, y);
8918 // don't let mole enter this field in this cycle;
8919 // (give priority to objects falling to this field from above)
8925 static void AmoebeAbleger(int ax, int ay)
8928 int element = Feld[ax][ay];
8929 int graphic = el2img(element);
8930 int newax = ax, neway = ay;
8931 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8932 static int xy[4][2] =
8940 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8942 Feld[ax][ay] = EL_AMOEBA_DEAD;
8943 TEST_DrawLevelField(ax, ay);
8947 if (IS_ANIMATED(graphic))
8948 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8950 if (!MovDelay[ax][ay]) // start making new amoeba field
8951 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8953 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8956 if (MovDelay[ax][ay])
8960 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8963 int x = ax + xy[start][0];
8964 int y = ay + xy[start][1];
8966 if (!IN_LEV_FIELD(x, y))
8969 if (IS_FREE(x, y) ||
8970 CAN_GROW_INTO(Feld[x][y]) ||
8971 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8972 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8978 if (newax == ax && neway == ay)
8981 else // normal or "filled" (BD style) amoeba
8984 boolean waiting_for_player = FALSE;
8986 for (i = 0; i < NUM_DIRECTIONS; i++)
8988 int j = (start + i) % 4;
8989 int x = ax + xy[j][0];
8990 int y = ay + xy[j][1];
8992 if (!IN_LEV_FIELD(x, y))
8995 if (IS_FREE(x, y) ||
8996 CAN_GROW_INTO(Feld[x][y]) ||
8997 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8998 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
9004 else if (IS_PLAYER(x, y))
9005 waiting_for_player = TRUE;
9008 if (newax == ax && neway == ay) // amoeba cannot grow
9010 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9012 Feld[ax][ay] = EL_AMOEBA_DEAD;
9013 TEST_DrawLevelField(ax, ay);
9014 AmoebaCnt[AmoebaNr[ax][ay]]--;
9016 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9018 if (element == EL_AMOEBA_FULL)
9019 AmoebeUmwandeln(ax, ay);
9020 else if (element == EL_BD_AMOEBA)
9021 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
9026 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9028 // amoeba gets larger by growing in some direction
9030 int new_group_nr = AmoebaNr[ax][ay];
9033 if (new_group_nr == 0)
9035 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
9036 printf("AmoebeAbleger(): This should never happen!\n");
9041 AmoebaNr[newax][neway] = new_group_nr;
9042 AmoebaCnt[new_group_nr]++;
9043 AmoebaCnt2[new_group_nr]++;
9045 // if amoeba touches other amoeba(s) after growing, unify them
9046 AmoebenVereinigen(newax, neway);
9048 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9050 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
9056 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9057 (neway == lev_fieldy - 1 && newax != ax))
9059 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9060 Store[newax][neway] = element;
9062 else if (neway == ay || element == EL_EMC_DRIPPER)
9064 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9066 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9070 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9071 Feld[ax][ay] = EL_AMOEBA_DROPPING;
9072 Store[ax][ay] = EL_AMOEBA_DROP;
9073 ContinueMoving(ax, ay);
9077 TEST_DrawLevelField(newax, neway);
9080 static void Life(int ax, int ay)
9084 int element = Feld[ax][ay];
9085 int graphic = el2img(element);
9086 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9088 boolean changed = FALSE;
9090 if (IS_ANIMATED(graphic))
9091 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9096 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9097 MovDelay[ax][ay] = life_time;
9099 if (MovDelay[ax][ay]) // wait some time before next cycle
9102 if (MovDelay[ax][ay])
9106 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9108 int xx = ax+x1, yy = ay+y1;
9109 int old_element = Feld[xx][yy];
9110 int num_neighbours = 0;
9112 if (!IN_LEV_FIELD(xx, yy))
9115 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9117 int x = xx+x2, y = yy+y2;
9119 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9122 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9123 boolean is_neighbour = FALSE;
9125 if (level.use_life_bugs)
9127 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9128 (IS_FREE(x, y) && Stop[x][y]));
9131 (Last[x][y] == element || is_player_cell);
9137 boolean is_free = FALSE;
9139 if (level.use_life_bugs)
9140 is_free = (IS_FREE(xx, yy));
9142 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9144 if (xx == ax && yy == ay) // field in the middle
9146 if (num_neighbours < life_parameter[0] ||
9147 num_neighbours > life_parameter[1])
9149 Feld[xx][yy] = EL_EMPTY;
9150 if (Feld[xx][yy] != old_element)
9151 TEST_DrawLevelField(xx, yy);
9152 Stop[xx][yy] = TRUE;
9156 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9157 { // free border field
9158 if (num_neighbours >= life_parameter[2] &&
9159 num_neighbours <= life_parameter[3])
9161 Feld[xx][yy] = element;
9162 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9163 if (Feld[xx][yy] != old_element)
9164 TEST_DrawLevelField(xx, yy);
9165 Stop[xx][yy] = TRUE;
9172 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9173 SND_GAME_OF_LIFE_GROWING);
9176 static void InitRobotWheel(int x, int y)
9178 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9181 static void RunRobotWheel(int x, int y)
9183 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9186 static void StopRobotWheel(int x, int y)
9188 if (game.robot_wheel_x == x &&
9189 game.robot_wheel_y == y)
9191 game.robot_wheel_x = -1;
9192 game.robot_wheel_y = -1;
9193 game.robot_wheel_active = FALSE;
9197 static void InitTimegateWheel(int x, int y)
9199 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9202 static void RunTimegateWheel(int x, int y)
9204 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9207 static void InitMagicBallDelay(int x, int y)
9209 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9212 static void ActivateMagicBall(int bx, int by)
9216 if (level.ball_random)
9218 int pos_border = RND(8); // select one of the eight border elements
9219 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9220 int xx = pos_content % 3;
9221 int yy = pos_content / 3;
9226 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9227 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9231 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9233 int xx = x - bx + 1;
9234 int yy = y - by + 1;
9236 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9237 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9241 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9244 static void CheckExit(int x, int y)
9246 if (game.gems_still_needed > 0 ||
9247 game.sokoban_fields_still_needed > 0 ||
9248 game.sokoban_objects_still_needed > 0 ||
9249 game.lights_still_needed > 0)
9251 int element = Feld[x][y];
9252 int graphic = el2img(element);
9254 if (IS_ANIMATED(graphic))
9255 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9260 // do not re-open exit door closed after last player
9261 if (game.all_players_gone)
9264 Feld[x][y] = EL_EXIT_OPENING;
9266 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9269 static void CheckExitEM(int x, int y)
9271 if (game.gems_still_needed > 0 ||
9272 game.sokoban_fields_still_needed > 0 ||
9273 game.sokoban_objects_still_needed > 0 ||
9274 game.lights_still_needed > 0)
9276 int element = Feld[x][y];
9277 int graphic = el2img(element);
9279 if (IS_ANIMATED(graphic))
9280 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9285 // do not re-open exit door closed after last player
9286 if (game.all_players_gone)
9289 Feld[x][y] = EL_EM_EXIT_OPENING;
9291 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9294 static void CheckExitSteel(int x, int y)
9296 if (game.gems_still_needed > 0 ||
9297 game.sokoban_fields_still_needed > 0 ||
9298 game.sokoban_objects_still_needed > 0 ||
9299 game.lights_still_needed > 0)
9301 int element = Feld[x][y];
9302 int graphic = el2img(element);
9304 if (IS_ANIMATED(graphic))
9305 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9310 // do not re-open exit door closed after last player
9311 if (game.all_players_gone)
9314 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9316 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9319 static void CheckExitSteelEM(int x, int y)
9321 if (game.gems_still_needed > 0 ||
9322 game.sokoban_fields_still_needed > 0 ||
9323 game.sokoban_objects_still_needed > 0 ||
9324 game.lights_still_needed > 0)
9326 int element = Feld[x][y];
9327 int graphic = el2img(element);
9329 if (IS_ANIMATED(graphic))
9330 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9335 // do not re-open exit door closed after last player
9336 if (game.all_players_gone)
9339 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9341 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9344 static void CheckExitSP(int x, int y)
9346 if (game.gems_still_needed > 0)
9348 int element = Feld[x][y];
9349 int graphic = el2img(element);
9351 if (IS_ANIMATED(graphic))
9352 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9357 // do not re-open exit door closed after last player
9358 if (game.all_players_gone)
9361 Feld[x][y] = EL_SP_EXIT_OPENING;
9363 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9366 static void CloseAllOpenTimegates(void)
9370 SCAN_PLAYFIELD(x, y)
9372 int element = Feld[x][y];
9374 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9376 Feld[x][y] = EL_TIMEGATE_CLOSING;
9378 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9383 static void DrawTwinkleOnField(int x, int y)
9385 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9388 if (Feld[x][y] == EL_BD_DIAMOND)
9391 if (MovDelay[x][y] == 0) // next animation frame
9392 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9394 if (MovDelay[x][y] != 0) // wait some time before next frame
9398 DrawLevelElementAnimation(x, y, Feld[x][y]);
9400 if (MovDelay[x][y] != 0)
9402 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9403 10 - MovDelay[x][y]);
9405 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9410 static void MauerWaechst(int x, int y)
9414 if (!MovDelay[x][y]) // next animation frame
9415 MovDelay[x][y] = 3 * delay;
9417 if (MovDelay[x][y]) // wait some time before next frame
9421 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9423 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9424 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9426 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9429 if (!MovDelay[x][y])
9431 if (MovDir[x][y] == MV_LEFT)
9433 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9434 TEST_DrawLevelField(x - 1, y);
9436 else if (MovDir[x][y] == MV_RIGHT)
9438 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9439 TEST_DrawLevelField(x + 1, y);
9441 else if (MovDir[x][y] == MV_UP)
9443 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9444 TEST_DrawLevelField(x, y - 1);
9448 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9449 TEST_DrawLevelField(x, y + 1);
9452 Feld[x][y] = Store[x][y];
9454 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9455 TEST_DrawLevelField(x, y);
9460 static void MauerAbleger(int ax, int ay)
9462 int element = Feld[ax][ay];
9463 int graphic = el2img(element);
9464 boolean oben_frei = FALSE, unten_frei = FALSE;
9465 boolean links_frei = FALSE, rechts_frei = FALSE;
9466 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9467 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9468 boolean new_wall = FALSE;
9470 if (IS_ANIMATED(graphic))
9471 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9473 if (!MovDelay[ax][ay]) // start building new wall
9474 MovDelay[ax][ay] = 6;
9476 if (MovDelay[ax][ay]) // wait some time before building new wall
9479 if (MovDelay[ax][ay])
9483 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9485 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9487 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9489 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9492 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9493 element == EL_EXPANDABLE_WALL_ANY)
9497 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9498 Store[ax][ay-1] = element;
9499 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9500 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9501 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9502 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9507 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9508 Store[ax][ay+1] = element;
9509 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9510 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9511 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9512 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9517 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9518 element == EL_EXPANDABLE_WALL_ANY ||
9519 element == EL_EXPANDABLE_WALL ||
9520 element == EL_BD_EXPANDABLE_WALL)
9524 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9525 Store[ax-1][ay] = element;
9526 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9527 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9528 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9529 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9535 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9536 Store[ax+1][ay] = element;
9537 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9538 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9539 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9540 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9545 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9546 TEST_DrawLevelField(ax, ay);
9548 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9550 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9551 unten_massiv = TRUE;
9552 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9553 links_massiv = TRUE;
9554 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9555 rechts_massiv = TRUE;
9557 if (((oben_massiv && unten_massiv) ||
9558 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9559 element == EL_EXPANDABLE_WALL) &&
9560 ((links_massiv && rechts_massiv) ||
9561 element == EL_EXPANDABLE_WALL_VERTICAL))
9562 Feld[ax][ay] = EL_WALL;
9565 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9568 static void MauerAblegerStahl(int ax, int ay)
9570 int element = Feld[ax][ay];
9571 int graphic = el2img(element);
9572 boolean oben_frei = FALSE, unten_frei = FALSE;
9573 boolean links_frei = FALSE, rechts_frei = FALSE;
9574 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9575 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9576 boolean new_wall = FALSE;
9578 if (IS_ANIMATED(graphic))
9579 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9581 if (!MovDelay[ax][ay]) // start building new wall
9582 MovDelay[ax][ay] = 6;
9584 if (MovDelay[ax][ay]) // wait some time before building new wall
9587 if (MovDelay[ax][ay])
9591 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9593 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9595 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9597 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9600 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9601 element == EL_EXPANDABLE_STEELWALL_ANY)
9605 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9606 Store[ax][ay-1] = element;
9607 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9608 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9609 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9610 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9615 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9616 Store[ax][ay+1] = element;
9617 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9618 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9619 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9620 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9625 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9626 element == EL_EXPANDABLE_STEELWALL_ANY)
9630 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9631 Store[ax-1][ay] = element;
9632 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9633 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9634 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9635 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9641 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9642 Store[ax+1][ay] = element;
9643 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9644 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9645 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9646 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9651 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9653 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9654 unten_massiv = TRUE;
9655 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9656 links_massiv = TRUE;
9657 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9658 rechts_massiv = TRUE;
9660 if (((oben_massiv && unten_massiv) ||
9661 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9662 ((links_massiv && rechts_massiv) ||
9663 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9664 Feld[ax][ay] = EL_STEELWALL;
9667 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9670 static void CheckForDragon(int x, int y)
9673 boolean dragon_found = FALSE;
9674 static int xy[4][2] =
9682 for (i = 0; i < NUM_DIRECTIONS; i++)
9684 for (j = 0; j < 4; j++)
9686 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9688 if (IN_LEV_FIELD(xx, yy) &&
9689 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9691 if (Feld[xx][yy] == EL_DRAGON)
9692 dragon_found = TRUE;
9701 for (i = 0; i < NUM_DIRECTIONS; i++)
9703 for (j = 0; j < 3; j++)
9705 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9707 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9709 Feld[xx][yy] = EL_EMPTY;
9710 TEST_DrawLevelField(xx, yy);
9719 static void InitBuggyBase(int x, int y)
9721 int element = Feld[x][y];
9722 int activating_delay = FRAMES_PER_SECOND / 4;
9725 (element == EL_SP_BUGGY_BASE ?
9726 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9727 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9729 element == EL_SP_BUGGY_BASE_ACTIVE ?
9730 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9733 static void WarnBuggyBase(int x, int y)
9736 static int xy[4][2] =
9744 for (i = 0; i < NUM_DIRECTIONS; i++)
9746 int xx = x + xy[i][0];
9747 int yy = y + xy[i][1];
9749 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9751 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9758 static void InitTrap(int x, int y)
9760 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9763 static void ActivateTrap(int x, int y)
9765 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9768 static void ChangeActiveTrap(int x, int y)
9770 int graphic = IMG_TRAP_ACTIVE;
9772 // if new animation frame was drawn, correct crumbled sand border
9773 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9774 TEST_DrawLevelFieldCrumbled(x, y);
9777 static int getSpecialActionElement(int element, int number, int base_element)
9779 return (element != EL_EMPTY ? element :
9780 number != -1 ? base_element + number - 1 :
9784 static int getModifiedActionNumber(int value_old, int operator, int operand,
9785 int value_min, int value_max)
9787 int value_new = (operator == CA_MODE_SET ? operand :
9788 operator == CA_MODE_ADD ? value_old + operand :
9789 operator == CA_MODE_SUBTRACT ? value_old - operand :
9790 operator == CA_MODE_MULTIPLY ? value_old * operand :
9791 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9792 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9795 return (value_new < value_min ? value_min :
9796 value_new > value_max ? value_max :
9800 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9802 struct ElementInfo *ei = &element_info[element];
9803 struct ElementChangeInfo *change = &ei->change_page[page];
9804 int target_element = change->target_element;
9805 int action_type = change->action_type;
9806 int action_mode = change->action_mode;
9807 int action_arg = change->action_arg;
9808 int action_element = change->action_element;
9811 if (!change->has_action)
9814 // ---------- determine action paramater values -----------------------------
9816 int level_time_value =
9817 (level.time > 0 ? TimeLeft :
9820 int action_arg_element_raw =
9821 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9822 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9823 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9824 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9825 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9826 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9827 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9829 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9831 int action_arg_direction =
9832 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9833 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9834 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9835 change->actual_trigger_side :
9836 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9837 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9840 int action_arg_number_min =
9841 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9844 int action_arg_number_max =
9845 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9846 action_type == CA_SET_LEVEL_GEMS ? 999 :
9847 action_type == CA_SET_LEVEL_TIME ? 9999 :
9848 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9849 action_type == CA_SET_CE_VALUE ? 9999 :
9850 action_type == CA_SET_CE_SCORE ? 9999 :
9853 int action_arg_number_reset =
9854 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9855 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9856 action_type == CA_SET_LEVEL_TIME ? level.time :
9857 action_type == CA_SET_LEVEL_SCORE ? 0 :
9858 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9859 action_type == CA_SET_CE_SCORE ? 0 :
9862 int action_arg_number =
9863 (action_arg <= CA_ARG_MAX ? action_arg :
9864 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9865 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9866 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9867 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9868 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9869 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9870 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9871 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9872 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9873 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9874 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9875 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9876 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9877 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9878 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9879 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9880 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9881 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9882 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9883 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9884 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9887 int action_arg_number_old =
9888 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9889 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9890 action_type == CA_SET_LEVEL_SCORE ? game.score :
9891 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9892 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9895 int action_arg_number_new =
9896 getModifiedActionNumber(action_arg_number_old,
9897 action_mode, action_arg_number,
9898 action_arg_number_min, action_arg_number_max);
9900 int trigger_player_bits =
9901 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9902 change->actual_trigger_player_bits : change->trigger_player);
9904 int action_arg_player_bits =
9905 (action_arg >= CA_ARG_PLAYER_1 &&
9906 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9907 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9908 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9911 // ---------- execute action -----------------------------------------------
9913 switch (action_type)
9920 // ---------- level actions ----------------------------------------------
9922 case CA_RESTART_LEVEL:
9924 game.restart_level = TRUE;
9929 case CA_SHOW_ENVELOPE:
9931 int element = getSpecialActionElement(action_arg_element,
9932 action_arg_number, EL_ENVELOPE_1);
9934 if (IS_ENVELOPE(element))
9935 local_player->show_envelope = element;
9940 case CA_SET_LEVEL_TIME:
9942 if (level.time > 0) // only modify limited time value
9944 TimeLeft = action_arg_number_new;
9946 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9948 DisplayGameControlValues();
9950 if (!TimeLeft && setup.time_limit)
9951 for (i = 0; i < MAX_PLAYERS; i++)
9952 KillPlayer(&stored_player[i]);
9958 case CA_SET_LEVEL_SCORE:
9960 game.score = action_arg_number_new;
9962 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9964 DisplayGameControlValues();
9969 case CA_SET_LEVEL_GEMS:
9971 game.gems_still_needed = action_arg_number_new;
9973 game.snapshot.collected_item = TRUE;
9975 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9977 DisplayGameControlValues();
9982 case CA_SET_LEVEL_WIND:
9984 game.wind_direction = action_arg_direction;
9989 case CA_SET_LEVEL_RANDOM_SEED:
9991 // ensure that setting a new random seed while playing is predictable
9992 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9997 // ---------- player actions ---------------------------------------------
9999 case CA_MOVE_PLAYER:
10000 case CA_MOVE_PLAYER_NEW:
10002 // automatically move to the next field in specified direction
10003 for (i = 0; i < MAX_PLAYERS; i++)
10004 if (trigger_player_bits & (1 << i))
10005 if (action_type == CA_MOVE_PLAYER ||
10006 stored_player[i].MovPos == 0)
10007 stored_player[i].programmed_action = action_arg_direction;
10012 case CA_EXIT_PLAYER:
10014 for (i = 0; i < MAX_PLAYERS; i++)
10015 if (action_arg_player_bits & (1 << i))
10016 ExitPlayer(&stored_player[i]);
10018 if (game.players_still_needed == 0)
10024 case CA_KILL_PLAYER:
10026 for (i = 0; i < MAX_PLAYERS; i++)
10027 if (action_arg_player_bits & (1 << i))
10028 KillPlayer(&stored_player[i]);
10033 case CA_SET_PLAYER_KEYS:
10035 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10036 int element = getSpecialActionElement(action_arg_element,
10037 action_arg_number, EL_KEY_1);
10039 if (IS_KEY(element))
10041 for (i = 0; i < MAX_PLAYERS; i++)
10043 if (trigger_player_bits & (1 << i))
10045 stored_player[i].key[KEY_NR(element)] = key_state;
10047 DrawGameDoorValues();
10055 case CA_SET_PLAYER_SPEED:
10057 for (i = 0; i < MAX_PLAYERS; i++)
10059 if (trigger_player_bits & (1 << i))
10061 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10063 if (action_arg == CA_ARG_SPEED_FASTER &&
10064 stored_player[i].cannot_move)
10066 action_arg_number = STEPSIZE_VERY_SLOW;
10068 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10069 action_arg == CA_ARG_SPEED_FASTER)
10071 action_arg_number = 2;
10072 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10075 else if (action_arg == CA_ARG_NUMBER_RESET)
10077 action_arg_number = level.initial_player_stepsize[i];
10081 getModifiedActionNumber(move_stepsize,
10084 action_arg_number_min,
10085 action_arg_number_max);
10087 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10094 case CA_SET_PLAYER_SHIELD:
10096 for (i = 0; i < MAX_PLAYERS; i++)
10098 if (trigger_player_bits & (1 << i))
10100 if (action_arg == CA_ARG_SHIELD_OFF)
10102 stored_player[i].shield_normal_time_left = 0;
10103 stored_player[i].shield_deadly_time_left = 0;
10105 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10107 stored_player[i].shield_normal_time_left = 999999;
10109 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10111 stored_player[i].shield_normal_time_left = 999999;
10112 stored_player[i].shield_deadly_time_left = 999999;
10120 case CA_SET_PLAYER_GRAVITY:
10122 for (i = 0; i < MAX_PLAYERS; i++)
10124 if (trigger_player_bits & (1 << i))
10126 stored_player[i].gravity =
10127 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10128 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10129 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10130 stored_player[i].gravity);
10137 case CA_SET_PLAYER_ARTWORK:
10139 for (i = 0; i < MAX_PLAYERS; i++)
10141 if (trigger_player_bits & (1 << i))
10143 int artwork_element = action_arg_element;
10145 if (action_arg == CA_ARG_ELEMENT_RESET)
10147 (level.use_artwork_element[i] ? level.artwork_element[i] :
10148 stored_player[i].element_nr);
10150 if (stored_player[i].artwork_element != artwork_element)
10151 stored_player[i].Frame = 0;
10153 stored_player[i].artwork_element = artwork_element;
10155 SetPlayerWaiting(&stored_player[i], FALSE);
10157 // set number of special actions for bored and sleeping animation
10158 stored_player[i].num_special_action_bored =
10159 get_num_special_action(artwork_element,
10160 ACTION_BORING_1, ACTION_BORING_LAST);
10161 stored_player[i].num_special_action_sleeping =
10162 get_num_special_action(artwork_element,
10163 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10170 case CA_SET_PLAYER_INVENTORY:
10172 for (i = 0; i < MAX_PLAYERS; i++)
10174 struct PlayerInfo *player = &stored_player[i];
10177 if (trigger_player_bits & (1 << i))
10179 int inventory_element = action_arg_element;
10181 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10182 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10183 action_arg == CA_ARG_ELEMENT_ACTION)
10185 int element = inventory_element;
10186 int collect_count = element_info[element].collect_count_initial;
10188 if (!IS_CUSTOM_ELEMENT(element))
10191 if (collect_count == 0)
10192 player->inventory_infinite_element = element;
10194 for (k = 0; k < collect_count; k++)
10195 if (player->inventory_size < MAX_INVENTORY_SIZE)
10196 player->inventory_element[player->inventory_size++] =
10199 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10200 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10201 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10203 if (player->inventory_infinite_element != EL_UNDEFINED &&
10204 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10205 action_arg_element_raw))
10206 player->inventory_infinite_element = EL_UNDEFINED;
10208 for (k = 0, j = 0; j < player->inventory_size; j++)
10210 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10211 action_arg_element_raw))
10212 player->inventory_element[k++] = player->inventory_element[j];
10215 player->inventory_size = k;
10217 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10219 if (player->inventory_size > 0)
10221 for (j = 0; j < player->inventory_size - 1; j++)
10222 player->inventory_element[j] = player->inventory_element[j + 1];
10224 player->inventory_size--;
10227 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10229 if (player->inventory_size > 0)
10230 player->inventory_size--;
10232 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10234 player->inventory_infinite_element = EL_UNDEFINED;
10235 player->inventory_size = 0;
10237 else if (action_arg == CA_ARG_INVENTORY_RESET)
10239 player->inventory_infinite_element = EL_UNDEFINED;
10240 player->inventory_size = 0;
10242 if (level.use_initial_inventory[i])
10244 for (j = 0; j < level.initial_inventory_size[i]; j++)
10246 int element = level.initial_inventory_content[i][j];
10247 int collect_count = element_info[element].collect_count_initial;
10249 if (!IS_CUSTOM_ELEMENT(element))
10252 if (collect_count == 0)
10253 player->inventory_infinite_element = element;
10255 for (k = 0; k < collect_count; k++)
10256 if (player->inventory_size < MAX_INVENTORY_SIZE)
10257 player->inventory_element[player->inventory_size++] =
10268 // ---------- CE actions -------------------------------------------------
10270 case CA_SET_CE_VALUE:
10272 int last_ce_value = CustomValue[x][y];
10274 CustomValue[x][y] = action_arg_number_new;
10276 if (CustomValue[x][y] != last_ce_value)
10278 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10279 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10281 if (CustomValue[x][y] == 0)
10283 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10284 ChangeCount[x][y] = 0; // allow at least one more change
10286 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10287 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10294 case CA_SET_CE_SCORE:
10296 int last_ce_score = ei->collect_score;
10298 ei->collect_score = action_arg_number_new;
10300 if (ei->collect_score != last_ce_score)
10302 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10303 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10305 if (ei->collect_score == 0)
10309 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10310 ChangeCount[x][y] = 0; // allow at least one more change
10312 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10313 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10316 This is a very special case that seems to be a mixture between
10317 CheckElementChange() and CheckTriggeredElementChange(): while
10318 the first one only affects single elements that are triggered
10319 directly, the second one affects multiple elements in the playfield
10320 that are triggered indirectly by another element. This is a third
10321 case: Changing the CE score always affects multiple identical CEs,
10322 so every affected CE must be checked, not only the single CE for
10323 which the CE score was changed in the first place (as every instance
10324 of that CE shares the same CE score, and therefore also can change)!
10326 SCAN_PLAYFIELD(xx, yy)
10328 if (Feld[xx][yy] == element)
10329 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10330 CE_SCORE_GETS_ZERO);
10338 case CA_SET_CE_ARTWORK:
10340 int artwork_element = action_arg_element;
10341 boolean reset_frame = FALSE;
10344 if (action_arg == CA_ARG_ELEMENT_RESET)
10345 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10348 if (ei->gfx_element != artwork_element)
10349 reset_frame = TRUE;
10351 ei->gfx_element = artwork_element;
10353 SCAN_PLAYFIELD(xx, yy)
10355 if (Feld[xx][yy] == element)
10359 ResetGfxAnimation(xx, yy);
10360 ResetRandomAnimationValue(xx, yy);
10363 TEST_DrawLevelField(xx, yy);
10370 // ---------- engine actions ---------------------------------------------
10372 case CA_SET_ENGINE_SCAN_MODE:
10374 InitPlayfieldScanMode(action_arg);
10384 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10386 int old_element = Feld[x][y];
10387 int new_element = GetElementFromGroupElement(element);
10388 int previous_move_direction = MovDir[x][y];
10389 int last_ce_value = CustomValue[x][y];
10390 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10391 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10392 boolean add_player_onto_element = (new_element_is_player &&
10393 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10394 IS_WALKABLE(old_element));
10396 if (!add_player_onto_element)
10398 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10399 RemoveMovingField(x, y);
10403 Feld[x][y] = new_element;
10405 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10406 MovDir[x][y] = previous_move_direction;
10408 if (element_info[new_element].use_last_ce_value)
10409 CustomValue[x][y] = last_ce_value;
10411 InitField_WithBug1(x, y, FALSE);
10413 new_element = Feld[x][y]; // element may have changed
10415 ResetGfxAnimation(x, y);
10416 ResetRandomAnimationValue(x, y);
10418 TEST_DrawLevelField(x, y);
10420 if (GFX_CRUMBLED(new_element))
10421 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10424 // check if element under the player changes from accessible to unaccessible
10425 // (needed for special case of dropping element which then changes)
10426 // (must be checked after creating new element for walkable group elements)
10427 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10428 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10435 // "ChangeCount" not set yet to allow "entered by player" change one time
10436 if (new_element_is_player)
10437 RelocatePlayer(x, y, new_element);
10440 ChangeCount[x][y]++; // count number of changes in the same frame
10442 TestIfBadThingTouchesPlayer(x, y);
10443 TestIfPlayerTouchesCustomElement(x, y);
10444 TestIfElementTouchesCustomElement(x, y);
10447 static void CreateField(int x, int y, int element)
10449 CreateFieldExt(x, y, element, FALSE);
10452 static void CreateElementFromChange(int x, int y, int element)
10454 element = GET_VALID_RUNTIME_ELEMENT(element);
10456 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10458 int old_element = Feld[x][y];
10460 // prevent changed element from moving in same engine frame
10461 // unless both old and new element can either fall or move
10462 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10463 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10467 CreateFieldExt(x, y, element, TRUE);
10470 static boolean ChangeElement(int x, int y, int element, int page)
10472 struct ElementInfo *ei = &element_info[element];
10473 struct ElementChangeInfo *change = &ei->change_page[page];
10474 int ce_value = CustomValue[x][y];
10475 int ce_score = ei->collect_score;
10476 int target_element;
10477 int old_element = Feld[x][y];
10479 // always use default change event to prevent running into a loop
10480 if (ChangeEvent[x][y] == -1)
10481 ChangeEvent[x][y] = CE_DELAY;
10483 if (ChangeEvent[x][y] == CE_DELAY)
10485 // reset actual trigger element, trigger player and action element
10486 change->actual_trigger_element = EL_EMPTY;
10487 change->actual_trigger_player = EL_EMPTY;
10488 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10489 change->actual_trigger_side = CH_SIDE_NONE;
10490 change->actual_trigger_ce_value = 0;
10491 change->actual_trigger_ce_score = 0;
10494 // do not change elements more than a specified maximum number of changes
10495 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10498 ChangeCount[x][y]++; // count number of changes in the same frame
10500 if (change->explode)
10507 if (change->use_target_content)
10509 boolean complete_replace = TRUE;
10510 boolean can_replace[3][3];
10513 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10516 boolean is_walkable;
10517 boolean is_diggable;
10518 boolean is_collectible;
10519 boolean is_removable;
10520 boolean is_destructible;
10521 int ex = x + xx - 1;
10522 int ey = y + yy - 1;
10523 int content_element = change->target_content.e[xx][yy];
10526 can_replace[xx][yy] = TRUE;
10528 if (ex == x && ey == y) // do not check changing element itself
10531 if (content_element == EL_EMPTY_SPACE)
10533 can_replace[xx][yy] = FALSE; // do not replace border with space
10538 if (!IN_LEV_FIELD(ex, ey))
10540 can_replace[xx][yy] = FALSE;
10541 complete_replace = FALSE;
10548 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10549 e = MovingOrBlocked2Element(ex, ey);
10551 is_empty = (IS_FREE(ex, ey) ||
10552 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10554 is_walkable = (is_empty || IS_WALKABLE(e));
10555 is_diggable = (is_empty || IS_DIGGABLE(e));
10556 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10557 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10558 is_removable = (is_diggable || is_collectible);
10560 can_replace[xx][yy] =
10561 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10562 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10563 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10564 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10565 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10566 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10567 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10569 if (!can_replace[xx][yy])
10570 complete_replace = FALSE;
10573 if (!change->only_if_complete || complete_replace)
10575 boolean something_has_changed = FALSE;
10577 if (change->only_if_complete && change->use_random_replace &&
10578 RND(100) < change->random_percentage)
10581 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10583 int ex = x + xx - 1;
10584 int ey = y + yy - 1;
10585 int content_element;
10587 if (can_replace[xx][yy] && (!change->use_random_replace ||
10588 RND(100) < change->random_percentage))
10590 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10591 RemoveMovingField(ex, ey);
10593 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10595 content_element = change->target_content.e[xx][yy];
10596 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10597 ce_value, ce_score);
10599 CreateElementFromChange(ex, ey, target_element);
10601 something_has_changed = TRUE;
10603 // for symmetry reasons, freeze newly created border elements
10604 if (ex != x || ey != y)
10605 Stop[ex][ey] = TRUE; // no more moving in this frame
10609 if (something_has_changed)
10611 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10612 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10618 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10619 ce_value, ce_score);
10621 if (element == EL_DIAGONAL_GROWING ||
10622 element == EL_DIAGONAL_SHRINKING)
10624 target_element = Store[x][y];
10626 Store[x][y] = EL_EMPTY;
10629 CreateElementFromChange(x, y, target_element);
10631 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10632 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10635 // this uses direct change before indirect change
10636 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10641 static void HandleElementChange(int x, int y, int page)
10643 int element = MovingOrBlocked2Element(x, y);
10644 struct ElementInfo *ei = &element_info[element];
10645 struct ElementChangeInfo *change = &ei->change_page[page];
10646 boolean handle_action_before_change = FALSE;
10649 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10650 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10653 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10654 x, y, element, element_info[element].token_name);
10655 printf("HandleElementChange(): This should never happen!\n");
10660 // this can happen with classic bombs on walkable, changing elements
10661 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10666 if (ChangeDelay[x][y] == 0) // initialize element change
10668 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10670 if (change->can_change)
10672 // !!! not clear why graphic animation should be reset at all here !!!
10673 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10674 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10677 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10679 When using an animation frame delay of 1 (this only happens with
10680 "sp_zonk.moving.left/right" in the classic graphics), the default
10681 (non-moving) animation shows wrong animation frames (while the
10682 moving animation, like "sp_zonk.moving.left/right", is correct,
10683 so this graphical bug never shows up with the classic graphics).
10684 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10685 be drawn instead of the correct frames 0,1,2,3. This is caused by
10686 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10687 an element change: First when the change delay ("ChangeDelay[][]")
10688 counter has reached zero after decrementing, then a second time in
10689 the next frame (after "GfxFrame[][]" was already incremented) when
10690 "ChangeDelay[][]" is reset to the initial delay value again.
10692 This causes frame 0 to be drawn twice, while the last frame won't
10693 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10695 As some animations may already be cleverly designed around this bug
10696 (at least the "Snake Bite" snake tail animation does this), it cannot
10697 simply be fixed here without breaking such existing animations.
10698 Unfortunately, it cannot easily be detected if a graphics set was
10699 designed "before" or "after" the bug was fixed. As a workaround,
10700 a new graphics set option "game.graphics_engine_version" was added
10701 to be able to specify the game's major release version for which the
10702 graphics set was designed, which can then be used to decide if the
10703 bugfix should be used (version 4 and above) or not (version 3 or
10704 below, or if no version was specified at all, as with old sets).
10706 (The wrong/fixed animation frames can be tested with the test level set
10707 "test_gfxframe" and level "000", which contains a specially prepared
10708 custom element at level position (x/y) == (11/9) which uses the zonk
10709 animation mentioned above. Using "game.graphics_engine_version: 4"
10710 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10711 This can also be seen from the debug output for this test element.)
10714 // when a custom element is about to change (for example by change delay),
10715 // do not reset graphic animation when the custom element is moving
10716 if (game.graphics_engine_version < 4 &&
10719 ResetGfxAnimation(x, y);
10720 ResetRandomAnimationValue(x, y);
10723 if (change->pre_change_function)
10724 change->pre_change_function(x, y);
10728 ChangeDelay[x][y]--;
10730 if (ChangeDelay[x][y] != 0) // continue element change
10732 if (change->can_change)
10734 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10736 if (IS_ANIMATED(graphic))
10737 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10739 if (change->change_function)
10740 change->change_function(x, y);
10743 else // finish element change
10745 if (ChangePage[x][y] != -1) // remember page from delayed change
10747 page = ChangePage[x][y];
10748 ChangePage[x][y] = -1;
10750 change = &ei->change_page[page];
10753 if (IS_MOVING(x, y)) // never change a running system ;-)
10755 ChangeDelay[x][y] = 1; // try change after next move step
10756 ChangePage[x][y] = page; // remember page to use for change
10761 // special case: set new level random seed before changing element
10762 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10763 handle_action_before_change = TRUE;
10765 if (change->has_action && handle_action_before_change)
10766 ExecuteCustomElementAction(x, y, element, page);
10768 if (change->can_change)
10770 if (ChangeElement(x, y, element, page))
10772 if (change->post_change_function)
10773 change->post_change_function(x, y);
10777 if (change->has_action && !handle_action_before_change)
10778 ExecuteCustomElementAction(x, y, element, page);
10782 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10783 int trigger_element,
10785 int trigger_player,
10789 boolean change_done_any = FALSE;
10790 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10793 if (!(trigger_events[trigger_element][trigger_event]))
10796 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10798 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10800 int element = EL_CUSTOM_START + i;
10801 boolean change_done = FALSE;
10804 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10805 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10808 for (p = 0; p < element_info[element].num_change_pages; p++)
10810 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10812 if (change->can_change_or_has_action &&
10813 change->has_event[trigger_event] &&
10814 change->trigger_side & trigger_side &&
10815 change->trigger_player & trigger_player &&
10816 change->trigger_page & trigger_page_bits &&
10817 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10819 change->actual_trigger_element = trigger_element;
10820 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10821 change->actual_trigger_player_bits = trigger_player;
10822 change->actual_trigger_side = trigger_side;
10823 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10824 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10826 if ((change->can_change && !change_done) || change->has_action)
10830 SCAN_PLAYFIELD(x, y)
10832 if (Feld[x][y] == element)
10834 if (change->can_change && !change_done)
10836 // if element already changed in this frame, not only prevent
10837 // another element change (checked in ChangeElement()), but
10838 // also prevent additional element actions for this element
10840 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10841 !level.use_action_after_change_bug)
10844 ChangeDelay[x][y] = 1;
10845 ChangeEvent[x][y] = trigger_event;
10847 HandleElementChange(x, y, p);
10849 else if (change->has_action)
10851 // if element already changed in this frame, not only prevent
10852 // another element change (checked in ChangeElement()), but
10853 // also prevent additional element actions for this element
10855 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10856 !level.use_action_after_change_bug)
10859 ExecuteCustomElementAction(x, y, element, p);
10860 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10865 if (change->can_change)
10867 change_done = TRUE;
10868 change_done_any = TRUE;
10875 RECURSION_LOOP_DETECTION_END();
10877 return change_done_any;
10880 static boolean CheckElementChangeExt(int x, int y,
10882 int trigger_element,
10884 int trigger_player,
10887 boolean change_done = FALSE;
10890 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10891 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10894 if (Feld[x][y] == EL_BLOCKED)
10896 Blocked2Moving(x, y, &x, &y);
10897 element = Feld[x][y];
10900 // check if element has already changed or is about to change after moving
10901 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10902 Feld[x][y] != element) ||
10904 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10905 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10906 ChangePage[x][y] != -1)))
10909 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10911 for (p = 0; p < element_info[element].num_change_pages; p++)
10913 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10915 /* check trigger element for all events where the element that is checked
10916 for changing interacts with a directly adjacent element -- this is
10917 different to element changes that affect other elements to change on the
10918 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10919 boolean check_trigger_element =
10920 (trigger_event == CE_TOUCHING_X ||
10921 trigger_event == CE_HITTING_X ||
10922 trigger_event == CE_HIT_BY_X ||
10923 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10925 if (change->can_change_or_has_action &&
10926 change->has_event[trigger_event] &&
10927 change->trigger_side & trigger_side &&
10928 change->trigger_player & trigger_player &&
10929 (!check_trigger_element ||
10930 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10932 change->actual_trigger_element = trigger_element;
10933 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10934 change->actual_trigger_player_bits = trigger_player;
10935 change->actual_trigger_side = trigger_side;
10936 change->actual_trigger_ce_value = CustomValue[x][y];
10937 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10939 // special case: trigger element not at (x,y) position for some events
10940 if (check_trigger_element)
10952 { 0, 0 }, { 0, 0 }, { 0, 0 },
10956 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10957 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10959 change->actual_trigger_ce_value = CustomValue[xx][yy];
10960 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10963 if (change->can_change && !change_done)
10965 ChangeDelay[x][y] = 1;
10966 ChangeEvent[x][y] = trigger_event;
10968 HandleElementChange(x, y, p);
10970 change_done = TRUE;
10972 else if (change->has_action)
10974 ExecuteCustomElementAction(x, y, element, p);
10975 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10980 RECURSION_LOOP_DETECTION_END();
10982 return change_done;
10985 static void PlayPlayerSound(struct PlayerInfo *player)
10987 int jx = player->jx, jy = player->jy;
10988 int sound_element = player->artwork_element;
10989 int last_action = player->last_action_waiting;
10990 int action = player->action_waiting;
10992 if (player->is_waiting)
10994 if (action != last_action)
10995 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10997 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11001 if (action != last_action)
11002 StopSound(element_info[sound_element].sound[last_action]);
11004 if (last_action == ACTION_SLEEPING)
11005 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11009 static void PlayAllPlayersSound(void)
11013 for (i = 0; i < MAX_PLAYERS; i++)
11014 if (stored_player[i].active)
11015 PlayPlayerSound(&stored_player[i]);
11018 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11020 boolean last_waiting = player->is_waiting;
11021 int move_dir = player->MovDir;
11023 player->dir_waiting = move_dir;
11024 player->last_action_waiting = player->action_waiting;
11028 if (!last_waiting) // not waiting -> waiting
11030 player->is_waiting = TRUE;
11032 player->frame_counter_bored =
11034 game.player_boring_delay_fixed +
11035 GetSimpleRandom(game.player_boring_delay_random);
11036 player->frame_counter_sleeping =
11038 game.player_sleeping_delay_fixed +
11039 GetSimpleRandom(game.player_sleeping_delay_random);
11041 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11044 if (game.player_sleeping_delay_fixed +
11045 game.player_sleeping_delay_random > 0 &&
11046 player->anim_delay_counter == 0 &&
11047 player->post_delay_counter == 0 &&
11048 FrameCounter >= player->frame_counter_sleeping)
11049 player->is_sleeping = TRUE;
11050 else if (game.player_boring_delay_fixed +
11051 game.player_boring_delay_random > 0 &&
11052 FrameCounter >= player->frame_counter_bored)
11053 player->is_bored = TRUE;
11055 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11056 player->is_bored ? ACTION_BORING :
11059 if (player->is_sleeping && player->use_murphy)
11061 // special case for sleeping Murphy when leaning against non-free tile
11063 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11064 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
11065 !IS_MOVING(player->jx - 1, player->jy)))
11066 move_dir = MV_LEFT;
11067 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11068 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
11069 !IS_MOVING(player->jx + 1, player->jy)))
11070 move_dir = MV_RIGHT;
11072 player->is_sleeping = FALSE;
11074 player->dir_waiting = move_dir;
11077 if (player->is_sleeping)
11079 if (player->num_special_action_sleeping > 0)
11081 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11083 int last_special_action = player->special_action_sleeping;
11084 int num_special_action = player->num_special_action_sleeping;
11085 int special_action =
11086 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11087 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11088 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11089 last_special_action + 1 : ACTION_SLEEPING);
11090 int special_graphic =
11091 el_act_dir2img(player->artwork_element, special_action, move_dir);
11093 player->anim_delay_counter =
11094 graphic_info[special_graphic].anim_delay_fixed +
11095 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11096 player->post_delay_counter =
11097 graphic_info[special_graphic].post_delay_fixed +
11098 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11100 player->special_action_sleeping = special_action;
11103 if (player->anim_delay_counter > 0)
11105 player->action_waiting = player->special_action_sleeping;
11106 player->anim_delay_counter--;
11108 else if (player->post_delay_counter > 0)
11110 player->post_delay_counter--;
11114 else if (player->is_bored)
11116 if (player->num_special_action_bored > 0)
11118 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11120 int special_action =
11121 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11122 int special_graphic =
11123 el_act_dir2img(player->artwork_element, special_action, move_dir);
11125 player->anim_delay_counter =
11126 graphic_info[special_graphic].anim_delay_fixed +
11127 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11128 player->post_delay_counter =
11129 graphic_info[special_graphic].post_delay_fixed +
11130 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11132 player->special_action_bored = special_action;
11135 if (player->anim_delay_counter > 0)
11137 player->action_waiting = player->special_action_bored;
11138 player->anim_delay_counter--;
11140 else if (player->post_delay_counter > 0)
11142 player->post_delay_counter--;
11147 else if (last_waiting) // waiting -> not waiting
11149 player->is_waiting = FALSE;
11150 player->is_bored = FALSE;
11151 player->is_sleeping = FALSE;
11153 player->frame_counter_bored = -1;
11154 player->frame_counter_sleeping = -1;
11156 player->anim_delay_counter = 0;
11157 player->post_delay_counter = 0;
11159 player->dir_waiting = player->MovDir;
11160 player->action_waiting = ACTION_DEFAULT;
11162 player->special_action_bored = ACTION_DEFAULT;
11163 player->special_action_sleeping = ACTION_DEFAULT;
11167 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11169 if ((!player->is_moving && player->was_moving) ||
11170 (player->MovPos == 0 && player->was_moving) ||
11171 (player->is_snapping && !player->was_snapping) ||
11172 (player->is_dropping && !player->was_dropping))
11174 if (!CheckSaveEngineSnapshotToList())
11177 player->was_moving = FALSE;
11178 player->was_snapping = TRUE;
11179 player->was_dropping = TRUE;
11183 if (player->is_moving)
11184 player->was_moving = TRUE;
11186 if (!player->is_snapping)
11187 player->was_snapping = FALSE;
11189 if (!player->is_dropping)
11190 player->was_dropping = FALSE;
11194 static void CheckSingleStepMode(struct PlayerInfo *player)
11196 if (tape.single_step && tape.recording && !tape.pausing)
11198 /* as it is called "single step mode", just return to pause mode when the
11199 player stopped moving after one tile (or never starts moving at all) */
11200 if (!player->is_moving &&
11201 !player->is_pushing &&
11202 !player->is_dropping_pressed)
11203 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11206 CheckSaveEngineSnapshot(player);
11209 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11211 int left = player_action & JOY_LEFT;
11212 int right = player_action & JOY_RIGHT;
11213 int up = player_action & JOY_UP;
11214 int down = player_action & JOY_DOWN;
11215 int button1 = player_action & JOY_BUTTON_1;
11216 int button2 = player_action & JOY_BUTTON_2;
11217 int dx = (left ? -1 : right ? 1 : 0);
11218 int dy = (up ? -1 : down ? 1 : 0);
11220 if (!player->active || tape.pausing)
11226 SnapField(player, dx, dy);
11230 DropElement(player);
11232 MovePlayer(player, dx, dy);
11235 CheckSingleStepMode(player);
11237 SetPlayerWaiting(player, FALSE);
11239 return player_action;
11243 // no actions for this player (no input at player's configured device)
11245 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11246 SnapField(player, 0, 0);
11247 CheckGravityMovementWhenNotMoving(player);
11249 if (player->MovPos == 0)
11250 SetPlayerWaiting(player, TRUE);
11252 if (player->MovPos == 0) // needed for tape.playing
11253 player->is_moving = FALSE;
11255 player->is_dropping = FALSE;
11256 player->is_dropping_pressed = FALSE;
11257 player->drop_pressed_delay = 0;
11259 CheckSingleStepMode(player);
11265 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11268 if (!tape.use_mouse_actions)
11271 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11272 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11273 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11276 static void SetTapeActionFromMouseAction(byte *tape_action,
11277 struct MouseActionInfo *mouse_action)
11279 if (!tape.use_mouse_actions)
11282 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11283 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11284 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11287 static void CheckLevelSolved(void)
11289 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11291 if (game_em.level_solved &&
11292 !game_em.game_over) // game won
11296 game_em.game_over = TRUE;
11298 game.all_players_gone = TRUE;
11301 if (game_em.game_over) // game lost
11302 game.all_players_gone = TRUE;
11304 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11306 if (game_sp.level_solved &&
11307 !game_sp.game_over) // game won
11311 game_sp.game_over = TRUE;
11313 game.all_players_gone = TRUE;
11316 if (game_sp.game_over) // game lost
11317 game.all_players_gone = TRUE;
11319 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11321 if (game_mm.level_solved &&
11322 !game_mm.game_over) // game won
11326 game_mm.game_over = TRUE;
11328 game.all_players_gone = TRUE;
11331 if (game_mm.game_over) // game lost
11332 game.all_players_gone = TRUE;
11336 static void CheckLevelTime(void)
11340 if (TimeFrames >= FRAMES_PER_SECOND)
11345 for (i = 0; i < MAX_PLAYERS; i++)
11347 struct PlayerInfo *player = &stored_player[i];
11349 if (SHIELD_ON(player))
11351 player->shield_normal_time_left--;
11353 if (player->shield_deadly_time_left > 0)
11354 player->shield_deadly_time_left--;
11358 if (!game.LevelSolved && !level.use_step_counter)
11366 if (TimeLeft <= 10 && setup.time_limit)
11367 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11369 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11370 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11372 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11374 if (!TimeLeft && setup.time_limit)
11376 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11377 game_em.lev->killed_out_of_time = TRUE;
11379 for (i = 0; i < MAX_PLAYERS; i++)
11380 KillPlayer(&stored_player[i]);
11383 else if (game.no_time_limit && !game.all_players_gone)
11385 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11388 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11391 if (tape.recording || tape.playing)
11392 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11395 if (tape.recording || tape.playing)
11396 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11398 UpdateAndDisplayGameControlValues();
11401 void AdvanceFrameAndPlayerCounters(int player_nr)
11405 // advance frame counters (global frame counter and time frame counter)
11409 // advance player counters (counters for move delay, move animation etc.)
11410 for (i = 0; i < MAX_PLAYERS; i++)
11412 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11413 int move_delay_value = stored_player[i].move_delay_value;
11414 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11416 if (!advance_player_counters) // not all players may be affected
11419 if (move_frames == 0) // less than one move per game frame
11421 int stepsize = TILEX / move_delay_value;
11422 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11423 int count = (stored_player[i].is_moving ?
11424 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11426 if (count % delay == 0)
11430 stored_player[i].Frame += move_frames;
11432 if (stored_player[i].MovPos != 0)
11433 stored_player[i].StepFrame += move_frames;
11435 if (stored_player[i].move_delay > 0)
11436 stored_player[i].move_delay--;
11438 // due to bugs in previous versions, counter must count up, not down
11439 if (stored_player[i].push_delay != -1)
11440 stored_player[i].push_delay++;
11442 if (stored_player[i].drop_delay > 0)
11443 stored_player[i].drop_delay--;
11445 if (stored_player[i].is_dropping_pressed)
11446 stored_player[i].drop_pressed_delay++;
11450 void StartGameActions(boolean init_network_game, boolean record_tape,
11453 unsigned int new_random_seed = InitRND(random_seed);
11456 TapeStartRecording(new_random_seed);
11458 if (init_network_game)
11460 SendToServer_LevelFile();
11461 SendToServer_StartPlaying();
11469 static void GameActionsExt(void)
11472 static unsigned int game_frame_delay = 0;
11474 unsigned int game_frame_delay_value;
11475 byte *recorded_player_action;
11476 byte summarized_player_action = 0;
11477 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11480 // detect endless loops, caused by custom element programming
11481 if (recursion_loop_detected && recursion_loop_depth == 0)
11483 char *message = getStringCat3("Internal Error! Element ",
11484 EL_NAME(recursion_loop_element),
11485 " caused endless loop! Quit the game?");
11487 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11488 EL_NAME(recursion_loop_element));
11490 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11492 recursion_loop_detected = FALSE; // if game should be continued
11499 if (game.restart_level)
11500 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11502 CheckLevelSolved();
11504 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11507 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11510 if (game_status != GAME_MODE_PLAYING) // status might have changed
11513 game_frame_delay_value =
11514 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11516 if (tape.playing && tape.warp_forward && !tape.pausing)
11517 game_frame_delay_value = 0;
11519 SetVideoFrameDelay(game_frame_delay_value);
11521 // (de)activate virtual buttons depending on current game status
11522 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11524 if (game.all_players_gone) // if no players there to be controlled anymore
11525 SetOverlayActive(FALSE);
11526 else if (!tape.playing) // if game continues after tape stopped playing
11527 SetOverlayActive(TRUE);
11532 // ---------- main game synchronization point ----------
11534 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11536 printf("::: skip == %d\n", skip);
11539 // ---------- main game synchronization point ----------
11541 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11545 if (network_playing && !network_player_action_received)
11547 // try to get network player actions in time
11549 // last chance to get network player actions without main loop delay
11550 HandleNetworking();
11552 // game was quit by network peer
11553 if (game_status != GAME_MODE_PLAYING)
11556 // check if network player actions still missing and game still running
11557 if (!network_player_action_received && !checkGameEnded())
11558 return; // failed to get network player actions in time
11560 // do not yet reset "network_player_action_received" (for tape.pausing)
11566 // at this point we know that we really continue executing the game
11568 network_player_action_received = FALSE;
11570 // when playing tape, read previously recorded player input from tape data
11571 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11573 local_player->effective_mouse_action = local_player->mouse_action;
11575 if (recorded_player_action != NULL)
11576 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11577 recorded_player_action);
11579 // TapePlayAction() may return NULL when toggling to "pause before death"
11583 if (tape.set_centered_player)
11585 game.centered_player_nr_next = tape.centered_player_nr_next;
11586 game.set_centered_player = TRUE;
11589 for (i = 0; i < MAX_PLAYERS; i++)
11591 summarized_player_action |= stored_player[i].action;
11593 if (!network_playing && (game.team_mode || tape.playing))
11594 stored_player[i].effective_action = stored_player[i].action;
11597 if (network_playing && !checkGameEnded())
11598 SendToServer_MovePlayer(summarized_player_action);
11600 // summarize all actions at local players mapped input device position
11601 // (this allows using different input devices in single player mode)
11602 if (!network.enabled && !game.team_mode)
11603 stored_player[map_player_action[local_player->index_nr]].effective_action =
11604 summarized_player_action;
11606 // summarize all actions at centered player in local team mode
11607 if (tape.recording &&
11608 setup.team_mode && !network.enabled &&
11609 setup.input_on_focus &&
11610 game.centered_player_nr != -1)
11612 for (i = 0; i < MAX_PLAYERS; i++)
11613 stored_player[map_player_action[i]].effective_action =
11614 (i == game.centered_player_nr ? summarized_player_action : 0);
11617 if (recorded_player_action != NULL)
11618 for (i = 0; i < MAX_PLAYERS; i++)
11619 stored_player[i].effective_action = recorded_player_action[i];
11621 for (i = 0; i < MAX_PLAYERS; i++)
11623 tape_action[i] = stored_player[i].effective_action;
11625 /* (this may happen in the RND game engine if a player was not present on
11626 the playfield on level start, but appeared later from a custom element */
11627 if (setup.team_mode &&
11630 !tape.player_participates[i])
11631 tape.player_participates[i] = TRUE;
11634 SetTapeActionFromMouseAction(tape_action,
11635 &local_player->effective_mouse_action);
11637 // only record actions from input devices, but not programmed actions
11638 if (tape.recording)
11639 TapeRecordAction(tape_action);
11641 // remember if game was played (especially after tape stopped playing)
11642 if (!tape.playing && summarized_player_action)
11643 game.GamePlayed = TRUE;
11645 #if USE_NEW_PLAYER_ASSIGNMENTS
11646 // !!! also map player actions in single player mode !!!
11647 // if (game.team_mode)
11650 byte mapped_action[MAX_PLAYERS];
11652 #if DEBUG_PLAYER_ACTIONS
11654 for (i = 0; i < MAX_PLAYERS; i++)
11655 printf(" %d, ", stored_player[i].effective_action);
11658 for (i = 0; i < MAX_PLAYERS; i++)
11659 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11661 for (i = 0; i < MAX_PLAYERS; i++)
11662 stored_player[i].effective_action = mapped_action[i];
11664 #if DEBUG_PLAYER_ACTIONS
11666 for (i = 0; i < MAX_PLAYERS; i++)
11667 printf(" %d, ", stored_player[i].effective_action);
11671 #if DEBUG_PLAYER_ACTIONS
11675 for (i = 0; i < MAX_PLAYERS; i++)
11676 printf(" %d, ", stored_player[i].effective_action);
11682 for (i = 0; i < MAX_PLAYERS; i++)
11684 // allow engine snapshot in case of changed movement attempt
11685 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11686 (stored_player[i].effective_action & KEY_MOTION))
11687 game.snapshot.changed_action = TRUE;
11689 // allow engine snapshot in case of snapping/dropping attempt
11690 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11691 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11692 game.snapshot.changed_action = TRUE;
11694 game.snapshot.last_action[i] = stored_player[i].effective_action;
11697 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11699 GameActions_EM_Main();
11701 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11703 GameActions_SP_Main();
11705 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11707 GameActions_MM_Main();
11711 GameActions_RND_Main();
11714 BlitScreenToBitmap(backbuffer);
11716 CheckLevelSolved();
11719 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11721 if (global.show_frames_per_second)
11723 static unsigned int fps_counter = 0;
11724 static int fps_frames = 0;
11725 unsigned int fps_delay_ms = Counter() - fps_counter;
11729 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11731 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11734 fps_counter = Counter();
11736 // always draw FPS to screen after FPS value was updated
11737 redraw_mask |= REDRAW_FPS;
11740 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11741 if (GetDrawDeactivationMask() == REDRAW_NONE)
11742 redraw_mask |= REDRAW_FPS;
11746 static void GameActions_CheckSaveEngineSnapshot(void)
11748 if (!game.snapshot.save_snapshot)
11751 // clear flag for saving snapshot _before_ saving snapshot
11752 game.snapshot.save_snapshot = FALSE;
11754 SaveEngineSnapshotToList();
11757 void GameActions(void)
11761 GameActions_CheckSaveEngineSnapshot();
11764 void GameActions_EM_Main(void)
11766 byte effective_action[MAX_PLAYERS];
11767 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11770 for (i = 0; i < MAX_PLAYERS; i++)
11771 effective_action[i] = stored_player[i].effective_action;
11773 GameActions_EM(effective_action, warp_mode);
11776 void GameActions_SP_Main(void)
11778 byte effective_action[MAX_PLAYERS];
11779 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11782 for (i = 0; i < MAX_PLAYERS; i++)
11783 effective_action[i] = stored_player[i].effective_action;
11785 GameActions_SP(effective_action, warp_mode);
11787 for (i = 0; i < MAX_PLAYERS; i++)
11789 if (stored_player[i].force_dropping)
11790 stored_player[i].action |= KEY_BUTTON_DROP;
11792 stored_player[i].force_dropping = FALSE;
11796 void GameActions_MM_Main(void)
11798 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11800 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11803 void GameActions_RND_Main(void)
11808 void GameActions_RND(void)
11810 static struct MouseActionInfo mouse_action_last = { 0 };
11811 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11812 int magic_wall_x = 0, magic_wall_y = 0;
11813 int i, x, y, element, graphic, last_gfx_frame;
11815 InitPlayfieldScanModeVars();
11817 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11819 SCAN_PLAYFIELD(x, y)
11821 ChangeCount[x][y] = 0;
11822 ChangeEvent[x][y] = -1;
11826 if (game.set_centered_player)
11828 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11830 // switching to "all players" only possible if all players fit to screen
11831 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11833 game.centered_player_nr_next = game.centered_player_nr;
11834 game.set_centered_player = FALSE;
11837 // do not switch focus to non-existing (or non-active) player
11838 if (game.centered_player_nr_next >= 0 &&
11839 !stored_player[game.centered_player_nr_next].active)
11841 game.centered_player_nr_next = game.centered_player_nr;
11842 game.set_centered_player = FALSE;
11846 if (game.set_centered_player &&
11847 ScreenMovPos == 0) // screen currently aligned at tile position
11851 if (game.centered_player_nr_next == -1)
11853 setScreenCenteredToAllPlayers(&sx, &sy);
11857 sx = stored_player[game.centered_player_nr_next].jx;
11858 sy = stored_player[game.centered_player_nr_next].jy;
11861 game.centered_player_nr = game.centered_player_nr_next;
11862 game.set_centered_player = FALSE;
11864 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11865 DrawGameDoorValues();
11868 for (i = 0; i < MAX_PLAYERS; i++)
11870 int actual_player_action = stored_player[i].effective_action;
11873 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11874 - rnd_equinox_tetrachloride 048
11875 - rnd_equinox_tetrachloride_ii 096
11876 - rnd_emanuel_schmieg 002
11877 - doctor_sloan_ww 001, 020
11879 if (stored_player[i].MovPos == 0)
11880 CheckGravityMovement(&stored_player[i]);
11883 // overwrite programmed action with tape action
11884 if (stored_player[i].programmed_action)
11885 actual_player_action = stored_player[i].programmed_action;
11887 PlayerActions(&stored_player[i], actual_player_action);
11889 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11892 ScrollScreen(NULL, SCROLL_GO_ON);
11894 /* for backwards compatibility, the following code emulates a fixed bug that
11895 occured when pushing elements (causing elements that just made their last
11896 pushing step to already (if possible) make their first falling step in the
11897 same game frame, which is bad); this code is also needed to use the famous
11898 "spring push bug" which is used in older levels and might be wanted to be
11899 used also in newer levels, but in this case the buggy pushing code is only
11900 affecting the "spring" element and no other elements */
11902 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11904 for (i = 0; i < MAX_PLAYERS; i++)
11906 struct PlayerInfo *player = &stored_player[i];
11907 int x = player->jx;
11908 int y = player->jy;
11910 if (player->active && player->is_pushing && player->is_moving &&
11912 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11913 Feld[x][y] == EL_SPRING))
11915 ContinueMoving(x, y);
11917 // continue moving after pushing (this is actually a bug)
11918 if (!IS_MOVING(x, y))
11919 Stop[x][y] = FALSE;
11924 SCAN_PLAYFIELD(x, y)
11926 Last[x][y] = Feld[x][y];
11928 ChangeCount[x][y] = 0;
11929 ChangeEvent[x][y] = -1;
11931 // this must be handled before main playfield loop
11932 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11935 if (MovDelay[x][y] <= 0)
11939 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11942 if (MovDelay[x][y] <= 0)
11945 TEST_DrawLevelField(x, y);
11947 TestIfElementTouchesCustomElement(x, y); // for empty space
11952 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11954 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11955 printf("GameActions(): This should never happen!\n");
11957 ChangePage[x][y] = -1;
11961 Stop[x][y] = FALSE;
11962 if (WasJustMoving[x][y] > 0)
11963 WasJustMoving[x][y]--;
11964 if (WasJustFalling[x][y] > 0)
11965 WasJustFalling[x][y]--;
11966 if (CheckCollision[x][y] > 0)
11967 CheckCollision[x][y]--;
11968 if (CheckImpact[x][y] > 0)
11969 CheckImpact[x][y]--;
11973 /* reset finished pushing action (not done in ContinueMoving() to allow
11974 continuous pushing animation for elements with zero push delay) */
11975 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11977 ResetGfxAnimation(x, y);
11978 TEST_DrawLevelField(x, y);
11982 if (IS_BLOCKED(x, y))
11986 Blocked2Moving(x, y, &oldx, &oldy);
11987 if (!IS_MOVING(oldx, oldy))
11989 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11990 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11991 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11992 printf("GameActions(): This should never happen!\n");
11998 if (mouse_action.button)
12000 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12002 x = mouse_action.lx;
12003 y = mouse_action.ly;
12004 element = Feld[x][y];
12008 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12009 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12012 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12013 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12016 SCAN_PLAYFIELD(x, y)
12018 element = Feld[x][y];
12019 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12020 last_gfx_frame = GfxFrame[x][y];
12022 ResetGfxFrame(x, y);
12024 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12025 DrawLevelGraphicAnimation(x, y, graphic);
12027 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12028 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12029 ResetRandomAnimationValue(x, y);
12031 SetRandomAnimationValue(x, y);
12033 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12035 if (IS_INACTIVE(element))
12037 if (IS_ANIMATED(graphic))
12038 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12043 // this may take place after moving, so 'element' may have changed
12044 if (IS_CHANGING(x, y) &&
12045 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12047 int page = element_info[element].event_page_nr[CE_DELAY];
12049 HandleElementChange(x, y, page);
12051 element = Feld[x][y];
12052 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12055 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12059 element = Feld[x][y];
12060 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12062 if (IS_ANIMATED(graphic) &&
12063 !IS_MOVING(x, y) &&
12065 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12067 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12068 TEST_DrawTwinkleOnField(x, y);
12070 else if (element == EL_ACID)
12073 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12075 else if ((element == EL_EXIT_OPEN ||
12076 element == EL_EM_EXIT_OPEN ||
12077 element == EL_SP_EXIT_OPEN ||
12078 element == EL_STEEL_EXIT_OPEN ||
12079 element == EL_EM_STEEL_EXIT_OPEN ||
12080 element == EL_SP_TERMINAL ||
12081 element == EL_SP_TERMINAL_ACTIVE ||
12082 element == EL_EXTRA_TIME ||
12083 element == EL_SHIELD_NORMAL ||
12084 element == EL_SHIELD_DEADLY) &&
12085 IS_ANIMATED(graphic))
12086 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12087 else if (IS_MOVING(x, y))
12088 ContinueMoving(x, y);
12089 else if (IS_ACTIVE_BOMB(element))
12090 CheckDynamite(x, y);
12091 else if (element == EL_AMOEBA_GROWING)
12092 AmoebeWaechst(x, y);
12093 else if (element == EL_AMOEBA_SHRINKING)
12094 AmoebaDisappearing(x, y);
12096 #if !USE_NEW_AMOEBA_CODE
12097 else if (IS_AMOEBALIVE(element))
12098 AmoebeAbleger(x, y);
12101 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12103 else if (element == EL_EXIT_CLOSED)
12105 else if (element == EL_EM_EXIT_CLOSED)
12107 else if (element == EL_STEEL_EXIT_CLOSED)
12108 CheckExitSteel(x, y);
12109 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12110 CheckExitSteelEM(x, y);
12111 else if (element == EL_SP_EXIT_CLOSED)
12113 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12114 element == EL_EXPANDABLE_STEELWALL_GROWING)
12115 MauerWaechst(x, y);
12116 else if (element == EL_EXPANDABLE_WALL ||
12117 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12118 element == EL_EXPANDABLE_WALL_VERTICAL ||
12119 element == EL_EXPANDABLE_WALL_ANY ||
12120 element == EL_BD_EXPANDABLE_WALL)
12121 MauerAbleger(x, y);
12122 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12123 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12124 element == EL_EXPANDABLE_STEELWALL_ANY)
12125 MauerAblegerStahl(x, y);
12126 else if (element == EL_FLAMES)
12127 CheckForDragon(x, y);
12128 else if (element == EL_EXPLOSION)
12129 ; // drawing of correct explosion animation is handled separately
12130 else if (element == EL_ELEMENT_SNAPPING ||
12131 element == EL_DIAGONAL_SHRINKING ||
12132 element == EL_DIAGONAL_GROWING)
12134 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12136 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12138 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12139 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12141 if (IS_BELT_ACTIVE(element))
12142 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12144 if (game.magic_wall_active)
12146 int jx = local_player->jx, jy = local_player->jy;
12148 // play the element sound at the position nearest to the player
12149 if ((element == EL_MAGIC_WALL_FULL ||
12150 element == EL_MAGIC_WALL_ACTIVE ||
12151 element == EL_MAGIC_WALL_EMPTYING ||
12152 element == EL_BD_MAGIC_WALL_FULL ||
12153 element == EL_BD_MAGIC_WALL_ACTIVE ||
12154 element == EL_BD_MAGIC_WALL_EMPTYING ||
12155 element == EL_DC_MAGIC_WALL_FULL ||
12156 element == EL_DC_MAGIC_WALL_ACTIVE ||
12157 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12158 ABS(x - jx) + ABS(y - jy) <
12159 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12167 #if USE_NEW_AMOEBA_CODE
12168 // new experimental amoeba growth stuff
12169 if (!(FrameCounter % 8))
12171 static unsigned int random = 1684108901;
12173 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12175 x = RND(lev_fieldx);
12176 y = RND(lev_fieldy);
12177 element = Feld[x][y];
12179 if (!IS_PLAYER(x,y) &&
12180 (element == EL_EMPTY ||
12181 CAN_GROW_INTO(element) ||
12182 element == EL_QUICKSAND_EMPTY ||
12183 element == EL_QUICKSAND_FAST_EMPTY ||
12184 element == EL_ACID_SPLASH_LEFT ||
12185 element == EL_ACID_SPLASH_RIGHT))
12187 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12188 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12189 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12190 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12191 Feld[x][y] = EL_AMOEBA_DROP;
12194 random = random * 129 + 1;
12199 game.explosions_delayed = FALSE;
12201 SCAN_PLAYFIELD(x, y)
12203 element = Feld[x][y];
12205 if (ExplodeField[x][y])
12206 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12207 else if (element == EL_EXPLOSION)
12208 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12210 ExplodeField[x][y] = EX_TYPE_NONE;
12213 game.explosions_delayed = TRUE;
12215 if (game.magic_wall_active)
12217 if (!(game.magic_wall_time_left % 4))
12219 int element = Feld[magic_wall_x][magic_wall_y];
12221 if (element == EL_BD_MAGIC_WALL_FULL ||
12222 element == EL_BD_MAGIC_WALL_ACTIVE ||
12223 element == EL_BD_MAGIC_WALL_EMPTYING)
12224 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12225 else if (element == EL_DC_MAGIC_WALL_FULL ||
12226 element == EL_DC_MAGIC_WALL_ACTIVE ||
12227 element == EL_DC_MAGIC_WALL_EMPTYING)
12228 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12230 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12233 if (game.magic_wall_time_left > 0)
12235 game.magic_wall_time_left--;
12237 if (!game.magic_wall_time_left)
12239 SCAN_PLAYFIELD(x, y)
12241 element = Feld[x][y];
12243 if (element == EL_MAGIC_WALL_ACTIVE ||
12244 element == EL_MAGIC_WALL_FULL)
12246 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12247 TEST_DrawLevelField(x, y);
12249 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12250 element == EL_BD_MAGIC_WALL_FULL)
12252 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12253 TEST_DrawLevelField(x, y);
12255 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12256 element == EL_DC_MAGIC_WALL_FULL)
12258 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12259 TEST_DrawLevelField(x, y);
12263 game.magic_wall_active = FALSE;
12268 if (game.light_time_left > 0)
12270 game.light_time_left--;
12272 if (game.light_time_left == 0)
12273 RedrawAllLightSwitchesAndInvisibleElements();
12276 if (game.timegate_time_left > 0)
12278 game.timegate_time_left--;
12280 if (game.timegate_time_left == 0)
12281 CloseAllOpenTimegates();
12284 if (game.lenses_time_left > 0)
12286 game.lenses_time_left--;
12288 if (game.lenses_time_left == 0)
12289 RedrawAllInvisibleElementsForLenses();
12292 if (game.magnify_time_left > 0)
12294 game.magnify_time_left--;
12296 if (game.magnify_time_left == 0)
12297 RedrawAllInvisibleElementsForMagnifier();
12300 for (i = 0; i < MAX_PLAYERS; i++)
12302 struct PlayerInfo *player = &stored_player[i];
12304 if (SHIELD_ON(player))
12306 if (player->shield_deadly_time_left)
12307 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12308 else if (player->shield_normal_time_left)
12309 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12313 #if USE_DELAYED_GFX_REDRAW
12314 SCAN_PLAYFIELD(x, y)
12316 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12318 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12319 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12321 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12322 DrawLevelField(x, y);
12324 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12325 DrawLevelFieldCrumbled(x, y);
12327 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12328 DrawLevelFieldCrumbledNeighbours(x, y);
12330 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12331 DrawTwinkleOnField(x, y);
12334 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12339 PlayAllPlayersSound();
12341 for (i = 0; i < MAX_PLAYERS; i++)
12343 struct PlayerInfo *player = &stored_player[i];
12345 if (player->show_envelope != 0 && (!player->active ||
12346 player->MovPos == 0))
12348 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12350 player->show_envelope = 0;
12354 // use random number generator in every frame to make it less predictable
12355 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12358 mouse_action_last = mouse_action;
12361 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12363 int min_x = x, min_y = y, max_x = x, max_y = y;
12366 for (i = 0; i < MAX_PLAYERS; i++)
12368 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12370 if (!stored_player[i].active || &stored_player[i] == player)
12373 min_x = MIN(min_x, jx);
12374 min_y = MIN(min_y, jy);
12375 max_x = MAX(max_x, jx);
12376 max_y = MAX(max_y, jy);
12379 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12382 static boolean AllPlayersInVisibleScreen(void)
12386 for (i = 0; i < MAX_PLAYERS; i++)
12388 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12390 if (!stored_player[i].active)
12393 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12400 void ScrollLevel(int dx, int dy)
12402 int scroll_offset = 2 * TILEX_VAR;
12405 BlitBitmap(drawto_field, drawto_field,
12406 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12407 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12408 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12409 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12410 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12411 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12415 x = (dx == 1 ? BX1 : BX2);
12416 for (y = BY1; y <= BY2; y++)
12417 DrawScreenField(x, y);
12422 y = (dy == 1 ? BY1 : BY2);
12423 for (x = BX1; x <= BX2; x++)
12424 DrawScreenField(x, y);
12427 redraw_mask |= REDRAW_FIELD;
12430 static boolean canFallDown(struct PlayerInfo *player)
12432 int jx = player->jx, jy = player->jy;
12434 return (IN_LEV_FIELD(jx, jy + 1) &&
12435 (IS_FREE(jx, jy + 1) ||
12436 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12437 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12438 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12441 static boolean canPassField(int x, int y, int move_dir)
12443 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12444 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12445 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12446 int nextx = x + dx;
12447 int nexty = y + dy;
12448 int element = Feld[x][y];
12450 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12451 !CAN_MOVE(element) &&
12452 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12453 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12454 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12457 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12459 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12460 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12461 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12465 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12466 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12467 (IS_DIGGABLE(Feld[newx][newy]) ||
12468 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12469 canPassField(newx, newy, move_dir)));
12472 static void CheckGravityMovement(struct PlayerInfo *player)
12474 if (player->gravity && !player->programmed_action)
12476 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12477 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12478 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12479 int jx = player->jx, jy = player->jy;
12480 boolean player_is_moving_to_valid_field =
12481 (!player_is_snapping &&
12482 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12483 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12484 boolean player_can_fall_down = canFallDown(player);
12486 if (player_can_fall_down &&
12487 !player_is_moving_to_valid_field)
12488 player->programmed_action = MV_DOWN;
12492 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12494 return CheckGravityMovement(player);
12496 if (player->gravity && !player->programmed_action)
12498 int jx = player->jx, jy = player->jy;
12499 boolean field_under_player_is_free =
12500 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12501 boolean player_is_standing_on_valid_field =
12502 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12503 (IS_WALKABLE(Feld[jx][jy]) &&
12504 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12506 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12507 player->programmed_action = MV_DOWN;
12512 MovePlayerOneStep()
12513 -----------------------------------------------------------------------------
12514 dx, dy: direction (non-diagonal) to try to move the player to
12515 real_dx, real_dy: direction as read from input device (can be diagonal)
12518 boolean MovePlayerOneStep(struct PlayerInfo *player,
12519 int dx, int dy, int real_dx, int real_dy)
12521 int jx = player->jx, jy = player->jy;
12522 int new_jx = jx + dx, new_jy = jy + dy;
12524 boolean player_can_move = !player->cannot_move;
12526 if (!player->active || (!dx && !dy))
12527 return MP_NO_ACTION;
12529 player->MovDir = (dx < 0 ? MV_LEFT :
12530 dx > 0 ? MV_RIGHT :
12532 dy > 0 ? MV_DOWN : MV_NONE);
12534 if (!IN_LEV_FIELD(new_jx, new_jy))
12535 return MP_NO_ACTION;
12537 if (!player_can_move)
12539 if (player->MovPos == 0)
12541 player->is_moving = FALSE;
12542 player->is_digging = FALSE;
12543 player->is_collecting = FALSE;
12544 player->is_snapping = FALSE;
12545 player->is_pushing = FALSE;
12549 if (!network.enabled && game.centered_player_nr == -1 &&
12550 !AllPlayersInSight(player, new_jx, new_jy))
12551 return MP_NO_ACTION;
12553 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12554 if (can_move != MP_MOVING)
12557 // check if DigField() has caused relocation of the player
12558 if (player->jx != jx || player->jy != jy)
12559 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12561 StorePlayer[jx][jy] = 0;
12562 player->last_jx = jx;
12563 player->last_jy = jy;
12564 player->jx = new_jx;
12565 player->jy = new_jy;
12566 StorePlayer[new_jx][new_jy] = player->element_nr;
12568 if (player->move_delay_value_next != -1)
12570 player->move_delay_value = player->move_delay_value_next;
12571 player->move_delay_value_next = -1;
12575 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12577 player->step_counter++;
12579 PlayerVisit[jx][jy] = FrameCounter;
12581 player->is_moving = TRUE;
12584 // should better be called in MovePlayer(), but this breaks some tapes
12585 ScrollPlayer(player, SCROLL_INIT);
12591 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12593 int jx = player->jx, jy = player->jy;
12594 int old_jx = jx, old_jy = jy;
12595 int moved = MP_NO_ACTION;
12597 if (!player->active)
12602 if (player->MovPos == 0)
12604 player->is_moving = FALSE;
12605 player->is_digging = FALSE;
12606 player->is_collecting = FALSE;
12607 player->is_snapping = FALSE;
12608 player->is_pushing = FALSE;
12614 if (player->move_delay > 0)
12617 player->move_delay = -1; // set to "uninitialized" value
12619 // store if player is automatically moved to next field
12620 player->is_auto_moving = (player->programmed_action != MV_NONE);
12622 // remove the last programmed player action
12623 player->programmed_action = 0;
12625 if (player->MovPos)
12627 // should only happen if pre-1.2 tape recordings are played
12628 // this is only for backward compatibility
12630 int original_move_delay_value = player->move_delay_value;
12633 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12637 // scroll remaining steps with finest movement resolution
12638 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12640 while (player->MovPos)
12642 ScrollPlayer(player, SCROLL_GO_ON);
12643 ScrollScreen(NULL, SCROLL_GO_ON);
12645 AdvanceFrameAndPlayerCounters(player->index_nr);
12648 BackToFront_WithFrameDelay(0);
12651 player->move_delay_value = original_move_delay_value;
12654 player->is_active = FALSE;
12656 if (player->last_move_dir & MV_HORIZONTAL)
12658 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12659 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12663 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12664 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12667 if (!moved && !player->is_active)
12669 player->is_moving = FALSE;
12670 player->is_digging = FALSE;
12671 player->is_collecting = FALSE;
12672 player->is_snapping = FALSE;
12673 player->is_pushing = FALSE;
12679 if (moved & MP_MOVING && !ScreenMovPos &&
12680 (player->index_nr == game.centered_player_nr ||
12681 game.centered_player_nr == -1))
12683 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12685 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12687 // actual player has left the screen -- scroll in that direction
12688 if (jx != old_jx) // player has moved horizontally
12689 scroll_x += (jx - old_jx);
12690 else // player has moved vertically
12691 scroll_y += (jy - old_jy);
12695 int offset_raw = game.scroll_delay_value;
12697 if (jx != old_jx) // player has moved horizontally
12699 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12700 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12701 int new_scroll_x = jx - MIDPOSX + offset_x;
12703 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12704 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12705 scroll_x = new_scroll_x;
12707 // don't scroll over playfield boundaries
12708 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12710 // don't scroll more than one field at a time
12711 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12713 // don't scroll against the player's moving direction
12714 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12715 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12716 scroll_x = old_scroll_x;
12718 else // player has moved vertically
12720 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12721 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12722 int new_scroll_y = jy - MIDPOSY + offset_y;
12724 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12725 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12726 scroll_y = new_scroll_y;
12728 // don't scroll over playfield boundaries
12729 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12731 // don't scroll more than one field at a time
12732 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12734 // don't scroll against the player's moving direction
12735 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12736 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12737 scroll_y = old_scroll_y;
12741 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12743 if (!network.enabled && game.centered_player_nr == -1 &&
12744 !AllPlayersInVisibleScreen())
12746 scroll_x = old_scroll_x;
12747 scroll_y = old_scroll_y;
12751 ScrollScreen(player, SCROLL_INIT);
12752 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12757 player->StepFrame = 0;
12759 if (moved & MP_MOVING)
12761 if (old_jx != jx && old_jy == jy)
12762 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12763 else if (old_jx == jx && old_jy != jy)
12764 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12766 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12768 player->last_move_dir = player->MovDir;
12769 player->is_moving = TRUE;
12770 player->is_snapping = FALSE;
12771 player->is_switching = FALSE;
12772 player->is_dropping = FALSE;
12773 player->is_dropping_pressed = FALSE;
12774 player->drop_pressed_delay = 0;
12777 // should better be called here than above, but this breaks some tapes
12778 ScrollPlayer(player, SCROLL_INIT);
12783 CheckGravityMovementWhenNotMoving(player);
12785 player->is_moving = FALSE;
12787 /* at this point, the player is allowed to move, but cannot move right now
12788 (e.g. because of something blocking the way) -- ensure that the player
12789 is also allowed to move in the next frame (in old versions before 3.1.1,
12790 the player was forced to wait again for eight frames before next try) */
12792 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12793 player->move_delay = 0; // allow direct movement in the next frame
12796 if (player->move_delay == -1) // not yet initialized by DigField()
12797 player->move_delay = player->move_delay_value;
12799 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12801 TestIfPlayerTouchesBadThing(jx, jy);
12802 TestIfPlayerTouchesCustomElement(jx, jy);
12805 if (!player->active)
12806 RemovePlayer(player);
12811 void ScrollPlayer(struct PlayerInfo *player, int mode)
12813 int jx = player->jx, jy = player->jy;
12814 int last_jx = player->last_jx, last_jy = player->last_jy;
12815 int move_stepsize = TILEX / player->move_delay_value;
12817 if (!player->active)
12820 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12823 if (mode == SCROLL_INIT)
12825 player->actual_frame_counter = FrameCounter;
12826 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12828 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12829 Feld[last_jx][last_jy] == EL_EMPTY)
12831 int last_field_block_delay = 0; // start with no blocking at all
12832 int block_delay_adjustment = player->block_delay_adjustment;
12834 // if player blocks last field, add delay for exactly one move
12835 if (player->block_last_field)
12837 last_field_block_delay += player->move_delay_value;
12839 // when blocking enabled, prevent moving up despite gravity
12840 if (player->gravity && player->MovDir == MV_UP)
12841 block_delay_adjustment = -1;
12844 // add block delay adjustment (also possible when not blocking)
12845 last_field_block_delay += block_delay_adjustment;
12847 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12848 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12851 if (player->MovPos != 0) // player has not yet reached destination
12854 else if (!FrameReached(&player->actual_frame_counter, 1))
12857 if (player->MovPos != 0)
12859 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12860 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12862 // before DrawPlayer() to draw correct player graphic for this case
12863 if (player->MovPos == 0)
12864 CheckGravityMovement(player);
12867 if (player->MovPos == 0) // player reached destination field
12869 if (player->move_delay_reset_counter > 0)
12871 player->move_delay_reset_counter--;
12873 if (player->move_delay_reset_counter == 0)
12875 // continue with normal speed after quickly moving through gate
12876 HALVE_PLAYER_SPEED(player);
12878 // be able to make the next move without delay
12879 player->move_delay = 0;
12883 player->last_jx = jx;
12884 player->last_jy = jy;
12886 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12887 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12888 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12889 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12890 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12891 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12892 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12893 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12895 ExitPlayer(player);
12897 if (game.players_still_needed == 0 &&
12898 (game.friends_still_needed == 0 ||
12899 IS_SP_ELEMENT(Feld[jx][jy])))
12903 // this breaks one level: "machine", level 000
12905 int move_direction = player->MovDir;
12906 int enter_side = MV_DIR_OPPOSITE(move_direction);
12907 int leave_side = move_direction;
12908 int old_jx = last_jx;
12909 int old_jy = last_jy;
12910 int old_element = Feld[old_jx][old_jy];
12911 int new_element = Feld[jx][jy];
12913 if (IS_CUSTOM_ELEMENT(old_element))
12914 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12916 player->index_bit, leave_side);
12918 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12919 CE_PLAYER_LEAVES_X,
12920 player->index_bit, leave_side);
12922 if (IS_CUSTOM_ELEMENT(new_element))
12923 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12924 player->index_bit, enter_side);
12926 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12927 CE_PLAYER_ENTERS_X,
12928 player->index_bit, enter_side);
12930 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12931 CE_MOVE_OF_X, move_direction);
12934 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12936 TestIfPlayerTouchesBadThing(jx, jy);
12937 TestIfPlayerTouchesCustomElement(jx, jy);
12939 /* needed because pushed element has not yet reached its destination,
12940 so it would trigger a change event at its previous field location */
12941 if (!player->is_pushing)
12942 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12944 if (!player->active)
12945 RemovePlayer(player);
12948 if (!game.LevelSolved && level.use_step_counter)
12958 if (TimeLeft <= 10 && setup.time_limit)
12959 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12961 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12963 DisplayGameControlValues();
12965 if (!TimeLeft && setup.time_limit)
12966 for (i = 0; i < MAX_PLAYERS; i++)
12967 KillPlayer(&stored_player[i]);
12969 else if (game.no_time_limit && !game.all_players_gone)
12971 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12973 DisplayGameControlValues();
12977 if (tape.single_step && tape.recording && !tape.pausing &&
12978 !player->programmed_action)
12979 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12981 if (!player->programmed_action)
12982 CheckSaveEngineSnapshot(player);
12986 void ScrollScreen(struct PlayerInfo *player, int mode)
12988 static unsigned int screen_frame_counter = 0;
12990 if (mode == SCROLL_INIT)
12992 // set scrolling step size according to actual player's moving speed
12993 ScrollStepSize = TILEX / player->move_delay_value;
12995 screen_frame_counter = FrameCounter;
12996 ScreenMovDir = player->MovDir;
12997 ScreenMovPos = player->MovPos;
12998 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13001 else if (!FrameReached(&screen_frame_counter, 1))
13006 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13007 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13008 redraw_mask |= REDRAW_FIELD;
13011 ScreenMovDir = MV_NONE;
13014 void TestIfPlayerTouchesCustomElement(int x, int y)
13016 static int xy[4][2] =
13023 static int trigger_sides[4][2] =
13025 // center side border side
13026 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13027 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13028 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13029 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13031 static int touch_dir[4] =
13033 MV_LEFT | MV_RIGHT,
13038 int center_element = Feld[x][y]; // should always be non-moving!
13041 for (i = 0; i < NUM_DIRECTIONS; i++)
13043 int xx = x + xy[i][0];
13044 int yy = y + xy[i][1];
13045 int center_side = trigger_sides[i][0];
13046 int border_side = trigger_sides[i][1];
13047 int border_element;
13049 if (!IN_LEV_FIELD(xx, yy))
13052 if (IS_PLAYER(x, y)) // player found at center element
13054 struct PlayerInfo *player = PLAYERINFO(x, y);
13056 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13057 border_element = Feld[xx][yy]; // may be moving!
13058 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13059 border_element = Feld[xx][yy];
13060 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13061 border_element = MovingOrBlocked2Element(xx, yy);
13063 continue; // center and border element do not touch
13065 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13066 player->index_bit, border_side);
13067 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13068 CE_PLAYER_TOUCHES_X,
13069 player->index_bit, border_side);
13072 /* use player element that is initially defined in the level playfield,
13073 not the player element that corresponds to the runtime player number
13074 (example: a level that contains EL_PLAYER_3 as the only player would
13075 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13076 int player_element = PLAYERINFO(x, y)->initial_element;
13078 CheckElementChangeBySide(xx, yy, border_element, player_element,
13079 CE_TOUCHING_X, border_side);
13082 else if (IS_PLAYER(xx, yy)) // player found at border element
13084 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13086 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13088 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13089 continue; // center and border element do not touch
13092 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13093 player->index_bit, center_side);
13094 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13095 CE_PLAYER_TOUCHES_X,
13096 player->index_bit, center_side);
13099 /* use player element that is initially defined in the level playfield,
13100 not the player element that corresponds to the runtime player number
13101 (example: a level that contains EL_PLAYER_3 as the only player would
13102 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13103 int player_element = PLAYERINFO(xx, yy)->initial_element;
13105 CheckElementChangeBySide(x, y, center_element, player_element,
13106 CE_TOUCHING_X, center_side);
13114 void TestIfElementTouchesCustomElement(int x, int y)
13116 static int xy[4][2] =
13123 static int trigger_sides[4][2] =
13125 // center side border side
13126 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13127 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13128 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13129 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13131 static int touch_dir[4] =
13133 MV_LEFT | MV_RIGHT,
13138 boolean change_center_element = FALSE;
13139 int center_element = Feld[x][y]; // should always be non-moving!
13140 int border_element_old[NUM_DIRECTIONS];
13143 for (i = 0; i < NUM_DIRECTIONS; i++)
13145 int xx = x + xy[i][0];
13146 int yy = y + xy[i][1];
13147 int border_element;
13149 border_element_old[i] = -1;
13151 if (!IN_LEV_FIELD(xx, yy))
13154 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13155 border_element = Feld[xx][yy]; // may be moving!
13156 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13157 border_element = Feld[xx][yy];
13158 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13159 border_element = MovingOrBlocked2Element(xx, yy);
13161 continue; // center and border element do not touch
13163 border_element_old[i] = border_element;
13166 for (i = 0; i < NUM_DIRECTIONS; i++)
13168 int xx = x + xy[i][0];
13169 int yy = y + xy[i][1];
13170 int center_side = trigger_sides[i][0];
13171 int border_element = border_element_old[i];
13173 if (border_element == -1)
13176 // check for change of border element
13177 CheckElementChangeBySide(xx, yy, border_element, center_element,
13178 CE_TOUCHING_X, center_side);
13180 // (center element cannot be player, so we dont have to check this here)
13183 for (i = 0; i < NUM_DIRECTIONS; i++)
13185 int xx = x + xy[i][0];
13186 int yy = y + xy[i][1];
13187 int border_side = trigger_sides[i][1];
13188 int border_element = border_element_old[i];
13190 if (border_element == -1)
13193 // check for change of center element (but change it only once)
13194 if (!change_center_element)
13195 change_center_element =
13196 CheckElementChangeBySide(x, y, center_element, border_element,
13197 CE_TOUCHING_X, border_side);
13199 if (IS_PLAYER(xx, yy))
13201 /* use player element that is initially defined in the level playfield,
13202 not the player element that corresponds to the runtime player number
13203 (example: a level that contains EL_PLAYER_3 as the only player would
13204 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13205 int player_element = PLAYERINFO(xx, yy)->initial_element;
13207 CheckElementChangeBySide(x, y, center_element, player_element,
13208 CE_TOUCHING_X, border_side);
13213 void TestIfElementHitsCustomElement(int x, int y, int direction)
13215 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13216 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13217 int hitx = x + dx, hity = y + dy;
13218 int hitting_element = Feld[x][y];
13219 int touched_element;
13221 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13224 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13225 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13227 if (IN_LEV_FIELD(hitx, hity))
13229 int opposite_direction = MV_DIR_OPPOSITE(direction);
13230 int hitting_side = direction;
13231 int touched_side = opposite_direction;
13232 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13233 MovDir[hitx][hity] != direction ||
13234 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13240 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13241 CE_HITTING_X, touched_side);
13243 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13244 CE_HIT_BY_X, hitting_side);
13246 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13247 CE_HIT_BY_SOMETHING, opposite_direction);
13249 if (IS_PLAYER(hitx, hity))
13251 /* use player element that is initially defined in the level playfield,
13252 not the player element that corresponds to the runtime player number
13253 (example: a level that contains EL_PLAYER_3 as the only player would
13254 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13255 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13257 CheckElementChangeBySide(x, y, hitting_element, player_element,
13258 CE_HITTING_X, touched_side);
13263 // "hitting something" is also true when hitting the playfield border
13264 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13265 CE_HITTING_SOMETHING, direction);
13268 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13270 int i, kill_x = -1, kill_y = -1;
13272 int bad_element = -1;
13273 static int test_xy[4][2] =
13280 static int test_dir[4] =
13288 for (i = 0; i < NUM_DIRECTIONS; i++)
13290 int test_x, test_y, test_move_dir, test_element;
13292 test_x = good_x + test_xy[i][0];
13293 test_y = good_y + test_xy[i][1];
13295 if (!IN_LEV_FIELD(test_x, test_y))
13299 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13301 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13303 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13304 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13306 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13307 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13311 bad_element = test_element;
13317 if (kill_x != -1 || kill_y != -1)
13319 if (IS_PLAYER(good_x, good_y))
13321 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13323 if (player->shield_deadly_time_left > 0 &&
13324 !IS_INDESTRUCTIBLE(bad_element))
13325 Bang(kill_x, kill_y);
13326 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13327 KillPlayer(player);
13330 Bang(good_x, good_y);
13334 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13336 int i, kill_x = -1, kill_y = -1;
13337 int bad_element = Feld[bad_x][bad_y];
13338 static int test_xy[4][2] =
13345 static int touch_dir[4] =
13347 MV_LEFT | MV_RIGHT,
13352 static int test_dir[4] =
13360 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13363 for (i = 0; i < NUM_DIRECTIONS; i++)
13365 int test_x, test_y, test_move_dir, test_element;
13367 test_x = bad_x + test_xy[i][0];
13368 test_y = bad_y + test_xy[i][1];
13370 if (!IN_LEV_FIELD(test_x, test_y))
13374 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13376 test_element = Feld[test_x][test_y];
13378 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13379 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13381 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13382 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13384 // good thing is player or penguin that does not move away
13385 if (IS_PLAYER(test_x, test_y))
13387 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13389 if (bad_element == EL_ROBOT && player->is_moving)
13390 continue; // robot does not kill player if he is moving
13392 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13394 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13395 continue; // center and border element do not touch
13403 else if (test_element == EL_PENGUIN)
13413 if (kill_x != -1 || kill_y != -1)
13415 if (IS_PLAYER(kill_x, kill_y))
13417 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13419 if (player->shield_deadly_time_left > 0 &&
13420 !IS_INDESTRUCTIBLE(bad_element))
13421 Bang(bad_x, bad_y);
13422 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13423 KillPlayer(player);
13426 Bang(kill_x, kill_y);
13430 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13432 int bad_element = Feld[bad_x][bad_y];
13433 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13434 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13435 int test_x = bad_x + dx, test_y = bad_y + dy;
13436 int test_move_dir, test_element;
13437 int kill_x = -1, kill_y = -1;
13439 if (!IN_LEV_FIELD(test_x, test_y))
13443 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13445 test_element = Feld[test_x][test_y];
13447 if (test_move_dir != bad_move_dir)
13449 // good thing can be player or penguin that does not move away
13450 if (IS_PLAYER(test_x, test_y))
13452 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13454 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13455 player as being hit when he is moving towards the bad thing, because
13456 the "get hit by" condition would be lost after the player stops) */
13457 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13458 return; // player moves away from bad thing
13463 else if (test_element == EL_PENGUIN)
13470 if (kill_x != -1 || kill_y != -1)
13472 if (IS_PLAYER(kill_x, kill_y))
13474 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13476 if (player->shield_deadly_time_left > 0 &&
13477 !IS_INDESTRUCTIBLE(bad_element))
13478 Bang(bad_x, bad_y);
13479 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13480 KillPlayer(player);
13483 Bang(kill_x, kill_y);
13487 void TestIfPlayerTouchesBadThing(int x, int y)
13489 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13492 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13494 TestIfGoodThingHitsBadThing(x, y, move_dir);
13497 void TestIfBadThingTouchesPlayer(int x, int y)
13499 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13502 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13504 TestIfBadThingHitsGoodThing(x, y, move_dir);
13507 void TestIfFriendTouchesBadThing(int x, int y)
13509 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13512 void TestIfBadThingTouchesFriend(int x, int y)
13514 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13517 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13519 int i, kill_x = bad_x, kill_y = bad_y;
13520 static int xy[4][2] =
13528 for (i = 0; i < NUM_DIRECTIONS; i++)
13532 x = bad_x + xy[i][0];
13533 y = bad_y + xy[i][1];
13534 if (!IN_LEV_FIELD(x, y))
13537 element = Feld[x][y];
13538 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13539 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13547 if (kill_x != bad_x || kill_y != bad_y)
13548 Bang(bad_x, bad_y);
13551 void KillPlayer(struct PlayerInfo *player)
13553 int jx = player->jx, jy = player->jy;
13555 if (!player->active)
13559 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13560 player->killed, player->active, player->reanimated);
13563 /* the following code was introduced to prevent an infinite loop when calling
13565 -> CheckTriggeredElementChangeExt()
13566 -> ExecuteCustomElementAction()
13568 -> (infinitely repeating the above sequence of function calls)
13569 which occurs when killing the player while having a CE with the setting
13570 "kill player X when explosion of <player X>"; the solution using a new
13571 field "player->killed" was chosen for backwards compatibility, although
13572 clever use of the fields "player->active" etc. would probably also work */
13574 if (player->killed)
13578 player->killed = TRUE;
13580 // remove accessible field at the player's position
13581 Feld[jx][jy] = EL_EMPTY;
13583 // deactivate shield (else Bang()/Explode() would not work right)
13584 player->shield_normal_time_left = 0;
13585 player->shield_deadly_time_left = 0;
13588 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13589 player->killed, player->active, player->reanimated);
13595 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13596 player->killed, player->active, player->reanimated);
13599 if (player->reanimated) // killed player may have been reanimated
13600 player->killed = player->reanimated = FALSE;
13602 BuryPlayer(player);
13605 static void KillPlayerUnlessEnemyProtected(int x, int y)
13607 if (!PLAYER_ENEMY_PROTECTED(x, y))
13608 KillPlayer(PLAYERINFO(x, y));
13611 static void KillPlayerUnlessExplosionProtected(int x, int y)
13613 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13614 KillPlayer(PLAYERINFO(x, y));
13617 void BuryPlayer(struct PlayerInfo *player)
13619 int jx = player->jx, jy = player->jy;
13621 if (!player->active)
13624 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13625 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13627 RemovePlayer(player);
13629 player->buried = TRUE;
13631 if (game.all_players_gone)
13632 game.GameOver = TRUE;
13635 void RemovePlayer(struct PlayerInfo *player)
13637 int jx = player->jx, jy = player->jy;
13638 int i, found = FALSE;
13640 player->present = FALSE;
13641 player->active = FALSE;
13643 // required for some CE actions (even if the player is not active anymore)
13644 player->MovPos = 0;
13646 if (!ExplodeField[jx][jy])
13647 StorePlayer[jx][jy] = 0;
13649 if (player->is_moving)
13650 TEST_DrawLevelField(player->last_jx, player->last_jy);
13652 for (i = 0; i < MAX_PLAYERS; i++)
13653 if (stored_player[i].active)
13658 game.all_players_gone = TRUE;
13659 game.GameOver = TRUE;
13662 game.exit_x = game.robot_wheel_x = jx;
13663 game.exit_y = game.robot_wheel_y = jy;
13666 void ExitPlayer(struct PlayerInfo *player)
13668 DrawPlayer(player); // needed here only to cleanup last field
13669 RemovePlayer(player);
13671 if (game.players_still_needed > 0)
13672 game.players_still_needed--;
13675 static void setFieldForSnapping(int x, int y, int element, int direction)
13677 struct ElementInfo *ei = &element_info[element];
13678 int direction_bit = MV_DIR_TO_BIT(direction);
13679 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13680 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13681 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13683 Feld[x][y] = EL_ELEMENT_SNAPPING;
13684 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13686 ResetGfxAnimation(x, y);
13688 GfxElement[x][y] = element;
13689 GfxAction[x][y] = action;
13690 GfxDir[x][y] = direction;
13691 GfxFrame[x][y] = -1;
13695 =============================================================================
13696 checkDiagonalPushing()
13697 -----------------------------------------------------------------------------
13698 check if diagonal input device direction results in pushing of object
13699 (by checking if the alternative direction is walkable, diggable, ...)
13700 =============================================================================
13703 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13704 int x, int y, int real_dx, int real_dy)
13706 int jx, jy, dx, dy, xx, yy;
13708 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13711 // diagonal direction: check alternative direction
13716 xx = jx + (dx == 0 ? real_dx : 0);
13717 yy = jy + (dy == 0 ? real_dy : 0);
13719 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13723 =============================================================================
13725 -----------------------------------------------------------------------------
13726 x, y: field next to player (non-diagonal) to try to dig to
13727 real_dx, real_dy: direction as read from input device (can be diagonal)
13728 =============================================================================
13731 static int DigField(struct PlayerInfo *player,
13732 int oldx, int oldy, int x, int y,
13733 int real_dx, int real_dy, int mode)
13735 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13736 boolean player_was_pushing = player->is_pushing;
13737 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13738 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13739 int jx = oldx, jy = oldy;
13740 int dx = x - jx, dy = y - jy;
13741 int nextx = x + dx, nexty = y + dy;
13742 int move_direction = (dx == -1 ? MV_LEFT :
13743 dx == +1 ? MV_RIGHT :
13745 dy == +1 ? MV_DOWN : MV_NONE);
13746 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13747 int dig_side = MV_DIR_OPPOSITE(move_direction);
13748 int old_element = Feld[jx][jy];
13749 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13752 if (is_player) // function can also be called by EL_PENGUIN
13754 if (player->MovPos == 0)
13756 player->is_digging = FALSE;
13757 player->is_collecting = FALSE;
13760 if (player->MovPos == 0) // last pushing move finished
13761 player->is_pushing = FALSE;
13763 if (mode == DF_NO_PUSH) // player just stopped pushing
13765 player->is_switching = FALSE;
13766 player->push_delay = -1;
13768 return MP_NO_ACTION;
13772 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13773 old_element = Back[jx][jy];
13775 // in case of element dropped at player position, check background
13776 else if (Back[jx][jy] != EL_EMPTY &&
13777 game.engine_version >= VERSION_IDENT(2,2,0,0))
13778 old_element = Back[jx][jy];
13780 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13781 return MP_NO_ACTION; // field has no opening in this direction
13783 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13784 return MP_NO_ACTION; // field has no opening in this direction
13786 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13790 Feld[jx][jy] = player->artwork_element;
13791 InitMovingField(jx, jy, MV_DOWN);
13792 Store[jx][jy] = EL_ACID;
13793 ContinueMoving(jx, jy);
13794 BuryPlayer(player);
13796 return MP_DONT_RUN_INTO;
13799 if (player_can_move && DONT_RUN_INTO(element))
13801 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13803 return MP_DONT_RUN_INTO;
13806 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13807 return MP_NO_ACTION;
13809 collect_count = element_info[element].collect_count_initial;
13811 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13812 return MP_NO_ACTION;
13814 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13815 player_can_move = player_can_move_or_snap;
13817 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13818 game.engine_version >= VERSION_IDENT(2,2,0,0))
13820 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13821 player->index_bit, dig_side);
13822 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13823 player->index_bit, dig_side);
13825 if (element == EL_DC_LANDMINE)
13828 if (Feld[x][y] != element) // field changed by snapping
13831 return MP_NO_ACTION;
13834 if (player->gravity && is_player && !player->is_auto_moving &&
13835 canFallDown(player) && move_direction != MV_DOWN &&
13836 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13837 return MP_NO_ACTION; // player cannot walk here due to gravity
13839 if (player_can_move &&
13840 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13842 int sound_element = SND_ELEMENT(element);
13843 int sound_action = ACTION_WALKING;
13845 if (IS_RND_GATE(element))
13847 if (!player->key[RND_GATE_NR(element)])
13848 return MP_NO_ACTION;
13850 else if (IS_RND_GATE_GRAY(element))
13852 if (!player->key[RND_GATE_GRAY_NR(element)])
13853 return MP_NO_ACTION;
13855 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13857 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13858 return MP_NO_ACTION;
13860 else if (element == EL_EXIT_OPEN ||
13861 element == EL_EM_EXIT_OPEN ||
13862 element == EL_EM_EXIT_OPENING ||
13863 element == EL_STEEL_EXIT_OPEN ||
13864 element == EL_EM_STEEL_EXIT_OPEN ||
13865 element == EL_EM_STEEL_EXIT_OPENING ||
13866 element == EL_SP_EXIT_OPEN ||
13867 element == EL_SP_EXIT_OPENING)
13869 sound_action = ACTION_PASSING; // player is passing exit
13871 else if (element == EL_EMPTY)
13873 sound_action = ACTION_MOVING; // nothing to walk on
13876 // play sound from background or player, whatever is available
13877 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13878 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13880 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13882 else if (player_can_move &&
13883 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13885 if (!ACCESS_FROM(element, opposite_direction))
13886 return MP_NO_ACTION; // field not accessible from this direction
13888 if (CAN_MOVE(element)) // only fixed elements can be passed!
13889 return MP_NO_ACTION;
13891 if (IS_EM_GATE(element))
13893 if (!player->key[EM_GATE_NR(element)])
13894 return MP_NO_ACTION;
13896 else if (IS_EM_GATE_GRAY(element))
13898 if (!player->key[EM_GATE_GRAY_NR(element)])
13899 return MP_NO_ACTION;
13901 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13903 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13904 return MP_NO_ACTION;
13906 else if (IS_EMC_GATE(element))
13908 if (!player->key[EMC_GATE_NR(element)])
13909 return MP_NO_ACTION;
13911 else if (IS_EMC_GATE_GRAY(element))
13913 if (!player->key[EMC_GATE_GRAY_NR(element)])
13914 return MP_NO_ACTION;
13916 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13918 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13919 return MP_NO_ACTION;
13921 else if (element == EL_DC_GATE_WHITE ||
13922 element == EL_DC_GATE_WHITE_GRAY ||
13923 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13925 if (player->num_white_keys == 0)
13926 return MP_NO_ACTION;
13928 player->num_white_keys--;
13930 else if (IS_SP_PORT(element))
13932 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13933 element == EL_SP_GRAVITY_PORT_RIGHT ||
13934 element == EL_SP_GRAVITY_PORT_UP ||
13935 element == EL_SP_GRAVITY_PORT_DOWN)
13936 player->gravity = !player->gravity;
13937 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13938 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13939 element == EL_SP_GRAVITY_ON_PORT_UP ||
13940 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13941 player->gravity = TRUE;
13942 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13943 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13944 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13945 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13946 player->gravity = FALSE;
13949 // automatically move to the next field with double speed
13950 player->programmed_action = move_direction;
13952 if (player->move_delay_reset_counter == 0)
13954 player->move_delay_reset_counter = 2; // two double speed steps
13956 DOUBLE_PLAYER_SPEED(player);
13959 PlayLevelSoundAction(x, y, ACTION_PASSING);
13961 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13965 if (mode != DF_SNAP)
13967 GfxElement[x][y] = GFX_ELEMENT(element);
13968 player->is_digging = TRUE;
13971 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13973 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13974 player->index_bit, dig_side);
13976 if (mode == DF_SNAP)
13978 if (level.block_snap_field)
13979 setFieldForSnapping(x, y, element, move_direction);
13981 TestIfElementTouchesCustomElement(x, y); // for empty space
13983 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13984 player->index_bit, dig_side);
13987 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13991 if (is_player && mode != DF_SNAP)
13993 GfxElement[x][y] = element;
13994 player->is_collecting = TRUE;
13997 if (element == EL_SPEED_PILL)
13999 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14001 else if (element == EL_EXTRA_TIME && level.time > 0)
14003 TimeLeft += level.extra_time;
14005 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14007 DisplayGameControlValues();
14009 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14011 player->shield_normal_time_left += level.shield_normal_time;
14012 if (element == EL_SHIELD_DEADLY)
14013 player->shield_deadly_time_left += level.shield_deadly_time;
14015 else if (element == EL_DYNAMITE ||
14016 element == EL_EM_DYNAMITE ||
14017 element == EL_SP_DISK_RED)
14019 if (player->inventory_size < MAX_INVENTORY_SIZE)
14020 player->inventory_element[player->inventory_size++] = element;
14022 DrawGameDoorValues();
14024 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14026 player->dynabomb_count++;
14027 player->dynabombs_left++;
14029 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14031 player->dynabomb_size++;
14033 else if (element == EL_DYNABOMB_INCREASE_POWER)
14035 player->dynabomb_xl = TRUE;
14037 else if (IS_KEY(element))
14039 player->key[KEY_NR(element)] = TRUE;
14041 DrawGameDoorValues();
14043 else if (element == EL_DC_KEY_WHITE)
14045 player->num_white_keys++;
14047 // display white keys?
14048 // DrawGameDoorValues();
14050 else if (IS_ENVELOPE(element))
14052 player->show_envelope = element;
14054 else if (element == EL_EMC_LENSES)
14056 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14058 RedrawAllInvisibleElementsForLenses();
14060 else if (element == EL_EMC_MAGNIFIER)
14062 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14064 RedrawAllInvisibleElementsForMagnifier();
14066 else if (IS_DROPPABLE(element) ||
14067 IS_THROWABLE(element)) // can be collected and dropped
14071 if (collect_count == 0)
14072 player->inventory_infinite_element = element;
14074 for (i = 0; i < collect_count; i++)
14075 if (player->inventory_size < MAX_INVENTORY_SIZE)
14076 player->inventory_element[player->inventory_size++] = element;
14078 DrawGameDoorValues();
14080 else if (collect_count > 0)
14082 game.gems_still_needed -= collect_count;
14083 if (game.gems_still_needed < 0)
14084 game.gems_still_needed = 0;
14086 game.snapshot.collected_item = TRUE;
14088 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14090 DisplayGameControlValues();
14093 RaiseScoreElement(element);
14094 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14097 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14098 player->index_bit, dig_side);
14100 if (mode == DF_SNAP)
14102 if (level.block_snap_field)
14103 setFieldForSnapping(x, y, element, move_direction);
14105 TestIfElementTouchesCustomElement(x, y); // for empty space
14107 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14108 player->index_bit, dig_side);
14111 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14113 if (mode == DF_SNAP && element != EL_BD_ROCK)
14114 return MP_NO_ACTION;
14116 if (CAN_FALL(element) && dy)
14117 return MP_NO_ACTION;
14119 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14120 !(element == EL_SPRING && level.use_spring_bug))
14121 return MP_NO_ACTION;
14123 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14124 ((move_direction & MV_VERTICAL &&
14125 ((element_info[element].move_pattern & MV_LEFT &&
14126 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14127 (element_info[element].move_pattern & MV_RIGHT &&
14128 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14129 (move_direction & MV_HORIZONTAL &&
14130 ((element_info[element].move_pattern & MV_UP &&
14131 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14132 (element_info[element].move_pattern & MV_DOWN &&
14133 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14134 return MP_NO_ACTION;
14136 // do not push elements already moving away faster than player
14137 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14138 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14139 return MP_NO_ACTION;
14141 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14143 if (player->push_delay_value == -1 || !player_was_pushing)
14144 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14146 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14148 if (player->push_delay_value == -1)
14149 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14151 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14153 if (!player->is_pushing)
14154 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14157 player->is_pushing = TRUE;
14158 player->is_active = TRUE;
14160 if (!(IN_LEV_FIELD(nextx, nexty) &&
14161 (IS_FREE(nextx, nexty) ||
14162 (IS_SB_ELEMENT(element) &&
14163 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14164 (IS_CUSTOM_ELEMENT(element) &&
14165 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14166 return MP_NO_ACTION;
14168 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14169 return MP_NO_ACTION;
14171 if (player->push_delay == -1) // new pushing; restart delay
14172 player->push_delay = 0;
14174 if (player->push_delay < player->push_delay_value &&
14175 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14176 element != EL_SPRING && element != EL_BALLOON)
14178 // make sure that there is no move delay before next try to push
14179 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14180 player->move_delay = 0;
14182 return MP_NO_ACTION;
14185 if (IS_CUSTOM_ELEMENT(element) &&
14186 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14188 if (!DigFieldByCE(nextx, nexty, element))
14189 return MP_NO_ACTION;
14192 if (IS_SB_ELEMENT(element))
14194 boolean sokoban_task_solved = FALSE;
14196 if (element == EL_SOKOBAN_FIELD_FULL)
14198 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14200 IncrementSokobanFieldsNeeded();
14201 IncrementSokobanObjectsNeeded();
14204 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14206 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14208 DecrementSokobanFieldsNeeded();
14209 DecrementSokobanObjectsNeeded();
14211 // sokoban object was pushed from empty field to sokoban field
14212 if (Back[x][y] == EL_EMPTY)
14213 sokoban_task_solved = TRUE;
14216 Feld[x][y] = EL_SOKOBAN_OBJECT;
14218 if (Back[x][y] == Back[nextx][nexty])
14219 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14220 else if (Back[x][y] != 0)
14221 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14224 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14227 if (sokoban_task_solved &&
14228 game.sokoban_fields_still_needed == 0 &&
14229 game.sokoban_objects_still_needed == 0 &&
14230 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14232 game.players_still_needed = 0;
14236 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14240 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14242 InitMovingField(x, y, move_direction);
14243 GfxAction[x][y] = ACTION_PUSHING;
14245 if (mode == DF_SNAP)
14246 ContinueMoving(x, y);
14248 MovPos[x][y] = (dx != 0 ? dx : dy);
14250 Pushed[x][y] = TRUE;
14251 Pushed[nextx][nexty] = TRUE;
14253 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14254 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14256 player->push_delay_value = -1; // get new value later
14258 // check for element change _after_ element has been pushed
14259 if (game.use_change_when_pushing_bug)
14261 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14262 player->index_bit, dig_side);
14263 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14264 player->index_bit, dig_side);
14267 else if (IS_SWITCHABLE(element))
14269 if (PLAYER_SWITCHING(player, x, y))
14271 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14272 player->index_bit, dig_side);
14277 player->is_switching = TRUE;
14278 player->switch_x = x;
14279 player->switch_y = y;
14281 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14283 if (element == EL_ROBOT_WHEEL)
14285 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14287 game.robot_wheel_x = x;
14288 game.robot_wheel_y = y;
14289 game.robot_wheel_active = TRUE;
14291 TEST_DrawLevelField(x, y);
14293 else if (element == EL_SP_TERMINAL)
14297 SCAN_PLAYFIELD(xx, yy)
14299 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14303 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14305 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14307 ResetGfxAnimation(xx, yy);
14308 TEST_DrawLevelField(xx, yy);
14312 else if (IS_BELT_SWITCH(element))
14314 ToggleBeltSwitch(x, y);
14316 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14317 element == EL_SWITCHGATE_SWITCH_DOWN ||
14318 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14319 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14321 ToggleSwitchgateSwitch(x, y);
14323 else if (element == EL_LIGHT_SWITCH ||
14324 element == EL_LIGHT_SWITCH_ACTIVE)
14326 ToggleLightSwitch(x, y);
14328 else if (element == EL_TIMEGATE_SWITCH ||
14329 element == EL_DC_TIMEGATE_SWITCH)
14331 ActivateTimegateSwitch(x, y);
14333 else if (element == EL_BALLOON_SWITCH_LEFT ||
14334 element == EL_BALLOON_SWITCH_RIGHT ||
14335 element == EL_BALLOON_SWITCH_UP ||
14336 element == EL_BALLOON_SWITCH_DOWN ||
14337 element == EL_BALLOON_SWITCH_NONE ||
14338 element == EL_BALLOON_SWITCH_ANY)
14340 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14341 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14342 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14343 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14344 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14347 else if (element == EL_LAMP)
14349 Feld[x][y] = EL_LAMP_ACTIVE;
14350 game.lights_still_needed--;
14352 ResetGfxAnimation(x, y);
14353 TEST_DrawLevelField(x, y);
14355 else if (element == EL_TIME_ORB_FULL)
14357 Feld[x][y] = EL_TIME_ORB_EMPTY;
14359 if (level.time > 0 || level.use_time_orb_bug)
14361 TimeLeft += level.time_orb_time;
14362 game.no_time_limit = FALSE;
14364 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14366 DisplayGameControlValues();
14369 ResetGfxAnimation(x, y);
14370 TEST_DrawLevelField(x, y);
14372 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14373 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14377 game.ball_active = !game.ball_active;
14379 SCAN_PLAYFIELD(xx, yy)
14381 int e = Feld[xx][yy];
14383 if (game.ball_active)
14385 if (e == EL_EMC_MAGIC_BALL)
14386 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14387 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14388 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14392 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14393 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14394 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14395 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14400 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14401 player->index_bit, dig_side);
14403 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14404 player->index_bit, dig_side);
14406 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14407 player->index_bit, dig_side);
14413 if (!PLAYER_SWITCHING(player, x, y))
14415 player->is_switching = TRUE;
14416 player->switch_x = x;
14417 player->switch_y = y;
14419 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14420 player->index_bit, dig_side);
14421 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14422 player->index_bit, dig_side);
14424 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14425 player->index_bit, dig_side);
14426 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14427 player->index_bit, dig_side);
14430 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14431 player->index_bit, dig_side);
14432 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14433 player->index_bit, dig_side);
14435 return MP_NO_ACTION;
14438 player->push_delay = -1;
14440 if (is_player) // function can also be called by EL_PENGUIN
14442 if (Feld[x][y] != element) // really digged/collected something
14444 player->is_collecting = !player->is_digging;
14445 player->is_active = TRUE;
14452 static boolean DigFieldByCE(int x, int y, int digging_element)
14454 int element = Feld[x][y];
14456 if (!IS_FREE(x, y))
14458 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14459 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14462 // no element can dig solid indestructible elements
14463 if (IS_INDESTRUCTIBLE(element) &&
14464 !IS_DIGGABLE(element) &&
14465 !IS_COLLECTIBLE(element))
14468 if (AmoebaNr[x][y] &&
14469 (element == EL_AMOEBA_FULL ||
14470 element == EL_BD_AMOEBA ||
14471 element == EL_AMOEBA_GROWING))
14473 AmoebaCnt[AmoebaNr[x][y]]--;
14474 AmoebaCnt2[AmoebaNr[x][y]]--;
14477 if (IS_MOVING(x, y))
14478 RemoveMovingField(x, y);
14482 TEST_DrawLevelField(x, y);
14485 // if digged element was about to explode, prevent the explosion
14486 ExplodeField[x][y] = EX_TYPE_NONE;
14488 PlayLevelSoundAction(x, y, action);
14491 Store[x][y] = EL_EMPTY;
14493 // this makes it possible to leave the removed element again
14494 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14495 Store[x][y] = element;
14500 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14502 int jx = player->jx, jy = player->jy;
14503 int x = jx + dx, y = jy + dy;
14504 int snap_direction = (dx == -1 ? MV_LEFT :
14505 dx == +1 ? MV_RIGHT :
14507 dy == +1 ? MV_DOWN : MV_NONE);
14508 boolean can_continue_snapping = (level.continuous_snapping &&
14509 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14511 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14514 if (!player->active || !IN_LEV_FIELD(x, y))
14522 if (player->MovPos == 0)
14523 player->is_pushing = FALSE;
14525 player->is_snapping = FALSE;
14527 if (player->MovPos == 0)
14529 player->is_moving = FALSE;
14530 player->is_digging = FALSE;
14531 player->is_collecting = FALSE;
14537 // prevent snapping with already pressed snap key when not allowed
14538 if (player->is_snapping && !can_continue_snapping)
14541 player->MovDir = snap_direction;
14543 if (player->MovPos == 0)
14545 player->is_moving = FALSE;
14546 player->is_digging = FALSE;
14547 player->is_collecting = FALSE;
14550 player->is_dropping = FALSE;
14551 player->is_dropping_pressed = FALSE;
14552 player->drop_pressed_delay = 0;
14554 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14557 player->is_snapping = TRUE;
14558 player->is_active = TRUE;
14560 if (player->MovPos == 0)
14562 player->is_moving = FALSE;
14563 player->is_digging = FALSE;
14564 player->is_collecting = FALSE;
14567 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14568 TEST_DrawLevelField(player->last_jx, player->last_jy);
14570 TEST_DrawLevelField(x, y);
14575 static boolean DropElement(struct PlayerInfo *player)
14577 int old_element, new_element;
14578 int dropx = player->jx, dropy = player->jy;
14579 int drop_direction = player->MovDir;
14580 int drop_side = drop_direction;
14581 int drop_element = get_next_dropped_element(player);
14583 /* do not drop an element on top of another element; when holding drop key
14584 pressed without moving, dropped element must move away before the next
14585 element can be dropped (this is especially important if the next element
14586 is dynamite, which can be placed on background for historical reasons) */
14587 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14590 if (IS_THROWABLE(drop_element))
14592 dropx += GET_DX_FROM_DIR(drop_direction);
14593 dropy += GET_DY_FROM_DIR(drop_direction);
14595 if (!IN_LEV_FIELD(dropx, dropy))
14599 old_element = Feld[dropx][dropy]; // old element at dropping position
14600 new_element = drop_element; // default: no change when dropping
14602 // check if player is active, not moving and ready to drop
14603 if (!player->active || player->MovPos || player->drop_delay > 0)
14606 // check if player has anything that can be dropped
14607 if (new_element == EL_UNDEFINED)
14610 // only set if player has anything that can be dropped
14611 player->is_dropping_pressed = TRUE;
14613 // check if drop key was pressed long enough for EM style dynamite
14614 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14617 // check if anything can be dropped at the current position
14618 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14621 // collected custom elements can only be dropped on empty fields
14622 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14625 if (old_element != EL_EMPTY)
14626 Back[dropx][dropy] = old_element; // store old element on this field
14628 ResetGfxAnimation(dropx, dropy);
14629 ResetRandomAnimationValue(dropx, dropy);
14631 if (player->inventory_size > 0 ||
14632 player->inventory_infinite_element != EL_UNDEFINED)
14634 if (player->inventory_size > 0)
14636 player->inventory_size--;
14638 DrawGameDoorValues();
14640 if (new_element == EL_DYNAMITE)
14641 new_element = EL_DYNAMITE_ACTIVE;
14642 else if (new_element == EL_EM_DYNAMITE)
14643 new_element = EL_EM_DYNAMITE_ACTIVE;
14644 else if (new_element == EL_SP_DISK_RED)
14645 new_element = EL_SP_DISK_RED_ACTIVE;
14648 Feld[dropx][dropy] = new_element;
14650 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14651 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14652 el2img(Feld[dropx][dropy]), 0);
14654 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14656 // needed if previous element just changed to "empty" in the last frame
14657 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14659 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14660 player->index_bit, drop_side);
14661 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14663 player->index_bit, drop_side);
14665 TestIfElementTouchesCustomElement(dropx, dropy);
14667 else // player is dropping a dyna bomb
14669 player->dynabombs_left--;
14671 Feld[dropx][dropy] = new_element;
14673 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14674 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14675 el2img(Feld[dropx][dropy]), 0);
14677 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14680 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14681 InitField_WithBug1(dropx, dropy, FALSE);
14683 new_element = Feld[dropx][dropy]; // element might have changed
14685 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14686 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14688 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14689 MovDir[dropx][dropy] = drop_direction;
14691 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14693 // do not cause impact style collision by dropping elements that can fall
14694 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14697 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14698 player->is_dropping = TRUE;
14700 player->drop_pressed_delay = 0;
14701 player->is_dropping_pressed = FALSE;
14703 player->drop_x = dropx;
14704 player->drop_y = dropy;
14709 // ----------------------------------------------------------------------------
14710 // game sound playing functions
14711 // ----------------------------------------------------------------------------
14713 static int *loop_sound_frame = NULL;
14714 static int *loop_sound_volume = NULL;
14716 void InitPlayLevelSound(void)
14718 int num_sounds = getSoundListSize();
14720 checked_free(loop_sound_frame);
14721 checked_free(loop_sound_volume);
14723 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14724 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14727 static void PlayLevelSound(int x, int y, int nr)
14729 int sx = SCREENX(x), sy = SCREENY(y);
14730 int volume, stereo_position;
14731 int max_distance = 8;
14732 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14734 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14735 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14738 if (!IN_LEV_FIELD(x, y) ||
14739 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14740 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14743 volume = SOUND_MAX_VOLUME;
14745 if (!IN_SCR_FIELD(sx, sy))
14747 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14748 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14750 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14753 stereo_position = (SOUND_MAX_LEFT +
14754 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14755 (SCR_FIELDX + 2 * max_distance));
14757 if (IS_LOOP_SOUND(nr))
14759 /* This assures that quieter loop sounds do not overwrite louder ones,
14760 while restarting sound volume comparison with each new game frame. */
14762 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14765 loop_sound_volume[nr] = volume;
14766 loop_sound_frame[nr] = FrameCounter;
14769 PlaySoundExt(nr, volume, stereo_position, type);
14772 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14774 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14775 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14776 y < LEVELY(BY1) ? LEVELY(BY1) :
14777 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14781 static void PlayLevelSoundAction(int x, int y, int action)
14783 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14786 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14788 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14790 if (sound_effect != SND_UNDEFINED)
14791 PlayLevelSound(x, y, sound_effect);
14794 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14797 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14799 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14800 PlayLevelSound(x, y, sound_effect);
14803 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14805 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14807 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14808 PlayLevelSound(x, y, sound_effect);
14811 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14813 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14815 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14816 StopSound(sound_effect);
14819 static int getLevelMusicNr(void)
14821 if (levelset.music[level_nr] != MUS_UNDEFINED)
14822 return levelset.music[level_nr]; // from config file
14824 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14827 static void FadeLevelSounds(void)
14832 static void FadeLevelMusic(void)
14834 int music_nr = getLevelMusicNr();
14835 char *curr_music = getCurrentlyPlayingMusicFilename();
14836 char *next_music = getMusicInfoEntryFilename(music_nr);
14838 if (!strEqual(curr_music, next_music))
14842 void FadeLevelSoundsAndMusic(void)
14848 static void PlayLevelMusic(void)
14850 int music_nr = getLevelMusicNr();
14851 char *curr_music = getCurrentlyPlayingMusicFilename();
14852 char *next_music = getMusicInfoEntryFilename(music_nr);
14854 if (!strEqual(curr_music, next_music))
14855 PlayMusicLoop(music_nr);
14858 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14860 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14862 int x = xx - offset;
14863 int y = yy - offset;
14865 x = correctLevelPosX_EM(x);
14866 y = correctLevelPosY_EM(y);
14871 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14875 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14879 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14883 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14887 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14891 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14895 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14898 case SOUND_android_clone:
14899 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14902 case SOUND_android_move:
14903 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14907 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14911 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14915 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14918 case SOUND_eater_eat:
14919 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14923 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14926 case SOUND_collect:
14927 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14930 case SOUND_diamond:
14931 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14935 // !!! CHECK THIS !!!
14937 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14939 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14943 case SOUND_wonderfall:
14944 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14948 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14952 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14956 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14960 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14964 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14968 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14972 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14976 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14979 case SOUND_exit_open:
14980 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14983 case SOUND_exit_leave:
14984 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14987 case SOUND_dynamite:
14988 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14992 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14996 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15000 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15004 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15008 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15012 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15016 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15021 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15023 int element = map_element_SP_to_RND(element_sp);
15024 int action = map_action_SP_to_RND(action_sp);
15025 int offset = (setup.sp_show_border_elements ? 0 : 1);
15026 int x = xx - offset;
15027 int y = yy - offset;
15029 PlayLevelSoundElementAction(x, y, element, action);
15032 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15034 int element = map_element_MM_to_RND(element_mm);
15035 int action = map_action_MM_to_RND(action_mm);
15037 int x = xx - offset;
15038 int y = yy - offset;
15040 if (!IS_MM_ELEMENT(element))
15041 element = EL_MM_DEFAULT;
15043 PlayLevelSoundElementAction(x, y, element, action);
15046 void PlaySound_MM(int sound_mm)
15048 int sound = map_sound_MM_to_RND(sound_mm);
15050 if (sound == SND_UNDEFINED)
15056 void PlaySoundLoop_MM(int sound_mm)
15058 int sound = map_sound_MM_to_RND(sound_mm);
15060 if (sound == SND_UNDEFINED)
15063 PlaySoundLoop(sound);
15066 void StopSound_MM(int sound_mm)
15068 int sound = map_sound_MM_to_RND(sound_mm);
15070 if (sound == SND_UNDEFINED)
15076 void RaiseScore(int value)
15078 game.score += value;
15080 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15082 DisplayGameControlValues();
15085 void RaiseScoreElement(int element)
15090 case EL_BD_DIAMOND:
15091 case EL_EMERALD_YELLOW:
15092 case EL_EMERALD_RED:
15093 case EL_EMERALD_PURPLE:
15094 case EL_SP_INFOTRON:
15095 RaiseScore(level.score[SC_EMERALD]);
15098 RaiseScore(level.score[SC_DIAMOND]);
15101 RaiseScore(level.score[SC_CRYSTAL]);
15104 RaiseScore(level.score[SC_PEARL]);
15107 case EL_BD_BUTTERFLY:
15108 case EL_SP_ELECTRON:
15109 RaiseScore(level.score[SC_BUG]);
15112 case EL_BD_FIREFLY:
15113 case EL_SP_SNIKSNAK:
15114 RaiseScore(level.score[SC_SPACESHIP]);
15117 case EL_DARK_YAMYAM:
15118 RaiseScore(level.score[SC_YAMYAM]);
15121 RaiseScore(level.score[SC_ROBOT]);
15124 RaiseScore(level.score[SC_PACMAN]);
15127 RaiseScore(level.score[SC_NUT]);
15130 case EL_EM_DYNAMITE:
15131 case EL_SP_DISK_RED:
15132 case EL_DYNABOMB_INCREASE_NUMBER:
15133 case EL_DYNABOMB_INCREASE_SIZE:
15134 case EL_DYNABOMB_INCREASE_POWER:
15135 RaiseScore(level.score[SC_DYNAMITE]);
15137 case EL_SHIELD_NORMAL:
15138 case EL_SHIELD_DEADLY:
15139 RaiseScore(level.score[SC_SHIELD]);
15141 case EL_EXTRA_TIME:
15142 RaiseScore(level.extra_time_score);
15156 case EL_DC_KEY_WHITE:
15157 RaiseScore(level.score[SC_KEY]);
15160 RaiseScore(element_info[element].collect_score);
15165 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15167 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15169 // closing door required in case of envelope style request dialogs
15172 // prevent short reactivation of overlay buttons while closing door
15173 SetOverlayActive(FALSE);
15175 CloseDoor(DOOR_CLOSE_1);
15178 if (network.enabled)
15179 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15183 FadeSkipNextFadeIn();
15185 SetGameStatus(GAME_MODE_MAIN);
15190 else // continue playing the game
15192 if (tape.playing && tape.deactivate_display)
15193 TapeDeactivateDisplayOff(TRUE);
15195 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15197 if (tape.playing && tape.deactivate_display)
15198 TapeDeactivateDisplayOn();
15202 void RequestQuitGame(boolean ask_if_really_quit)
15204 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15205 boolean skip_request = game.all_players_gone || quick_quit;
15207 RequestQuitGameExt(skip_request, quick_quit,
15208 "Do you really want to quit the game?");
15211 void RequestRestartGame(char *message)
15213 game.restart_game_message = NULL;
15215 boolean has_started_game = hasStartedNetworkGame();
15216 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15218 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15220 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15224 SetGameStatus(GAME_MODE_MAIN);
15230 void CheckGameOver(void)
15232 static boolean last_game_over = FALSE;
15233 static int game_over_delay = 0;
15234 int game_over_delay_value = 50;
15235 boolean game_over = checkGameFailed();
15237 // do not handle game over if request dialog is already active
15238 if (game.request_active)
15241 // do not ask to play again if game was never actually played
15242 if (!game.GamePlayed)
15247 last_game_over = FALSE;
15248 game_over_delay = game_over_delay_value;
15253 if (game_over_delay > 0)
15260 if (last_game_over != game_over)
15261 game.restart_game_message = (hasStartedNetworkGame() ?
15262 "Game over! Play it again?" :
15265 last_game_over = game_over;
15268 boolean checkGameSolved(void)
15270 // set for all game engines if level was solved
15271 return game.LevelSolved_GameEnd;
15274 boolean checkGameFailed(void)
15276 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15277 return (game_em.game_over && !game_em.level_solved);
15278 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15279 return (game_sp.game_over && !game_sp.level_solved);
15280 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15281 return (game_mm.game_over && !game_mm.level_solved);
15282 else // GAME_ENGINE_TYPE_RND
15283 return (game.GameOver && !game.LevelSolved);
15286 boolean checkGameEnded(void)
15288 return (checkGameSolved() || checkGameFailed());
15292 // ----------------------------------------------------------------------------
15293 // random generator functions
15294 // ----------------------------------------------------------------------------
15296 unsigned int InitEngineRandom_RND(int seed)
15298 game.num_random_calls = 0;
15300 return InitEngineRandom(seed);
15303 unsigned int RND(int max)
15307 game.num_random_calls++;
15309 return GetEngineRandom(max);
15316 // ----------------------------------------------------------------------------
15317 // game engine snapshot handling functions
15318 // ----------------------------------------------------------------------------
15320 struct EngineSnapshotInfo
15322 // runtime values for custom element collect score
15323 int collect_score[NUM_CUSTOM_ELEMENTS];
15325 // runtime values for group element choice position
15326 int choice_pos[NUM_GROUP_ELEMENTS];
15328 // runtime values for belt position animations
15329 int belt_graphic[4][NUM_BELT_PARTS];
15330 int belt_anim_mode[4][NUM_BELT_PARTS];
15333 static struct EngineSnapshotInfo engine_snapshot_rnd;
15334 static char *snapshot_level_identifier = NULL;
15335 static int snapshot_level_nr = -1;
15337 static void SaveEngineSnapshotValues_RND(void)
15339 static int belt_base_active_element[4] =
15341 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15342 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15343 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15344 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15348 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15350 int element = EL_CUSTOM_START + i;
15352 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15355 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15357 int element = EL_GROUP_START + i;
15359 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15362 for (i = 0; i < 4; i++)
15364 for (j = 0; j < NUM_BELT_PARTS; j++)
15366 int element = belt_base_active_element[i] + j;
15367 int graphic = el2img(element);
15368 int anim_mode = graphic_info[graphic].anim_mode;
15370 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15371 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15376 static void LoadEngineSnapshotValues_RND(void)
15378 unsigned int num_random_calls = game.num_random_calls;
15381 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15383 int element = EL_CUSTOM_START + i;
15385 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15388 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15390 int element = EL_GROUP_START + i;
15392 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15395 for (i = 0; i < 4; i++)
15397 for (j = 0; j < NUM_BELT_PARTS; j++)
15399 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15400 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15402 graphic_info[graphic].anim_mode = anim_mode;
15406 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15408 InitRND(tape.random_seed);
15409 for (i = 0; i < num_random_calls; i++)
15413 if (game.num_random_calls != num_random_calls)
15415 Error(ERR_INFO, "number of random calls out of sync");
15416 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15417 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15418 Error(ERR_EXIT, "this should not happen -- please debug");
15422 void FreeEngineSnapshotSingle(void)
15424 FreeSnapshotSingle();
15426 setString(&snapshot_level_identifier, NULL);
15427 snapshot_level_nr = -1;
15430 void FreeEngineSnapshotList(void)
15432 FreeSnapshotList();
15435 static ListNode *SaveEngineSnapshotBuffers(void)
15437 ListNode *buffers = NULL;
15439 // copy some special values to a structure better suited for the snapshot
15441 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15442 SaveEngineSnapshotValues_RND();
15443 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15444 SaveEngineSnapshotValues_EM();
15445 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15446 SaveEngineSnapshotValues_SP(&buffers);
15447 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15448 SaveEngineSnapshotValues_MM(&buffers);
15450 // save values stored in special snapshot structure
15452 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15453 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15454 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15455 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15456 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15457 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15458 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15459 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15461 // save further RND engine values
15463 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15464 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15465 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15467 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15469 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15470 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15471 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15474 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15475 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15479 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15480 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15484 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15486 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15489 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15490 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15492 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15494 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15511 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15513 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15514 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15515 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15517 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15518 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15521 ListNode *node = engine_snapshot_list_rnd;
15524 while (node != NULL)
15526 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15531 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15537 void SaveEngineSnapshotSingle(void)
15539 ListNode *buffers = SaveEngineSnapshotBuffers();
15541 // finally save all snapshot buffers to single snapshot
15542 SaveSnapshotSingle(buffers);
15544 // save level identification information
15545 setString(&snapshot_level_identifier, leveldir_current->identifier);
15546 snapshot_level_nr = level_nr;
15549 boolean CheckSaveEngineSnapshotToList(void)
15551 boolean save_snapshot =
15552 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15553 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15554 game.snapshot.changed_action) ||
15555 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15556 game.snapshot.collected_item));
15558 game.snapshot.changed_action = FALSE;
15559 game.snapshot.collected_item = FALSE;
15560 game.snapshot.save_snapshot = save_snapshot;
15562 return save_snapshot;
15565 void SaveEngineSnapshotToList(void)
15567 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15571 ListNode *buffers = SaveEngineSnapshotBuffers();
15573 // finally save all snapshot buffers to snapshot list
15574 SaveSnapshotToList(buffers);
15577 void SaveEngineSnapshotToListInitial(void)
15579 FreeEngineSnapshotList();
15581 SaveEngineSnapshotToList();
15584 static void LoadEngineSnapshotValues(void)
15586 // restore special values from snapshot structure
15588 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15589 LoadEngineSnapshotValues_RND();
15590 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15591 LoadEngineSnapshotValues_EM();
15592 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15593 LoadEngineSnapshotValues_SP();
15594 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15595 LoadEngineSnapshotValues_MM();
15598 void LoadEngineSnapshotSingle(void)
15600 LoadSnapshotSingle();
15602 LoadEngineSnapshotValues();
15605 static void LoadEngineSnapshot_Undo(int steps)
15607 LoadSnapshotFromList_Older(steps);
15609 LoadEngineSnapshotValues();
15612 static void LoadEngineSnapshot_Redo(int steps)
15614 LoadSnapshotFromList_Newer(steps);
15616 LoadEngineSnapshotValues();
15619 boolean CheckEngineSnapshotSingle(void)
15621 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15622 snapshot_level_nr == level_nr);
15625 boolean CheckEngineSnapshotList(void)
15627 return CheckSnapshotList();
15631 // ---------- new game button stuff -------------------------------------------
15638 boolean *setup_value;
15639 boolean allowed_on_tape;
15640 boolean is_touch_button;
15642 } gamebutton_info[NUM_GAME_BUTTONS] =
15645 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15646 GAME_CTRL_ID_STOP, NULL,
15647 TRUE, FALSE, "stop game"
15650 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15651 GAME_CTRL_ID_PAUSE, NULL,
15652 TRUE, FALSE, "pause game"
15655 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15656 GAME_CTRL_ID_PLAY, NULL,
15657 TRUE, FALSE, "play game"
15660 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15661 GAME_CTRL_ID_UNDO, NULL,
15662 TRUE, FALSE, "undo step"
15665 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15666 GAME_CTRL_ID_REDO, NULL,
15667 TRUE, FALSE, "redo step"
15670 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15671 GAME_CTRL_ID_SAVE, NULL,
15672 TRUE, FALSE, "save game"
15675 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15676 GAME_CTRL_ID_PAUSE2, NULL,
15677 TRUE, FALSE, "pause game"
15680 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15681 GAME_CTRL_ID_LOAD, NULL,
15682 TRUE, FALSE, "load game"
15685 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15686 GAME_CTRL_ID_PANEL_STOP, NULL,
15687 FALSE, FALSE, "stop game"
15690 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15691 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15692 FALSE, FALSE, "pause game"
15695 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15696 GAME_CTRL_ID_PANEL_PLAY, NULL,
15697 FALSE, FALSE, "play game"
15700 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15701 GAME_CTRL_ID_TOUCH_STOP, NULL,
15702 FALSE, TRUE, "stop game"
15705 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15706 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15707 FALSE, TRUE, "pause game"
15710 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15711 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15712 TRUE, FALSE, "background music on/off"
15715 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15716 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15717 TRUE, FALSE, "sound loops on/off"
15720 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15721 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15722 TRUE, FALSE, "normal sounds on/off"
15725 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15726 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15727 FALSE, FALSE, "background music on/off"
15730 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15731 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15732 FALSE, FALSE, "sound loops on/off"
15735 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15736 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15737 FALSE, FALSE, "normal sounds on/off"
15741 void CreateGameButtons(void)
15745 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15747 int graphic = gamebutton_info[i].graphic;
15748 struct GraphicInfo *gfx = &graphic_info[graphic];
15749 struct XY *pos = gamebutton_info[i].pos;
15750 struct GadgetInfo *gi;
15753 unsigned int event_mask;
15754 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15755 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15756 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15757 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15758 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15759 int gd_x = gfx->src_x;
15760 int gd_y = gfx->src_y;
15761 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15762 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15763 int gd_xa = gfx->src_x + gfx->active_xoffset;
15764 int gd_ya = gfx->src_y + gfx->active_yoffset;
15765 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15766 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15767 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15768 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15771 if (gfx->bitmap == NULL)
15773 game_gadget[id] = NULL;
15778 if (id == GAME_CTRL_ID_STOP ||
15779 id == GAME_CTRL_ID_PANEL_STOP ||
15780 id == GAME_CTRL_ID_TOUCH_STOP ||
15781 id == GAME_CTRL_ID_PLAY ||
15782 id == GAME_CTRL_ID_PANEL_PLAY ||
15783 id == GAME_CTRL_ID_SAVE ||
15784 id == GAME_CTRL_ID_LOAD)
15786 button_type = GD_TYPE_NORMAL_BUTTON;
15788 event_mask = GD_EVENT_RELEASED;
15790 else if (id == GAME_CTRL_ID_UNDO ||
15791 id == GAME_CTRL_ID_REDO)
15793 button_type = GD_TYPE_NORMAL_BUTTON;
15795 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15799 button_type = GD_TYPE_CHECK_BUTTON;
15800 checked = (gamebutton_info[i].setup_value != NULL ?
15801 *gamebutton_info[i].setup_value : FALSE);
15802 event_mask = GD_EVENT_PRESSED;
15805 gi = CreateGadget(GDI_CUSTOM_ID, id,
15806 GDI_IMAGE_ID, graphic,
15807 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15810 GDI_WIDTH, gfx->width,
15811 GDI_HEIGHT, gfx->height,
15812 GDI_TYPE, button_type,
15813 GDI_STATE, GD_BUTTON_UNPRESSED,
15814 GDI_CHECKED, checked,
15815 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15816 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15817 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15818 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15819 GDI_DIRECT_DRAW, FALSE,
15820 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15821 GDI_EVENT_MASK, event_mask,
15822 GDI_CALLBACK_ACTION, HandleGameButtons,
15826 Error(ERR_EXIT, "cannot create gadget");
15828 game_gadget[id] = gi;
15832 void FreeGameButtons(void)
15836 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15837 FreeGadget(game_gadget[i]);
15840 static void UnmapGameButtonsAtSamePosition(int id)
15844 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15846 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15847 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15848 UnmapGadget(game_gadget[i]);
15851 static void UnmapGameButtonsAtSamePosition_All(void)
15853 if (setup.show_snapshot_buttons)
15855 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15856 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15857 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15861 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15862 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15863 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15865 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15866 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15867 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15871 static void MapGameButtonsAtSamePosition(int id)
15875 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15877 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15878 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15879 MapGadget(game_gadget[i]);
15881 UnmapGameButtonsAtSamePosition_All();
15884 void MapUndoRedoButtons(void)
15886 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15887 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15889 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15890 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15893 void UnmapUndoRedoButtons(void)
15895 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15896 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15898 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15899 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15902 void ModifyPauseButtons(void)
15906 GAME_CTRL_ID_PAUSE,
15907 GAME_CTRL_ID_PAUSE2,
15908 GAME_CTRL_ID_PANEL_PAUSE,
15909 GAME_CTRL_ID_TOUCH_PAUSE,
15914 for (i = 0; ids[i] > -1; i++)
15915 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15918 static void MapGameButtonsExt(boolean on_tape)
15922 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15923 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15924 i != GAME_CTRL_ID_UNDO &&
15925 i != GAME_CTRL_ID_REDO)
15926 MapGadget(game_gadget[i]);
15928 UnmapGameButtonsAtSamePosition_All();
15930 RedrawGameButtons();
15933 static void UnmapGameButtonsExt(boolean on_tape)
15937 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15938 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15939 UnmapGadget(game_gadget[i]);
15942 static void RedrawGameButtonsExt(boolean on_tape)
15946 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15947 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15948 RedrawGadget(game_gadget[i]);
15951 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15956 gi->checked = state;
15959 static void RedrawSoundButtonGadget(int id)
15961 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15962 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15963 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15964 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15965 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15966 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15969 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15970 RedrawGadget(game_gadget[id2]);
15973 void MapGameButtons(void)
15975 MapGameButtonsExt(FALSE);
15978 void UnmapGameButtons(void)
15980 UnmapGameButtonsExt(FALSE);
15983 void RedrawGameButtons(void)
15985 RedrawGameButtonsExt(FALSE);
15988 void MapGameButtonsOnTape(void)
15990 MapGameButtonsExt(TRUE);
15993 void UnmapGameButtonsOnTape(void)
15995 UnmapGameButtonsExt(TRUE);
15998 void RedrawGameButtonsOnTape(void)
16000 RedrawGameButtonsExt(TRUE);
16003 static void GameUndoRedoExt(void)
16005 ClearPlayerAction();
16007 tape.pausing = TRUE;
16010 UpdateAndDisplayGameControlValues();
16012 DrawCompleteVideoDisplay();
16013 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16014 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16015 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16020 static void GameUndo(int steps)
16022 if (!CheckEngineSnapshotList())
16025 LoadEngineSnapshot_Undo(steps);
16030 static void GameRedo(int steps)
16032 if (!CheckEngineSnapshotList())
16035 LoadEngineSnapshot_Redo(steps);
16040 static void HandleGameButtonsExt(int id, int button)
16042 static boolean game_undo_executed = FALSE;
16043 int steps = BUTTON_STEPSIZE(button);
16044 boolean handle_game_buttons =
16045 (game_status == GAME_MODE_PLAYING ||
16046 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16048 if (!handle_game_buttons)
16053 case GAME_CTRL_ID_STOP:
16054 case GAME_CTRL_ID_PANEL_STOP:
16055 case GAME_CTRL_ID_TOUCH_STOP:
16056 if (game_status == GAME_MODE_MAIN)
16062 RequestQuitGame(TRUE);
16066 case GAME_CTRL_ID_PAUSE:
16067 case GAME_CTRL_ID_PAUSE2:
16068 case GAME_CTRL_ID_PANEL_PAUSE:
16069 case GAME_CTRL_ID_TOUCH_PAUSE:
16070 if (network.enabled && game_status == GAME_MODE_PLAYING)
16073 SendToServer_ContinuePlaying();
16075 SendToServer_PausePlaying();
16078 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16080 game_undo_executed = FALSE;
16084 case GAME_CTRL_ID_PLAY:
16085 case GAME_CTRL_ID_PANEL_PLAY:
16086 if (game_status == GAME_MODE_MAIN)
16088 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16090 else if (tape.pausing)
16092 if (network.enabled)
16093 SendToServer_ContinuePlaying();
16095 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16099 case GAME_CTRL_ID_UNDO:
16100 // Important: When using "save snapshot when collecting an item" mode,
16101 // load last (current) snapshot for first "undo" after pressing "pause"
16102 // (else the last-but-one snapshot would be loaded, because the snapshot
16103 // pointer already points to the last snapshot when pressing "pause",
16104 // which is fine for "every step/move" mode, but not for "every collect")
16105 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16106 !game_undo_executed)
16109 game_undo_executed = TRUE;
16114 case GAME_CTRL_ID_REDO:
16118 case GAME_CTRL_ID_SAVE:
16122 case GAME_CTRL_ID_LOAD:
16126 case SOUND_CTRL_ID_MUSIC:
16127 case SOUND_CTRL_ID_PANEL_MUSIC:
16128 if (setup.sound_music)
16130 setup.sound_music = FALSE;
16134 else if (audio.music_available)
16136 setup.sound = setup.sound_music = TRUE;
16138 SetAudioMode(setup.sound);
16140 if (game_status == GAME_MODE_PLAYING)
16144 RedrawSoundButtonGadget(id);
16148 case SOUND_CTRL_ID_LOOPS:
16149 case SOUND_CTRL_ID_PANEL_LOOPS:
16150 if (setup.sound_loops)
16151 setup.sound_loops = FALSE;
16152 else if (audio.loops_available)
16154 setup.sound = setup.sound_loops = TRUE;
16156 SetAudioMode(setup.sound);
16159 RedrawSoundButtonGadget(id);
16163 case SOUND_CTRL_ID_SIMPLE:
16164 case SOUND_CTRL_ID_PANEL_SIMPLE:
16165 if (setup.sound_simple)
16166 setup.sound_simple = FALSE;
16167 else if (audio.sound_available)
16169 setup.sound = setup.sound_simple = TRUE;
16171 SetAudioMode(setup.sound);
16174 RedrawSoundButtonGadget(id);
16183 static void HandleGameButtons(struct GadgetInfo *gi)
16185 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16188 void HandleSoundButtonKeys(Key key)
16190 if (key == setup.shortcut.sound_simple)
16191 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16192 else if (key == setup.shortcut.sound_loops)
16193 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16194 else if (key == setup.shortcut.sound_music)
16195 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);