1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Feld[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Feld[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Feld[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
971 Feld[x][y] == EL_EM_EXIT_OPEN || \
972 Feld[x][y] == EL_STEEL_EXIT_OPEN || \
973 Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Feld[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \
986 Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define GAME_CTRL_ID_TOUCH_STOP 11
1020 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1021 #define SOUND_CTRL_ID_MUSIC 13
1022 #define SOUND_CTRL_ID_LOOPS 14
1023 #define SOUND_CTRL_ID_SIMPLE 15
1024 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1025 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1026 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1028 #define NUM_GAME_BUTTONS 19
1031 // forward declaration for internal use
1033 static void CreateField(int, int, int);
1035 static void ResetGfxAnimation(int, int);
1037 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1038 static void AdvanceFrameAndPlayerCounters(int);
1040 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1041 static boolean MovePlayer(struct PlayerInfo *, int, int);
1042 static void ScrollPlayer(struct PlayerInfo *, int);
1043 static void ScrollScreen(struct PlayerInfo *, int);
1045 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1046 static boolean DigFieldByCE(int, int, int);
1047 static boolean SnapField(struct PlayerInfo *, int, int);
1048 static boolean DropElement(struct PlayerInfo *);
1050 static void InitBeltMovement(void);
1051 static void CloseAllOpenTimegates(void);
1052 static void CheckGravityMovement(struct PlayerInfo *);
1053 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1054 static void KillPlayerUnlessEnemyProtected(int, int);
1055 static void KillPlayerUnlessExplosionProtected(int, int);
1057 static void TestIfPlayerTouchesCustomElement(int, int);
1058 static void TestIfElementTouchesCustomElement(int, int);
1059 static void TestIfElementHitsCustomElement(int, int, int);
1061 static void HandleElementChange(int, int, int);
1062 static void ExecuteCustomElementAction(int, int, int, int);
1063 static boolean ChangeElement(int, int, int, int);
1065 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1066 #define CheckTriggeredElementChange(x, y, e, ev) \
1067 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1068 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1070 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1071 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1072 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1073 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1075 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckElementChange(x, y, e, te, ev) \
1077 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1078 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1080 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1083 static void PlayLevelSound(int, int, int);
1084 static void PlayLevelSoundNearest(int, int, int);
1085 static void PlayLevelSoundAction(int, int, int);
1086 static void PlayLevelSoundElementAction(int, int, int, int);
1087 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1088 static void PlayLevelSoundActionIfLoop(int, int, int);
1089 static void StopLevelSoundActionIfLoop(int, int, int);
1090 static void PlayLevelMusic(void);
1091 static void FadeLevelSoundsAndMusic(void);
1093 static void HandleGameButtons(struct GadgetInfo *);
1095 int AmoebeNachbarNr(int, int);
1096 void AmoebeUmwandeln(int, int);
1097 void ContinueMoving(int, int);
1098 void Bang(int, int);
1099 void InitMovDir(int, int);
1100 void InitAmoebaNr(int, int);
1101 int NewHiScore(int);
1103 void TestIfGoodThingHitsBadThing(int, int, int);
1104 void TestIfBadThingHitsGoodThing(int, int, int);
1105 void TestIfPlayerTouchesBadThing(int, int);
1106 void TestIfPlayerRunsIntoBadThing(int, int, int);
1107 void TestIfBadThingTouchesPlayer(int, int);
1108 void TestIfBadThingRunsIntoPlayer(int, int, int);
1109 void TestIfFriendTouchesBadThing(int, int);
1110 void TestIfBadThingTouchesFriend(int, int);
1111 void TestIfBadThingTouchesOtherBadThing(int, int);
1112 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1114 void KillPlayer(struct PlayerInfo *);
1115 void BuryPlayer(struct PlayerInfo *);
1116 void RemovePlayer(struct PlayerInfo *);
1117 void ExitPlayer(struct PlayerInfo *);
1119 static int getInvisibleActiveFromInvisibleElement(int);
1120 static int getInvisibleFromInvisibleActiveElement(int);
1122 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Feld[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Feld[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Feld[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Feld[x][y];
1771 #if DEBUG_INIT_PLAYER
1774 printf("- player element %d activated", player->element_nr);
1775 printf(" (local player is %d and currently %s)\n",
1776 local_player->element_nr,
1777 local_player->active ? "active" : "not active");
1782 Feld[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Feld[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Feld[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1832 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Feld[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Feld[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Feld[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Feld[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Feld[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2151 Error(ERR_EXIT, "this should not happen -- please debug");
2154 // force update of game controls after initialization
2155 gpc->value = gpc->last_value = -1;
2156 gpc->frame = gpc->last_frame = -1;
2157 gpc->gfx_frame = -1;
2159 // determine panel value width for later calculation of alignment
2160 if (type == TYPE_INTEGER || type == TYPE_STRING)
2162 pos->width = pos->size * getFontWidth(pos->font);
2163 pos->height = getFontHeight(pos->font);
2165 else if (type == TYPE_ELEMENT)
2167 pos->width = pos->size;
2168 pos->height = pos->size;
2171 // fill structure for game panel draw order
2173 gpo->sort_priority = pos->sort_priority;
2176 // sort game panel controls according to sort_priority and control number
2177 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2178 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2181 static void UpdatePlayfieldElementCount(void)
2183 boolean use_element_count = FALSE;
2186 // first check if it is needed at all to calculate playfield element count
2187 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2188 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2189 use_element_count = TRUE;
2191 if (!use_element_count)
2194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2195 element_info[i].element_count = 0;
2197 SCAN_PLAYFIELD(x, y)
2199 element_info[Feld[x][y]].element_count++;
2202 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2203 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2204 if (IS_IN_GROUP(j, i))
2205 element_info[EL_GROUP_START + i].element_count +=
2206 element_info[j].element_count;
2209 static void UpdateGameControlValues(void)
2212 int time = (game.LevelSolved ?
2213 game.LevelSolved_CountingTime :
2214 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 game_sp.time_played :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 game_mm.energy_left :
2220 game.no_time_limit ? TimePlayed : TimeLeft);
2221 int score = (game.LevelSolved ?
2222 game.LevelSolved_CountingScore :
2223 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2224 game_em.lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->gems_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 game_sp.infotrons_still_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2235 game_mm.kettles_still_needed :
2236 game.gems_still_needed);
2237 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed > 0 ||
2243 game_mm.lights_still_needed > 0 :
2244 game.gems_still_needed > 0 ||
2245 game.sokoban_fields_still_needed > 0 ||
2246 game.sokoban_objects_still_needed > 0 ||
2247 game.lights_still_needed > 0);
2248 int health = (game.LevelSolved ?
2249 game.LevelSolved_CountingHealth :
2250 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2251 MM_HEALTH(game_mm.laser_overload_value) :
2254 UpdatePlayfieldElementCount();
2256 // update game panel control values
2258 // used instead of "level_nr" (for network games)
2259 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2260 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2262 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2263 for (i = 0; i < MAX_NUM_KEYS; i++)
2264 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2265 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2268 if (game.centered_player_nr == -1)
2270 for (i = 0; i < MAX_PLAYERS; i++)
2272 // only one player in Supaplex game engine
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2276 for (k = 0; k < MAX_NUM_KEYS; k++)
2278 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2280 if (game_em.ply[i]->keys & (1 << k))
2281 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2282 get_key_element_from_nr(k);
2284 else if (stored_player[i].key[k])
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2289 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2290 getPlayerInventorySize(i);
2292 if (stored_player[i].num_white_keys > 0)
2293 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2296 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2297 stored_player[i].num_white_keys;
2302 int player_nr = game.centered_player_nr;
2304 for (k = 0; k < MAX_NUM_KEYS; k++)
2306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2308 if (game_em.ply[player_nr]->keys & (1 << k))
2309 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2310 get_key_element_from_nr(k);
2312 else if (stored_player[player_nr].key[k])
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2317 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2318 getPlayerInventorySize(player_nr);
2320 if (stored_player[player_nr].num_white_keys > 0)
2321 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2324 stored_player[player_nr].num_white_keys;
2327 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2329 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, i);
2331 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, -i - 1);
2335 game_panel_controls[GAME_PANEL_SCORE].value = score;
2336 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2338 game_panel_controls[GAME_PANEL_TIME].value = time;
2340 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2341 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2342 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2344 if (level.time == 0)
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2349 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2350 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2352 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2354 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2355 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2357 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2358 local_player->shield_normal_time_left;
2359 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2360 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2362 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2363 local_player->shield_deadly_time_left;
2365 game_panel_controls[GAME_PANEL_EXIT].value =
2366 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2369 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2372 EL_EMC_MAGIC_BALL_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2375 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2377 game.light_time_left;
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2380 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2382 game.timegate_time_left;
2384 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2385 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2387 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2388 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2389 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2390 game.lenses_time_left;
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2393 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2395 game.magnify_time_left;
2397 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2398 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2399 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2400 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2401 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2402 EL_BALLOON_SWITCH_NONE);
2404 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2405 local_player->dynabomb_count;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2407 local_player->dynabomb_size;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2409 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2411 game_panel_controls[GAME_PANEL_PENGUINS].value =
2412 game.friends_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2415 game.sokoban_objects_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2417 game.sokoban_fields_still_needed;
2419 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2420 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2422 for (i = 0; i < NUM_BELTS; i++)
2424 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2425 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2426 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2427 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2428 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2432 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2434 game.magic_wall_time_left;
2436 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2437 local_player->gravity;
2439 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2440 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2442 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2443 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2444 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2445 game.panel.element[i].id : EL_UNDEFINED);
2447 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2448 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2449 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2450 element_info[game.panel.element_count[i].id].element_count : 0);
2452 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2453 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2454 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2455 element_info[game.panel.ce_score[i].id].collect_score : 0);
2457 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2458 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2459 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2460 element_info[game.panel.ce_score_element[i].id].collect_score :
2463 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2464 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2467 // update game panel control frames
2469 for (i = 0; game_panel_controls[i].nr != -1; i++)
2471 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2473 if (gpc->type == TYPE_ELEMENT)
2475 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2477 int last_anim_random_frame = gfx.anim_random_frame;
2478 int element = gpc->value;
2479 int graphic = el2panelimg(element);
2481 if (gpc->value != gpc->last_value)
2484 gpc->gfx_random = INIT_GFX_RANDOM();
2490 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2491 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2495 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2496 gfx.anim_random_frame = gpc->gfx_random;
2498 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2499 gpc->gfx_frame = element_info[element].collect_score;
2501 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2504 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2505 gfx.anim_random_frame = last_anim_random_frame;
2508 else if (gpc->type == TYPE_GRAPHIC)
2510 if (gpc->graphic != IMG_UNDEFINED)
2512 int last_anim_random_frame = gfx.anim_random_frame;
2513 int graphic = gpc->graphic;
2515 if (gpc->value != gpc->last_value)
2518 gpc->gfx_random = INIT_GFX_RANDOM();
2524 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2525 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2526 gpc->gfx_random = INIT_GFX_RANDOM();
2529 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2530 gfx.anim_random_frame = gpc->gfx_random;
2532 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2534 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2535 gfx.anim_random_frame = last_anim_random_frame;
2541 static void DisplayGameControlValues(void)
2543 boolean redraw_panel = FALSE;
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (PANEL_DEACTIVATED(gpc->pos))
2553 if (gpc->value == gpc->last_value &&
2554 gpc->frame == gpc->last_frame)
2557 redraw_panel = TRUE;
2563 // copy default game door content to main double buffer
2565 // !!! CHECK AGAIN !!!
2566 SetPanelBackground();
2567 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2568 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2570 // redraw game control buttons
2571 RedrawGameButtons();
2573 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2575 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2577 int nr = game_panel_order[i].nr;
2578 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2579 struct TextPosInfo *pos = gpc->pos;
2580 int type = gpc->type;
2581 int value = gpc->value;
2582 int frame = gpc->frame;
2583 int size = pos->size;
2584 int font = pos->font;
2585 boolean draw_masked = pos->draw_masked;
2586 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2588 if (PANEL_DEACTIVATED(pos))
2591 gpc->last_value = value;
2592 gpc->last_frame = frame;
2594 if (type == TYPE_INTEGER)
2596 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2597 nr == GAME_PANEL_TIME)
2599 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2601 if (use_dynamic_size) // use dynamic number of digits
2603 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2604 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2605 int size2 = size1 + 1;
2606 int font1 = pos->font;
2607 int font2 = pos->font_alt;
2609 size = (value < value_change ? size1 : size2);
2610 font = (value < value_change ? font1 : font2);
2614 // correct text size if "digits" is zero or less
2616 size = strlen(int2str(value, size));
2618 // dynamically correct text alignment
2619 pos->width = size * getFontWidth(font);
2621 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2622 int2str(value, size), font, mask_mode);
2624 else if (type == TYPE_ELEMENT)
2626 int element, graphic;
2630 int dst_x = PANEL_XPOS(pos);
2631 int dst_y = PANEL_YPOS(pos);
2633 if (value != EL_UNDEFINED && value != EL_EMPTY)
2636 graphic = el2panelimg(value);
2638 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2640 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2643 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2646 width = graphic_info[graphic].width * size / TILESIZE;
2647 height = graphic_info[graphic].height * size / TILESIZE;
2650 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2653 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2657 else if (type == TYPE_GRAPHIC)
2659 int graphic = gpc->graphic;
2660 int graphic_active = gpc->graphic_active;
2664 int dst_x = PANEL_XPOS(pos);
2665 int dst_y = PANEL_YPOS(pos);
2666 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2667 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2669 if (graphic != IMG_UNDEFINED && !skip)
2671 if (pos->style == STYLE_REVERSE)
2672 value = 100 - value;
2674 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2676 if (pos->direction & MV_HORIZONTAL)
2678 width = graphic_info[graphic_active].width * value / 100;
2679 height = graphic_info[graphic_active].height;
2681 if (pos->direction == MV_LEFT)
2683 src_x += graphic_info[graphic_active].width - width;
2684 dst_x += graphic_info[graphic_active].width - width;
2689 width = graphic_info[graphic_active].width;
2690 height = graphic_info[graphic_active].height * value / 100;
2692 if (pos->direction == MV_UP)
2694 src_y += graphic_info[graphic_active].height - height;
2695 dst_y += graphic_info[graphic_active].height - height;
2700 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2703 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2708 if (pos->direction & MV_HORIZONTAL)
2710 if (pos->direction == MV_RIGHT)
2717 dst_x = PANEL_XPOS(pos);
2720 width = graphic_info[graphic].width - width;
2724 if (pos->direction == MV_DOWN)
2731 dst_y = PANEL_YPOS(pos);
2734 height = graphic_info[graphic].height - height;
2738 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2741 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2745 else if (type == TYPE_STRING)
2747 boolean active = (value != 0);
2748 char *state_normal = "off";
2749 char *state_active = "on";
2750 char *state = (active ? state_active : state_normal);
2751 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2752 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2753 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2754 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2756 if (nr == GAME_PANEL_GRAVITY_STATE)
2758 int font1 = pos->font; // (used for normal state)
2759 int font2 = pos->font_alt; // (used for active state)
2761 font = (active ? font2 : font1);
2770 // don't truncate output if "chars" is zero or less
2773 // dynamically correct text alignment
2774 pos->width = size * getFontWidth(font);
2777 s_cut = getStringCopyN(s, size);
2779 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2780 s_cut, font, mask_mode);
2786 redraw_mask |= REDRAW_DOOR_1;
2789 SetGameStatus(GAME_MODE_PLAYING);
2792 void UpdateAndDisplayGameControlValues(void)
2794 if (tape.deactivate_display)
2797 UpdateGameControlValues();
2798 DisplayGameControlValues();
2802 static void UpdateGameDoorValues(void)
2804 UpdateGameControlValues();
2808 void DrawGameDoorValues(void)
2810 DisplayGameControlValues();
2814 // ============================================================================
2816 // ----------------------------------------------------------------------------
2817 // initialize game engine due to level / tape version number
2818 // ============================================================================
2820 static void InitGameEngine(void)
2822 int i, j, k, l, x, y;
2824 // set game engine from tape file when re-playing, else from level file
2825 game.engine_version = (tape.playing ? tape.engine_version :
2826 level.game_version);
2828 // set single or multi-player game mode (needed for re-playing tapes)
2829 game.team_mode = setup.team_mode;
2833 int num_players = 0;
2835 for (i = 0; i < MAX_PLAYERS; i++)
2836 if (tape.player_participates[i])
2839 // multi-player tapes contain input data for more than one player
2840 game.team_mode = (num_players > 1);
2843 // --------------------------------------------------------------------------
2844 // set flags for bugs and changes according to active game engine version
2845 // --------------------------------------------------------------------------
2848 Summary of bugfix/change:
2849 Fixed handling for custom elements that change when pushed by the player.
2851 Fixed/changed in version:
2855 Before 3.1.0, custom elements that "change when pushing" changed directly
2856 after the player started pushing them (until then handled in "DigField()").
2857 Since 3.1.0, these custom elements are not changed until the "pushing"
2858 move of the element is finished (now handled in "ContinueMoving()").
2860 Affected levels/tapes:
2861 The first condition is generally needed for all levels/tapes before version
2862 3.1.0, which might use the old behaviour before it was changed; known tapes
2863 that are affected are some tapes from the level set "Walpurgis Gardens" by
2865 The second condition is an exception from the above case and is needed for
2866 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2867 above (including some development versions of 3.1.0), but before it was
2868 known that this change would break tapes like the above and was fixed in
2869 3.1.1, so that the changed behaviour was active although the engine version
2870 while recording maybe was before 3.1.0. There is at least one tape that is
2871 affected by this exception, which is the tape for the one-level set "Bug
2872 Machine" by Juergen Bonhagen.
2875 game.use_change_when_pushing_bug =
2876 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2878 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2879 tape.game_version < VERSION_IDENT(3,1,1,0)));
2882 Summary of bugfix/change:
2883 Fixed handling for blocking the field the player leaves when moving.
2885 Fixed/changed in version:
2889 Before 3.1.1, when "block last field when moving" was enabled, the field
2890 the player is leaving when moving was blocked for the time of the move,
2891 and was directly unblocked afterwards. This resulted in the last field
2892 being blocked for exactly one less than the number of frames of one player
2893 move. Additionally, even when blocking was disabled, the last field was
2894 blocked for exactly one frame.
2895 Since 3.1.1, due to changes in player movement handling, the last field
2896 is not blocked at all when blocking is disabled. When blocking is enabled,
2897 the last field is blocked for exactly the number of frames of one player
2898 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2899 last field is blocked for exactly one more than the number of frames of
2902 Affected levels/tapes:
2903 (!!! yet to be determined -- probably many !!!)
2906 game.use_block_last_field_bug =
2907 (game.engine_version < VERSION_IDENT(3,1,1,0));
2909 /* various special flags and settings for native Emerald Mine game engine */
2911 game_em.use_single_button =
2912 (game.engine_version > VERSION_IDENT(4,0,0,2));
2914 game_em.use_snap_key_bug =
2915 (game.engine_version < VERSION_IDENT(4,0,1,0));
2917 game_em.use_old_explosions =
2918 (game.engine_version < VERSION_IDENT(4,1,4,2));
2920 // --------------------------------------------------------------------------
2922 // set maximal allowed number of custom element changes per game frame
2923 game.max_num_changes_per_frame = 1;
2925 // default scan direction: scan playfield from top/left to bottom/right
2926 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2928 // dynamically adjust element properties according to game engine version
2929 InitElementPropertiesEngine(game.engine_version);
2932 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2933 printf(" tape version == %06d [%s] [file: %06d]\n",
2934 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2936 printf(" => game.engine_version == %06d\n", game.engine_version);
2939 // ---------- initialize player's initial move delay ------------------------
2941 // dynamically adjust player properties according to level information
2942 for (i = 0; i < MAX_PLAYERS; i++)
2943 game.initial_move_delay_value[i] =
2944 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2946 // dynamically adjust player properties according to game engine version
2947 for (i = 0; i < MAX_PLAYERS; i++)
2948 game.initial_move_delay[i] =
2949 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2950 game.initial_move_delay_value[i] : 0);
2952 // ---------- initialize player's initial push delay ------------------------
2954 // dynamically adjust player properties according to game engine version
2955 game.initial_push_delay_value =
2956 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2958 // ---------- initialize changing elements ----------------------------------
2960 // initialize changing elements information
2961 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2963 struct ElementInfo *ei = &element_info[i];
2965 // this pointer might have been changed in the level editor
2966 ei->change = &ei->change_page[0];
2968 if (!IS_CUSTOM_ELEMENT(i))
2970 ei->change->target_element = EL_EMPTY_SPACE;
2971 ei->change->delay_fixed = 0;
2972 ei->change->delay_random = 0;
2973 ei->change->delay_frames = 1;
2976 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2978 ei->has_change_event[j] = FALSE;
2980 ei->event_page_nr[j] = 0;
2981 ei->event_page[j] = &ei->change_page[0];
2985 // add changing elements from pre-defined list
2986 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2988 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2989 struct ElementInfo *ei = &element_info[ch_delay->element];
2991 ei->change->target_element = ch_delay->target_element;
2992 ei->change->delay_fixed = ch_delay->change_delay;
2994 ei->change->pre_change_function = ch_delay->pre_change_function;
2995 ei->change->change_function = ch_delay->change_function;
2996 ei->change->post_change_function = ch_delay->post_change_function;
2998 ei->change->can_change = TRUE;
2999 ei->change->can_change_or_has_action = TRUE;
3001 ei->has_change_event[CE_DELAY] = TRUE;
3003 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3004 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3007 // ---------- initialize internal run-time variables ------------------------
3009 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3011 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3013 for (j = 0; j < ei->num_change_pages; j++)
3015 ei->change_page[j].can_change_or_has_action =
3016 (ei->change_page[j].can_change |
3017 ei->change_page[j].has_action);
3021 // add change events from custom element configuration
3022 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3024 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3026 for (j = 0; j < ei->num_change_pages; j++)
3028 if (!ei->change_page[j].can_change_or_has_action)
3031 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3033 // only add event page for the first page found with this event
3034 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3036 ei->has_change_event[k] = TRUE;
3038 ei->event_page_nr[k] = j;
3039 ei->event_page[k] = &ei->change_page[j];
3045 // ---------- initialize reference elements in change conditions ------------
3047 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3049 int element = EL_CUSTOM_START + i;
3050 struct ElementInfo *ei = &element_info[element];
3052 for (j = 0; j < ei->num_change_pages; j++)
3054 int trigger_element = ei->change_page[j].initial_trigger_element;
3056 if (trigger_element >= EL_PREV_CE_8 &&
3057 trigger_element <= EL_NEXT_CE_8)
3058 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3060 ei->change_page[j].trigger_element = trigger_element;
3064 // ---------- initialize run-time trigger player and element ----------------
3066 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3068 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3070 for (j = 0; j < ei->num_change_pages; j++)
3072 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3073 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3074 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3075 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3076 ei->change_page[j].actual_trigger_ce_value = 0;
3077 ei->change_page[j].actual_trigger_ce_score = 0;
3081 // ---------- initialize trigger events -------------------------------------
3083 // initialize trigger events information
3084 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3085 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3086 trigger_events[i][j] = FALSE;
3088 // add trigger events from element change event properties
3089 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3091 struct ElementInfo *ei = &element_info[i];
3093 for (j = 0; j < ei->num_change_pages; j++)
3095 if (!ei->change_page[j].can_change_or_has_action)
3098 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3100 int trigger_element = ei->change_page[j].trigger_element;
3102 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3104 if (ei->change_page[j].has_event[k])
3106 if (IS_GROUP_ELEMENT(trigger_element))
3108 struct ElementGroupInfo *group =
3109 element_info[trigger_element].group;
3111 for (l = 0; l < group->num_elements_resolved; l++)
3112 trigger_events[group->element_resolved[l]][k] = TRUE;
3114 else if (trigger_element == EL_ANY_ELEMENT)
3115 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3116 trigger_events[l][k] = TRUE;
3118 trigger_events[trigger_element][k] = TRUE;
3125 // ---------- initialize push delay -----------------------------------------
3127 // initialize push delay values to default
3128 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3130 if (!IS_CUSTOM_ELEMENT(i))
3132 // set default push delay values (corrected since version 3.0.7-1)
3133 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3135 element_info[i].push_delay_fixed = 2;
3136 element_info[i].push_delay_random = 8;
3140 element_info[i].push_delay_fixed = 8;
3141 element_info[i].push_delay_random = 8;
3146 // set push delay value for certain elements from pre-defined list
3147 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3149 int e = push_delay_list[i].element;
3151 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3152 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3155 // set push delay value for Supaplex elements for newer engine versions
3156 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3158 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3160 if (IS_SP_ELEMENT(i))
3162 // set SP push delay to just enough to push under a falling zonk
3163 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3165 element_info[i].push_delay_fixed = delay;
3166 element_info[i].push_delay_random = 0;
3171 // ---------- initialize move stepsize --------------------------------------
3173 // initialize move stepsize values to default
3174 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3175 if (!IS_CUSTOM_ELEMENT(i))
3176 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3178 // set move stepsize value for certain elements from pre-defined list
3179 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3181 int e = move_stepsize_list[i].element;
3183 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3186 // ---------- initialize collect score --------------------------------------
3188 // initialize collect score values for custom elements from initial value
3189 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3190 if (IS_CUSTOM_ELEMENT(i))
3191 element_info[i].collect_score = element_info[i].collect_score_initial;
3193 // ---------- initialize collect count --------------------------------------
3195 // initialize collect count values for non-custom elements
3196 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3197 if (!IS_CUSTOM_ELEMENT(i))
3198 element_info[i].collect_count_initial = 0;
3200 // add collect count values for all elements from pre-defined list
3201 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3202 element_info[collect_count_list[i].element].collect_count_initial =
3203 collect_count_list[i].count;
3205 // ---------- initialize access direction -----------------------------------
3207 // initialize access direction values to default (access from every side)
3208 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3209 if (!IS_CUSTOM_ELEMENT(i))
3210 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3212 // set access direction value for certain elements from pre-defined list
3213 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3214 element_info[access_direction_list[i].element].access_direction =
3215 access_direction_list[i].direction;
3217 // ---------- initialize explosion content ----------------------------------
3218 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3220 if (IS_CUSTOM_ELEMENT(i))
3223 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3225 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3227 element_info[i].content.e[x][y] =
3228 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3229 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3230 i == EL_PLAYER_3 ? EL_EMERALD :
3231 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3232 i == EL_MOLE ? EL_EMERALD_RED :
3233 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3234 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3235 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3236 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3237 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3238 i == EL_WALL_EMERALD ? EL_EMERALD :
3239 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3240 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3241 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3242 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3243 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3244 i == EL_WALL_PEARL ? EL_PEARL :
3245 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3250 // ---------- initialize recursion detection --------------------------------
3251 recursion_loop_depth = 0;
3252 recursion_loop_detected = FALSE;
3253 recursion_loop_element = EL_UNDEFINED;
3255 // ---------- initialize graphics engine ------------------------------------
3256 game.scroll_delay_value =
3257 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3258 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3259 !setup.forced_scroll_delay ? 0 :
3260 setup.scroll_delay ? setup.scroll_delay_value : 0);
3261 game.scroll_delay_value =
3262 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3264 // ---------- initialize game engine snapshots ------------------------------
3265 for (i = 0; i < MAX_PLAYERS; i++)
3266 game.snapshot.last_action[i] = 0;
3267 game.snapshot.changed_action = FALSE;
3268 game.snapshot.collected_item = FALSE;
3269 game.snapshot.mode =
3270 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3271 SNAPSHOT_MODE_EVERY_STEP :
3272 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3273 SNAPSHOT_MODE_EVERY_MOVE :
3274 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3275 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3276 game.snapshot.save_snapshot = FALSE;
3278 // ---------- initialize level time for Supaplex engine ---------------------
3279 // Supaplex levels with time limit currently unsupported -- should be added
3280 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3284 static int get_num_special_action(int element, int action_first,
3287 int num_special_action = 0;
3290 for (i = action_first; i <= action_last; i++)
3292 boolean found = FALSE;
3294 for (j = 0; j < NUM_DIRECTIONS; j++)
3295 if (el_act_dir2img(element, i, j) !=
3296 el_act_dir2img(element, ACTION_DEFAULT, j))
3300 num_special_action++;
3305 return num_special_action;
3309 // ============================================================================
3311 // ----------------------------------------------------------------------------
3312 // initialize and start new game
3313 // ============================================================================
3315 #if DEBUG_INIT_PLAYER
3316 static void DebugPrintPlayerStatus(char *message)
3323 printf("%s:\n", message);
3325 for (i = 0; i < MAX_PLAYERS; i++)
3327 struct PlayerInfo *player = &stored_player[i];
3329 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3333 player->connected_locally,
3334 player->connected_network,
3337 if (local_player == player)
3338 printf(" (local player)");
3347 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3348 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3349 int fade_mask = REDRAW_FIELD;
3351 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3352 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3353 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3354 int initial_move_dir = MV_DOWN;
3357 // required here to update video display before fading (FIX THIS)
3358 DrawMaskedBorder(REDRAW_DOOR_2);
3360 if (!game.restart_level)
3361 CloseDoor(DOOR_CLOSE_1);
3363 SetGameStatus(GAME_MODE_PLAYING);
3365 if (level_editor_test_game)
3366 FadeSkipNextFadeOut();
3368 FadeSetEnterScreen();
3371 fade_mask = REDRAW_ALL;
3373 FadeLevelSoundsAndMusic();
3375 ExpireSoundLoops(TRUE);
3379 if (level_editor_test_game)
3380 FadeSkipNextFadeIn();
3382 // needed if different viewport properties defined for playing
3383 ChangeViewportPropertiesIfNeeded();
3387 DrawCompleteVideoDisplay();
3389 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3392 InitGameControlValues();
3394 // don't play tapes over network
3395 network_playing = (network.enabled && !tape.playing);
3397 for (i = 0; i < MAX_PLAYERS; i++)
3399 struct PlayerInfo *player = &stored_player[i];
3401 player->index_nr = i;
3402 player->index_bit = (1 << i);
3403 player->element_nr = EL_PLAYER_1 + i;
3405 player->present = FALSE;
3406 player->active = FALSE;
3407 player->mapped = FALSE;
3409 player->killed = FALSE;
3410 player->reanimated = FALSE;
3411 player->buried = FALSE;
3414 player->effective_action = 0;
3415 player->programmed_action = 0;
3416 player->snap_action = 0;
3418 player->mouse_action.lx = 0;
3419 player->mouse_action.ly = 0;
3420 player->mouse_action.button = 0;
3421 player->mouse_action.button_hint = 0;
3423 player->effective_mouse_action.lx = 0;
3424 player->effective_mouse_action.ly = 0;
3425 player->effective_mouse_action.button = 0;
3426 player->effective_mouse_action.button_hint = 0;
3428 for (j = 0; j < MAX_NUM_KEYS; j++)
3429 player->key[j] = FALSE;
3431 player->num_white_keys = 0;
3433 player->dynabomb_count = 0;
3434 player->dynabomb_size = 1;
3435 player->dynabombs_left = 0;
3436 player->dynabomb_xl = FALSE;
3438 player->MovDir = initial_move_dir;
3441 player->GfxDir = initial_move_dir;
3442 player->GfxAction = ACTION_DEFAULT;
3444 player->StepFrame = 0;
3446 player->initial_element = player->element_nr;
3447 player->artwork_element =
3448 (level.use_artwork_element[i] ? level.artwork_element[i] :
3449 player->element_nr);
3450 player->use_murphy = FALSE;
3452 player->block_last_field = FALSE; // initialized in InitPlayerField()
3453 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3455 player->gravity = level.initial_player_gravity[i];
3457 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3459 player->actual_frame_counter = 0;
3461 player->step_counter = 0;
3463 player->last_move_dir = initial_move_dir;
3465 player->is_active = FALSE;
3467 player->is_waiting = FALSE;
3468 player->is_moving = FALSE;
3469 player->is_auto_moving = FALSE;
3470 player->is_digging = FALSE;
3471 player->is_snapping = FALSE;
3472 player->is_collecting = FALSE;
3473 player->is_pushing = FALSE;
3474 player->is_switching = FALSE;
3475 player->is_dropping = FALSE;
3476 player->is_dropping_pressed = FALSE;
3478 player->is_bored = FALSE;
3479 player->is_sleeping = FALSE;
3481 player->was_waiting = TRUE;
3482 player->was_moving = FALSE;
3483 player->was_snapping = FALSE;
3484 player->was_dropping = FALSE;
3486 player->force_dropping = FALSE;
3488 player->frame_counter_bored = -1;
3489 player->frame_counter_sleeping = -1;
3491 player->anim_delay_counter = 0;
3492 player->post_delay_counter = 0;
3494 player->dir_waiting = initial_move_dir;
3495 player->action_waiting = ACTION_DEFAULT;
3496 player->last_action_waiting = ACTION_DEFAULT;
3497 player->special_action_bored = ACTION_DEFAULT;
3498 player->special_action_sleeping = ACTION_DEFAULT;
3500 player->switch_x = -1;
3501 player->switch_y = -1;
3503 player->drop_x = -1;
3504 player->drop_y = -1;
3506 player->show_envelope = 0;
3508 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3510 player->push_delay = -1; // initialized when pushing starts
3511 player->push_delay_value = game.initial_push_delay_value;
3513 player->drop_delay = 0;
3514 player->drop_pressed_delay = 0;
3516 player->last_jx = -1;
3517 player->last_jy = -1;
3521 player->shield_normal_time_left = 0;
3522 player->shield_deadly_time_left = 0;
3524 player->inventory_infinite_element = EL_UNDEFINED;
3525 player->inventory_size = 0;
3527 if (level.use_initial_inventory[i])
3529 for (j = 0; j < level.initial_inventory_size[i]; j++)
3531 int element = level.initial_inventory_content[i][j];
3532 int collect_count = element_info[element].collect_count_initial;
3535 if (!IS_CUSTOM_ELEMENT(element))
3538 if (collect_count == 0)
3539 player->inventory_infinite_element = element;
3541 for (k = 0; k < collect_count; k++)
3542 if (player->inventory_size < MAX_INVENTORY_SIZE)
3543 player->inventory_element[player->inventory_size++] = element;
3547 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3548 SnapField(player, 0, 0);
3550 map_player_action[i] = i;
3553 network_player_action_received = FALSE;
3555 // initial null action
3556 if (network_playing)
3557 SendToServer_MovePlayer(MV_NONE);
3562 TimeLeft = level.time;
3565 ScreenMovDir = MV_NONE;
3569 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3571 game.robot_wheel_x = -1;
3572 game.robot_wheel_y = -1;
3577 game.all_players_gone = FALSE;
3579 game.LevelSolved = FALSE;
3580 game.GameOver = FALSE;
3582 game.GamePlayed = !tape.playing;
3584 game.LevelSolved_GameWon = FALSE;
3585 game.LevelSolved_GameEnd = FALSE;
3586 game.LevelSolved_SaveTape = FALSE;
3587 game.LevelSolved_SaveScore = FALSE;
3589 game.LevelSolved_CountingTime = 0;
3590 game.LevelSolved_CountingScore = 0;
3591 game.LevelSolved_CountingHealth = 0;
3593 game.panel.active = TRUE;
3595 game.no_time_limit = (level.time == 0);
3597 game.yamyam_content_nr = 0;
3598 game.robot_wheel_active = FALSE;
3599 game.magic_wall_active = FALSE;
3600 game.magic_wall_time_left = 0;
3601 game.light_time_left = 0;
3602 game.timegate_time_left = 0;
3603 game.switchgate_pos = 0;
3604 game.wind_direction = level.wind_direction_initial;
3607 game.score_final = 0;
3609 game.health = MAX_HEALTH;
3610 game.health_final = MAX_HEALTH;
3612 game.gems_still_needed = level.gems_needed;
3613 game.sokoban_fields_still_needed = 0;
3614 game.sokoban_objects_still_needed = 0;
3615 game.lights_still_needed = 0;
3616 game.players_still_needed = 0;
3617 game.friends_still_needed = 0;
3619 game.lenses_time_left = 0;
3620 game.magnify_time_left = 0;
3622 game.ball_active = level.ball_active_initial;
3623 game.ball_content_nr = 0;
3625 game.explosions_delayed = TRUE;
3627 game.envelope_active = FALSE;
3629 for (i = 0; i < NUM_BELTS; i++)
3631 game.belt_dir[i] = MV_NONE;
3632 game.belt_dir_nr[i] = 3; // not moving, next moving left
3635 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3636 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3638 #if DEBUG_INIT_PLAYER
3639 DebugPrintPlayerStatus("Player status at level initialization");
3642 SCAN_PLAYFIELD(x, y)
3644 Feld[x][y] = Last[x][y] = level.field[x][y];
3645 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3646 ChangeDelay[x][y] = 0;
3647 ChangePage[x][y] = -1;
3648 CustomValue[x][y] = 0; // initialized in InitField()
3649 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3651 WasJustMoving[x][y] = 0;
3652 WasJustFalling[x][y] = 0;
3653 CheckCollision[x][y] = 0;
3654 CheckImpact[x][y] = 0;
3656 Pushed[x][y] = FALSE;
3658 ChangeCount[x][y] = 0;
3659 ChangeEvent[x][y] = -1;
3661 ExplodePhase[x][y] = 0;
3662 ExplodeDelay[x][y] = 0;
3663 ExplodeField[x][y] = EX_TYPE_NONE;
3665 RunnerVisit[x][y] = 0;
3666 PlayerVisit[x][y] = 0;
3669 GfxRandom[x][y] = INIT_GFX_RANDOM();
3670 GfxElement[x][y] = EL_UNDEFINED;
3671 GfxAction[x][y] = ACTION_DEFAULT;
3672 GfxDir[x][y] = MV_NONE;
3673 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3676 SCAN_PLAYFIELD(x, y)
3678 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3680 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3682 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3685 InitField(x, y, TRUE);
3687 ResetGfxAnimation(x, y);
3692 for (i = 0; i < MAX_PLAYERS; i++)
3694 struct PlayerInfo *player = &stored_player[i];
3696 // set number of special actions for bored and sleeping animation
3697 player->num_special_action_bored =
3698 get_num_special_action(player->artwork_element,
3699 ACTION_BORING_1, ACTION_BORING_LAST);
3700 player->num_special_action_sleeping =
3701 get_num_special_action(player->artwork_element,
3702 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3705 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3706 emulate_sb ? EMU_SOKOBAN :
3707 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3709 // initialize type of slippery elements
3710 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3712 if (!IS_CUSTOM_ELEMENT(i))
3714 // default: elements slip down either to the left or right randomly
3715 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3717 // SP style elements prefer to slip down on the left side
3718 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3719 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3721 // BD style elements prefer to slip down on the left side
3722 if (game.emulation == EMU_BOULDERDASH)
3723 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3727 // initialize explosion and ignition delay
3728 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3730 if (!IS_CUSTOM_ELEMENT(i))
3733 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3734 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3735 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3736 int last_phase = (num_phase + 1) * delay;
3737 int half_phase = (num_phase / 2) * delay;
3739 element_info[i].explosion_delay = last_phase - 1;
3740 element_info[i].ignition_delay = half_phase;
3742 if (i == EL_BLACK_ORB)
3743 element_info[i].ignition_delay = 1;
3747 // correct non-moving belts to start moving left
3748 for (i = 0; i < NUM_BELTS; i++)
3749 if (game.belt_dir[i] == MV_NONE)
3750 game.belt_dir_nr[i] = 3; // not moving, next moving left
3752 #if USE_NEW_PLAYER_ASSIGNMENTS
3753 // use preferred player also in local single-player mode
3754 if (!network.enabled && !game.team_mode)
3756 int new_index_nr = setup.network_player_nr;
3758 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3760 for (i = 0; i < MAX_PLAYERS; i++)
3761 stored_player[i].connected_locally = FALSE;
3763 stored_player[new_index_nr].connected_locally = TRUE;
3767 for (i = 0; i < MAX_PLAYERS; i++)
3769 stored_player[i].connected = FALSE;
3771 // in network game mode, the local player might not be the first player
3772 if (stored_player[i].connected_locally)
3773 local_player = &stored_player[i];
3776 if (!network.enabled)
3777 local_player->connected = TRUE;
3781 for (i = 0; i < MAX_PLAYERS; i++)
3782 stored_player[i].connected = tape.player_participates[i];
3784 else if (network.enabled)
3786 // add team mode players connected over the network (needed for correct
3787 // assignment of player figures from level to locally playing players)
3789 for (i = 0; i < MAX_PLAYERS; i++)
3790 if (stored_player[i].connected_network)
3791 stored_player[i].connected = TRUE;
3793 else if (game.team_mode)
3795 // try to guess locally connected team mode players (needed for correct
3796 // assignment of player figures from level to locally playing players)
3798 for (i = 0; i < MAX_PLAYERS; i++)
3799 if (setup.input[i].use_joystick ||
3800 setup.input[i].key.left != KSYM_UNDEFINED)
3801 stored_player[i].connected = TRUE;
3804 #if DEBUG_INIT_PLAYER
3805 DebugPrintPlayerStatus("Player status after level initialization");
3808 #if DEBUG_INIT_PLAYER
3810 printf("Reassigning players ...\n");
3813 // check if any connected player was not found in playfield
3814 for (i = 0; i < MAX_PLAYERS; i++)
3816 struct PlayerInfo *player = &stored_player[i];
3818 if (player->connected && !player->present)
3820 struct PlayerInfo *field_player = NULL;
3822 #if DEBUG_INIT_PLAYER
3824 printf("- looking for field player for player %d ...\n", i + 1);
3827 // assign first free player found that is present in the playfield
3829 // first try: look for unmapped playfield player that is not connected
3830 for (j = 0; j < MAX_PLAYERS; j++)
3831 if (field_player == NULL &&
3832 stored_player[j].present &&
3833 !stored_player[j].mapped &&
3834 !stored_player[j].connected)
3835 field_player = &stored_player[j];
3837 // second try: look for *any* unmapped playfield player
3838 for (j = 0; j < MAX_PLAYERS; j++)
3839 if (field_player == NULL &&
3840 stored_player[j].present &&
3841 !stored_player[j].mapped)
3842 field_player = &stored_player[j];
3844 if (field_player != NULL)
3846 int jx = field_player->jx, jy = field_player->jy;
3848 #if DEBUG_INIT_PLAYER
3850 printf("- found player %d\n", field_player->index_nr + 1);
3853 player->present = FALSE;
3854 player->active = FALSE;
3856 field_player->present = TRUE;
3857 field_player->active = TRUE;
3860 player->initial_element = field_player->initial_element;
3861 player->artwork_element = field_player->artwork_element;
3863 player->block_last_field = field_player->block_last_field;
3864 player->block_delay_adjustment = field_player->block_delay_adjustment;
3867 StorePlayer[jx][jy] = field_player->element_nr;
3869 field_player->jx = field_player->last_jx = jx;
3870 field_player->jy = field_player->last_jy = jy;
3872 if (local_player == player)
3873 local_player = field_player;
3875 map_player_action[field_player->index_nr] = i;
3877 field_player->mapped = TRUE;
3879 #if DEBUG_INIT_PLAYER
3881 printf("- map_player_action[%d] == %d\n",
3882 field_player->index_nr + 1, i + 1);
3887 if (player->connected && player->present)
3888 player->mapped = TRUE;
3891 #if DEBUG_INIT_PLAYER
3892 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3897 // check if any connected player was not found in playfield
3898 for (i = 0; i < MAX_PLAYERS; i++)
3900 struct PlayerInfo *player = &stored_player[i];
3902 if (player->connected && !player->present)
3904 for (j = 0; j < MAX_PLAYERS; j++)
3906 struct PlayerInfo *field_player = &stored_player[j];
3907 int jx = field_player->jx, jy = field_player->jy;
3909 // assign first free player found that is present in the playfield
3910 if (field_player->present && !field_player->connected)
3912 player->present = TRUE;
3913 player->active = TRUE;
3915 field_player->present = FALSE;
3916 field_player->active = FALSE;
3918 player->initial_element = field_player->initial_element;
3919 player->artwork_element = field_player->artwork_element;
3921 player->block_last_field = field_player->block_last_field;
3922 player->block_delay_adjustment = field_player->block_delay_adjustment;
3924 StorePlayer[jx][jy] = player->element_nr;
3926 player->jx = player->last_jx = jx;
3927 player->jy = player->last_jy = jy;
3937 printf("::: local_player->present == %d\n", local_player->present);
3940 // set focus to local player for network games, else to all players
3941 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3942 game.centered_player_nr_next = game.centered_player_nr;
3943 game.set_centered_player = FALSE;
3944 game.set_centered_player_wrap = FALSE;
3946 if (network_playing && tape.recording)
3948 // store client dependent player focus when recording network games
3949 tape.centered_player_nr_next = game.centered_player_nr_next;
3950 tape.set_centered_player = TRUE;
3955 // when playing a tape, eliminate all players who do not participate
3957 #if USE_NEW_PLAYER_ASSIGNMENTS
3959 if (!game.team_mode)
3961 for (i = 0; i < MAX_PLAYERS; i++)
3963 if (stored_player[i].active &&
3964 !tape.player_participates[map_player_action[i]])
3966 struct PlayerInfo *player = &stored_player[i];
3967 int jx = player->jx, jy = player->jy;
3969 #if DEBUG_INIT_PLAYER
3971 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3974 player->active = FALSE;
3975 StorePlayer[jx][jy] = 0;
3976 Feld[jx][jy] = EL_EMPTY;
3983 for (i = 0; i < MAX_PLAYERS; i++)
3985 if (stored_player[i].active &&
3986 !tape.player_participates[i])
3988 struct PlayerInfo *player = &stored_player[i];
3989 int jx = player->jx, jy = player->jy;
3991 player->active = FALSE;
3992 StorePlayer[jx][jy] = 0;
3993 Feld[jx][jy] = EL_EMPTY;
3998 else if (!network.enabled && !game.team_mode) // && !tape.playing
4000 // when in single player mode, eliminate all but the local player
4002 for (i = 0; i < MAX_PLAYERS; i++)
4004 struct PlayerInfo *player = &stored_player[i];
4006 if (player->active && player != local_player)
4008 int jx = player->jx, jy = player->jy;
4010 player->active = FALSE;
4011 player->present = FALSE;
4013 StorePlayer[jx][jy] = 0;
4014 Feld[jx][jy] = EL_EMPTY;
4019 for (i = 0; i < MAX_PLAYERS; i++)
4020 if (stored_player[i].active)
4021 game.players_still_needed++;
4023 if (level.solved_by_one_player)
4024 game.players_still_needed = 1;
4026 // when recording the game, store which players take part in the game
4029 #if USE_NEW_PLAYER_ASSIGNMENTS
4030 for (i = 0; i < MAX_PLAYERS; i++)
4031 if (stored_player[i].connected)
4032 tape.player_participates[i] = TRUE;
4034 for (i = 0; i < MAX_PLAYERS; i++)
4035 if (stored_player[i].active)
4036 tape.player_participates[i] = TRUE;
4040 #if DEBUG_INIT_PLAYER
4041 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4044 if (BorderElement == EL_EMPTY)
4047 SBX_Right = lev_fieldx - SCR_FIELDX;
4049 SBY_Lower = lev_fieldy - SCR_FIELDY;
4054 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4056 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4059 if (full_lev_fieldx <= SCR_FIELDX)
4060 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4061 if (full_lev_fieldy <= SCR_FIELDY)
4062 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4064 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4066 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4069 // if local player not found, look for custom element that might create
4070 // the player (make some assumptions about the right custom element)
4071 if (!local_player->present)
4073 int start_x = 0, start_y = 0;
4074 int found_rating = 0;
4075 int found_element = EL_UNDEFINED;
4076 int player_nr = local_player->index_nr;
4078 SCAN_PLAYFIELD(x, y)
4080 int element = Feld[x][y];
4085 if (level.use_start_element[player_nr] &&
4086 level.start_element[player_nr] == element &&
4093 found_element = element;
4096 if (!IS_CUSTOM_ELEMENT(element))
4099 if (CAN_CHANGE(element))
4101 for (i = 0; i < element_info[element].num_change_pages; i++)
4103 // check for player created from custom element as single target
4104 content = element_info[element].change_page[i].target_element;
4105 is_player = ELEM_IS_PLAYER(content);
4107 if (is_player && (found_rating < 3 ||
4108 (found_rating == 3 && element < found_element)))
4114 found_element = element;
4119 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4121 // check for player created from custom element as explosion content
4122 content = element_info[element].content.e[xx][yy];
4123 is_player = ELEM_IS_PLAYER(content);
4125 if (is_player && (found_rating < 2 ||
4126 (found_rating == 2 && element < found_element)))
4128 start_x = x + xx - 1;
4129 start_y = y + yy - 1;
4132 found_element = element;
4135 if (!CAN_CHANGE(element))
4138 for (i = 0; i < element_info[element].num_change_pages; i++)
4140 // check for player created from custom element as extended target
4142 element_info[element].change_page[i].target_content.e[xx][yy];
4144 is_player = ELEM_IS_PLAYER(content);
4146 if (is_player && (found_rating < 1 ||
4147 (found_rating == 1 && element < found_element)))
4149 start_x = x + xx - 1;
4150 start_y = y + yy - 1;
4153 found_element = element;
4159 scroll_x = SCROLL_POSITION_X(start_x);
4160 scroll_y = SCROLL_POSITION_Y(start_y);
4164 scroll_x = SCROLL_POSITION_X(local_player->jx);
4165 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4168 // !!! FIX THIS (START) !!!
4169 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4171 InitGameEngine_EM();
4173 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4175 InitGameEngine_SP();
4177 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4179 InitGameEngine_MM();
4183 DrawLevel(REDRAW_FIELD);
4186 // after drawing the level, correct some elements
4187 if (game.timegate_time_left == 0)
4188 CloseAllOpenTimegates();
4191 // blit playfield from scroll buffer to normal back buffer for fading in
4192 BlitScreenToBitmap(backbuffer);
4193 // !!! FIX THIS (END) !!!
4195 DrawMaskedBorder(fade_mask);
4200 // full screen redraw is required at this point in the following cases:
4201 // - special editor door undrawn when game was started from level editor
4202 // - drawing area (playfield) was changed and has to be removed completely
4203 redraw_mask = REDRAW_ALL;
4207 if (!game.restart_level)
4209 // copy default game door content to main double buffer
4211 // !!! CHECK AGAIN !!!
4212 SetPanelBackground();
4213 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4214 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4217 SetPanelBackground();
4218 SetDrawBackgroundMask(REDRAW_DOOR_1);
4220 UpdateAndDisplayGameControlValues();
4222 if (!game.restart_level)
4228 CreateGameButtons();
4233 // copy actual game door content to door double buffer for OpenDoor()
4234 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4236 OpenDoor(DOOR_OPEN_ALL);
4238 KeyboardAutoRepeatOffUnlessAutoplay();
4240 #if DEBUG_INIT_PLAYER
4241 DebugPrintPlayerStatus("Player status (final)");
4250 if (!game.restart_level && !tape.playing)
4252 LevelStats_incPlayed(level_nr);
4254 SaveLevelSetup_SeriesInfo();
4257 game.restart_level = FALSE;
4258 game.restart_game_message = NULL;
4259 game.request_active = FALSE;
4261 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4262 InitGameActions_MM();
4264 SaveEngineSnapshotToListInitial();
4266 if (!game.restart_level)
4268 PlaySound(SND_GAME_STARTING);
4270 if (setup.sound_music)
4275 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4276 int actual_player_x, int actual_player_y)
4278 // this is used for non-R'n'D game engines to update certain engine values
4280 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4282 actual_player_x = correctLevelPosX_EM(actual_player_x);
4283 actual_player_y = correctLevelPosY_EM(actual_player_y);
4286 // needed to determine if sounds are played within the visible screen area
4287 scroll_x = actual_scroll_x;
4288 scroll_y = actual_scroll_y;
4290 // needed to get player position for "follow finger" playing input method
4291 local_player->jx = actual_player_x;
4292 local_player->jy = actual_player_y;
4295 void InitMovDir(int x, int y)
4297 int i, element = Feld[x][y];
4298 static int xy[4][2] =
4305 static int direction[3][4] =
4307 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4308 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4309 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4318 Feld[x][y] = EL_BUG;
4319 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4322 case EL_SPACESHIP_RIGHT:
4323 case EL_SPACESHIP_UP:
4324 case EL_SPACESHIP_LEFT:
4325 case EL_SPACESHIP_DOWN:
4326 Feld[x][y] = EL_SPACESHIP;
4327 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4330 case EL_BD_BUTTERFLY_RIGHT:
4331 case EL_BD_BUTTERFLY_UP:
4332 case EL_BD_BUTTERFLY_LEFT:
4333 case EL_BD_BUTTERFLY_DOWN:
4334 Feld[x][y] = EL_BD_BUTTERFLY;
4335 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4338 case EL_BD_FIREFLY_RIGHT:
4339 case EL_BD_FIREFLY_UP:
4340 case EL_BD_FIREFLY_LEFT:
4341 case EL_BD_FIREFLY_DOWN:
4342 Feld[x][y] = EL_BD_FIREFLY;
4343 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4346 case EL_PACMAN_RIGHT:
4348 case EL_PACMAN_LEFT:
4349 case EL_PACMAN_DOWN:
4350 Feld[x][y] = EL_PACMAN;
4351 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4354 case EL_YAMYAM_LEFT:
4355 case EL_YAMYAM_RIGHT:
4357 case EL_YAMYAM_DOWN:
4358 Feld[x][y] = EL_YAMYAM;
4359 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4362 case EL_SP_SNIKSNAK:
4363 MovDir[x][y] = MV_UP;
4366 case EL_SP_ELECTRON:
4367 MovDir[x][y] = MV_LEFT;
4374 Feld[x][y] = EL_MOLE;
4375 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4379 if (IS_CUSTOM_ELEMENT(element))
4381 struct ElementInfo *ei = &element_info[element];
4382 int move_direction_initial = ei->move_direction_initial;
4383 int move_pattern = ei->move_pattern;
4385 if (move_direction_initial == MV_START_PREVIOUS)
4387 if (MovDir[x][y] != MV_NONE)
4390 move_direction_initial = MV_START_AUTOMATIC;
4393 if (move_direction_initial == MV_START_RANDOM)
4394 MovDir[x][y] = 1 << RND(4);
4395 else if (move_direction_initial & MV_ANY_DIRECTION)
4396 MovDir[x][y] = move_direction_initial;
4397 else if (move_pattern == MV_ALL_DIRECTIONS ||
4398 move_pattern == MV_TURNING_LEFT ||
4399 move_pattern == MV_TURNING_RIGHT ||
4400 move_pattern == MV_TURNING_LEFT_RIGHT ||
4401 move_pattern == MV_TURNING_RIGHT_LEFT ||
4402 move_pattern == MV_TURNING_RANDOM)
4403 MovDir[x][y] = 1 << RND(4);
4404 else if (move_pattern == MV_HORIZONTAL)
4405 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4406 else if (move_pattern == MV_VERTICAL)
4407 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4408 else if (move_pattern & MV_ANY_DIRECTION)
4409 MovDir[x][y] = element_info[element].move_pattern;
4410 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4411 move_pattern == MV_ALONG_RIGHT_SIDE)
4413 // use random direction as default start direction
4414 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4415 MovDir[x][y] = 1 << RND(4);
4417 for (i = 0; i < NUM_DIRECTIONS; i++)
4419 int x1 = x + xy[i][0];
4420 int y1 = y + xy[i][1];
4422 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4424 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4425 MovDir[x][y] = direction[0][i];
4427 MovDir[x][y] = direction[1][i];
4436 MovDir[x][y] = 1 << RND(4);
4438 if (element != EL_BUG &&
4439 element != EL_SPACESHIP &&
4440 element != EL_BD_BUTTERFLY &&
4441 element != EL_BD_FIREFLY)
4444 for (i = 0; i < NUM_DIRECTIONS; i++)
4446 int x1 = x + xy[i][0];
4447 int y1 = y + xy[i][1];
4449 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4451 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4453 MovDir[x][y] = direction[0][i];
4456 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4457 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4459 MovDir[x][y] = direction[1][i];
4468 GfxDir[x][y] = MovDir[x][y];
4471 void InitAmoebaNr(int x, int y)
4474 int group_nr = AmoebeNachbarNr(x, y);
4478 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4480 if (AmoebaCnt[i] == 0)
4488 AmoebaNr[x][y] = group_nr;
4489 AmoebaCnt[group_nr]++;
4490 AmoebaCnt2[group_nr]++;
4493 static void LevelSolved(void)
4495 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4496 game.players_still_needed > 0)
4499 game.LevelSolved = TRUE;
4500 game.GameOver = TRUE;
4502 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4503 game_em.lev->score :
4504 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4507 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4508 MM_HEALTH(game_mm.laser_overload_value) :
4511 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4512 game.LevelSolved_CountingScore = game.score_final;
4513 game.LevelSolved_CountingHealth = game.health_final;
4518 static int time_count_steps;
4519 static int time, time_final;
4520 static int score, score_final;
4521 static int health, health_final;
4522 static int game_over_delay_1 = 0;
4523 static int game_over_delay_2 = 0;
4524 static int game_over_delay_3 = 0;
4525 int game_over_delay_value_1 = 50;
4526 int game_over_delay_value_2 = 25;
4527 int game_over_delay_value_3 = 50;
4529 if (!game.LevelSolved_GameWon)
4533 // do not start end game actions before the player stops moving (to exit)
4534 if (local_player->active && local_player->MovPos)
4537 game.LevelSolved_GameWon = TRUE;
4538 game.LevelSolved_SaveTape = tape.recording;
4539 game.LevelSolved_SaveScore = !tape.playing;
4543 LevelStats_incSolved(level_nr);
4545 SaveLevelSetup_SeriesInfo();
4548 if (tape.auto_play) // tape might already be stopped here
4549 tape.auto_play_level_solved = TRUE;
4553 game_over_delay_1 = 0;
4554 game_over_delay_2 = 0;
4555 game_over_delay_3 = game_over_delay_value_3;
4557 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4558 score = score_final = game.score_final;
4559 health = health_final = game.health_final;
4561 if (level.score[SC_TIME_BONUS] > 0)
4566 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4568 else if (game.no_time_limit && TimePlayed < 999)
4571 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4574 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4576 game_over_delay_1 = game_over_delay_value_1;
4578 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4581 score_final += health * level.score[SC_TIME_BONUS];
4583 game_over_delay_2 = game_over_delay_value_2;
4586 game.score_final = score_final;
4587 game.health_final = health_final;
4590 if (level_editor_test_game)
4593 score = score_final;
4595 game.LevelSolved_CountingTime = time;
4596 game.LevelSolved_CountingScore = score;
4598 game_panel_controls[GAME_PANEL_TIME].value = time;
4599 game_panel_controls[GAME_PANEL_SCORE].value = score;
4601 DisplayGameControlValues();
4604 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4606 // check if last player has left the level
4607 if (game.exit_x >= 0 &&
4610 int x = game.exit_x;
4611 int y = game.exit_y;
4612 int element = Feld[x][y];
4614 // close exit door after last player
4615 if ((game.all_players_gone &&
4616 (element == EL_EXIT_OPEN ||
4617 element == EL_SP_EXIT_OPEN ||
4618 element == EL_STEEL_EXIT_OPEN)) ||
4619 element == EL_EM_EXIT_OPEN ||
4620 element == EL_EM_STEEL_EXIT_OPEN)
4624 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4625 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4626 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4627 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4628 EL_EM_STEEL_EXIT_CLOSING);
4630 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4633 // player disappears
4634 DrawLevelField(x, y);
4637 for (i = 0; i < MAX_PLAYERS; i++)
4639 struct PlayerInfo *player = &stored_player[i];
4641 if (player->present)
4643 RemovePlayer(player);
4645 // player disappears
4646 DrawLevelField(player->jx, player->jy);
4651 PlaySound(SND_GAME_WINNING);
4654 if (game_over_delay_1 > 0)
4656 game_over_delay_1--;
4661 if (time != time_final)
4663 int time_to_go = ABS(time_final - time);
4664 int time_count_dir = (time < time_final ? +1 : -1);
4666 if (time_to_go < time_count_steps)
4667 time_count_steps = 1;
4669 time += time_count_steps * time_count_dir;
4670 score += time_count_steps * level.score[SC_TIME_BONUS];
4672 game.LevelSolved_CountingTime = time;
4673 game.LevelSolved_CountingScore = score;
4675 game_panel_controls[GAME_PANEL_TIME].value = time;
4676 game_panel_controls[GAME_PANEL_SCORE].value = score;
4678 DisplayGameControlValues();
4680 if (time == time_final)
4681 StopSound(SND_GAME_LEVELTIME_BONUS);
4682 else if (setup.sound_loops)
4683 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4685 PlaySound(SND_GAME_LEVELTIME_BONUS);
4690 if (game_over_delay_2 > 0)
4692 game_over_delay_2--;
4697 if (health != health_final)
4699 int health_count_dir = (health < health_final ? +1 : -1);
4701 health += health_count_dir;
4702 score += level.score[SC_TIME_BONUS];
4704 game.LevelSolved_CountingHealth = health;
4705 game.LevelSolved_CountingScore = score;
4707 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4708 game_panel_controls[GAME_PANEL_SCORE].value = score;
4710 DisplayGameControlValues();
4712 if (health == health_final)
4713 StopSound(SND_GAME_LEVELTIME_BONUS);
4714 else if (setup.sound_loops)
4715 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4717 PlaySound(SND_GAME_LEVELTIME_BONUS);
4722 game.panel.active = FALSE;
4724 if (game_over_delay_3 > 0)
4726 game_over_delay_3--;
4736 // used instead of "level_nr" (needed for network games)
4737 int last_level_nr = levelset.level_nr;
4740 game.LevelSolved_GameEnd = TRUE;
4742 if (game.LevelSolved_SaveTape)
4744 // make sure that request dialog to save tape does not open door again
4745 if (!global.use_envelope_request)
4746 CloseDoor(DOOR_CLOSE_1);
4748 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4751 // if no tape is to be saved, close both doors simultaneously
4752 CloseDoor(DOOR_CLOSE_ALL);
4754 if (level_editor_test_game)
4756 SetGameStatus(GAME_MODE_MAIN);
4763 if (!game.LevelSolved_SaveScore)
4765 SetGameStatus(GAME_MODE_MAIN);
4772 if (level_nr == leveldir_current->handicap_level)
4774 leveldir_current->handicap_level++;
4776 SaveLevelSetup_SeriesInfo();
4779 if (setup.increment_levels &&
4780 level_nr < leveldir_current->last_level &&
4783 level_nr++; // advance to next level
4784 TapeErase(); // start with empty tape
4786 if (setup.auto_play_next_level)
4788 LoadLevel(level_nr);
4790 SaveLevelSetup_SeriesInfo();
4794 hi_pos = NewHiScore(last_level_nr);
4796 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4798 SetGameStatus(GAME_MODE_SCORES);
4800 DrawHallOfFame(last_level_nr, hi_pos);
4802 else if (setup.auto_play_next_level && setup.increment_levels &&
4803 last_level_nr < leveldir_current->last_level &&
4806 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4810 SetGameStatus(GAME_MODE_MAIN);
4816 int NewHiScore(int level_nr)
4820 boolean one_score_entry_per_name = !program.many_scores_per_name;
4822 LoadScore(level_nr);
4824 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4825 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4828 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4830 if (game.score_final > highscore[k].Score)
4832 // player has made it to the hall of fame
4834 if (k < MAX_SCORE_ENTRIES - 1)
4836 int m = MAX_SCORE_ENTRIES - 1;
4838 if (one_score_entry_per_name)
4840 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4841 if (strEqual(setup.player_name, highscore[l].Name))
4844 if (m == k) // player's new highscore overwrites his old one
4848 for (l = m; l > k; l--)
4850 strcpy(highscore[l].Name, highscore[l - 1].Name);
4851 highscore[l].Score = highscore[l - 1].Score;
4857 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4858 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4859 highscore[k].Score = game.score_final;
4864 else if (one_score_entry_per_name &&
4865 !strncmp(setup.player_name, highscore[k].Name,
4866 MAX_PLAYER_NAME_LEN))
4867 break; // player already there with a higher score
4871 SaveScore(level_nr);
4876 static int getElementMoveStepsizeExt(int x, int y, int direction)
4878 int element = Feld[x][y];
4879 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4880 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4881 int horiz_move = (dx != 0);
4882 int sign = (horiz_move ? dx : dy);
4883 int step = sign * element_info[element].move_stepsize;
4885 // special values for move stepsize for spring and things on conveyor belt
4888 if (CAN_FALL(element) &&
4889 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4890 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4891 else if (element == EL_SPRING)
4892 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4898 static int getElementMoveStepsize(int x, int y)
4900 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4903 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4905 if (player->GfxAction != action || player->GfxDir != dir)
4907 player->GfxAction = action;
4908 player->GfxDir = dir;
4910 player->StepFrame = 0;
4914 static void ResetGfxFrame(int x, int y)
4916 // profiling showed that "autotest" spends 10~20% of its time in this function
4917 if (DrawingDeactivatedField())
4920 int element = Feld[x][y];
4921 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4923 if (graphic_info[graphic].anim_global_sync)
4924 GfxFrame[x][y] = FrameCounter;
4925 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4926 GfxFrame[x][y] = CustomValue[x][y];
4927 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4928 GfxFrame[x][y] = element_info[element].collect_score;
4929 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4930 GfxFrame[x][y] = ChangeDelay[x][y];
4933 static void ResetGfxAnimation(int x, int y)
4935 GfxAction[x][y] = ACTION_DEFAULT;
4936 GfxDir[x][y] = MovDir[x][y];
4939 ResetGfxFrame(x, y);
4942 static void ResetRandomAnimationValue(int x, int y)
4944 GfxRandom[x][y] = INIT_GFX_RANDOM();
4947 static void InitMovingField(int x, int y, int direction)
4949 int element = Feld[x][y];
4950 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4951 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4954 boolean is_moving_before, is_moving_after;
4956 // check if element was/is moving or being moved before/after mode change
4957 is_moving_before = (WasJustMoving[x][y] != 0);
4958 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4960 // reset animation only for moving elements which change direction of moving
4961 // or which just started or stopped moving
4962 // (else CEs with property "can move" / "not moving" are reset each frame)
4963 if (is_moving_before != is_moving_after ||
4964 direction != MovDir[x][y])
4965 ResetGfxAnimation(x, y);
4967 MovDir[x][y] = direction;
4968 GfxDir[x][y] = direction;
4970 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4971 direction == MV_DOWN && CAN_FALL(element) ?
4972 ACTION_FALLING : ACTION_MOVING);
4974 // this is needed for CEs with property "can move" / "not moving"
4976 if (is_moving_after)
4978 if (Feld[newx][newy] == EL_EMPTY)
4979 Feld[newx][newy] = EL_BLOCKED;
4981 MovDir[newx][newy] = MovDir[x][y];
4983 CustomValue[newx][newy] = CustomValue[x][y];
4985 GfxFrame[newx][newy] = GfxFrame[x][y];
4986 GfxRandom[newx][newy] = GfxRandom[x][y];
4987 GfxAction[newx][newy] = GfxAction[x][y];
4988 GfxDir[newx][newy] = GfxDir[x][y];
4992 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4994 int direction = MovDir[x][y];
4995 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4996 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5002 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5004 int oldx = x, oldy = y;
5005 int direction = MovDir[x][y];
5007 if (direction == MV_LEFT)
5009 else if (direction == MV_RIGHT)
5011 else if (direction == MV_UP)
5013 else if (direction == MV_DOWN)
5016 *comes_from_x = oldx;
5017 *comes_from_y = oldy;
5020 static int MovingOrBlocked2Element(int x, int y)
5022 int element = Feld[x][y];
5024 if (element == EL_BLOCKED)
5028 Blocked2Moving(x, y, &oldx, &oldy);
5029 return Feld[oldx][oldy];
5035 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5037 // like MovingOrBlocked2Element(), but if element is moving
5038 // and (x,y) is the field the moving element is just leaving,
5039 // return EL_BLOCKED instead of the element value
5040 int element = Feld[x][y];
5042 if (IS_MOVING(x, y))
5044 if (element == EL_BLOCKED)
5048 Blocked2Moving(x, y, &oldx, &oldy);
5049 return Feld[oldx][oldy];
5058 static void RemoveField(int x, int y)
5060 Feld[x][y] = EL_EMPTY;
5066 CustomValue[x][y] = 0;
5069 ChangeDelay[x][y] = 0;
5070 ChangePage[x][y] = -1;
5071 Pushed[x][y] = FALSE;
5073 GfxElement[x][y] = EL_UNDEFINED;
5074 GfxAction[x][y] = ACTION_DEFAULT;
5075 GfxDir[x][y] = MV_NONE;
5078 static void RemoveMovingField(int x, int y)
5080 int oldx = x, oldy = y, newx = x, newy = y;
5081 int element = Feld[x][y];
5082 int next_element = EL_UNDEFINED;
5084 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5087 if (IS_MOVING(x, y))
5089 Moving2Blocked(x, y, &newx, &newy);
5091 if (Feld[newx][newy] != EL_BLOCKED)
5093 // element is moving, but target field is not free (blocked), but
5094 // already occupied by something different (example: acid pool);
5095 // in this case, only remove the moving field, but not the target
5097 RemoveField(oldx, oldy);
5099 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5101 TEST_DrawLevelField(oldx, oldy);
5106 else if (element == EL_BLOCKED)
5108 Blocked2Moving(x, y, &oldx, &oldy);
5109 if (!IS_MOVING(oldx, oldy))
5113 if (element == EL_BLOCKED &&
5114 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5115 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5116 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5117 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5118 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5119 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5120 next_element = get_next_element(Feld[oldx][oldy]);
5122 RemoveField(oldx, oldy);
5123 RemoveField(newx, newy);
5125 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5127 if (next_element != EL_UNDEFINED)
5128 Feld[oldx][oldy] = next_element;
5130 TEST_DrawLevelField(oldx, oldy);
5131 TEST_DrawLevelField(newx, newy);
5134 void DrawDynamite(int x, int y)
5136 int sx = SCREENX(x), sy = SCREENY(y);
5137 int graphic = el2img(Feld[x][y]);
5140 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5143 if (IS_WALKABLE_INSIDE(Back[x][y]))
5147 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5148 else if (Store[x][y])
5149 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5151 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5153 if (Back[x][y] || Store[x][y])
5154 DrawGraphicThruMask(sx, sy, graphic, frame);
5156 DrawGraphic(sx, sy, graphic, frame);
5159 static void CheckDynamite(int x, int y)
5161 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5165 if (MovDelay[x][y] != 0)
5168 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5174 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5179 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5181 boolean num_checked_players = 0;
5184 for (i = 0; i < MAX_PLAYERS; i++)
5186 if (stored_player[i].active)
5188 int sx = stored_player[i].jx;
5189 int sy = stored_player[i].jy;
5191 if (num_checked_players == 0)
5198 *sx1 = MIN(*sx1, sx);
5199 *sy1 = MIN(*sy1, sy);
5200 *sx2 = MAX(*sx2, sx);
5201 *sy2 = MAX(*sy2, sy);
5204 num_checked_players++;
5209 static boolean checkIfAllPlayersFitToScreen_RND(void)
5211 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5213 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5215 return (sx2 - sx1 < SCR_FIELDX &&
5216 sy2 - sy1 < SCR_FIELDY);
5219 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5221 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5223 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5225 *sx = (sx1 + sx2) / 2;
5226 *sy = (sy1 + sy2) / 2;
5229 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5230 boolean center_screen, boolean quick_relocation)
5232 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5233 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5234 boolean no_delay = (tape.warp_forward);
5235 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5236 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5237 int new_scroll_x, new_scroll_y;
5239 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5241 // case 1: quick relocation inside visible screen (without scrolling)
5248 if (!level.shifted_relocation || center_screen)
5250 // relocation _with_ centering of screen
5252 new_scroll_x = SCROLL_POSITION_X(x);
5253 new_scroll_y = SCROLL_POSITION_Y(y);
5257 // relocation _without_ centering of screen
5259 int center_scroll_x = SCROLL_POSITION_X(old_x);
5260 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5261 int offset_x = x + (scroll_x - center_scroll_x);
5262 int offset_y = y + (scroll_y - center_scroll_y);
5264 // for new screen position, apply previous offset to center position
5265 new_scroll_x = SCROLL_POSITION_X(offset_x);
5266 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5269 if (quick_relocation)
5271 // case 2: quick relocation (redraw without visible scrolling)
5273 scroll_x = new_scroll_x;
5274 scroll_y = new_scroll_y;
5281 // case 3: visible relocation (with scrolling to new position)
5283 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5285 SetVideoFrameDelay(wait_delay_value);
5287 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5289 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5290 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5292 if (dx == 0 && dy == 0) // no scrolling needed at all
5298 // set values for horizontal/vertical screen scrolling (half tile size)
5299 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5300 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5301 int pos_x = dx * TILEX / 2;
5302 int pos_y = dy * TILEY / 2;
5303 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5304 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5306 ScrollLevel(dx, dy);
5309 // scroll in two steps of half tile size to make things smoother
5310 BlitScreenToBitmapExt_RND(window, fx, fy);
5312 // scroll second step to align at full tile size
5313 BlitScreenToBitmap(window);
5319 SetVideoFrameDelay(frame_delay_value_old);
5322 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5324 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5325 int player_nr = GET_PLAYER_NR(el_player);
5326 struct PlayerInfo *player = &stored_player[player_nr];
5327 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5328 boolean no_delay = (tape.warp_forward);
5329 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5330 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5331 int old_jx = player->jx;
5332 int old_jy = player->jy;
5333 int old_element = Feld[old_jx][old_jy];
5334 int element = Feld[jx][jy];
5335 boolean player_relocated = (old_jx != jx || old_jy != jy);
5337 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5338 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5339 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5340 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5341 int leave_side_horiz = move_dir_horiz;
5342 int leave_side_vert = move_dir_vert;
5343 int enter_side = enter_side_horiz | enter_side_vert;
5344 int leave_side = leave_side_horiz | leave_side_vert;
5346 if (player->buried) // do not reanimate dead player
5349 if (!player_relocated) // no need to relocate the player
5352 if (IS_PLAYER(jx, jy)) // player already placed at new position
5354 RemoveField(jx, jy); // temporarily remove newly placed player
5355 DrawLevelField(jx, jy);
5358 if (player->present)
5360 while (player->MovPos)
5362 ScrollPlayer(player, SCROLL_GO_ON);
5363 ScrollScreen(NULL, SCROLL_GO_ON);
5365 AdvanceFrameAndPlayerCounters(player->index_nr);
5369 BackToFront_WithFrameDelay(wait_delay_value);
5372 DrawPlayer(player); // needed here only to cleanup last field
5373 DrawLevelField(player->jx, player->jy); // remove player graphic
5375 player->is_moving = FALSE;
5378 if (IS_CUSTOM_ELEMENT(old_element))
5379 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5381 player->index_bit, leave_side);
5383 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5385 player->index_bit, leave_side);
5387 Feld[jx][jy] = el_player;
5388 InitPlayerField(jx, jy, el_player, TRUE);
5390 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5391 possible that the relocation target field did not contain a player element,
5392 but a walkable element, to which the new player was relocated -- in this
5393 case, restore that (already initialized!) element on the player field */
5394 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5396 Feld[jx][jy] = element; // restore previously existing element
5399 // only visually relocate centered player
5400 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5401 FALSE, level.instant_relocation);
5403 TestIfPlayerTouchesBadThing(jx, jy);
5404 TestIfPlayerTouchesCustomElement(jx, jy);
5406 if (IS_CUSTOM_ELEMENT(element))
5407 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5408 player->index_bit, enter_side);
5410 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5411 player->index_bit, enter_side);
5413 if (player->is_switching)
5415 /* ensure that relocation while still switching an element does not cause
5416 a new element to be treated as also switched directly after relocation
5417 (this is important for teleporter switches that teleport the player to
5418 a place where another teleporter switch is in the same direction, which
5419 would then incorrectly be treated as immediately switched before the
5420 direction key that caused the switch was released) */
5422 player->switch_x += jx - old_jx;
5423 player->switch_y += jy - old_jy;
5427 static void Explode(int ex, int ey, int phase, int mode)
5433 // !!! eliminate this variable !!!
5434 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5436 if (game.explosions_delayed)
5438 ExplodeField[ex][ey] = mode;
5442 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5444 int center_element = Feld[ex][ey];
5445 int artwork_element, explosion_element; // set these values later
5447 // remove things displayed in background while burning dynamite
5448 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5451 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5453 // put moving element to center field (and let it explode there)
5454 center_element = MovingOrBlocked2Element(ex, ey);
5455 RemoveMovingField(ex, ey);
5456 Feld[ex][ey] = center_element;
5459 // now "center_element" is finally determined -- set related values now
5460 artwork_element = center_element; // for custom player artwork
5461 explosion_element = center_element; // for custom player artwork
5463 if (IS_PLAYER(ex, ey))
5465 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5467 artwork_element = stored_player[player_nr].artwork_element;
5469 if (level.use_explosion_element[player_nr])
5471 explosion_element = level.explosion_element[player_nr];
5472 artwork_element = explosion_element;
5476 if (mode == EX_TYPE_NORMAL ||
5477 mode == EX_TYPE_CENTER ||
5478 mode == EX_TYPE_CROSS)
5479 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5481 last_phase = element_info[explosion_element].explosion_delay + 1;
5483 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5485 int xx = x - ex + 1;
5486 int yy = y - ey + 1;
5489 if (!IN_LEV_FIELD(x, y) ||
5490 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5491 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5494 element = Feld[x][y];
5496 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5498 element = MovingOrBlocked2Element(x, y);
5500 if (!IS_EXPLOSION_PROOF(element))
5501 RemoveMovingField(x, y);
5504 // indestructible elements can only explode in center (but not flames)
5505 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5506 mode == EX_TYPE_BORDER)) ||
5507 element == EL_FLAMES)
5510 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5511 behaviour, for example when touching a yamyam that explodes to rocks
5512 with active deadly shield, a rock is created under the player !!! */
5513 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5515 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5516 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5517 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5519 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5522 if (IS_ACTIVE_BOMB(element))
5524 // re-activate things under the bomb like gate or penguin
5525 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5532 // save walkable background elements while explosion on same tile
5533 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5534 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5535 Back[x][y] = element;
5537 // ignite explodable elements reached by other explosion
5538 if (element == EL_EXPLOSION)
5539 element = Store2[x][y];
5541 if (AmoebaNr[x][y] &&
5542 (element == EL_AMOEBA_FULL ||
5543 element == EL_BD_AMOEBA ||
5544 element == EL_AMOEBA_GROWING))
5546 AmoebaCnt[AmoebaNr[x][y]]--;
5547 AmoebaCnt2[AmoebaNr[x][y]]--;
5552 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5554 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5556 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5558 if (PLAYERINFO(ex, ey)->use_murphy)
5559 Store[x][y] = EL_EMPTY;
5562 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5563 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5564 else if (ELEM_IS_PLAYER(center_element))
5565 Store[x][y] = EL_EMPTY;
5566 else if (center_element == EL_YAMYAM)
5567 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5568 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5569 Store[x][y] = element_info[center_element].content.e[xx][yy];
5571 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5572 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5573 // otherwise) -- FIX THIS !!!
5574 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5575 Store[x][y] = element_info[element].content.e[1][1];
5577 else if (!CAN_EXPLODE(element))
5578 Store[x][y] = element_info[element].content.e[1][1];
5581 Store[x][y] = EL_EMPTY;
5583 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5584 center_element == EL_AMOEBA_TO_DIAMOND)
5585 Store2[x][y] = element;
5587 Feld[x][y] = EL_EXPLOSION;
5588 GfxElement[x][y] = artwork_element;
5590 ExplodePhase[x][y] = 1;
5591 ExplodeDelay[x][y] = last_phase;
5596 if (center_element == EL_YAMYAM)
5597 game.yamyam_content_nr =
5598 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5610 GfxFrame[x][y] = 0; // restart explosion animation
5612 last_phase = ExplodeDelay[x][y];
5614 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5616 // this can happen if the player leaves an explosion just in time
5617 if (GfxElement[x][y] == EL_UNDEFINED)
5618 GfxElement[x][y] = EL_EMPTY;
5620 border_element = Store2[x][y];
5621 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5622 border_element = StorePlayer[x][y];
5624 if (phase == element_info[border_element].ignition_delay ||
5625 phase == last_phase)
5627 boolean border_explosion = FALSE;
5629 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5630 !PLAYER_EXPLOSION_PROTECTED(x, y))
5632 KillPlayerUnlessExplosionProtected(x, y);
5633 border_explosion = TRUE;
5635 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5637 Feld[x][y] = Store2[x][y];
5640 border_explosion = TRUE;
5642 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5644 AmoebeUmwandeln(x, y);
5646 border_explosion = TRUE;
5649 // if an element just explodes due to another explosion (chain-reaction),
5650 // do not immediately end the new explosion when it was the last frame of
5651 // the explosion (as it would be done in the following "if"-statement!)
5652 if (border_explosion && phase == last_phase)
5656 if (phase == last_phase)
5660 element = Feld[x][y] = Store[x][y];
5661 Store[x][y] = Store2[x][y] = 0;
5662 GfxElement[x][y] = EL_UNDEFINED;
5664 // player can escape from explosions and might therefore be still alive
5665 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5666 element <= EL_PLAYER_IS_EXPLODING_4)
5668 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5669 int explosion_element = EL_PLAYER_1 + player_nr;
5670 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5671 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5673 if (level.use_explosion_element[player_nr])
5674 explosion_element = level.explosion_element[player_nr];
5676 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5677 element_info[explosion_element].content.e[xx][yy]);
5680 // restore probably existing indestructible background element
5681 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5682 element = Feld[x][y] = Back[x][y];
5685 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5686 GfxDir[x][y] = MV_NONE;
5687 ChangeDelay[x][y] = 0;
5688 ChangePage[x][y] = -1;
5690 CustomValue[x][y] = 0;
5692 InitField_WithBug2(x, y, FALSE);
5694 TEST_DrawLevelField(x, y);
5696 TestIfElementTouchesCustomElement(x, y);
5698 if (GFX_CRUMBLED(element))
5699 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5701 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5702 StorePlayer[x][y] = 0;
5704 if (ELEM_IS_PLAYER(element))
5705 RelocatePlayer(x, y, element);
5707 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5709 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5710 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5713 TEST_DrawLevelFieldCrumbled(x, y);
5715 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5717 DrawLevelElement(x, y, Back[x][y]);
5718 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5720 else if (IS_WALKABLE_UNDER(Back[x][y]))
5722 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5723 DrawLevelElementThruMask(x, y, Back[x][y]);
5725 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5726 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5730 static void DynaExplode(int ex, int ey)
5733 int dynabomb_element = Feld[ex][ey];
5734 int dynabomb_size = 1;
5735 boolean dynabomb_xl = FALSE;
5736 struct PlayerInfo *player;
5737 static int xy[4][2] =
5745 if (IS_ACTIVE_BOMB(dynabomb_element))
5747 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5748 dynabomb_size = player->dynabomb_size;
5749 dynabomb_xl = player->dynabomb_xl;
5750 player->dynabombs_left++;
5753 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5755 for (i = 0; i < NUM_DIRECTIONS; i++)
5757 for (j = 1; j <= dynabomb_size; j++)
5759 int x = ex + j * xy[i][0];
5760 int y = ey + j * xy[i][1];
5763 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5766 element = Feld[x][y];
5768 // do not restart explosions of fields with active bombs
5769 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5772 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5774 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5775 !IS_DIGGABLE(element) && !dynabomb_xl)
5781 void Bang(int x, int y)
5783 int element = MovingOrBlocked2Element(x, y);
5784 int explosion_type = EX_TYPE_NORMAL;
5786 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5788 struct PlayerInfo *player = PLAYERINFO(x, y);
5790 element = Feld[x][y] = player->initial_element;
5792 if (level.use_explosion_element[player->index_nr])
5794 int explosion_element = level.explosion_element[player->index_nr];
5796 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5797 explosion_type = EX_TYPE_CROSS;
5798 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5799 explosion_type = EX_TYPE_CENTER;
5807 case EL_BD_BUTTERFLY:
5810 case EL_DARK_YAMYAM:
5814 RaiseScoreElement(element);
5817 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5818 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5819 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5820 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5821 case EL_DYNABOMB_INCREASE_NUMBER:
5822 case EL_DYNABOMB_INCREASE_SIZE:
5823 case EL_DYNABOMB_INCREASE_POWER:
5824 explosion_type = EX_TYPE_DYNA;
5827 case EL_DC_LANDMINE:
5828 explosion_type = EX_TYPE_CENTER;
5833 case EL_LAMP_ACTIVE:
5834 case EL_AMOEBA_TO_DIAMOND:
5835 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5836 explosion_type = EX_TYPE_CENTER;
5840 if (element_info[element].explosion_type == EXPLODES_CROSS)
5841 explosion_type = EX_TYPE_CROSS;
5842 else if (element_info[element].explosion_type == EXPLODES_1X1)
5843 explosion_type = EX_TYPE_CENTER;
5847 if (explosion_type == EX_TYPE_DYNA)
5850 Explode(x, y, EX_PHASE_START, explosion_type);
5852 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5855 static void SplashAcid(int x, int y)
5857 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5858 (!IN_LEV_FIELD(x - 1, y - 2) ||
5859 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5860 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5862 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5863 (!IN_LEV_FIELD(x + 1, y - 2) ||
5864 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5865 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5867 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5870 static void InitBeltMovement(void)
5872 static int belt_base_element[4] =
5874 EL_CONVEYOR_BELT_1_LEFT,
5875 EL_CONVEYOR_BELT_2_LEFT,
5876 EL_CONVEYOR_BELT_3_LEFT,
5877 EL_CONVEYOR_BELT_4_LEFT
5879 static int belt_base_active_element[4] =
5881 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5882 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5883 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5884 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5889 // set frame order for belt animation graphic according to belt direction
5890 for (i = 0; i < NUM_BELTS; i++)
5894 for (j = 0; j < NUM_BELT_PARTS; j++)
5896 int element = belt_base_active_element[belt_nr] + j;
5897 int graphic_1 = el2img(element);
5898 int graphic_2 = el2panelimg(element);
5900 if (game.belt_dir[i] == MV_LEFT)
5902 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5903 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5907 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5908 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5913 SCAN_PLAYFIELD(x, y)
5915 int element = Feld[x][y];
5917 for (i = 0; i < NUM_BELTS; i++)
5919 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5921 int e_belt_nr = getBeltNrFromBeltElement(element);
5924 if (e_belt_nr == belt_nr)
5926 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5928 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5935 static void ToggleBeltSwitch(int x, int y)
5937 static int belt_base_element[4] =
5939 EL_CONVEYOR_BELT_1_LEFT,
5940 EL_CONVEYOR_BELT_2_LEFT,
5941 EL_CONVEYOR_BELT_3_LEFT,
5942 EL_CONVEYOR_BELT_4_LEFT
5944 static int belt_base_active_element[4] =
5946 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5947 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5948 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5949 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5951 static int belt_base_switch_element[4] =
5953 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5954 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5955 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5956 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5958 static int belt_move_dir[4] =
5966 int element = Feld[x][y];
5967 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5968 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5969 int belt_dir = belt_move_dir[belt_dir_nr];
5972 if (!IS_BELT_SWITCH(element))
5975 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5976 game.belt_dir[belt_nr] = belt_dir;
5978 if (belt_dir_nr == 3)
5981 // set frame order for belt animation graphic according to belt direction
5982 for (i = 0; i < NUM_BELT_PARTS; i++)
5984 int element = belt_base_active_element[belt_nr] + i;
5985 int graphic_1 = el2img(element);
5986 int graphic_2 = el2panelimg(element);
5988 if (belt_dir == MV_LEFT)
5990 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5991 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5995 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5996 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6000 SCAN_PLAYFIELD(xx, yy)
6002 int element = Feld[xx][yy];
6004 if (IS_BELT_SWITCH(element))
6006 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6008 if (e_belt_nr == belt_nr)
6010 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6011 TEST_DrawLevelField(xx, yy);
6014 else if (IS_BELT(element) && belt_dir != MV_NONE)
6016 int e_belt_nr = getBeltNrFromBeltElement(element);
6018 if (e_belt_nr == belt_nr)
6020 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6022 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6023 TEST_DrawLevelField(xx, yy);
6026 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6028 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6030 if (e_belt_nr == belt_nr)
6032 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6034 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6035 TEST_DrawLevelField(xx, yy);
6041 static void ToggleSwitchgateSwitch(int x, int y)
6045 game.switchgate_pos = !game.switchgate_pos;
6047 SCAN_PLAYFIELD(xx, yy)
6049 int element = Feld[xx][yy];
6051 if (element == EL_SWITCHGATE_SWITCH_UP)
6053 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6054 TEST_DrawLevelField(xx, yy);
6056 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6058 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6059 TEST_DrawLevelField(xx, yy);
6061 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6063 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6064 TEST_DrawLevelField(xx, yy);
6066 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6068 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6069 TEST_DrawLevelField(xx, yy);
6071 else if (element == EL_SWITCHGATE_OPEN ||
6072 element == EL_SWITCHGATE_OPENING)
6074 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6076 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6078 else if (element == EL_SWITCHGATE_CLOSED ||
6079 element == EL_SWITCHGATE_CLOSING)
6081 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6083 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6088 static int getInvisibleActiveFromInvisibleElement(int element)
6090 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6091 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6092 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6096 static int getInvisibleFromInvisibleActiveElement(int element)
6098 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6099 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6100 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6104 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6108 SCAN_PLAYFIELD(x, y)
6110 int element = Feld[x][y];
6112 if (element == EL_LIGHT_SWITCH &&
6113 game.light_time_left > 0)
6115 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6116 TEST_DrawLevelField(x, y);
6118 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6119 game.light_time_left == 0)
6121 Feld[x][y] = EL_LIGHT_SWITCH;
6122 TEST_DrawLevelField(x, y);
6124 else if (element == EL_EMC_DRIPPER &&
6125 game.light_time_left > 0)
6127 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6128 TEST_DrawLevelField(x, y);
6130 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6131 game.light_time_left == 0)
6133 Feld[x][y] = EL_EMC_DRIPPER;
6134 TEST_DrawLevelField(x, y);
6136 else if (element == EL_INVISIBLE_STEELWALL ||
6137 element == EL_INVISIBLE_WALL ||
6138 element == EL_INVISIBLE_SAND)
6140 if (game.light_time_left > 0)
6141 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6143 TEST_DrawLevelField(x, y);
6145 // uncrumble neighbour fields, if needed
6146 if (element == EL_INVISIBLE_SAND)
6147 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6149 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6150 element == EL_INVISIBLE_WALL_ACTIVE ||
6151 element == EL_INVISIBLE_SAND_ACTIVE)
6153 if (game.light_time_left == 0)
6154 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6156 TEST_DrawLevelField(x, y);
6158 // re-crumble neighbour fields, if needed
6159 if (element == EL_INVISIBLE_SAND)
6160 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6165 static void RedrawAllInvisibleElementsForLenses(void)
6169 SCAN_PLAYFIELD(x, y)
6171 int element = Feld[x][y];
6173 if (element == EL_EMC_DRIPPER &&
6174 game.lenses_time_left > 0)
6176 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6177 TEST_DrawLevelField(x, y);
6179 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6180 game.lenses_time_left == 0)
6182 Feld[x][y] = EL_EMC_DRIPPER;
6183 TEST_DrawLevelField(x, y);
6185 else if (element == EL_INVISIBLE_STEELWALL ||
6186 element == EL_INVISIBLE_WALL ||
6187 element == EL_INVISIBLE_SAND)
6189 if (game.lenses_time_left > 0)
6190 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6192 TEST_DrawLevelField(x, y);
6194 // uncrumble neighbour fields, if needed
6195 if (element == EL_INVISIBLE_SAND)
6196 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6198 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6199 element == EL_INVISIBLE_WALL_ACTIVE ||
6200 element == EL_INVISIBLE_SAND_ACTIVE)
6202 if (game.lenses_time_left == 0)
6203 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6205 TEST_DrawLevelField(x, y);
6207 // re-crumble neighbour fields, if needed
6208 if (element == EL_INVISIBLE_SAND)
6209 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6214 static void RedrawAllInvisibleElementsForMagnifier(void)
6218 SCAN_PLAYFIELD(x, y)
6220 int element = Feld[x][y];
6222 if (element == EL_EMC_FAKE_GRASS &&
6223 game.magnify_time_left > 0)
6225 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6226 TEST_DrawLevelField(x, y);
6228 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6229 game.magnify_time_left == 0)
6231 Feld[x][y] = EL_EMC_FAKE_GRASS;
6232 TEST_DrawLevelField(x, y);
6234 else if (IS_GATE_GRAY(element) &&
6235 game.magnify_time_left > 0)
6237 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6238 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6239 IS_EM_GATE_GRAY(element) ?
6240 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6241 IS_EMC_GATE_GRAY(element) ?
6242 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6243 IS_DC_GATE_GRAY(element) ?
6244 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6246 TEST_DrawLevelField(x, y);
6248 else if (IS_GATE_GRAY_ACTIVE(element) &&
6249 game.magnify_time_left == 0)
6251 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6252 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6253 IS_EM_GATE_GRAY_ACTIVE(element) ?
6254 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6255 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6256 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6257 IS_DC_GATE_GRAY_ACTIVE(element) ?
6258 EL_DC_GATE_WHITE_GRAY :
6260 TEST_DrawLevelField(x, y);
6265 static void ToggleLightSwitch(int x, int y)
6267 int element = Feld[x][y];
6269 game.light_time_left =
6270 (element == EL_LIGHT_SWITCH ?
6271 level.time_light * FRAMES_PER_SECOND : 0);
6273 RedrawAllLightSwitchesAndInvisibleElements();
6276 static void ActivateTimegateSwitch(int x, int y)
6280 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6282 SCAN_PLAYFIELD(xx, yy)
6284 int element = Feld[xx][yy];
6286 if (element == EL_TIMEGATE_CLOSED ||
6287 element == EL_TIMEGATE_CLOSING)
6289 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6290 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6294 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6296 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6297 TEST_DrawLevelField(xx, yy);
6303 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6304 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6307 static void Impact(int x, int y)
6309 boolean last_line = (y == lev_fieldy - 1);
6310 boolean object_hit = FALSE;
6311 boolean impact = (last_line || object_hit);
6312 int element = Feld[x][y];
6313 int smashed = EL_STEELWALL;
6315 if (!last_line) // check if element below was hit
6317 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6320 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6321 MovDir[x][y + 1] != MV_DOWN ||
6322 MovPos[x][y + 1] <= TILEY / 2));
6324 // do not smash moving elements that left the smashed field in time
6325 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6326 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6329 #if USE_QUICKSAND_IMPACT_BUGFIX
6330 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6332 RemoveMovingField(x, y + 1);
6333 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6334 Feld[x][y + 2] = EL_ROCK;
6335 TEST_DrawLevelField(x, y + 2);
6340 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6342 RemoveMovingField(x, y + 1);
6343 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6344 Feld[x][y + 2] = EL_ROCK;
6345 TEST_DrawLevelField(x, y + 2);
6352 smashed = MovingOrBlocked2Element(x, y + 1);
6354 impact = (last_line || object_hit);
6357 if (!last_line && smashed == EL_ACID) // element falls into acid
6359 SplashAcid(x, y + 1);
6363 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6364 // only reset graphic animation if graphic really changes after impact
6366 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6368 ResetGfxAnimation(x, y);
6369 TEST_DrawLevelField(x, y);
6372 if (impact && CAN_EXPLODE_IMPACT(element))
6377 else if (impact && element == EL_PEARL &&
6378 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6380 ResetGfxAnimation(x, y);
6382 Feld[x][y] = EL_PEARL_BREAKING;
6383 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6386 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6388 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6393 if (impact && element == EL_AMOEBA_DROP)
6395 if (object_hit && IS_PLAYER(x, y + 1))
6396 KillPlayerUnlessEnemyProtected(x, y + 1);
6397 else if (object_hit && smashed == EL_PENGUIN)
6401 Feld[x][y] = EL_AMOEBA_GROWING;
6402 Store[x][y] = EL_AMOEBA_WET;
6404 ResetRandomAnimationValue(x, y);
6409 if (object_hit) // check which object was hit
6411 if ((CAN_PASS_MAGIC_WALL(element) &&
6412 (smashed == EL_MAGIC_WALL ||
6413 smashed == EL_BD_MAGIC_WALL)) ||
6414 (CAN_PASS_DC_MAGIC_WALL(element) &&
6415 smashed == EL_DC_MAGIC_WALL))
6418 int activated_magic_wall =
6419 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6420 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6421 EL_DC_MAGIC_WALL_ACTIVE);
6423 // activate magic wall / mill
6424 SCAN_PLAYFIELD(xx, yy)
6426 if (Feld[xx][yy] == smashed)
6427 Feld[xx][yy] = activated_magic_wall;
6430 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6431 game.magic_wall_active = TRUE;
6433 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6434 SND_MAGIC_WALL_ACTIVATING :
6435 smashed == EL_BD_MAGIC_WALL ?
6436 SND_BD_MAGIC_WALL_ACTIVATING :
6437 SND_DC_MAGIC_WALL_ACTIVATING));
6440 if (IS_PLAYER(x, y + 1))
6442 if (CAN_SMASH_PLAYER(element))
6444 KillPlayerUnlessEnemyProtected(x, y + 1);
6448 else if (smashed == EL_PENGUIN)
6450 if (CAN_SMASH_PLAYER(element))
6456 else if (element == EL_BD_DIAMOND)
6458 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6464 else if (((element == EL_SP_INFOTRON ||
6465 element == EL_SP_ZONK) &&
6466 (smashed == EL_SP_SNIKSNAK ||
6467 smashed == EL_SP_ELECTRON ||
6468 smashed == EL_SP_DISK_ORANGE)) ||
6469 (element == EL_SP_INFOTRON &&
6470 smashed == EL_SP_DISK_YELLOW))
6475 else if (CAN_SMASH_EVERYTHING(element))
6477 if (IS_CLASSIC_ENEMY(smashed) ||
6478 CAN_EXPLODE_SMASHED(smashed))
6483 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6485 if (smashed == EL_LAMP ||
6486 smashed == EL_LAMP_ACTIVE)
6491 else if (smashed == EL_NUT)
6493 Feld[x][y + 1] = EL_NUT_BREAKING;
6494 PlayLevelSound(x, y, SND_NUT_BREAKING);
6495 RaiseScoreElement(EL_NUT);
6498 else if (smashed == EL_PEARL)
6500 ResetGfxAnimation(x, y);
6502 Feld[x][y + 1] = EL_PEARL_BREAKING;
6503 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6506 else if (smashed == EL_DIAMOND)
6508 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6509 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6512 else if (IS_BELT_SWITCH(smashed))
6514 ToggleBeltSwitch(x, y + 1);
6516 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6517 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6518 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6519 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6521 ToggleSwitchgateSwitch(x, y + 1);
6523 else if (smashed == EL_LIGHT_SWITCH ||
6524 smashed == EL_LIGHT_SWITCH_ACTIVE)
6526 ToggleLightSwitch(x, y + 1);
6530 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6532 CheckElementChangeBySide(x, y + 1, smashed, element,
6533 CE_SWITCHED, CH_SIDE_TOP);
6534 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6540 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6545 // play sound of magic wall / mill
6547 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6548 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6549 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6551 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6552 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6553 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6554 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6555 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6556 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6561 // play sound of object that hits the ground
6562 if (last_line || object_hit)
6563 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6566 static void TurnRoundExt(int x, int y)
6578 { 0, 0 }, { 0, 0 }, { 0, 0 },
6583 int left, right, back;
6587 { MV_DOWN, MV_UP, MV_RIGHT },
6588 { MV_UP, MV_DOWN, MV_LEFT },
6590 { MV_LEFT, MV_RIGHT, MV_DOWN },
6594 { MV_RIGHT, MV_LEFT, MV_UP }
6597 int element = Feld[x][y];
6598 int move_pattern = element_info[element].move_pattern;
6600 int old_move_dir = MovDir[x][y];
6601 int left_dir = turn[old_move_dir].left;
6602 int right_dir = turn[old_move_dir].right;
6603 int back_dir = turn[old_move_dir].back;
6605 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6606 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6607 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6608 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6610 int left_x = x + left_dx, left_y = y + left_dy;
6611 int right_x = x + right_dx, right_y = y + right_dy;
6612 int move_x = x + move_dx, move_y = y + move_dy;
6616 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6618 TestIfBadThingTouchesOtherBadThing(x, y);
6620 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6621 MovDir[x][y] = right_dir;
6622 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6623 MovDir[x][y] = left_dir;
6625 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6627 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6630 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6632 TestIfBadThingTouchesOtherBadThing(x, y);
6634 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6635 MovDir[x][y] = left_dir;
6636 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6637 MovDir[x][y] = right_dir;
6639 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6641 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6644 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6646 TestIfBadThingTouchesOtherBadThing(x, y);
6648 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6649 MovDir[x][y] = left_dir;
6650 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6651 MovDir[x][y] = right_dir;
6653 if (MovDir[x][y] != old_move_dir)
6656 else if (element == EL_YAMYAM)
6658 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6659 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6661 if (can_turn_left && can_turn_right)
6662 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6663 else if (can_turn_left)
6664 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6665 else if (can_turn_right)
6666 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6668 MovDir[x][y] = back_dir;
6670 MovDelay[x][y] = 16 + 16 * RND(3);
6672 else if (element == EL_DARK_YAMYAM)
6674 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6676 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6679 if (can_turn_left && can_turn_right)
6680 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6681 else if (can_turn_left)
6682 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6683 else if (can_turn_right)
6684 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6686 MovDir[x][y] = back_dir;
6688 MovDelay[x][y] = 16 + 16 * RND(3);
6690 else if (element == EL_PACMAN)
6692 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6693 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6695 if (can_turn_left && can_turn_right)
6696 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6697 else if (can_turn_left)
6698 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6699 else if (can_turn_right)
6700 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6702 MovDir[x][y] = back_dir;
6704 MovDelay[x][y] = 6 + RND(40);
6706 else if (element == EL_PIG)
6708 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6709 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6710 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6711 boolean should_turn_left, should_turn_right, should_move_on;
6713 int rnd = RND(rnd_value);
6715 should_turn_left = (can_turn_left &&
6717 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6718 y + back_dy + left_dy)));
6719 should_turn_right = (can_turn_right &&
6721 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6722 y + back_dy + right_dy)));
6723 should_move_on = (can_move_on &&
6726 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6727 y + move_dy + left_dy) ||
6728 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6729 y + move_dy + right_dy)));
6731 if (should_turn_left || should_turn_right || should_move_on)
6733 if (should_turn_left && should_turn_right && should_move_on)
6734 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6735 rnd < 2 * rnd_value / 3 ? right_dir :
6737 else if (should_turn_left && should_turn_right)
6738 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6739 else if (should_turn_left && should_move_on)
6740 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6741 else if (should_turn_right && should_move_on)
6742 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6743 else if (should_turn_left)
6744 MovDir[x][y] = left_dir;
6745 else if (should_turn_right)
6746 MovDir[x][y] = right_dir;
6747 else if (should_move_on)
6748 MovDir[x][y] = old_move_dir;
6750 else if (can_move_on && rnd > rnd_value / 8)
6751 MovDir[x][y] = old_move_dir;
6752 else if (can_turn_left && can_turn_right)
6753 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6754 else if (can_turn_left && rnd > rnd_value / 8)
6755 MovDir[x][y] = left_dir;
6756 else if (can_turn_right && rnd > rnd_value/8)
6757 MovDir[x][y] = right_dir;
6759 MovDir[x][y] = back_dir;
6761 xx = x + move_xy[MovDir[x][y]].dx;
6762 yy = y + move_xy[MovDir[x][y]].dy;
6764 if (!IN_LEV_FIELD(xx, yy) ||
6765 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6766 MovDir[x][y] = old_move_dir;
6770 else if (element == EL_DRAGON)
6772 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6773 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6774 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6776 int rnd = RND(rnd_value);
6778 if (can_move_on && rnd > rnd_value / 8)
6779 MovDir[x][y] = old_move_dir;
6780 else if (can_turn_left && can_turn_right)
6781 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6782 else if (can_turn_left && rnd > rnd_value / 8)
6783 MovDir[x][y] = left_dir;
6784 else if (can_turn_right && rnd > rnd_value / 8)
6785 MovDir[x][y] = right_dir;
6787 MovDir[x][y] = back_dir;
6789 xx = x + move_xy[MovDir[x][y]].dx;
6790 yy = y + move_xy[MovDir[x][y]].dy;
6792 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6793 MovDir[x][y] = old_move_dir;
6797 else if (element == EL_MOLE)
6799 boolean can_move_on =
6800 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6801 IS_AMOEBOID(Feld[move_x][move_y]) ||
6802 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6805 boolean can_turn_left =
6806 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6807 IS_AMOEBOID(Feld[left_x][left_y])));
6809 boolean can_turn_right =
6810 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6811 IS_AMOEBOID(Feld[right_x][right_y])));
6813 if (can_turn_left && can_turn_right)
6814 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6815 else if (can_turn_left)
6816 MovDir[x][y] = left_dir;
6818 MovDir[x][y] = right_dir;
6821 if (MovDir[x][y] != old_move_dir)
6824 else if (element == EL_BALLOON)
6826 MovDir[x][y] = game.wind_direction;
6829 else if (element == EL_SPRING)
6831 if (MovDir[x][y] & MV_HORIZONTAL)
6833 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6834 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6836 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6837 ResetGfxAnimation(move_x, move_y);
6838 TEST_DrawLevelField(move_x, move_y);
6840 MovDir[x][y] = back_dir;
6842 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6843 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6844 MovDir[x][y] = MV_NONE;
6849 else if (element == EL_ROBOT ||
6850 element == EL_SATELLITE ||
6851 element == EL_PENGUIN ||
6852 element == EL_EMC_ANDROID)
6854 int attr_x = -1, attr_y = -1;
6856 if (game.all_players_gone)
6858 attr_x = game.exit_x;
6859 attr_y = game.exit_y;
6865 for (i = 0; i < MAX_PLAYERS; i++)
6867 struct PlayerInfo *player = &stored_player[i];
6868 int jx = player->jx, jy = player->jy;
6870 if (!player->active)
6874 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6882 if (element == EL_ROBOT &&
6883 game.robot_wheel_x >= 0 &&
6884 game.robot_wheel_y >= 0 &&
6885 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6886 game.engine_version < VERSION_IDENT(3,1,0,0)))
6888 attr_x = game.robot_wheel_x;
6889 attr_y = game.robot_wheel_y;
6892 if (element == EL_PENGUIN)
6895 static int xy[4][2] =
6903 for (i = 0; i < NUM_DIRECTIONS; i++)
6905 int ex = x + xy[i][0];
6906 int ey = y + xy[i][1];
6908 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6909 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6910 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6911 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6920 MovDir[x][y] = MV_NONE;
6922 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6923 else if (attr_x > x)
6924 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6926 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6927 else if (attr_y > y)
6928 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6930 if (element == EL_ROBOT)
6934 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6935 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6936 Moving2Blocked(x, y, &newx, &newy);
6938 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6939 MovDelay[x][y] = 8 + 8 * !RND(3);
6941 MovDelay[x][y] = 16;
6943 else if (element == EL_PENGUIN)
6949 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6951 boolean first_horiz = RND(2);
6952 int new_move_dir = MovDir[x][y];
6955 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6956 Moving2Blocked(x, y, &newx, &newy);
6958 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6962 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6963 Moving2Blocked(x, y, &newx, &newy);
6965 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6968 MovDir[x][y] = old_move_dir;
6972 else if (element == EL_SATELLITE)
6978 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6980 boolean first_horiz = RND(2);
6981 int new_move_dir = MovDir[x][y];
6984 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6985 Moving2Blocked(x, y, &newx, &newy);
6987 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6991 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6992 Moving2Blocked(x, y, &newx, &newy);
6994 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6997 MovDir[x][y] = old_move_dir;
7001 else if (element == EL_EMC_ANDROID)
7003 static int check_pos[16] =
7005 -1, // 0 => (invalid)
7008 -1, // 3 => (invalid)
7010 0, // 5 => MV_LEFT | MV_UP
7011 2, // 6 => MV_RIGHT | MV_UP
7012 -1, // 7 => (invalid)
7014 6, // 9 => MV_LEFT | MV_DOWN
7015 4, // 10 => MV_RIGHT | MV_DOWN
7016 -1, // 11 => (invalid)
7017 -1, // 12 => (invalid)
7018 -1, // 13 => (invalid)
7019 -1, // 14 => (invalid)
7020 -1, // 15 => (invalid)
7028 { -1, -1, MV_LEFT | MV_UP },
7030 { +1, -1, MV_RIGHT | MV_UP },
7031 { +1, 0, MV_RIGHT },
7032 { +1, +1, MV_RIGHT | MV_DOWN },
7034 { -1, +1, MV_LEFT | MV_DOWN },
7037 int start_pos, check_order;
7038 boolean can_clone = FALSE;
7041 // check if there is any free field around current position
7042 for (i = 0; i < 8; i++)
7044 int newx = x + check_xy[i].dx;
7045 int newy = y + check_xy[i].dy;
7047 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7055 if (can_clone) // randomly find an element to clone
7059 start_pos = check_pos[RND(8)];
7060 check_order = (RND(2) ? -1 : +1);
7062 for (i = 0; i < 8; i++)
7064 int pos_raw = start_pos + i * check_order;
7065 int pos = (pos_raw + 8) % 8;
7066 int newx = x + check_xy[pos].dx;
7067 int newy = y + check_xy[pos].dy;
7069 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7071 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7072 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7074 Store[x][y] = Feld[newx][newy];
7083 if (can_clone) // randomly find a direction to move
7087 start_pos = check_pos[RND(8)];
7088 check_order = (RND(2) ? -1 : +1);
7090 for (i = 0; i < 8; i++)
7092 int pos_raw = start_pos + i * check_order;
7093 int pos = (pos_raw + 8) % 8;
7094 int newx = x + check_xy[pos].dx;
7095 int newy = y + check_xy[pos].dy;
7096 int new_move_dir = check_xy[pos].dir;
7098 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7100 MovDir[x][y] = new_move_dir;
7101 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7110 if (can_clone) // cloning and moving successful
7113 // cannot clone -- try to move towards player
7115 start_pos = check_pos[MovDir[x][y] & 0x0f];
7116 check_order = (RND(2) ? -1 : +1);
7118 for (i = 0; i < 3; i++)
7120 // first check start_pos, then previous/next or (next/previous) pos
7121 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7122 int pos = (pos_raw + 8) % 8;
7123 int newx = x + check_xy[pos].dx;
7124 int newy = y + check_xy[pos].dy;
7125 int new_move_dir = check_xy[pos].dir;
7127 if (IS_PLAYER(newx, newy))
7130 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7132 MovDir[x][y] = new_move_dir;
7133 MovDelay[x][y] = level.android_move_time * 8 + 1;
7140 else if (move_pattern == MV_TURNING_LEFT ||
7141 move_pattern == MV_TURNING_RIGHT ||
7142 move_pattern == MV_TURNING_LEFT_RIGHT ||
7143 move_pattern == MV_TURNING_RIGHT_LEFT ||
7144 move_pattern == MV_TURNING_RANDOM ||
7145 move_pattern == MV_ALL_DIRECTIONS)
7147 boolean can_turn_left =
7148 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7149 boolean can_turn_right =
7150 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7152 if (element_info[element].move_stepsize == 0) // "not moving"
7155 if (move_pattern == MV_TURNING_LEFT)
7156 MovDir[x][y] = left_dir;
7157 else if (move_pattern == MV_TURNING_RIGHT)
7158 MovDir[x][y] = right_dir;
7159 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7160 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7161 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7162 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7163 else if (move_pattern == MV_TURNING_RANDOM)
7164 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7165 can_turn_right && !can_turn_left ? right_dir :
7166 RND(2) ? left_dir : right_dir);
7167 else if (can_turn_left && can_turn_right)
7168 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7169 else if (can_turn_left)
7170 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7171 else if (can_turn_right)
7172 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7174 MovDir[x][y] = back_dir;
7176 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7178 else if (move_pattern == MV_HORIZONTAL ||
7179 move_pattern == MV_VERTICAL)
7181 if (move_pattern & old_move_dir)
7182 MovDir[x][y] = back_dir;
7183 else if (move_pattern == MV_HORIZONTAL)
7184 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7185 else if (move_pattern == MV_VERTICAL)
7186 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7188 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7190 else if (move_pattern & MV_ANY_DIRECTION)
7192 MovDir[x][y] = move_pattern;
7193 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7195 else if (move_pattern & MV_WIND_DIRECTION)
7197 MovDir[x][y] = game.wind_direction;
7198 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7200 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7202 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7203 MovDir[x][y] = left_dir;
7204 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7205 MovDir[x][y] = right_dir;
7207 if (MovDir[x][y] != old_move_dir)
7208 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7210 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7212 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7213 MovDir[x][y] = right_dir;
7214 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7215 MovDir[x][y] = left_dir;
7217 if (MovDir[x][y] != old_move_dir)
7218 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7220 else if (move_pattern == MV_TOWARDS_PLAYER ||
7221 move_pattern == MV_AWAY_FROM_PLAYER)
7223 int attr_x = -1, attr_y = -1;
7225 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7227 if (game.all_players_gone)
7229 attr_x = game.exit_x;
7230 attr_y = game.exit_y;
7236 for (i = 0; i < MAX_PLAYERS; i++)
7238 struct PlayerInfo *player = &stored_player[i];
7239 int jx = player->jx, jy = player->jy;
7241 if (!player->active)
7245 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7253 MovDir[x][y] = MV_NONE;
7255 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7256 else if (attr_x > x)
7257 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7259 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7260 else if (attr_y > y)
7261 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7263 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7265 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7267 boolean first_horiz = RND(2);
7268 int new_move_dir = MovDir[x][y];
7270 if (element_info[element].move_stepsize == 0) // "not moving"
7272 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7273 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7279 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7280 Moving2Blocked(x, y, &newx, &newy);
7282 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7286 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7287 Moving2Blocked(x, y, &newx, &newy);
7289 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7292 MovDir[x][y] = old_move_dir;
7295 else if (move_pattern == MV_WHEN_PUSHED ||
7296 move_pattern == MV_WHEN_DROPPED)
7298 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7299 MovDir[x][y] = MV_NONE;
7303 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7305 static int test_xy[7][2] =
7315 static int test_dir[7] =
7325 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7326 int move_preference = -1000000; // start with very low preference
7327 int new_move_dir = MV_NONE;
7328 int start_test = RND(4);
7331 for (i = 0; i < NUM_DIRECTIONS; i++)
7333 int move_dir = test_dir[start_test + i];
7334 int move_dir_preference;
7336 xx = x + test_xy[start_test + i][0];
7337 yy = y + test_xy[start_test + i][1];
7339 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7340 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7342 new_move_dir = move_dir;
7347 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7350 move_dir_preference = -1 * RunnerVisit[xx][yy];
7351 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7352 move_dir_preference = PlayerVisit[xx][yy];
7354 if (move_dir_preference > move_preference)
7356 // prefer field that has not been visited for the longest time
7357 move_preference = move_dir_preference;
7358 new_move_dir = move_dir;
7360 else if (move_dir_preference == move_preference &&
7361 move_dir == old_move_dir)
7363 // prefer last direction when all directions are preferred equally
7364 move_preference = move_dir_preference;
7365 new_move_dir = move_dir;
7369 MovDir[x][y] = new_move_dir;
7370 if (old_move_dir != new_move_dir)
7371 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7375 static void TurnRound(int x, int y)
7377 int direction = MovDir[x][y];
7381 GfxDir[x][y] = MovDir[x][y];
7383 if (direction != MovDir[x][y])
7387 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7389 ResetGfxFrame(x, y);
7392 static boolean JustBeingPushed(int x, int y)
7396 for (i = 0; i < MAX_PLAYERS; i++)
7398 struct PlayerInfo *player = &stored_player[i];
7400 if (player->active && player->is_pushing && player->MovPos)
7402 int next_jx = player->jx + (player->jx - player->last_jx);
7403 int next_jy = player->jy + (player->jy - player->last_jy);
7405 if (x == next_jx && y == next_jy)
7413 static void StartMoving(int x, int y)
7415 boolean started_moving = FALSE; // some elements can fall _and_ move
7416 int element = Feld[x][y];
7421 if (MovDelay[x][y] == 0)
7422 GfxAction[x][y] = ACTION_DEFAULT;
7424 if (CAN_FALL(element) && y < lev_fieldy - 1)
7426 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7427 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7428 if (JustBeingPushed(x, y))
7431 if (element == EL_QUICKSAND_FULL)
7433 if (IS_FREE(x, y + 1))
7435 InitMovingField(x, y, MV_DOWN);
7436 started_moving = TRUE;
7438 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7439 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7440 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7441 Store[x][y] = EL_ROCK;
7443 Store[x][y] = EL_ROCK;
7446 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7448 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7450 if (!MovDelay[x][y])
7452 MovDelay[x][y] = TILEY + 1;
7454 ResetGfxAnimation(x, y);
7455 ResetGfxAnimation(x, y + 1);
7460 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7461 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7468 Feld[x][y] = EL_QUICKSAND_EMPTY;
7469 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7470 Store[x][y + 1] = Store[x][y];
7473 PlayLevelSoundAction(x, y, ACTION_FILLING);
7475 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7477 if (!MovDelay[x][y])
7479 MovDelay[x][y] = TILEY + 1;
7481 ResetGfxAnimation(x, y);
7482 ResetGfxAnimation(x, y + 1);
7487 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7488 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7495 Feld[x][y] = EL_QUICKSAND_EMPTY;
7496 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7497 Store[x][y + 1] = Store[x][y];
7500 PlayLevelSoundAction(x, y, ACTION_FILLING);
7503 else if (element == EL_QUICKSAND_FAST_FULL)
7505 if (IS_FREE(x, y + 1))
7507 InitMovingField(x, y, MV_DOWN);
7508 started_moving = TRUE;
7510 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7511 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7512 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7513 Store[x][y] = EL_ROCK;
7515 Store[x][y] = EL_ROCK;
7518 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7520 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7522 if (!MovDelay[x][y])
7524 MovDelay[x][y] = TILEY + 1;
7526 ResetGfxAnimation(x, y);
7527 ResetGfxAnimation(x, y + 1);
7532 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7533 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7540 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7541 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7542 Store[x][y + 1] = Store[x][y];
7545 PlayLevelSoundAction(x, y, ACTION_FILLING);
7547 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7549 if (!MovDelay[x][y])
7551 MovDelay[x][y] = TILEY + 1;
7553 ResetGfxAnimation(x, y);
7554 ResetGfxAnimation(x, y + 1);
7559 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7560 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7567 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7568 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7569 Store[x][y + 1] = Store[x][y];
7572 PlayLevelSoundAction(x, y, ACTION_FILLING);
7575 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7576 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7578 InitMovingField(x, y, MV_DOWN);
7579 started_moving = TRUE;
7581 Feld[x][y] = EL_QUICKSAND_FILLING;
7582 Store[x][y] = element;
7584 PlayLevelSoundAction(x, y, ACTION_FILLING);
7586 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7587 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7589 InitMovingField(x, y, MV_DOWN);
7590 started_moving = TRUE;
7592 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7593 Store[x][y] = element;
7595 PlayLevelSoundAction(x, y, ACTION_FILLING);
7597 else if (element == EL_MAGIC_WALL_FULL)
7599 if (IS_FREE(x, y + 1))
7601 InitMovingField(x, y, MV_DOWN);
7602 started_moving = TRUE;
7604 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7605 Store[x][y] = EL_CHANGED(Store[x][y]);
7607 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7609 if (!MovDelay[x][y])
7610 MovDelay[x][y] = TILEY / 4 + 1;
7619 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7620 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7621 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7625 else if (element == EL_BD_MAGIC_WALL_FULL)
7627 if (IS_FREE(x, y + 1))
7629 InitMovingField(x, y, MV_DOWN);
7630 started_moving = TRUE;
7632 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7633 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7635 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7637 if (!MovDelay[x][y])
7638 MovDelay[x][y] = TILEY / 4 + 1;
7647 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7648 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7649 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7653 else if (element == EL_DC_MAGIC_WALL_FULL)
7655 if (IS_FREE(x, y + 1))
7657 InitMovingField(x, y, MV_DOWN);
7658 started_moving = TRUE;
7660 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7661 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7663 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7665 if (!MovDelay[x][y])
7666 MovDelay[x][y] = TILEY / 4 + 1;
7675 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7676 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7677 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7681 else if ((CAN_PASS_MAGIC_WALL(element) &&
7682 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7683 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7684 (CAN_PASS_DC_MAGIC_WALL(element) &&
7685 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7688 InitMovingField(x, y, MV_DOWN);
7689 started_moving = TRUE;
7692 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7693 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7694 EL_DC_MAGIC_WALL_FILLING);
7695 Store[x][y] = element;
7697 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7699 SplashAcid(x, y + 1);
7701 InitMovingField(x, y, MV_DOWN);
7702 started_moving = TRUE;
7704 Store[x][y] = EL_ACID;
7707 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7708 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7709 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7710 CAN_FALL(element) && WasJustFalling[x][y] &&
7711 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7713 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7714 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7715 (Feld[x][y + 1] == EL_BLOCKED)))
7717 /* this is needed for a special case not covered by calling "Impact()"
7718 from "ContinueMoving()": if an element moves to a tile directly below
7719 another element which was just falling on that tile (which was empty
7720 in the previous frame), the falling element above would just stop
7721 instead of smashing the element below (in previous version, the above
7722 element was just checked for "moving" instead of "falling", resulting
7723 in incorrect smashes caused by horizontal movement of the above
7724 element; also, the case of the player being the element to smash was
7725 simply not covered here... :-/ ) */
7727 CheckCollision[x][y] = 0;
7728 CheckImpact[x][y] = 0;
7732 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7734 if (MovDir[x][y] == MV_NONE)
7736 InitMovingField(x, y, MV_DOWN);
7737 started_moving = TRUE;
7740 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7742 if (WasJustFalling[x][y]) // prevent animation from being restarted
7743 MovDir[x][y] = MV_DOWN;
7745 InitMovingField(x, y, MV_DOWN);
7746 started_moving = TRUE;
7748 else if (element == EL_AMOEBA_DROP)
7750 Feld[x][y] = EL_AMOEBA_GROWING;
7751 Store[x][y] = EL_AMOEBA_WET;
7753 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7754 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7755 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7756 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7758 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7759 (IS_FREE(x - 1, y + 1) ||
7760 Feld[x - 1][y + 1] == EL_ACID));
7761 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7762 (IS_FREE(x + 1, y + 1) ||
7763 Feld[x + 1][y + 1] == EL_ACID));
7764 boolean can_fall_any = (can_fall_left || can_fall_right);
7765 boolean can_fall_both = (can_fall_left && can_fall_right);
7766 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7768 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7770 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7771 can_fall_right = FALSE;
7772 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7773 can_fall_left = FALSE;
7774 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7775 can_fall_right = FALSE;
7776 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7777 can_fall_left = FALSE;
7779 can_fall_any = (can_fall_left || can_fall_right);
7780 can_fall_both = FALSE;
7785 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7786 can_fall_right = FALSE; // slip down on left side
7788 can_fall_left = !(can_fall_right = RND(2));
7790 can_fall_both = FALSE;
7795 // if not determined otherwise, prefer left side for slipping down
7796 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7797 started_moving = TRUE;
7800 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7802 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7803 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7804 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7805 int belt_dir = game.belt_dir[belt_nr];
7807 if ((belt_dir == MV_LEFT && left_is_free) ||
7808 (belt_dir == MV_RIGHT && right_is_free))
7810 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7812 InitMovingField(x, y, belt_dir);
7813 started_moving = TRUE;
7815 Pushed[x][y] = TRUE;
7816 Pushed[nextx][y] = TRUE;
7818 GfxAction[x][y] = ACTION_DEFAULT;
7822 MovDir[x][y] = 0; // if element was moving, stop it
7827 // not "else if" because of elements that can fall and move (EL_SPRING)
7828 if (CAN_MOVE(element) && !started_moving)
7830 int move_pattern = element_info[element].move_pattern;
7833 Moving2Blocked(x, y, &newx, &newy);
7835 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7838 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7839 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7841 WasJustMoving[x][y] = 0;
7842 CheckCollision[x][y] = 0;
7844 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7846 if (Feld[x][y] != element) // element has changed
7850 if (!MovDelay[x][y]) // start new movement phase
7852 // all objects that can change their move direction after each step
7853 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7855 if (element != EL_YAMYAM &&
7856 element != EL_DARK_YAMYAM &&
7857 element != EL_PACMAN &&
7858 !(move_pattern & MV_ANY_DIRECTION) &&
7859 move_pattern != MV_TURNING_LEFT &&
7860 move_pattern != MV_TURNING_RIGHT &&
7861 move_pattern != MV_TURNING_LEFT_RIGHT &&
7862 move_pattern != MV_TURNING_RIGHT_LEFT &&
7863 move_pattern != MV_TURNING_RANDOM)
7867 if (MovDelay[x][y] && (element == EL_BUG ||
7868 element == EL_SPACESHIP ||
7869 element == EL_SP_SNIKSNAK ||
7870 element == EL_SP_ELECTRON ||
7871 element == EL_MOLE))
7872 TEST_DrawLevelField(x, y);
7876 if (MovDelay[x][y]) // wait some time before next movement
7880 if (element == EL_ROBOT ||
7881 element == EL_YAMYAM ||
7882 element == EL_DARK_YAMYAM)
7884 DrawLevelElementAnimationIfNeeded(x, y, element);
7885 PlayLevelSoundAction(x, y, ACTION_WAITING);
7887 else if (element == EL_SP_ELECTRON)
7888 DrawLevelElementAnimationIfNeeded(x, y, element);
7889 else if (element == EL_DRAGON)
7892 int dir = MovDir[x][y];
7893 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7894 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7895 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7896 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7897 dir == MV_UP ? IMG_FLAMES_1_UP :
7898 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7899 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7901 GfxAction[x][y] = ACTION_ATTACKING;
7903 if (IS_PLAYER(x, y))
7904 DrawPlayerField(x, y);
7906 TEST_DrawLevelField(x, y);
7908 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7910 for (i = 1; i <= 3; i++)
7912 int xx = x + i * dx;
7913 int yy = y + i * dy;
7914 int sx = SCREENX(xx);
7915 int sy = SCREENY(yy);
7916 int flame_graphic = graphic + (i - 1);
7918 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7923 int flamed = MovingOrBlocked2Element(xx, yy);
7925 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7928 RemoveMovingField(xx, yy);
7930 ChangeDelay[xx][yy] = 0;
7932 Feld[xx][yy] = EL_FLAMES;
7934 if (IN_SCR_FIELD(sx, sy))
7936 TEST_DrawLevelFieldCrumbled(xx, yy);
7937 DrawGraphic(sx, sy, flame_graphic, frame);
7942 if (Feld[xx][yy] == EL_FLAMES)
7943 Feld[xx][yy] = EL_EMPTY;
7944 TEST_DrawLevelField(xx, yy);
7949 if (MovDelay[x][y]) // element still has to wait some time
7951 PlayLevelSoundAction(x, y, ACTION_WAITING);
7957 // now make next step
7959 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7961 if (DONT_COLLIDE_WITH(element) &&
7962 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7963 !PLAYER_ENEMY_PROTECTED(newx, newy))
7965 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7970 else if (CAN_MOVE_INTO_ACID(element) &&
7971 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7972 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7973 (MovDir[x][y] == MV_DOWN ||
7974 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7976 SplashAcid(newx, newy);
7977 Store[x][y] = EL_ACID;
7979 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7981 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7982 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7983 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7984 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7987 TEST_DrawLevelField(x, y);
7989 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7990 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7991 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7993 game.friends_still_needed--;
7994 if (!game.friends_still_needed &&
7996 game.all_players_gone)
8001 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8003 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8004 TEST_DrawLevelField(newx, newy);
8006 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8008 else if (!IS_FREE(newx, newy))
8010 GfxAction[x][y] = ACTION_WAITING;
8012 if (IS_PLAYER(x, y))
8013 DrawPlayerField(x, y);
8015 TEST_DrawLevelField(x, y);
8020 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8022 if (IS_FOOD_PIG(Feld[newx][newy]))
8024 if (IS_MOVING(newx, newy))
8025 RemoveMovingField(newx, newy);
8028 Feld[newx][newy] = EL_EMPTY;
8029 TEST_DrawLevelField(newx, newy);
8032 PlayLevelSound(x, y, SND_PIG_DIGGING);
8034 else if (!IS_FREE(newx, newy))
8036 if (IS_PLAYER(x, y))
8037 DrawPlayerField(x, y);
8039 TEST_DrawLevelField(x, y);
8044 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8046 if (Store[x][y] != EL_EMPTY)
8048 boolean can_clone = FALSE;
8051 // check if element to clone is still there
8052 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8054 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8062 // cannot clone or target field not free anymore -- do not clone
8063 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8064 Store[x][y] = EL_EMPTY;
8067 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8069 if (IS_MV_DIAGONAL(MovDir[x][y]))
8071 int diagonal_move_dir = MovDir[x][y];
8072 int stored = Store[x][y];
8073 int change_delay = 8;
8076 // android is moving diagonally
8078 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8080 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8081 GfxElement[x][y] = EL_EMC_ANDROID;
8082 GfxAction[x][y] = ACTION_SHRINKING;
8083 GfxDir[x][y] = diagonal_move_dir;
8084 ChangeDelay[x][y] = change_delay;
8086 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8089 DrawLevelGraphicAnimation(x, y, graphic);
8090 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8092 if (Feld[newx][newy] == EL_ACID)
8094 SplashAcid(newx, newy);
8099 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8101 Store[newx][newy] = EL_EMC_ANDROID;
8102 GfxElement[newx][newy] = EL_EMC_ANDROID;
8103 GfxAction[newx][newy] = ACTION_GROWING;
8104 GfxDir[newx][newy] = diagonal_move_dir;
8105 ChangeDelay[newx][newy] = change_delay;
8107 graphic = el_act_dir2img(GfxElement[newx][newy],
8108 GfxAction[newx][newy], GfxDir[newx][newy]);
8110 DrawLevelGraphicAnimation(newx, newy, graphic);
8111 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8117 Feld[newx][newy] = EL_EMPTY;
8118 TEST_DrawLevelField(newx, newy);
8120 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8123 else if (!IS_FREE(newx, newy))
8128 else if (IS_CUSTOM_ELEMENT(element) &&
8129 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8131 if (!DigFieldByCE(newx, newy, element))
8134 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8136 RunnerVisit[x][y] = FrameCounter;
8137 PlayerVisit[x][y] /= 8; // expire player visit path
8140 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8142 if (!IS_FREE(newx, newy))
8144 if (IS_PLAYER(x, y))
8145 DrawPlayerField(x, y);
8147 TEST_DrawLevelField(x, y);
8153 boolean wanna_flame = !RND(10);
8154 int dx = newx - x, dy = newy - y;
8155 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8156 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8157 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8158 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8159 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8160 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8163 IS_CLASSIC_ENEMY(element1) ||
8164 IS_CLASSIC_ENEMY(element2)) &&
8165 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8166 element1 != EL_FLAMES && element2 != EL_FLAMES)
8168 ResetGfxAnimation(x, y);
8169 GfxAction[x][y] = ACTION_ATTACKING;
8171 if (IS_PLAYER(x, y))
8172 DrawPlayerField(x, y);
8174 TEST_DrawLevelField(x, y);
8176 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8178 MovDelay[x][y] = 50;
8180 Feld[newx][newy] = EL_FLAMES;
8181 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8182 Feld[newx1][newy1] = EL_FLAMES;
8183 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8184 Feld[newx2][newy2] = EL_FLAMES;
8190 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8191 Feld[newx][newy] == EL_DIAMOND)
8193 if (IS_MOVING(newx, newy))
8194 RemoveMovingField(newx, newy);
8197 Feld[newx][newy] = EL_EMPTY;
8198 TEST_DrawLevelField(newx, newy);
8201 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8203 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8204 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8206 if (AmoebaNr[newx][newy])
8208 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8209 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8210 Feld[newx][newy] == EL_BD_AMOEBA)
8211 AmoebaCnt[AmoebaNr[newx][newy]]--;
8214 if (IS_MOVING(newx, newy))
8216 RemoveMovingField(newx, newy);
8220 Feld[newx][newy] = EL_EMPTY;
8221 TEST_DrawLevelField(newx, newy);
8224 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8226 else if ((element == EL_PACMAN || element == EL_MOLE)
8227 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8229 if (AmoebaNr[newx][newy])
8231 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8232 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8233 Feld[newx][newy] == EL_BD_AMOEBA)
8234 AmoebaCnt[AmoebaNr[newx][newy]]--;
8237 if (element == EL_MOLE)
8239 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8240 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8242 ResetGfxAnimation(x, y);
8243 GfxAction[x][y] = ACTION_DIGGING;
8244 TEST_DrawLevelField(x, y);
8246 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8248 return; // wait for shrinking amoeba
8250 else // element == EL_PACMAN
8252 Feld[newx][newy] = EL_EMPTY;
8253 TEST_DrawLevelField(newx, newy);
8254 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8257 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8258 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8259 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8261 // wait for shrinking amoeba to completely disappear
8264 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8266 // object was running against a wall
8270 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8271 DrawLevelElementAnimation(x, y, element);
8273 if (DONT_TOUCH(element))
8274 TestIfBadThingTouchesPlayer(x, y);
8279 InitMovingField(x, y, MovDir[x][y]);
8281 PlayLevelSoundAction(x, y, ACTION_MOVING);
8285 ContinueMoving(x, y);
8288 void ContinueMoving(int x, int y)
8290 int element = Feld[x][y];
8291 struct ElementInfo *ei = &element_info[element];
8292 int direction = MovDir[x][y];
8293 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8294 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8295 int newx = x + dx, newy = y + dy;
8296 int stored = Store[x][y];
8297 int stored_new = Store[newx][newy];
8298 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8299 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8300 boolean last_line = (newy == lev_fieldy - 1);
8302 MovPos[x][y] += getElementMoveStepsize(x, y);
8304 if (pushed_by_player) // special case: moving object pushed by player
8305 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8307 if (ABS(MovPos[x][y]) < TILEX)
8309 TEST_DrawLevelField(x, y);
8311 return; // element is still moving
8314 // element reached destination field
8316 Feld[x][y] = EL_EMPTY;
8317 Feld[newx][newy] = element;
8318 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8320 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8322 element = Feld[newx][newy] = EL_ACID;
8324 else if (element == EL_MOLE)
8326 Feld[x][y] = EL_SAND;
8328 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8330 else if (element == EL_QUICKSAND_FILLING)
8332 element = Feld[newx][newy] = get_next_element(element);
8333 Store[newx][newy] = Store[x][y];
8335 else if (element == EL_QUICKSAND_EMPTYING)
8337 Feld[x][y] = get_next_element(element);
8338 element = Feld[newx][newy] = Store[x][y];
8340 else if (element == EL_QUICKSAND_FAST_FILLING)
8342 element = Feld[newx][newy] = get_next_element(element);
8343 Store[newx][newy] = Store[x][y];
8345 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8347 Feld[x][y] = get_next_element(element);
8348 element = Feld[newx][newy] = Store[x][y];
8350 else if (element == EL_MAGIC_WALL_FILLING)
8352 element = Feld[newx][newy] = get_next_element(element);
8353 if (!game.magic_wall_active)
8354 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8355 Store[newx][newy] = Store[x][y];
8357 else if (element == EL_MAGIC_WALL_EMPTYING)
8359 Feld[x][y] = get_next_element(element);
8360 if (!game.magic_wall_active)
8361 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8362 element = Feld[newx][newy] = Store[x][y];
8364 InitField(newx, newy, FALSE);
8366 else if (element == EL_BD_MAGIC_WALL_FILLING)
8368 element = Feld[newx][newy] = get_next_element(element);
8369 if (!game.magic_wall_active)
8370 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8371 Store[newx][newy] = Store[x][y];
8373 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8375 Feld[x][y] = get_next_element(element);
8376 if (!game.magic_wall_active)
8377 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8378 element = Feld[newx][newy] = Store[x][y];
8380 InitField(newx, newy, FALSE);
8382 else if (element == EL_DC_MAGIC_WALL_FILLING)
8384 element = Feld[newx][newy] = get_next_element(element);
8385 if (!game.magic_wall_active)
8386 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8387 Store[newx][newy] = Store[x][y];
8389 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8391 Feld[x][y] = get_next_element(element);
8392 if (!game.magic_wall_active)
8393 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8394 element = Feld[newx][newy] = Store[x][y];
8396 InitField(newx, newy, FALSE);
8398 else if (element == EL_AMOEBA_DROPPING)
8400 Feld[x][y] = get_next_element(element);
8401 element = Feld[newx][newy] = Store[x][y];
8403 else if (element == EL_SOKOBAN_OBJECT)
8406 Feld[x][y] = Back[x][y];
8408 if (Back[newx][newy])
8409 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8411 Back[x][y] = Back[newx][newy] = 0;
8414 Store[x][y] = EL_EMPTY;
8419 MovDelay[newx][newy] = 0;
8421 if (CAN_CHANGE_OR_HAS_ACTION(element))
8423 // copy element change control values to new field
8424 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8425 ChangePage[newx][newy] = ChangePage[x][y];
8426 ChangeCount[newx][newy] = ChangeCount[x][y];
8427 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8430 CustomValue[newx][newy] = CustomValue[x][y];
8432 ChangeDelay[x][y] = 0;
8433 ChangePage[x][y] = -1;
8434 ChangeCount[x][y] = 0;
8435 ChangeEvent[x][y] = -1;
8437 CustomValue[x][y] = 0;
8439 // copy animation control values to new field
8440 GfxFrame[newx][newy] = GfxFrame[x][y];
8441 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8442 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8443 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8445 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8447 // some elements can leave other elements behind after moving
8448 if (ei->move_leave_element != EL_EMPTY &&
8449 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8450 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8452 int move_leave_element = ei->move_leave_element;
8454 // this makes it possible to leave the removed element again
8455 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8456 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8458 Feld[x][y] = move_leave_element;
8460 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8461 MovDir[x][y] = direction;
8463 InitField(x, y, FALSE);
8465 if (GFX_CRUMBLED(Feld[x][y]))
8466 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8468 if (ELEM_IS_PLAYER(move_leave_element))
8469 RelocatePlayer(x, y, move_leave_element);
8472 // do this after checking for left-behind element
8473 ResetGfxAnimation(x, y); // reset animation values for old field
8475 if (!CAN_MOVE(element) ||
8476 (CAN_FALL(element) && direction == MV_DOWN &&
8477 (element == EL_SPRING ||
8478 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8479 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8480 GfxDir[x][y] = MovDir[newx][newy] = 0;
8482 TEST_DrawLevelField(x, y);
8483 TEST_DrawLevelField(newx, newy);
8485 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8487 // prevent pushed element from moving on in pushed direction
8488 if (pushed_by_player && CAN_MOVE(element) &&
8489 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8490 !(element_info[element].move_pattern & direction))
8491 TurnRound(newx, newy);
8493 // prevent elements on conveyor belt from moving on in last direction
8494 if (pushed_by_conveyor && CAN_FALL(element) &&
8495 direction & MV_HORIZONTAL)
8496 MovDir[newx][newy] = 0;
8498 if (!pushed_by_player)
8500 int nextx = newx + dx, nexty = newy + dy;
8501 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8503 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8505 if (CAN_FALL(element) && direction == MV_DOWN)
8506 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8508 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8509 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8511 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8512 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8515 if (DONT_TOUCH(element)) // object may be nasty to player or others
8517 TestIfBadThingTouchesPlayer(newx, newy);
8518 TestIfBadThingTouchesFriend(newx, newy);
8520 if (!IS_CUSTOM_ELEMENT(element))
8521 TestIfBadThingTouchesOtherBadThing(newx, newy);
8523 else if (element == EL_PENGUIN)
8524 TestIfFriendTouchesBadThing(newx, newy);
8526 if (DONT_GET_HIT_BY(element))
8528 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8531 // give the player one last chance (one more frame) to move away
8532 if (CAN_FALL(element) && direction == MV_DOWN &&
8533 (last_line || (!IS_FREE(x, newy + 1) &&
8534 (!IS_PLAYER(x, newy + 1) ||
8535 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8538 if (pushed_by_player && !game.use_change_when_pushing_bug)
8540 int push_side = MV_DIR_OPPOSITE(direction);
8541 struct PlayerInfo *player = PLAYERINFO(x, y);
8543 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8544 player->index_bit, push_side);
8545 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8546 player->index_bit, push_side);
8549 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8550 MovDelay[newx][newy] = 1;
8552 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8554 TestIfElementTouchesCustomElement(x, y); // empty or new element
8555 TestIfElementHitsCustomElement(newx, newy, direction);
8556 TestIfPlayerTouchesCustomElement(newx, newy);
8557 TestIfElementTouchesCustomElement(newx, newy);
8559 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8560 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8561 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8562 MV_DIR_OPPOSITE(direction));
8565 int AmoebeNachbarNr(int ax, int ay)
8568 int element = Feld[ax][ay];
8570 static int xy[4][2] =
8578 for (i = 0; i < NUM_DIRECTIONS; i++)
8580 int x = ax + xy[i][0];
8581 int y = ay + xy[i][1];
8583 if (!IN_LEV_FIELD(x, y))
8586 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8587 group_nr = AmoebaNr[x][y];
8593 static void AmoebenVereinigen(int ax, int ay)
8595 int i, x, y, xx, yy;
8596 int new_group_nr = AmoebaNr[ax][ay];
8597 static int xy[4][2] =
8605 if (new_group_nr == 0)
8608 for (i = 0; i < NUM_DIRECTIONS; i++)
8613 if (!IN_LEV_FIELD(x, y))
8616 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8617 Feld[x][y] == EL_BD_AMOEBA ||
8618 Feld[x][y] == EL_AMOEBA_DEAD) &&
8619 AmoebaNr[x][y] != new_group_nr)
8621 int old_group_nr = AmoebaNr[x][y];
8623 if (old_group_nr == 0)
8626 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8627 AmoebaCnt[old_group_nr] = 0;
8628 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8629 AmoebaCnt2[old_group_nr] = 0;
8631 SCAN_PLAYFIELD(xx, yy)
8633 if (AmoebaNr[xx][yy] == old_group_nr)
8634 AmoebaNr[xx][yy] = new_group_nr;
8640 void AmoebeUmwandeln(int ax, int ay)
8644 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8646 int group_nr = AmoebaNr[ax][ay];
8651 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8652 printf("AmoebeUmwandeln(): This should never happen!\n");
8657 SCAN_PLAYFIELD(x, y)
8659 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8662 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8666 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8667 SND_AMOEBA_TURNING_TO_GEM :
8668 SND_AMOEBA_TURNING_TO_ROCK));
8673 static int xy[4][2] =
8681 for (i = 0; i < NUM_DIRECTIONS; i++)
8686 if (!IN_LEV_FIELD(x, y))
8689 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8691 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8692 SND_AMOEBA_TURNING_TO_GEM :
8693 SND_AMOEBA_TURNING_TO_ROCK));
8700 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8703 int group_nr = AmoebaNr[ax][ay];
8704 boolean done = FALSE;
8709 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8710 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8715 SCAN_PLAYFIELD(x, y)
8717 if (AmoebaNr[x][y] == group_nr &&
8718 (Feld[x][y] == EL_AMOEBA_DEAD ||
8719 Feld[x][y] == EL_BD_AMOEBA ||
8720 Feld[x][y] == EL_AMOEBA_GROWING))
8723 Feld[x][y] = new_element;
8724 InitField(x, y, FALSE);
8725 TEST_DrawLevelField(x, y);
8731 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8732 SND_BD_AMOEBA_TURNING_TO_ROCK :
8733 SND_BD_AMOEBA_TURNING_TO_GEM));
8736 static void AmoebeWaechst(int x, int y)
8738 static unsigned int sound_delay = 0;
8739 static unsigned int sound_delay_value = 0;
8741 if (!MovDelay[x][y]) // start new growing cycle
8745 if (DelayReached(&sound_delay, sound_delay_value))
8747 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8748 sound_delay_value = 30;
8752 if (MovDelay[x][y]) // wait some time before growing bigger
8755 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8757 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8758 6 - MovDelay[x][y]);
8760 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8763 if (!MovDelay[x][y])
8765 Feld[x][y] = Store[x][y];
8767 TEST_DrawLevelField(x, y);
8772 static void AmoebaDisappearing(int x, int y)
8774 static unsigned int sound_delay = 0;
8775 static unsigned int sound_delay_value = 0;
8777 if (!MovDelay[x][y]) // start new shrinking cycle
8781 if (DelayReached(&sound_delay, sound_delay_value))
8782 sound_delay_value = 30;
8785 if (MovDelay[x][y]) // wait some time before shrinking
8788 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8790 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8791 6 - MovDelay[x][y]);
8793 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8796 if (!MovDelay[x][y])
8798 Feld[x][y] = EL_EMPTY;
8799 TEST_DrawLevelField(x, y);
8801 // don't let mole enter this field in this cycle;
8802 // (give priority to objects falling to this field from above)
8808 static void AmoebeAbleger(int ax, int ay)
8811 int element = Feld[ax][ay];
8812 int graphic = el2img(element);
8813 int newax = ax, neway = ay;
8814 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8815 static int xy[4][2] =
8823 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8825 Feld[ax][ay] = EL_AMOEBA_DEAD;
8826 TEST_DrawLevelField(ax, ay);
8830 if (IS_ANIMATED(graphic))
8831 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8833 if (!MovDelay[ax][ay]) // start making new amoeba field
8834 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8836 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8839 if (MovDelay[ax][ay])
8843 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8846 int x = ax + xy[start][0];
8847 int y = ay + xy[start][1];
8849 if (!IN_LEV_FIELD(x, y))
8852 if (IS_FREE(x, y) ||
8853 CAN_GROW_INTO(Feld[x][y]) ||
8854 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8855 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8861 if (newax == ax && neway == ay)
8864 else // normal or "filled" (BD style) amoeba
8867 boolean waiting_for_player = FALSE;
8869 for (i = 0; i < NUM_DIRECTIONS; i++)
8871 int j = (start + i) % 4;
8872 int x = ax + xy[j][0];
8873 int y = ay + xy[j][1];
8875 if (!IN_LEV_FIELD(x, y))
8878 if (IS_FREE(x, y) ||
8879 CAN_GROW_INTO(Feld[x][y]) ||
8880 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8881 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8887 else if (IS_PLAYER(x, y))
8888 waiting_for_player = TRUE;
8891 if (newax == ax && neway == ay) // amoeba cannot grow
8893 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8895 Feld[ax][ay] = EL_AMOEBA_DEAD;
8896 TEST_DrawLevelField(ax, ay);
8897 AmoebaCnt[AmoebaNr[ax][ay]]--;
8899 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8901 if (element == EL_AMOEBA_FULL)
8902 AmoebeUmwandeln(ax, ay);
8903 else if (element == EL_BD_AMOEBA)
8904 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8909 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8911 // amoeba gets larger by growing in some direction
8913 int new_group_nr = AmoebaNr[ax][ay];
8916 if (new_group_nr == 0)
8918 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8919 printf("AmoebeAbleger(): This should never happen!\n");
8924 AmoebaNr[newax][neway] = new_group_nr;
8925 AmoebaCnt[new_group_nr]++;
8926 AmoebaCnt2[new_group_nr]++;
8928 // if amoeba touches other amoeba(s) after growing, unify them
8929 AmoebenVereinigen(newax, neway);
8931 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8933 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8939 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8940 (neway == lev_fieldy - 1 && newax != ax))
8942 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8943 Store[newax][neway] = element;
8945 else if (neway == ay || element == EL_EMC_DRIPPER)
8947 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8949 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8953 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8954 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8955 Store[ax][ay] = EL_AMOEBA_DROP;
8956 ContinueMoving(ax, ay);
8960 TEST_DrawLevelField(newax, neway);
8963 static void Life(int ax, int ay)
8967 int element = Feld[ax][ay];
8968 int graphic = el2img(element);
8969 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8971 boolean changed = FALSE;
8973 if (IS_ANIMATED(graphic))
8974 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8979 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8980 MovDelay[ax][ay] = life_time;
8982 if (MovDelay[ax][ay]) // wait some time before next cycle
8985 if (MovDelay[ax][ay])
8989 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8991 int xx = ax+x1, yy = ay+y1;
8992 int old_element = Feld[xx][yy];
8993 int num_neighbours = 0;
8995 if (!IN_LEV_FIELD(xx, yy))
8998 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9000 int x = xx+x2, y = yy+y2;
9002 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9005 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9006 boolean is_neighbour = FALSE;
9008 if (level.use_life_bugs)
9010 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9011 (IS_FREE(x, y) && Stop[x][y]));
9014 (Last[x][y] == element || is_player_cell);
9020 boolean is_free = FALSE;
9022 if (level.use_life_bugs)
9023 is_free = (IS_FREE(xx, yy));
9025 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9027 if (xx == ax && yy == ay) // field in the middle
9029 if (num_neighbours < life_parameter[0] ||
9030 num_neighbours > life_parameter[1])
9032 Feld[xx][yy] = EL_EMPTY;
9033 if (Feld[xx][yy] != old_element)
9034 TEST_DrawLevelField(xx, yy);
9035 Stop[xx][yy] = TRUE;
9039 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9040 { // free border field
9041 if (num_neighbours >= life_parameter[2] &&
9042 num_neighbours <= life_parameter[3])
9044 Feld[xx][yy] = element;
9045 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9046 if (Feld[xx][yy] != old_element)
9047 TEST_DrawLevelField(xx, yy);
9048 Stop[xx][yy] = TRUE;
9055 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9056 SND_GAME_OF_LIFE_GROWING);
9059 static void InitRobotWheel(int x, int y)
9061 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9064 static void RunRobotWheel(int x, int y)
9066 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9069 static void StopRobotWheel(int x, int y)
9071 if (game.robot_wheel_x == x &&
9072 game.robot_wheel_y == y)
9074 game.robot_wheel_x = -1;
9075 game.robot_wheel_y = -1;
9076 game.robot_wheel_active = FALSE;
9080 static void InitTimegateWheel(int x, int y)
9082 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9085 static void RunTimegateWheel(int x, int y)
9087 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9090 static void InitMagicBallDelay(int x, int y)
9092 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9095 static void ActivateMagicBall(int bx, int by)
9099 if (level.ball_random)
9101 int pos_border = RND(8); // select one of the eight border elements
9102 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9103 int xx = pos_content % 3;
9104 int yy = pos_content / 3;
9109 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9110 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9114 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9116 int xx = x - bx + 1;
9117 int yy = y - by + 1;
9119 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9120 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9124 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9127 static void CheckExit(int x, int y)
9129 if (game.gems_still_needed > 0 ||
9130 game.sokoban_fields_still_needed > 0 ||
9131 game.sokoban_objects_still_needed > 0 ||
9132 game.lights_still_needed > 0)
9134 int element = Feld[x][y];
9135 int graphic = el2img(element);
9137 if (IS_ANIMATED(graphic))
9138 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9143 // do not re-open exit door closed after last player
9144 if (game.all_players_gone)
9147 Feld[x][y] = EL_EXIT_OPENING;
9149 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9152 static void CheckExitEM(int x, int y)
9154 if (game.gems_still_needed > 0 ||
9155 game.sokoban_fields_still_needed > 0 ||
9156 game.sokoban_objects_still_needed > 0 ||
9157 game.lights_still_needed > 0)
9159 int element = Feld[x][y];
9160 int graphic = el2img(element);
9162 if (IS_ANIMATED(graphic))
9163 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9168 // do not re-open exit door closed after last player
9169 if (game.all_players_gone)
9172 Feld[x][y] = EL_EM_EXIT_OPENING;
9174 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9177 static void CheckExitSteel(int x, int y)
9179 if (game.gems_still_needed > 0 ||
9180 game.sokoban_fields_still_needed > 0 ||
9181 game.sokoban_objects_still_needed > 0 ||
9182 game.lights_still_needed > 0)
9184 int element = Feld[x][y];
9185 int graphic = el2img(element);
9187 if (IS_ANIMATED(graphic))
9188 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9193 // do not re-open exit door closed after last player
9194 if (game.all_players_gone)
9197 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9199 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9202 static void CheckExitSteelEM(int x, int y)
9204 if (game.gems_still_needed > 0 ||
9205 game.sokoban_fields_still_needed > 0 ||
9206 game.sokoban_objects_still_needed > 0 ||
9207 game.lights_still_needed > 0)
9209 int element = Feld[x][y];
9210 int graphic = el2img(element);
9212 if (IS_ANIMATED(graphic))
9213 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9218 // do not re-open exit door closed after last player
9219 if (game.all_players_gone)
9222 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9224 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9227 static void CheckExitSP(int x, int y)
9229 if (game.gems_still_needed > 0)
9231 int element = Feld[x][y];
9232 int graphic = el2img(element);
9234 if (IS_ANIMATED(graphic))
9235 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9240 // do not re-open exit door closed after last player
9241 if (game.all_players_gone)
9244 Feld[x][y] = EL_SP_EXIT_OPENING;
9246 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9249 static void CloseAllOpenTimegates(void)
9253 SCAN_PLAYFIELD(x, y)
9255 int element = Feld[x][y];
9257 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9259 Feld[x][y] = EL_TIMEGATE_CLOSING;
9261 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9266 static void DrawTwinkleOnField(int x, int y)
9268 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9271 if (Feld[x][y] == EL_BD_DIAMOND)
9274 if (MovDelay[x][y] == 0) // next animation frame
9275 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9277 if (MovDelay[x][y] != 0) // wait some time before next frame
9281 DrawLevelElementAnimation(x, y, Feld[x][y]);
9283 if (MovDelay[x][y] != 0)
9285 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9286 10 - MovDelay[x][y]);
9288 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9293 static void MauerWaechst(int x, int y)
9297 if (!MovDelay[x][y]) // next animation frame
9298 MovDelay[x][y] = 3 * delay;
9300 if (MovDelay[x][y]) // wait some time before next frame
9304 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9306 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9307 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9309 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9312 if (!MovDelay[x][y])
9314 if (MovDir[x][y] == MV_LEFT)
9316 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9317 TEST_DrawLevelField(x - 1, y);
9319 else if (MovDir[x][y] == MV_RIGHT)
9321 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9322 TEST_DrawLevelField(x + 1, y);
9324 else if (MovDir[x][y] == MV_UP)
9326 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9327 TEST_DrawLevelField(x, y - 1);
9331 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9332 TEST_DrawLevelField(x, y + 1);
9335 Feld[x][y] = Store[x][y];
9337 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9338 TEST_DrawLevelField(x, y);
9343 static void MauerAbleger(int ax, int ay)
9345 int element = Feld[ax][ay];
9346 int graphic = el2img(element);
9347 boolean oben_frei = FALSE, unten_frei = FALSE;
9348 boolean links_frei = FALSE, rechts_frei = FALSE;
9349 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9350 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9351 boolean new_wall = FALSE;
9353 if (IS_ANIMATED(graphic))
9354 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9356 if (!MovDelay[ax][ay]) // start building new wall
9357 MovDelay[ax][ay] = 6;
9359 if (MovDelay[ax][ay]) // wait some time before building new wall
9362 if (MovDelay[ax][ay])
9366 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9368 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9370 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9372 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9375 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9376 element == EL_EXPANDABLE_WALL_ANY)
9380 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9381 Store[ax][ay-1] = element;
9382 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9383 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9384 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9385 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9390 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9391 Store[ax][ay+1] = element;
9392 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9393 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9394 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9395 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9400 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9401 element == EL_EXPANDABLE_WALL_ANY ||
9402 element == EL_EXPANDABLE_WALL ||
9403 element == EL_BD_EXPANDABLE_WALL)
9407 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9408 Store[ax-1][ay] = element;
9409 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9410 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9411 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9412 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9418 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9419 Store[ax+1][ay] = element;
9420 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9421 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9422 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9423 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9428 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9429 TEST_DrawLevelField(ax, ay);
9431 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9433 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9434 unten_massiv = TRUE;
9435 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9436 links_massiv = TRUE;
9437 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9438 rechts_massiv = TRUE;
9440 if (((oben_massiv && unten_massiv) ||
9441 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9442 element == EL_EXPANDABLE_WALL) &&
9443 ((links_massiv && rechts_massiv) ||
9444 element == EL_EXPANDABLE_WALL_VERTICAL))
9445 Feld[ax][ay] = EL_WALL;
9448 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9451 static void MauerAblegerStahl(int ax, int ay)
9453 int element = Feld[ax][ay];
9454 int graphic = el2img(element);
9455 boolean oben_frei = FALSE, unten_frei = FALSE;
9456 boolean links_frei = FALSE, rechts_frei = FALSE;
9457 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9458 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9459 boolean new_wall = FALSE;
9461 if (IS_ANIMATED(graphic))
9462 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9464 if (!MovDelay[ax][ay]) // start building new wall
9465 MovDelay[ax][ay] = 6;
9467 if (MovDelay[ax][ay]) // wait some time before building new wall
9470 if (MovDelay[ax][ay])
9474 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9476 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9478 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9480 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9483 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9484 element == EL_EXPANDABLE_STEELWALL_ANY)
9488 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9489 Store[ax][ay-1] = element;
9490 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9491 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9492 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9493 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9498 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9499 Store[ax][ay+1] = element;
9500 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9501 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9502 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9503 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9508 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9509 element == EL_EXPANDABLE_STEELWALL_ANY)
9513 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9514 Store[ax-1][ay] = element;
9515 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9516 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9517 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9518 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9524 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9525 Store[ax+1][ay] = element;
9526 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9527 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9528 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9529 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9534 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9536 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9537 unten_massiv = TRUE;
9538 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9539 links_massiv = TRUE;
9540 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9541 rechts_massiv = TRUE;
9543 if (((oben_massiv && unten_massiv) ||
9544 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9545 ((links_massiv && rechts_massiv) ||
9546 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9547 Feld[ax][ay] = EL_STEELWALL;
9550 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9553 static void CheckForDragon(int x, int y)
9556 boolean dragon_found = FALSE;
9557 static int xy[4][2] =
9565 for (i = 0; i < NUM_DIRECTIONS; i++)
9567 for (j = 0; j < 4; j++)
9569 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9571 if (IN_LEV_FIELD(xx, yy) &&
9572 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9574 if (Feld[xx][yy] == EL_DRAGON)
9575 dragon_found = TRUE;
9584 for (i = 0; i < NUM_DIRECTIONS; i++)
9586 for (j = 0; j < 3; j++)
9588 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9590 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9592 Feld[xx][yy] = EL_EMPTY;
9593 TEST_DrawLevelField(xx, yy);
9602 static void InitBuggyBase(int x, int y)
9604 int element = Feld[x][y];
9605 int activating_delay = FRAMES_PER_SECOND / 4;
9608 (element == EL_SP_BUGGY_BASE ?
9609 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9610 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9612 element == EL_SP_BUGGY_BASE_ACTIVE ?
9613 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9616 static void WarnBuggyBase(int x, int y)
9619 static int xy[4][2] =
9627 for (i = 0; i < NUM_DIRECTIONS; i++)
9629 int xx = x + xy[i][0];
9630 int yy = y + xy[i][1];
9632 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9634 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9641 static void InitTrap(int x, int y)
9643 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9646 static void ActivateTrap(int x, int y)
9648 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9651 static void ChangeActiveTrap(int x, int y)
9653 int graphic = IMG_TRAP_ACTIVE;
9655 // if new animation frame was drawn, correct crumbled sand border
9656 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9657 TEST_DrawLevelFieldCrumbled(x, y);
9660 static int getSpecialActionElement(int element, int number, int base_element)
9662 return (element != EL_EMPTY ? element :
9663 number != -1 ? base_element + number - 1 :
9667 static int getModifiedActionNumber(int value_old, int operator, int operand,
9668 int value_min, int value_max)
9670 int value_new = (operator == CA_MODE_SET ? operand :
9671 operator == CA_MODE_ADD ? value_old + operand :
9672 operator == CA_MODE_SUBTRACT ? value_old - operand :
9673 operator == CA_MODE_MULTIPLY ? value_old * operand :
9674 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9675 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9678 return (value_new < value_min ? value_min :
9679 value_new > value_max ? value_max :
9683 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9685 struct ElementInfo *ei = &element_info[element];
9686 struct ElementChangeInfo *change = &ei->change_page[page];
9687 int target_element = change->target_element;
9688 int action_type = change->action_type;
9689 int action_mode = change->action_mode;
9690 int action_arg = change->action_arg;
9691 int action_element = change->action_element;
9694 if (!change->has_action)
9697 // ---------- determine action paramater values -----------------------------
9699 int level_time_value =
9700 (level.time > 0 ? TimeLeft :
9703 int action_arg_element_raw =
9704 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9705 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9706 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9707 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9708 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9709 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9710 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9712 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9714 int action_arg_direction =
9715 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9716 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9717 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9718 change->actual_trigger_side :
9719 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9720 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9723 int action_arg_number_min =
9724 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9727 int action_arg_number_max =
9728 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9729 action_type == CA_SET_LEVEL_GEMS ? 999 :
9730 action_type == CA_SET_LEVEL_TIME ? 9999 :
9731 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9732 action_type == CA_SET_CE_VALUE ? 9999 :
9733 action_type == CA_SET_CE_SCORE ? 9999 :
9736 int action_arg_number_reset =
9737 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9738 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9739 action_type == CA_SET_LEVEL_TIME ? level.time :
9740 action_type == CA_SET_LEVEL_SCORE ? 0 :
9741 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9742 action_type == CA_SET_CE_SCORE ? 0 :
9745 int action_arg_number =
9746 (action_arg <= CA_ARG_MAX ? action_arg :
9747 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9748 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9749 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9750 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9751 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9752 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9753 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9754 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9755 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9756 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9757 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9758 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9759 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9760 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9761 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9762 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9763 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9764 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9765 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9766 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9767 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9770 int action_arg_number_old =
9771 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9772 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9773 action_type == CA_SET_LEVEL_SCORE ? game.score :
9774 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9775 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9778 int action_arg_number_new =
9779 getModifiedActionNumber(action_arg_number_old,
9780 action_mode, action_arg_number,
9781 action_arg_number_min, action_arg_number_max);
9783 int trigger_player_bits =
9784 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9785 change->actual_trigger_player_bits : change->trigger_player);
9787 int action_arg_player_bits =
9788 (action_arg >= CA_ARG_PLAYER_1 &&
9789 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9790 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9791 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9794 // ---------- execute action -----------------------------------------------
9796 switch (action_type)
9803 // ---------- level actions ----------------------------------------------
9805 case CA_RESTART_LEVEL:
9807 game.restart_level = TRUE;
9812 case CA_SHOW_ENVELOPE:
9814 int element = getSpecialActionElement(action_arg_element,
9815 action_arg_number, EL_ENVELOPE_1);
9817 if (IS_ENVELOPE(element))
9818 local_player->show_envelope = element;
9823 case CA_SET_LEVEL_TIME:
9825 if (level.time > 0) // only modify limited time value
9827 TimeLeft = action_arg_number_new;
9829 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9831 DisplayGameControlValues();
9833 if (!TimeLeft && setup.time_limit)
9834 for (i = 0; i < MAX_PLAYERS; i++)
9835 KillPlayer(&stored_player[i]);
9841 case CA_SET_LEVEL_SCORE:
9843 game.score = action_arg_number_new;
9845 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9847 DisplayGameControlValues();
9852 case CA_SET_LEVEL_GEMS:
9854 game.gems_still_needed = action_arg_number_new;
9856 game.snapshot.collected_item = TRUE;
9858 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9860 DisplayGameControlValues();
9865 case CA_SET_LEVEL_WIND:
9867 game.wind_direction = action_arg_direction;
9872 case CA_SET_LEVEL_RANDOM_SEED:
9874 // ensure that setting a new random seed while playing is predictable
9875 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9880 // ---------- player actions ---------------------------------------------
9882 case CA_MOVE_PLAYER:
9883 case CA_MOVE_PLAYER_NEW:
9885 // automatically move to the next field in specified direction
9886 for (i = 0; i < MAX_PLAYERS; i++)
9887 if (trigger_player_bits & (1 << i))
9888 if (action_type == CA_MOVE_PLAYER ||
9889 stored_player[i].MovPos == 0)
9890 stored_player[i].programmed_action = action_arg_direction;
9895 case CA_EXIT_PLAYER:
9897 for (i = 0; i < MAX_PLAYERS; i++)
9898 if (action_arg_player_bits & (1 << i))
9899 ExitPlayer(&stored_player[i]);
9901 if (game.players_still_needed == 0)
9907 case CA_KILL_PLAYER:
9909 for (i = 0; i < MAX_PLAYERS; i++)
9910 if (action_arg_player_bits & (1 << i))
9911 KillPlayer(&stored_player[i]);
9916 case CA_SET_PLAYER_KEYS:
9918 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9919 int element = getSpecialActionElement(action_arg_element,
9920 action_arg_number, EL_KEY_1);
9922 if (IS_KEY(element))
9924 for (i = 0; i < MAX_PLAYERS; i++)
9926 if (trigger_player_bits & (1 << i))
9928 stored_player[i].key[KEY_NR(element)] = key_state;
9930 DrawGameDoorValues();
9938 case CA_SET_PLAYER_SPEED:
9940 for (i = 0; i < MAX_PLAYERS; i++)
9942 if (trigger_player_bits & (1 << i))
9944 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9946 if (action_arg == CA_ARG_SPEED_FASTER &&
9947 stored_player[i].cannot_move)
9949 action_arg_number = STEPSIZE_VERY_SLOW;
9951 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9952 action_arg == CA_ARG_SPEED_FASTER)
9954 action_arg_number = 2;
9955 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9958 else if (action_arg == CA_ARG_NUMBER_RESET)
9960 action_arg_number = level.initial_player_stepsize[i];
9964 getModifiedActionNumber(move_stepsize,
9967 action_arg_number_min,
9968 action_arg_number_max);
9970 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9977 case CA_SET_PLAYER_SHIELD:
9979 for (i = 0; i < MAX_PLAYERS; i++)
9981 if (trigger_player_bits & (1 << i))
9983 if (action_arg == CA_ARG_SHIELD_OFF)
9985 stored_player[i].shield_normal_time_left = 0;
9986 stored_player[i].shield_deadly_time_left = 0;
9988 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9990 stored_player[i].shield_normal_time_left = 999999;
9992 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9994 stored_player[i].shield_normal_time_left = 999999;
9995 stored_player[i].shield_deadly_time_left = 999999;
10003 case CA_SET_PLAYER_GRAVITY:
10005 for (i = 0; i < MAX_PLAYERS; i++)
10007 if (trigger_player_bits & (1 << i))
10009 stored_player[i].gravity =
10010 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10011 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10012 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10013 stored_player[i].gravity);
10020 case CA_SET_PLAYER_ARTWORK:
10022 for (i = 0; i < MAX_PLAYERS; i++)
10024 if (trigger_player_bits & (1 << i))
10026 int artwork_element = action_arg_element;
10028 if (action_arg == CA_ARG_ELEMENT_RESET)
10030 (level.use_artwork_element[i] ? level.artwork_element[i] :
10031 stored_player[i].element_nr);
10033 if (stored_player[i].artwork_element != artwork_element)
10034 stored_player[i].Frame = 0;
10036 stored_player[i].artwork_element = artwork_element;
10038 SetPlayerWaiting(&stored_player[i], FALSE);
10040 // set number of special actions for bored and sleeping animation
10041 stored_player[i].num_special_action_bored =
10042 get_num_special_action(artwork_element,
10043 ACTION_BORING_1, ACTION_BORING_LAST);
10044 stored_player[i].num_special_action_sleeping =
10045 get_num_special_action(artwork_element,
10046 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10053 case CA_SET_PLAYER_INVENTORY:
10055 for (i = 0; i < MAX_PLAYERS; i++)
10057 struct PlayerInfo *player = &stored_player[i];
10060 if (trigger_player_bits & (1 << i))
10062 int inventory_element = action_arg_element;
10064 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10065 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10066 action_arg == CA_ARG_ELEMENT_ACTION)
10068 int element = inventory_element;
10069 int collect_count = element_info[element].collect_count_initial;
10071 if (!IS_CUSTOM_ELEMENT(element))
10074 if (collect_count == 0)
10075 player->inventory_infinite_element = element;
10077 for (k = 0; k < collect_count; k++)
10078 if (player->inventory_size < MAX_INVENTORY_SIZE)
10079 player->inventory_element[player->inventory_size++] =
10082 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10083 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10084 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10086 if (player->inventory_infinite_element != EL_UNDEFINED &&
10087 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10088 action_arg_element_raw))
10089 player->inventory_infinite_element = EL_UNDEFINED;
10091 for (k = 0, j = 0; j < player->inventory_size; j++)
10093 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10094 action_arg_element_raw))
10095 player->inventory_element[k++] = player->inventory_element[j];
10098 player->inventory_size = k;
10100 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10102 if (player->inventory_size > 0)
10104 for (j = 0; j < player->inventory_size - 1; j++)
10105 player->inventory_element[j] = player->inventory_element[j + 1];
10107 player->inventory_size--;
10110 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10112 if (player->inventory_size > 0)
10113 player->inventory_size--;
10115 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10117 player->inventory_infinite_element = EL_UNDEFINED;
10118 player->inventory_size = 0;
10120 else if (action_arg == CA_ARG_INVENTORY_RESET)
10122 player->inventory_infinite_element = EL_UNDEFINED;
10123 player->inventory_size = 0;
10125 if (level.use_initial_inventory[i])
10127 for (j = 0; j < level.initial_inventory_size[i]; j++)
10129 int element = level.initial_inventory_content[i][j];
10130 int collect_count = element_info[element].collect_count_initial;
10132 if (!IS_CUSTOM_ELEMENT(element))
10135 if (collect_count == 0)
10136 player->inventory_infinite_element = element;
10138 for (k = 0; k < collect_count; k++)
10139 if (player->inventory_size < MAX_INVENTORY_SIZE)
10140 player->inventory_element[player->inventory_size++] =
10151 // ---------- CE actions -------------------------------------------------
10153 case CA_SET_CE_VALUE:
10155 int last_ce_value = CustomValue[x][y];
10157 CustomValue[x][y] = action_arg_number_new;
10159 if (CustomValue[x][y] != last_ce_value)
10161 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10162 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10164 if (CustomValue[x][y] == 0)
10166 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10167 ChangeCount[x][y] = 0; // allow at least one more change
10169 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10170 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10177 case CA_SET_CE_SCORE:
10179 int last_ce_score = ei->collect_score;
10181 ei->collect_score = action_arg_number_new;
10183 if (ei->collect_score != last_ce_score)
10185 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10186 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10188 if (ei->collect_score == 0)
10192 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10193 ChangeCount[x][y] = 0; // allow at least one more change
10195 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10196 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10199 This is a very special case that seems to be a mixture between
10200 CheckElementChange() and CheckTriggeredElementChange(): while
10201 the first one only affects single elements that are triggered
10202 directly, the second one affects multiple elements in the playfield
10203 that are triggered indirectly by another element. This is a third
10204 case: Changing the CE score always affects multiple identical CEs,
10205 so every affected CE must be checked, not only the single CE for
10206 which the CE score was changed in the first place (as every instance
10207 of that CE shares the same CE score, and therefore also can change)!
10209 SCAN_PLAYFIELD(xx, yy)
10211 if (Feld[xx][yy] == element)
10212 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10213 CE_SCORE_GETS_ZERO);
10221 case CA_SET_CE_ARTWORK:
10223 int artwork_element = action_arg_element;
10224 boolean reset_frame = FALSE;
10227 if (action_arg == CA_ARG_ELEMENT_RESET)
10228 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10231 if (ei->gfx_element != artwork_element)
10232 reset_frame = TRUE;
10234 ei->gfx_element = artwork_element;
10236 SCAN_PLAYFIELD(xx, yy)
10238 if (Feld[xx][yy] == element)
10242 ResetGfxAnimation(xx, yy);
10243 ResetRandomAnimationValue(xx, yy);
10246 TEST_DrawLevelField(xx, yy);
10253 // ---------- engine actions ---------------------------------------------
10255 case CA_SET_ENGINE_SCAN_MODE:
10257 InitPlayfieldScanMode(action_arg);
10267 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10269 int old_element = Feld[x][y];
10270 int new_element = GetElementFromGroupElement(element);
10271 int previous_move_direction = MovDir[x][y];
10272 int last_ce_value = CustomValue[x][y];
10273 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10274 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10275 boolean add_player_onto_element = (new_element_is_player &&
10276 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10277 IS_WALKABLE(old_element));
10279 if (!add_player_onto_element)
10281 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10282 RemoveMovingField(x, y);
10286 Feld[x][y] = new_element;
10288 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10289 MovDir[x][y] = previous_move_direction;
10291 if (element_info[new_element].use_last_ce_value)
10292 CustomValue[x][y] = last_ce_value;
10294 InitField_WithBug1(x, y, FALSE);
10296 new_element = Feld[x][y]; // element may have changed
10298 ResetGfxAnimation(x, y);
10299 ResetRandomAnimationValue(x, y);
10301 TEST_DrawLevelField(x, y);
10303 if (GFX_CRUMBLED(new_element))
10304 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10307 // check if element under the player changes from accessible to unaccessible
10308 // (needed for special case of dropping element which then changes)
10309 // (must be checked after creating new element for walkable group elements)
10310 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10311 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10318 // "ChangeCount" not set yet to allow "entered by player" change one time
10319 if (new_element_is_player)
10320 RelocatePlayer(x, y, new_element);
10323 ChangeCount[x][y]++; // count number of changes in the same frame
10325 TestIfBadThingTouchesPlayer(x, y);
10326 TestIfPlayerTouchesCustomElement(x, y);
10327 TestIfElementTouchesCustomElement(x, y);
10330 static void CreateField(int x, int y, int element)
10332 CreateFieldExt(x, y, element, FALSE);
10335 static void CreateElementFromChange(int x, int y, int element)
10337 element = GET_VALID_RUNTIME_ELEMENT(element);
10339 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10341 int old_element = Feld[x][y];
10343 // prevent changed element from moving in same engine frame
10344 // unless both old and new element can either fall or move
10345 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10346 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10350 CreateFieldExt(x, y, element, TRUE);
10353 static boolean ChangeElement(int x, int y, int element, int page)
10355 struct ElementInfo *ei = &element_info[element];
10356 struct ElementChangeInfo *change = &ei->change_page[page];
10357 int ce_value = CustomValue[x][y];
10358 int ce_score = ei->collect_score;
10359 int target_element;
10360 int old_element = Feld[x][y];
10362 // always use default change event to prevent running into a loop
10363 if (ChangeEvent[x][y] == -1)
10364 ChangeEvent[x][y] = CE_DELAY;
10366 if (ChangeEvent[x][y] == CE_DELAY)
10368 // reset actual trigger element, trigger player and action element
10369 change->actual_trigger_element = EL_EMPTY;
10370 change->actual_trigger_player = EL_EMPTY;
10371 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10372 change->actual_trigger_side = CH_SIDE_NONE;
10373 change->actual_trigger_ce_value = 0;
10374 change->actual_trigger_ce_score = 0;
10377 // do not change elements more than a specified maximum number of changes
10378 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10381 ChangeCount[x][y]++; // count number of changes in the same frame
10383 if (change->explode)
10390 if (change->use_target_content)
10392 boolean complete_replace = TRUE;
10393 boolean can_replace[3][3];
10396 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10399 boolean is_walkable;
10400 boolean is_diggable;
10401 boolean is_collectible;
10402 boolean is_removable;
10403 boolean is_destructible;
10404 int ex = x + xx - 1;
10405 int ey = y + yy - 1;
10406 int content_element = change->target_content.e[xx][yy];
10409 can_replace[xx][yy] = TRUE;
10411 if (ex == x && ey == y) // do not check changing element itself
10414 if (content_element == EL_EMPTY_SPACE)
10416 can_replace[xx][yy] = FALSE; // do not replace border with space
10421 if (!IN_LEV_FIELD(ex, ey))
10423 can_replace[xx][yy] = FALSE;
10424 complete_replace = FALSE;
10431 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10432 e = MovingOrBlocked2Element(ex, ey);
10434 is_empty = (IS_FREE(ex, ey) ||
10435 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10437 is_walkable = (is_empty || IS_WALKABLE(e));
10438 is_diggable = (is_empty || IS_DIGGABLE(e));
10439 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10440 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10441 is_removable = (is_diggable || is_collectible);
10443 can_replace[xx][yy] =
10444 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10445 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10446 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10447 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10448 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10449 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10450 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10452 if (!can_replace[xx][yy])
10453 complete_replace = FALSE;
10456 if (!change->only_if_complete || complete_replace)
10458 boolean something_has_changed = FALSE;
10460 if (change->only_if_complete && change->use_random_replace &&
10461 RND(100) < change->random_percentage)
10464 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10466 int ex = x + xx - 1;
10467 int ey = y + yy - 1;
10468 int content_element;
10470 if (can_replace[xx][yy] && (!change->use_random_replace ||
10471 RND(100) < change->random_percentage))
10473 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10474 RemoveMovingField(ex, ey);
10476 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10478 content_element = change->target_content.e[xx][yy];
10479 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10480 ce_value, ce_score);
10482 CreateElementFromChange(ex, ey, target_element);
10484 something_has_changed = TRUE;
10486 // for symmetry reasons, freeze newly created border elements
10487 if (ex != x || ey != y)
10488 Stop[ex][ey] = TRUE; // no more moving in this frame
10492 if (something_has_changed)
10494 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10495 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10501 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10502 ce_value, ce_score);
10504 if (element == EL_DIAGONAL_GROWING ||
10505 element == EL_DIAGONAL_SHRINKING)
10507 target_element = Store[x][y];
10509 Store[x][y] = EL_EMPTY;
10512 CreateElementFromChange(x, y, target_element);
10514 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10515 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10518 // this uses direct change before indirect change
10519 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10524 static void HandleElementChange(int x, int y, int page)
10526 int element = MovingOrBlocked2Element(x, y);
10527 struct ElementInfo *ei = &element_info[element];
10528 struct ElementChangeInfo *change = &ei->change_page[page];
10529 boolean handle_action_before_change = FALSE;
10532 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10533 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10536 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10537 x, y, element, element_info[element].token_name);
10538 printf("HandleElementChange(): This should never happen!\n");
10543 // this can happen with classic bombs on walkable, changing elements
10544 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10549 if (ChangeDelay[x][y] == 0) // initialize element change
10551 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10553 if (change->can_change)
10555 // !!! not clear why graphic animation should be reset at all here !!!
10556 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10557 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10560 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10562 When using an animation frame delay of 1 (this only happens with
10563 "sp_zonk.moving.left/right" in the classic graphics), the default
10564 (non-moving) animation shows wrong animation frames (while the
10565 moving animation, like "sp_zonk.moving.left/right", is correct,
10566 so this graphical bug never shows up with the classic graphics).
10567 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10568 be drawn instead of the correct frames 0,1,2,3. This is caused by
10569 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10570 an element change: First when the change delay ("ChangeDelay[][]")
10571 counter has reached zero after decrementing, then a second time in
10572 the next frame (after "GfxFrame[][]" was already incremented) when
10573 "ChangeDelay[][]" is reset to the initial delay value again.
10575 This causes frame 0 to be drawn twice, while the last frame won't
10576 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10578 As some animations may already be cleverly designed around this bug
10579 (at least the "Snake Bite" snake tail animation does this), it cannot
10580 simply be fixed here without breaking such existing animations.
10581 Unfortunately, it cannot easily be detected if a graphics set was
10582 designed "before" or "after" the bug was fixed. As a workaround,
10583 a new graphics set option "game.graphics_engine_version" was added
10584 to be able to specify the game's major release version for which the
10585 graphics set was designed, which can then be used to decide if the
10586 bugfix should be used (version 4 and above) or not (version 3 or
10587 below, or if no version was specified at all, as with old sets).
10589 (The wrong/fixed animation frames can be tested with the test level set
10590 "test_gfxframe" and level "000", which contains a specially prepared
10591 custom element at level position (x/y) == (11/9) which uses the zonk
10592 animation mentioned above. Using "game.graphics_engine_version: 4"
10593 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10594 This can also be seen from the debug output for this test element.)
10597 // when a custom element is about to change (for example by change delay),
10598 // do not reset graphic animation when the custom element is moving
10599 if (game.graphics_engine_version < 4 &&
10602 ResetGfxAnimation(x, y);
10603 ResetRandomAnimationValue(x, y);
10606 if (change->pre_change_function)
10607 change->pre_change_function(x, y);
10611 ChangeDelay[x][y]--;
10613 if (ChangeDelay[x][y] != 0) // continue element change
10615 if (change->can_change)
10617 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10619 if (IS_ANIMATED(graphic))
10620 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10622 if (change->change_function)
10623 change->change_function(x, y);
10626 else // finish element change
10628 if (ChangePage[x][y] != -1) // remember page from delayed change
10630 page = ChangePage[x][y];
10631 ChangePage[x][y] = -1;
10633 change = &ei->change_page[page];
10636 if (IS_MOVING(x, y)) // never change a running system ;-)
10638 ChangeDelay[x][y] = 1; // try change after next move step
10639 ChangePage[x][y] = page; // remember page to use for change
10644 // special case: set new level random seed before changing element
10645 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10646 handle_action_before_change = TRUE;
10648 if (change->has_action && handle_action_before_change)
10649 ExecuteCustomElementAction(x, y, element, page);
10651 if (change->can_change)
10653 if (ChangeElement(x, y, element, page))
10655 if (change->post_change_function)
10656 change->post_change_function(x, y);
10660 if (change->has_action && !handle_action_before_change)
10661 ExecuteCustomElementAction(x, y, element, page);
10665 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10666 int trigger_element,
10668 int trigger_player,
10672 boolean change_done_any = FALSE;
10673 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10676 if (!(trigger_events[trigger_element][trigger_event]))
10679 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10681 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10683 int element = EL_CUSTOM_START + i;
10684 boolean change_done = FALSE;
10687 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10688 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10691 for (p = 0; p < element_info[element].num_change_pages; p++)
10693 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10695 if (change->can_change_or_has_action &&
10696 change->has_event[trigger_event] &&
10697 change->trigger_side & trigger_side &&
10698 change->trigger_player & trigger_player &&
10699 change->trigger_page & trigger_page_bits &&
10700 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10702 change->actual_trigger_element = trigger_element;
10703 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10704 change->actual_trigger_player_bits = trigger_player;
10705 change->actual_trigger_side = trigger_side;
10706 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10707 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10709 if ((change->can_change && !change_done) || change->has_action)
10713 SCAN_PLAYFIELD(x, y)
10715 if (Feld[x][y] == element)
10717 if (change->can_change && !change_done)
10719 // if element already changed in this frame, not only prevent
10720 // another element change (checked in ChangeElement()), but
10721 // also prevent additional element actions for this element
10723 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10724 !level.use_action_after_change_bug)
10727 ChangeDelay[x][y] = 1;
10728 ChangeEvent[x][y] = trigger_event;
10730 HandleElementChange(x, y, p);
10732 else if (change->has_action)
10734 // if element already changed in this frame, not only prevent
10735 // another element change (checked in ChangeElement()), but
10736 // also prevent additional element actions for this element
10738 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10739 !level.use_action_after_change_bug)
10742 ExecuteCustomElementAction(x, y, element, p);
10743 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10748 if (change->can_change)
10750 change_done = TRUE;
10751 change_done_any = TRUE;
10758 RECURSION_LOOP_DETECTION_END();
10760 return change_done_any;
10763 static boolean CheckElementChangeExt(int x, int y,
10765 int trigger_element,
10767 int trigger_player,
10770 boolean change_done = FALSE;
10773 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10774 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10777 if (Feld[x][y] == EL_BLOCKED)
10779 Blocked2Moving(x, y, &x, &y);
10780 element = Feld[x][y];
10783 // check if element has already changed or is about to change after moving
10784 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10785 Feld[x][y] != element) ||
10787 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10788 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10789 ChangePage[x][y] != -1)))
10792 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10794 for (p = 0; p < element_info[element].num_change_pages; p++)
10796 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10798 /* check trigger element for all events where the element that is checked
10799 for changing interacts with a directly adjacent element -- this is
10800 different to element changes that affect other elements to change on the
10801 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10802 boolean check_trigger_element =
10803 (trigger_event == CE_TOUCHING_X ||
10804 trigger_event == CE_HITTING_X ||
10805 trigger_event == CE_HIT_BY_X ||
10806 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10808 if (change->can_change_or_has_action &&
10809 change->has_event[trigger_event] &&
10810 change->trigger_side & trigger_side &&
10811 change->trigger_player & trigger_player &&
10812 (!check_trigger_element ||
10813 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10815 change->actual_trigger_element = trigger_element;
10816 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10817 change->actual_trigger_player_bits = trigger_player;
10818 change->actual_trigger_side = trigger_side;
10819 change->actual_trigger_ce_value = CustomValue[x][y];
10820 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10822 // special case: trigger element not at (x,y) position for some events
10823 if (check_trigger_element)
10835 { 0, 0 }, { 0, 0 }, { 0, 0 },
10839 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10840 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10842 change->actual_trigger_ce_value = CustomValue[xx][yy];
10843 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10846 if (change->can_change && !change_done)
10848 ChangeDelay[x][y] = 1;
10849 ChangeEvent[x][y] = trigger_event;
10851 HandleElementChange(x, y, p);
10853 change_done = TRUE;
10855 else if (change->has_action)
10857 ExecuteCustomElementAction(x, y, element, p);
10858 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10863 RECURSION_LOOP_DETECTION_END();
10865 return change_done;
10868 static void PlayPlayerSound(struct PlayerInfo *player)
10870 int jx = player->jx, jy = player->jy;
10871 int sound_element = player->artwork_element;
10872 int last_action = player->last_action_waiting;
10873 int action = player->action_waiting;
10875 if (player->is_waiting)
10877 if (action != last_action)
10878 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10880 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10884 if (action != last_action)
10885 StopSound(element_info[sound_element].sound[last_action]);
10887 if (last_action == ACTION_SLEEPING)
10888 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10892 static void PlayAllPlayersSound(void)
10896 for (i = 0; i < MAX_PLAYERS; i++)
10897 if (stored_player[i].active)
10898 PlayPlayerSound(&stored_player[i]);
10901 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10903 boolean last_waiting = player->is_waiting;
10904 int move_dir = player->MovDir;
10906 player->dir_waiting = move_dir;
10907 player->last_action_waiting = player->action_waiting;
10911 if (!last_waiting) // not waiting -> waiting
10913 player->is_waiting = TRUE;
10915 player->frame_counter_bored =
10917 game.player_boring_delay_fixed +
10918 GetSimpleRandom(game.player_boring_delay_random);
10919 player->frame_counter_sleeping =
10921 game.player_sleeping_delay_fixed +
10922 GetSimpleRandom(game.player_sleeping_delay_random);
10924 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10927 if (game.player_sleeping_delay_fixed +
10928 game.player_sleeping_delay_random > 0 &&
10929 player->anim_delay_counter == 0 &&
10930 player->post_delay_counter == 0 &&
10931 FrameCounter >= player->frame_counter_sleeping)
10932 player->is_sleeping = TRUE;
10933 else if (game.player_boring_delay_fixed +
10934 game.player_boring_delay_random > 0 &&
10935 FrameCounter >= player->frame_counter_bored)
10936 player->is_bored = TRUE;
10938 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10939 player->is_bored ? ACTION_BORING :
10942 if (player->is_sleeping && player->use_murphy)
10944 // special case for sleeping Murphy when leaning against non-free tile
10946 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10947 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10948 !IS_MOVING(player->jx - 1, player->jy)))
10949 move_dir = MV_LEFT;
10950 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10951 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10952 !IS_MOVING(player->jx + 1, player->jy)))
10953 move_dir = MV_RIGHT;
10955 player->is_sleeping = FALSE;
10957 player->dir_waiting = move_dir;
10960 if (player->is_sleeping)
10962 if (player->num_special_action_sleeping > 0)
10964 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10966 int last_special_action = player->special_action_sleeping;
10967 int num_special_action = player->num_special_action_sleeping;
10968 int special_action =
10969 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10970 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10971 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10972 last_special_action + 1 : ACTION_SLEEPING);
10973 int special_graphic =
10974 el_act_dir2img(player->artwork_element, special_action, move_dir);
10976 player->anim_delay_counter =
10977 graphic_info[special_graphic].anim_delay_fixed +
10978 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10979 player->post_delay_counter =
10980 graphic_info[special_graphic].post_delay_fixed +
10981 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10983 player->special_action_sleeping = special_action;
10986 if (player->anim_delay_counter > 0)
10988 player->action_waiting = player->special_action_sleeping;
10989 player->anim_delay_counter--;
10991 else if (player->post_delay_counter > 0)
10993 player->post_delay_counter--;
10997 else if (player->is_bored)
10999 if (player->num_special_action_bored > 0)
11001 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11003 int special_action =
11004 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11005 int special_graphic =
11006 el_act_dir2img(player->artwork_element, special_action, move_dir);
11008 player->anim_delay_counter =
11009 graphic_info[special_graphic].anim_delay_fixed +
11010 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11011 player->post_delay_counter =
11012 graphic_info[special_graphic].post_delay_fixed +
11013 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11015 player->special_action_bored = special_action;
11018 if (player->anim_delay_counter > 0)
11020 player->action_waiting = player->special_action_bored;
11021 player->anim_delay_counter--;
11023 else if (player->post_delay_counter > 0)
11025 player->post_delay_counter--;
11030 else if (last_waiting) // waiting -> not waiting
11032 player->is_waiting = FALSE;
11033 player->is_bored = FALSE;
11034 player->is_sleeping = FALSE;
11036 player->frame_counter_bored = -1;
11037 player->frame_counter_sleeping = -1;
11039 player->anim_delay_counter = 0;
11040 player->post_delay_counter = 0;
11042 player->dir_waiting = player->MovDir;
11043 player->action_waiting = ACTION_DEFAULT;
11045 player->special_action_bored = ACTION_DEFAULT;
11046 player->special_action_sleeping = ACTION_DEFAULT;
11050 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11052 if ((!player->is_moving && player->was_moving) ||
11053 (player->MovPos == 0 && player->was_moving) ||
11054 (player->is_snapping && !player->was_snapping) ||
11055 (player->is_dropping && !player->was_dropping))
11057 if (!CheckSaveEngineSnapshotToList())
11060 player->was_moving = FALSE;
11061 player->was_snapping = TRUE;
11062 player->was_dropping = TRUE;
11066 if (player->is_moving)
11067 player->was_moving = TRUE;
11069 if (!player->is_snapping)
11070 player->was_snapping = FALSE;
11072 if (!player->is_dropping)
11073 player->was_dropping = FALSE;
11077 static void CheckSingleStepMode(struct PlayerInfo *player)
11079 if (tape.single_step && tape.recording && !tape.pausing)
11081 /* as it is called "single step mode", just return to pause mode when the
11082 player stopped moving after one tile (or never starts moving at all) */
11083 if (!player->is_moving &&
11084 !player->is_pushing &&
11085 !player->is_dropping_pressed)
11086 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11089 CheckSaveEngineSnapshot(player);
11092 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11094 int left = player_action & JOY_LEFT;
11095 int right = player_action & JOY_RIGHT;
11096 int up = player_action & JOY_UP;
11097 int down = player_action & JOY_DOWN;
11098 int button1 = player_action & JOY_BUTTON_1;
11099 int button2 = player_action & JOY_BUTTON_2;
11100 int dx = (left ? -1 : right ? 1 : 0);
11101 int dy = (up ? -1 : down ? 1 : 0);
11103 if (!player->active || tape.pausing)
11109 SnapField(player, dx, dy);
11113 DropElement(player);
11115 MovePlayer(player, dx, dy);
11118 CheckSingleStepMode(player);
11120 SetPlayerWaiting(player, FALSE);
11122 return player_action;
11126 // no actions for this player (no input at player's configured device)
11128 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11129 SnapField(player, 0, 0);
11130 CheckGravityMovementWhenNotMoving(player);
11132 if (player->MovPos == 0)
11133 SetPlayerWaiting(player, TRUE);
11135 if (player->MovPos == 0) // needed for tape.playing
11136 player->is_moving = FALSE;
11138 player->is_dropping = FALSE;
11139 player->is_dropping_pressed = FALSE;
11140 player->drop_pressed_delay = 0;
11142 CheckSingleStepMode(player);
11148 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11151 if (!tape.use_mouse)
11154 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11155 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11156 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11159 static void SetTapeActionFromMouseAction(byte *tape_action,
11160 struct MouseActionInfo *mouse_action)
11162 if (!tape.use_mouse)
11165 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11166 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11167 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11170 static void CheckLevelSolved(void)
11172 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11174 if (game_em.level_solved &&
11175 !game_em.game_over) // game won
11179 game_em.game_over = TRUE;
11181 game.all_players_gone = TRUE;
11184 if (game_em.game_over) // game lost
11185 game.all_players_gone = TRUE;
11187 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11189 if (game_sp.level_solved &&
11190 !game_sp.game_over) // game won
11194 game_sp.game_over = TRUE;
11196 game.all_players_gone = TRUE;
11199 if (game_sp.game_over) // game lost
11200 game.all_players_gone = TRUE;
11202 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11204 if (game_mm.level_solved &&
11205 !game_mm.game_over) // game won
11209 game_mm.game_over = TRUE;
11211 game.all_players_gone = TRUE;
11214 if (game_mm.game_over) // game lost
11215 game.all_players_gone = TRUE;
11219 static void CheckLevelTime(void)
11223 if (TimeFrames >= FRAMES_PER_SECOND)
11228 for (i = 0; i < MAX_PLAYERS; i++)
11230 struct PlayerInfo *player = &stored_player[i];
11232 if (SHIELD_ON(player))
11234 player->shield_normal_time_left--;
11236 if (player->shield_deadly_time_left > 0)
11237 player->shield_deadly_time_left--;
11241 if (!game.LevelSolved && !level.use_step_counter)
11249 if (TimeLeft <= 10 && setup.time_limit)
11250 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11252 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11253 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11255 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11257 if (!TimeLeft && setup.time_limit)
11259 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11260 game_em.lev->killed_out_of_time = TRUE;
11262 for (i = 0; i < MAX_PLAYERS; i++)
11263 KillPlayer(&stored_player[i]);
11266 else if (game.no_time_limit && !game.all_players_gone)
11268 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11271 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11274 if (tape.recording || tape.playing)
11275 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11278 if (tape.recording || tape.playing)
11279 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11281 UpdateAndDisplayGameControlValues();
11284 void AdvanceFrameAndPlayerCounters(int player_nr)
11288 // advance frame counters (global frame counter and time frame counter)
11292 // advance player counters (counters for move delay, move animation etc.)
11293 for (i = 0; i < MAX_PLAYERS; i++)
11295 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11296 int move_delay_value = stored_player[i].move_delay_value;
11297 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11299 if (!advance_player_counters) // not all players may be affected
11302 if (move_frames == 0) // less than one move per game frame
11304 int stepsize = TILEX / move_delay_value;
11305 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11306 int count = (stored_player[i].is_moving ?
11307 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11309 if (count % delay == 0)
11313 stored_player[i].Frame += move_frames;
11315 if (stored_player[i].MovPos != 0)
11316 stored_player[i].StepFrame += move_frames;
11318 if (stored_player[i].move_delay > 0)
11319 stored_player[i].move_delay--;
11321 // due to bugs in previous versions, counter must count up, not down
11322 if (stored_player[i].push_delay != -1)
11323 stored_player[i].push_delay++;
11325 if (stored_player[i].drop_delay > 0)
11326 stored_player[i].drop_delay--;
11328 if (stored_player[i].is_dropping_pressed)
11329 stored_player[i].drop_pressed_delay++;
11333 void StartGameActions(boolean init_network_game, boolean record_tape,
11336 unsigned int new_random_seed = InitRND(random_seed);
11339 TapeStartRecording(new_random_seed);
11341 if (init_network_game)
11343 SendToServer_LevelFile();
11344 SendToServer_StartPlaying();
11352 static void GameActionsExt(void)
11355 static unsigned int game_frame_delay = 0;
11357 unsigned int game_frame_delay_value;
11358 byte *recorded_player_action;
11359 byte summarized_player_action = 0;
11360 byte tape_action[MAX_PLAYERS];
11363 // detect endless loops, caused by custom element programming
11364 if (recursion_loop_detected && recursion_loop_depth == 0)
11366 char *message = getStringCat3("Internal Error! Element ",
11367 EL_NAME(recursion_loop_element),
11368 " caused endless loop! Quit the game?");
11370 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11371 EL_NAME(recursion_loop_element));
11373 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11375 recursion_loop_detected = FALSE; // if game should be continued
11382 if (game.restart_level)
11383 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11385 CheckLevelSolved();
11387 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11390 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11393 if (game_status != GAME_MODE_PLAYING) // status might have changed
11396 game_frame_delay_value =
11397 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11399 if (tape.playing && tape.warp_forward && !tape.pausing)
11400 game_frame_delay_value = 0;
11402 SetVideoFrameDelay(game_frame_delay_value);
11404 // (de)activate virtual buttons depending on current game status
11405 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11407 if (game.all_players_gone) // if no players there to be controlled anymore
11408 SetOverlayActive(FALSE);
11409 else if (!tape.playing) // if game continues after tape stopped playing
11410 SetOverlayActive(TRUE);
11415 // ---------- main game synchronization point ----------
11417 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11419 printf("::: skip == %d\n", skip);
11422 // ---------- main game synchronization point ----------
11424 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11428 if (network_playing && !network_player_action_received)
11430 // try to get network player actions in time
11432 // last chance to get network player actions without main loop delay
11433 HandleNetworking();
11435 // game was quit by network peer
11436 if (game_status != GAME_MODE_PLAYING)
11439 // check if network player actions still missing and game still running
11440 if (!network_player_action_received && !checkGameEnded())
11441 return; // failed to get network player actions in time
11443 // do not yet reset "network_player_action_received" (for tape.pausing)
11449 // at this point we know that we really continue executing the game
11451 network_player_action_received = FALSE;
11453 // when playing tape, read previously recorded player input from tape data
11454 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11456 local_player->effective_mouse_action = local_player->mouse_action;
11458 if (recorded_player_action != NULL)
11459 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11460 recorded_player_action);
11462 // TapePlayAction() may return NULL when toggling to "pause before death"
11466 if (tape.set_centered_player)
11468 game.centered_player_nr_next = tape.centered_player_nr_next;
11469 game.set_centered_player = TRUE;
11472 for (i = 0; i < MAX_PLAYERS; i++)
11474 summarized_player_action |= stored_player[i].action;
11476 if (!network_playing && (game.team_mode || tape.playing))
11477 stored_player[i].effective_action = stored_player[i].action;
11480 if (network_playing && !checkGameEnded())
11481 SendToServer_MovePlayer(summarized_player_action);
11483 // summarize all actions at local players mapped input device position
11484 // (this allows using different input devices in single player mode)
11485 if (!network.enabled && !game.team_mode)
11486 stored_player[map_player_action[local_player->index_nr]].effective_action =
11487 summarized_player_action;
11489 // summarize all actions at centered player in local team mode
11490 if (tape.recording &&
11491 setup.team_mode && !network.enabled &&
11492 setup.input_on_focus &&
11493 game.centered_player_nr != -1)
11495 for (i = 0; i < MAX_PLAYERS; i++)
11496 stored_player[map_player_action[i]].effective_action =
11497 (i == game.centered_player_nr ? summarized_player_action : 0);
11500 if (recorded_player_action != NULL)
11501 for (i = 0; i < MAX_PLAYERS; i++)
11502 stored_player[i].effective_action = recorded_player_action[i];
11504 for (i = 0; i < MAX_PLAYERS; i++)
11506 tape_action[i] = stored_player[i].effective_action;
11508 /* (this may happen in the RND game engine if a player was not present on
11509 the playfield on level start, but appeared later from a custom element */
11510 if (setup.team_mode &&
11513 !tape.player_participates[i])
11514 tape.player_participates[i] = TRUE;
11517 SetTapeActionFromMouseAction(tape_action,
11518 &local_player->effective_mouse_action);
11520 // only record actions from input devices, but not programmed actions
11521 if (tape.recording)
11522 TapeRecordAction(tape_action);
11524 // remember if game was played (especially after tape stopped playing)
11525 if (!tape.playing && summarized_player_action)
11526 game.GamePlayed = TRUE;
11528 #if USE_NEW_PLAYER_ASSIGNMENTS
11529 // !!! also map player actions in single player mode !!!
11530 // if (game.team_mode)
11533 byte mapped_action[MAX_PLAYERS];
11535 #if DEBUG_PLAYER_ACTIONS
11537 for (i = 0; i < MAX_PLAYERS; i++)
11538 printf(" %d, ", stored_player[i].effective_action);
11541 for (i = 0; i < MAX_PLAYERS; i++)
11542 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11544 for (i = 0; i < MAX_PLAYERS; i++)
11545 stored_player[i].effective_action = mapped_action[i];
11547 #if DEBUG_PLAYER_ACTIONS
11549 for (i = 0; i < MAX_PLAYERS; i++)
11550 printf(" %d, ", stored_player[i].effective_action);
11554 #if DEBUG_PLAYER_ACTIONS
11558 for (i = 0; i < MAX_PLAYERS; i++)
11559 printf(" %d, ", stored_player[i].effective_action);
11565 for (i = 0; i < MAX_PLAYERS; i++)
11567 // allow engine snapshot in case of changed movement attempt
11568 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11569 (stored_player[i].effective_action & KEY_MOTION))
11570 game.snapshot.changed_action = TRUE;
11572 // allow engine snapshot in case of snapping/dropping attempt
11573 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11574 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11575 game.snapshot.changed_action = TRUE;
11577 game.snapshot.last_action[i] = stored_player[i].effective_action;
11580 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11582 GameActions_EM_Main();
11584 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11586 GameActions_SP_Main();
11588 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11590 GameActions_MM_Main();
11594 GameActions_RND_Main();
11597 BlitScreenToBitmap(backbuffer);
11599 CheckLevelSolved();
11602 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11604 if (global.show_frames_per_second)
11606 static unsigned int fps_counter = 0;
11607 static int fps_frames = 0;
11608 unsigned int fps_delay_ms = Counter() - fps_counter;
11612 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11614 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11617 fps_counter = Counter();
11619 // always draw FPS to screen after FPS value was updated
11620 redraw_mask |= REDRAW_FPS;
11623 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11624 if (GetDrawDeactivationMask() == REDRAW_NONE)
11625 redraw_mask |= REDRAW_FPS;
11629 static void GameActions_CheckSaveEngineSnapshot(void)
11631 if (!game.snapshot.save_snapshot)
11634 // clear flag for saving snapshot _before_ saving snapshot
11635 game.snapshot.save_snapshot = FALSE;
11637 SaveEngineSnapshotToList();
11640 void GameActions(void)
11644 GameActions_CheckSaveEngineSnapshot();
11647 void GameActions_EM_Main(void)
11649 byte effective_action[MAX_PLAYERS];
11650 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11653 for (i = 0; i < MAX_PLAYERS; i++)
11654 effective_action[i] = stored_player[i].effective_action;
11656 GameActions_EM(effective_action, warp_mode);
11659 void GameActions_SP_Main(void)
11661 byte effective_action[MAX_PLAYERS];
11662 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11665 for (i = 0; i < MAX_PLAYERS; i++)
11666 effective_action[i] = stored_player[i].effective_action;
11668 GameActions_SP(effective_action, warp_mode);
11670 for (i = 0; i < MAX_PLAYERS; i++)
11672 if (stored_player[i].force_dropping)
11673 stored_player[i].action |= KEY_BUTTON_DROP;
11675 stored_player[i].force_dropping = FALSE;
11679 void GameActions_MM_Main(void)
11681 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11683 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11686 void GameActions_RND_Main(void)
11691 void GameActions_RND(void)
11693 static struct MouseActionInfo mouse_action_last = { 0 };
11694 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11695 int magic_wall_x = 0, magic_wall_y = 0;
11696 int i, x, y, element, graphic, last_gfx_frame;
11698 InitPlayfieldScanModeVars();
11700 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11702 SCAN_PLAYFIELD(x, y)
11704 ChangeCount[x][y] = 0;
11705 ChangeEvent[x][y] = -1;
11709 if (game.set_centered_player)
11711 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11713 // switching to "all players" only possible if all players fit to screen
11714 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11716 game.centered_player_nr_next = game.centered_player_nr;
11717 game.set_centered_player = FALSE;
11720 // do not switch focus to non-existing (or non-active) player
11721 if (game.centered_player_nr_next >= 0 &&
11722 !stored_player[game.centered_player_nr_next].active)
11724 game.centered_player_nr_next = game.centered_player_nr;
11725 game.set_centered_player = FALSE;
11729 if (game.set_centered_player &&
11730 ScreenMovPos == 0) // screen currently aligned at tile position
11734 if (game.centered_player_nr_next == -1)
11736 setScreenCenteredToAllPlayers(&sx, &sy);
11740 sx = stored_player[game.centered_player_nr_next].jx;
11741 sy = stored_player[game.centered_player_nr_next].jy;
11744 game.centered_player_nr = game.centered_player_nr_next;
11745 game.set_centered_player = FALSE;
11747 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11748 DrawGameDoorValues();
11751 for (i = 0; i < MAX_PLAYERS; i++)
11753 int actual_player_action = stored_player[i].effective_action;
11756 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11757 - rnd_equinox_tetrachloride 048
11758 - rnd_equinox_tetrachloride_ii 096
11759 - rnd_emanuel_schmieg 002
11760 - doctor_sloan_ww 001, 020
11762 if (stored_player[i].MovPos == 0)
11763 CheckGravityMovement(&stored_player[i]);
11766 // overwrite programmed action with tape action
11767 if (stored_player[i].programmed_action)
11768 actual_player_action = stored_player[i].programmed_action;
11770 PlayerActions(&stored_player[i], actual_player_action);
11772 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11775 ScrollScreen(NULL, SCROLL_GO_ON);
11777 /* for backwards compatibility, the following code emulates a fixed bug that
11778 occured when pushing elements (causing elements that just made their last
11779 pushing step to already (if possible) make their first falling step in the
11780 same game frame, which is bad); this code is also needed to use the famous
11781 "spring push bug" which is used in older levels and might be wanted to be
11782 used also in newer levels, but in this case the buggy pushing code is only
11783 affecting the "spring" element and no other elements */
11785 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11787 for (i = 0; i < MAX_PLAYERS; i++)
11789 struct PlayerInfo *player = &stored_player[i];
11790 int x = player->jx;
11791 int y = player->jy;
11793 if (player->active && player->is_pushing && player->is_moving &&
11795 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11796 Feld[x][y] == EL_SPRING))
11798 ContinueMoving(x, y);
11800 // continue moving after pushing (this is actually a bug)
11801 if (!IS_MOVING(x, y))
11802 Stop[x][y] = FALSE;
11807 SCAN_PLAYFIELD(x, y)
11809 Last[x][y] = Feld[x][y];
11811 ChangeCount[x][y] = 0;
11812 ChangeEvent[x][y] = -1;
11814 // this must be handled before main playfield loop
11815 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11818 if (MovDelay[x][y] <= 0)
11822 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11825 if (MovDelay[x][y] <= 0)
11828 TEST_DrawLevelField(x, y);
11830 TestIfElementTouchesCustomElement(x, y); // for empty space
11835 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11837 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11838 printf("GameActions(): This should never happen!\n");
11840 ChangePage[x][y] = -1;
11844 Stop[x][y] = FALSE;
11845 if (WasJustMoving[x][y] > 0)
11846 WasJustMoving[x][y]--;
11847 if (WasJustFalling[x][y] > 0)
11848 WasJustFalling[x][y]--;
11849 if (CheckCollision[x][y] > 0)
11850 CheckCollision[x][y]--;
11851 if (CheckImpact[x][y] > 0)
11852 CheckImpact[x][y]--;
11856 /* reset finished pushing action (not done in ContinueMoving() to allow
11857 continuous pushing animation for elements with zero push delay) */
11858 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11860 ResetGfxAnimation(x, y);
11861 TEST_DrawLevelField(x, y);
11865 if (IS_BLOCKED(x, y))
11869 Blocked2Moving(x, y, &oldx, &oldy);
11870 if (!IS_MOVING(oldx, oldy))
11872 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11873 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11874 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11875 printf("GameActions(): This should never happen!\n");
11881 if (mouse_action.button)
11883 int new_button = (mouse_action.button && mouse_action_last.button == 0);
11885 x = local_player->mouse_action.lx;
11886 y = local_player->mouse_action.ly;
11887 element = Feld[x][y];
11891 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
11892 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
11895 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
11896 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
11899 SCAN_PLAYFIELD(x, y)
11901 element = Feld[x][y];
11902 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11903 last_gfx_frame = GfxFrame[x][y];
11905 ResetGfxFrame(x, y);
11907 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11908 DrawLevelGraphicAnimation(x, y, graphic);
11910 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11911 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11912 ResetRandomAnimationValue(x, y);
11914 SetRandomAnimationValue(x, y);
11916 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11918 if (IS_INACTIVE(element))
11920 if (IS_ANIMATED(graphic))
11921 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11926 // this may take place after moving, so 'element' may have changed
11927 if (IS_CHANGING(x, y) &&
11928 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11930 int page = element_info[element].event_page_nr[CE_DELAY];
11932 HandleElementChange(x, y, page);
11934 element = Feld[x][y];
11935 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11938 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11942 element = Feld[x][y];
11943 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11945 if (IS_ANIMATED(graphic) &&
11946 !IS_MOVING(x, y) &&
11948 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11950 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11951 TEST_DrawTwinkleOnField(x, y);
11953 else if (element == EL_ACID)
11956 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11958 else if ((element == EL_EXIT_OPEN ||
11959 element == EL_EM_EXIT_OPEN ||
11960 element == EL_SP_EXIT_OPEN ||
11961 element == EL_STEEL_EXIT_OPEN ||
11962 element == EL_EM_STEEL_EXIT_OPEN ||
11963 element == EL_SP_TERMINAL ||
11964 element == EL_SP_TERMINAL_ACTIVE ||
11965 element == EL_EXTRA_TIME ||
11966 element == EL_SHIELD_NORMAL ||
11967 element == EL_SHIELD_DEADLY) &&
11968 IS_ANIMATED(graphic))
11969 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11970 else if (IS_MOVING(x, y))
11971 ContinueMoving(x, y);
11972 else if (IS_ACTIVE_BOMB(element))
11973 CheckDynamite(x, y);
11974 else if (element == EL_AMOEBA_GROWING)
11975 AmoebeWaechst(x, y);
11976 else if (element == EL_AMOEBA_SHRINKING)
11977 AmoebaDisappearing(x, y);
11979 #if !USE_NEW_AMOEBA_CODE
11980 else if (IS_AMOEBALIVE(element))
11981 AmoebeAbleger(x, y);
11984 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11986 else if (element == EL_EXIT_CLOSED)
11988 else if (element == EL_EM_EXIT_CLOSED)
11990 else if (element == EL_STEEL_EXIT_CLOSED)
11991 CheckExitSteel(x, y);
11992 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11993 CheckExitSteelEM(x, y);
11994 else if (element == EL_SP_EXIT_CLOSED)
11996 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11997 element == EL_EXPANDABLE_STEELWALL_GROWING)
11998 MauerWaechst(x, y);
11999 else if (element == EL_EXPANDABLE_WALL ||
12000 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12001 element == EL_EXPANDABLE_WALL_VERTICAL ||
12002 element == EL_EXPANDABLE_WALL_ANY ||
12003 element == EL_BD_EXPANDABLE_WALL)
12004 MauerAbleger(x, y);
12005 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12006 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12007 element == EL_EXPANDABLE_STEELWALL_ANY)
12008 MauerAblegerStahl(x, y);
12009 else if (element == EL_FLAMES)
12010 CheckForDragon(x, y);
12011 else if (element == EL_EXPLOSION)
12012 ; // drawing of correct explosion animation is handled separately
12013 else if (element == EL_ELEMENT_SNAPPING ||
12014 element == EL_DIAGONAL_SHRINKING ||
12015 element == EL_DIAGONAL_GROWING)
12017 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12019 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12021 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12022 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12024 if (IS_BELT_ACTIVE(element))
12025 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12027 if (game.magic_wall_active)
12029 int jx = local_player->jx, jy = local_player->jy;
12031 // play the element sound at the position nearest to the player
12032 if ((element == EL_MAGIC_WALL_FULL ||
12033 element == EL_MAGIC_WALL_ACTIVE ||
12034 element == EL_MAGIC_WALL_EMPTYING ||
12035 element == EL_BD_MAGIC_WALL_FULL ||
12036 element == EL_BD_MAGIC_WALL_ACTIVE ||
12037 element == EL_BD_MAGIC_WALL_EMPTYING ||
12038 element == EL_DC_MAGIC_WALL_FULL ||
12039 element == EL_DC_MAGIC_WALL_ACTIVE ||
12040 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12041 ABS(x - jx) + ABS(y - jy) <
12042 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12050 #if USE_NEW_AMOEBA_CODE
12051 // new experimental amoeba growth stuff
12052 if (!(FrameCounter % 8))
12054 static unsigned int random = 1684108901;
12056 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12058 x = RND(lev_fieldx);
12059 y = RND(lev_fieldy);
12060 element = Feld[x][y];
12062 if (!IS_PLAYER(x,y) &&
12063 (element == EL_EMPTY ||
12064 CAN_GROW_INTO(element) ||
12065 element == EL_QUICKSAND_EMPTY ||
12066 element == EL_QUICKSAND_FAST_EMPTY ||
12067 element == EL_ACID_SPLASH_LEFT ||
12068 element == EL_ACID_SPLASH_RIGHT))
12070 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12071 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12072 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12073 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12074 Feld[x][y] = EL_AMOEBA_DROP;
12077 random = random * 129 + 1;
12082 game.explosions_delayed = FALSE;
12084 SCAN_PLAYFIELD(x, y)
12086 element = Feld[x][y];
12088 if (ExplodeField[x][y])
12089 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12090 else if (element == EL_EXPLOSION)
12091 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12093 ExplodeField[x][y] = EX_TYPE_NONE;
12096 game.explosions_delayed = TRUE;
12098 if (game.magic_wall_active)
12100 if (!(game.magic_wall_time_left % 4))
12102 int element = Feld[magic_wall_x][magic_wall_y];
12104 if (element == EL_BD_MAGIC_WALL_FULL ||
12105 element == EL_BD_MAGIC_WALL_ACTIVE ||
12106 element == EL_BD_MAGIC_WALL_EMPTYING)
12107 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12108 else if (element == EL_DC_MAGIC_WALL_FULL ||
12109 element == EL_DC_MAGIC_WALL_ACTIVE ||
12110 element == EL_DC_MAGIC_WALL_EMPTYING)
12111 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12113 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12116 if (game.magic_wall_time_left > 0)
12118 game.magic_wall_time_left--;
12120 if (!game.magic_wall_time_left)
12122 SCAN_PLAYFIELD(x, y)
12124 element = Feld[x][y];
12126 if (element == EL_MAGIC_WALL_ACTIVE ||
12127 element == EL_MAGIC_WALL_FULL)
12129 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12130 TEST_DrawLevelField(x, y);
12132 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12133 element == EL_BD_MAGIC_WALL_FULL)
12135 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12136 TEST_DrawLevelField(x, y);
12138 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12139 element == EL_DC_MAGIC_WALL_FULL)
12141 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12142 TEST_DrawLevelField(x, y);
12146 game.magic_wall_active = FALSE;
12151 if (game.light_time_left > 0)
12153 game.light_time_left--;
12155 if (game.light_time_left == 0)
12156 RedrawAllLightSwitchesAndInvisibleElements();
12159 if (game.timegate_time_left > 0)
12161 game.timegate_time_left--;
12163 if (game.timegate_time_left == 0)
12164 CloseAllOpenTimegates();
12167 if (game.lenses_time_left > 0)
12169 game.lenses_time_left--;
12171 if (game.lenses_time_left == 0)
12172 RedrawAllInvisibleElementsForLenses();
12175 if (game.magnify_time_left > 0)
12177 game.magnify_time_left--;
12179 if (game.magnify_time_left == 0)
12180 RedrawAllInvisibleElementsForMagnifier();
12183 for (i = 0; i < MAX_PLAYERS; i++)
12185 struct PlayerInfo *player = &stored_player[i];
12187 if (SHIELD_ON(player))
12189 if (player->shield_deadly_time_left)
12190 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12191 else if (player->shield_normal_time_left)
12192 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12196 #if USE_DELAYED_GFX_REDRAW
12197 SCAN_PLAYFIELD(x, y)
12199 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12201 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12202 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12204 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12205 DrawLevelField(x, y);
12207 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12208 DrawLevelFieldCrumbled(x, y);
12210 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12211 DrawLevelFieldCrumbledNeighbours(x, y);
12213 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12214 DrawTwinkleOnField(x, y);
12217 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12222 PlayAllPlayersSound();
12224 for (i = 0; i < MAX_PLAYERS; i++)
12226 struct PlayerInfo *player = &stored_player[i];
12228 if (player->show_envelope != 0 && (!player->active ||
12229 player->MovPos == 0))
12231 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12233 player->show_envelope = 0;
12237 // use random number generator in every frame to make it less predictable
12238 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12241 mouse_action_last = mouse_action;
12244 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12246 int min_x = x, min_y = y, max_x = x, max_y = y;
12249 for (i = 0; i < MAX_PLAYERS; i++)
12251 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12253 if (!stored_player[i].active || &stored_player[i] == player)
12256 min_x = MIN(min_x, jx);
12257 min_y = MIN(min_y, jy);
12258 max_x = MAX(max_x, jx);
12259 max_y = MAX(max_y, jy);
12262 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12265 static boolean AllPlayersInVisibleScreen(void)
12269 for (i = 0; i < MAX_PLAYERS; i++)
12271 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12273 if (!stored_player[i].active)
12276 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12283 void ScrollLevel(int dx, int dy)
12285 int scroll_offset = 2 * TILEX_VAR;
12288 BlitBitmap(drawto_field, drawto_field,
12289 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12290 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12291 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12292 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12293 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12294 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12298 x = (dx == 1 ? BX1 : BX2);
12299 for (y = BY1; y <= BY2; y++)
12300 DrawScreenField(x, y);
12305 y = (dy == 1 ? BY1 : BY2);
12306 for (x = BX1; x <= BX2; x++)
12307 DrawScreenField(x, y);
12310 redraw_mask |= REDRAW_FIELD;
12313 static boolean canFallDown(struct PlayerInfo *player)
12315 int jx = player->jx, jy = player->jy;
12317 return (IN_LEV_FIELD(jx, jy + 1) &&
12318 (IS_FREE(jx, jy + 1) ||
12319 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12320 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12321 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12324 static boolean canPassField(int x, int y, int move_dir)
12326 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12327 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12328 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12329 int nextx = x + dx;
12330 int nexty = y + dy;
12331 int element = Feld[x][y];
12333 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12334 !CAN_MOVE(element) &&
12335 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12336 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12337 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12340 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12342 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12343 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12344 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12348 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12349 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12350 (IS_DIGGABLE(Feld[newx][newy]) ||
12351 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12352 canPassField(newx, newy, move_dir)));
12355 static void CheckGravityMovement(struct PlayerInfo *player)
12357 if (player->gravity && !player->programmed_action)
12359 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12360 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12361 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12362 int jx = player->jx, jy = player->jy;
12363 boolean player_is_moving_to_valid_field =
12364 (!player_is_snapping &&
12365 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12366 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12367 boolean player_can_fall_down = canFallDown(player);
12369 if (player_can_fall_down &&
12370 !player_is_moving_to_valid_field)
12371 player->programmed_action = MV_DOWN;
12375 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12377 return CheckGravityMovement(player);
12379 if (player->gravity && !player->programmed_action)
12381 int jx = player->jx, jy = player->jy;
12382 boolean field_under_player_is_free =
12383 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12384 boolean player_is_standing_on_valid_field =
12385 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12386 (IS_WALKABLE(Feld[jx][jy]) &&
12387 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12389 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12390 player->programmed_action = MV_DOWN;
12395 MovePlayerOneStep()
12396 -----------------------------------------------------------------------------
12397 dx, dy: direction (non-diagonal) to try to move the player to
12398 real_dx, real_dy: direction as read from input device (can be diagonal)
12401 boolean MovePlayerOneStep(struct PlayerInfo *player,
12402 int dx, int dy, int real_dx, int real_dy)
12404 int jx = player->jx, jy = player->jy;
12405 int new_jx = jx + dx, new_jy = jy + dy;
12407 boolean player_can_move = !player->cannot_move;
12409 if (!player->active || (!dx && !dy))
12410 return MP_NO_ACTION;
12412 player->MovDir = (dx < 0 ? MV_LEFT :
12413 dx > 0 ? MV_RIGHT :
12415 dy > 0 ? MV_DOWN : MV_NONE);
12417 if (!IN_LEV_FIELD(new_jx, new_jy))
12418 return MP_NO_ACTION;
12420 if (!player_can_move)
12422 if (player->MovPos == 0)
12424 player->is_moving = FALSE;
12425 player->is_digging = FALSE;
12426 player->is_collecting = FALSE;
12427 player->is_snapping = FALSE;
12428 player->is_pushing = FALSE;
12432 if (!network.enabled && game.centered_player_nr == -1 &&
12433 !AllPlayersInSight(player, new_jx, new_jy))
12434 return MP_NO_ACTION;
12436 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12437 if (can_move != MP_MOVING)
12440 // check if DigField() has caused relocation of the player
12441 if (player->jx != jx || player->jy != jy)
12442 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12444 StorePlayer[jx][jy] = 0;
12445 player->last_jx = jx;
12446 player->last_jy = jy;
12447 player->jx = new_jx;
12448 player->jy = new_jy;
12449 StorePlayer[new_jx][new_jy] = player->element_nr;
12451 if (player->move_delay_value_next != -1)
12453 player->move_delay_value = player->move_delay_value_next;
12454 player->move_delay_value_next = -1;
12458 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12460 player->step_counter++;
12462 PlayerVisit[jx][jy] = FrameCounter;
12464 player->is_moving = TRUE;
12467 // should better be called in MovePlayer(), but this breaks some tapes
12468 ScrollPlayer(player, SCROLL_INIT);
12474 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12476 int jx = player->jx, jy = player->jy;
12477 int old_jx = jx, old_jy = jy;
12478 int moved = MP_NO_ACTION;
12480 if (!player->active)
12485 if (player->MovPos == 0)
12487 player->is_moving = FALSE;
12488 player->is_digging = FALSE;
12489 player->is_collecting = FALSE;
12490 player->is_snapping = FALSE;
12491 player->is_pushing = FALSE;
12497 if (player->move_delay > 0)
12500 player->move_delay = -1; // set to "uninitialized" value
12502 // store if player is automatically moved to next field
12503 player->is_auto_moving = (player->programmed_action != MV_NONE);
12505 // remove the last programmed player action
12506 player->programmed_action = 0;
12508 if (player->MovPos)
12510 // should only happen if pre-1.2 tape recordings are played
12511 // this is only for backward compatibility
12513 int original_move_delay_value = player->move_delay_value;
12516 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12520 // scroll remaining steps with finest movement resolution
12521 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12523 while (player->MovPos)
12525 ScrollPlayer(player, SCROLL_GO_ON);
12526 ScrollScreen(NULL, SCROLL_GO_ON);
12528 AdvanceFrameAndPlayerCounters(player->index_nr);
12531 BackToFront_WithFrameDelay(0);
12534 player->move_delay_value = original_move_delay_value;
12537 player->is_active = FALSE;
12539 if (player->last_move_dir & MV_HORIZONTAL)
12541 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12542 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12546 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12547 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12550 if (!moved && !player->is_active)
12552 player->is_moving = FALSE;
12553 player->is_digging = FALSE;
12554 player->is_collecting = FALSE;
12555 player->is_snapping = FALSE;
12556 player->is_pushing = FALSE;
12562 if (moved & MP_MOVING && !ScreenMovPos &&
12563 (player->index_nr == game.centered_player_nr ||
12564 game.centered_player_nr == -1))
12566 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12568 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12570 // actual player has left the screen -- scroll in that direction
12571 if (jx != old_jx) // player has moved horizontally
12572 scroll_x += (jx - old_jx);
12573 else // player has moved vertically
12574 scroll_y += (jy - old_jy);
12578 int offset_raw = game.scroll_delay_value;
12580 if (jx != old_jx) // player has moved horizontally
12582 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12583 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12584 int new_scroll_x = jx - MIDPOSX + offset_x;
12586 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12587 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12588 scroll_x = new_scroll_x;
12590 // don't scroll over playfield boundaries
12591 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12593 // don't scroll more than one field at a time
12594 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12596 // don't scroll against the player's moving direction
12597 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12598 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12599 scroll_x = old_scroll_x;
12601 else // player has moved vertically
12603 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12604 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12605 int new_scroll_y = jy - MIDPOSY + offset_y;
12607 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12608 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12609 scroll_y = new_scroll_y;
12611 // don't scroll over playfield boundaries
12612 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12614 // don't scroll more than one field at a time
12615 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12617 // don't scroll against the player's moving direction
12618 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12619 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12620 scroll_y = old_scroll_y;
12624 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12626 if (!network.enabled && game.centered_player_nr == -1 &&
12627 !AllPlayersInVisibleScreen())
12629 scroll_x = old_scroll_x;
12630 scroll_y = old_scroll_y;
12634 ScrollScreen(player, SCROLL_INIT);
12635 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12640 player->StepFrame = 0;
12642 if (moved & MP_MOVING)
12644 if (old_jx != jx && old_jy == jy)
12645 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12646 else if (old_jx == jx && old_jy != jy)
12647 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12649 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12651 player->last_move_dir = player->MovDir;
12652 player->is_moving = TRUE;
12653 player->is_snapping = FALSE;
12654 player->is_switching = FALSE;
12655 player->is_dropping = FALSE;
12656 player->is_dropping_pressed = FALSE;
12657 player->drop_pressed_delay = 0;
12660 // should better be called here than above, but this breaks some tapes
12661 ScrollPlayer(player, SCROLL_INIT);
12666 CheckGravityMovementWhenNotMoving(player);
12668 player->is_moving = FALSE;
12670 /* at this point, the player is allowed to move, but cannot move right now
12671 (e.g. because of something blocking the way) -- ensure that the player
12672 is also allowed to move in the next frame (in old versions before 3.1.1,
12673 the player was forced to wait again for eight frames before next try) */
12675 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12676 player->move_delay = 0; // allow direct movement in the next frame
12679 if (player->move_delay == -1) // not yet initialized by DigField()
12680 player->move_delay = player->move_delay_value;
12682 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12684 TestIfPlayerTouchesBadThing(jx, jy);
12685 TestIfPlayerTouchesCustomElement(jx, jy);
12688 if (!player->active)
12689 RemovePlayer(player);
12694 void ScrollPlayer(struct PlayerInfo *player, int mode)
12696 int jx = player->jx, jy = player->jy;
12697 int last_jx = player->last_jx, last_jy = player->last_jy;
12698 int move_stepsize = TILEX / player->move_delay_value;
12700 if (!player->active)
12703 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12706 if (mode == SCROLL_INIT)
12708 player->actual_frame_counter = FrameCounter;
12709 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12711 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12712 Feld[last_jx][last_jy] == EL_EMPTY)
12714 int last_field_block_delay = 0; // start with no blocking at all
12715 int block_delay_adjustment = player->block_delay_adjustment;
12717 // if player blocks last field, add delay for exactly one move
12718 if (player->block_last_field)
12720 last_field_block_delay += player->move_delay_value;
12722 // when blocking enabled, prevent moving up despite gravity
12723 if (player->gravity && player->MovDir == MV_UP)
12724 block_delay_adjustment = -1;
12727 // add block delay adjustment (also possible when not blocking)
12728 last_field_block_delay += block_delay_adjustment;
12730 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12731 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12734 if (player->MovPos != 0) // player has not yet reached destination
12737 else if (!FrameReached(&player->actual_frame_counter, 1))
12740 if (player->MovPos != 0)
12742 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12743 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12745 // before DrawPlayer() to draw correct player graphic for this case
12746 if (player->MovPos == 0)
12747 CheckGravityMovement(player);
12750 if (player->MovPos == 0) // player reached destination field
12752 if (player->move_delay_reset_counter > 0)
12754 player->move_delay_reset_counter--;
12756 if (player->move_delay_reset_counter == 0)
12758 // continue with normal speed after quickly moving through gate
12759 HALVE_PLAYER_SPEED(player);
12761 // be able to make the next move without delay
12762 player->move_delay = 0;
12766 player->last_jx = jx;
12767 player->last_jy = jy;
12769 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12770 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12771 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12772 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12773 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12774 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12775 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12776 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12778 ExitPlayer(player);
12780 if (game.players_still_needed == 0 &&
12781 (game.friends_still_needed == 0 ||
12782 IS_SP_ELEMENT(Feld[jx][jy])))
12786 // this breaks one level: "machine", level 000
12788 int move_direction = player->MovDir;
12789 int enter_side = MV_DIR_OPPOSITE(move_direction);
12790 int leave_side = move_direction;
12791 int old_jx = last_jx;
12792 int old_jy = last_jy;
12793 int old_element = Feld[old_jx][old_jy];
12794 int new_element = Feld[jx][jy];
12796 if (IS_CUSTOM_ELEMENT(old_element))
12797 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12799 player->index_bit, leave_side);
12801 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12802 CE_PLAYER_LEAVES_X,
12803 player->index_bit, leave_side);
12805 if (IS_CUSTOM_ELEMENT(new_element))
12806 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12807 player->index_bit, enter_side);
12809 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12810 CE_PLAYER_ENTERS_X,
12811 player->index_bit, enter_side);
12813 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12814 CE_MOVE_OF_X, move_direction);
12817 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12819 TestIfPlayerTouchesBadThing(jx, jy);
12820 TestIfPlayerTouchesCustomElement(jx, jy);
12822 /* needed because pushed element has not yet reached its destination,
12823 so it would trigger a change event at its previous field location */
12824 if (!player->is_pushing)
12825 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12827 if (!player->active)
12828 RemovePlayer(player);
12831 if (!game.LevelSolved && level.use_step_counter)
12841 if (TimeLeft <= 10 && setup.time_limit)
12842 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12844 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12846 DisplayGameControlValues();
12848 if (!TimeLeft && setup.time_limit)
12849 for (i = 0; i < MAX_PLAYERS; i++)
12850 KillPlayer(&stored_player[i]);
12852 else if (game.no_time_limit && !game.all_players_gone)
12854 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12856 DisplayGameControlValues();
12860 if (tape.single_step && tape.recording && !tape.pausing &&
12861 !player->programmed_action)
12862 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12864 if (!player->programmed_action)
12865 CheckSaveEngineSnapshot(player);
12869 void ScrollScreen(struct PlayerInfo *player, int mode)
12871 static unsigned int screen_frame_counter = 0;
12873 if (mode == SCROLL_INIT)
12875 // set scrolling step size according to actual player's moving speed
12876 ScrollStepSize = TILEX / player->move_delay_value;
12878 screen_frame_counter = FrameCounter;
12879 ScreenMovDir = player->MovDir;
12880 ScreenMovPos = player->MovPos;
12881 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12884 else if (!FrameReached(&screen_frame_counter, 1))
12889 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12890 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12891 redraw_mask |= REDRAW_FIELD;
12894 ScreenMovDir = MV_NONE;
12897 void TestIfPlayerTouchesCustomElement(int x, int y)
12899 static int xy[4][2] =
12906 static int trigger_sides[4][2] =
12908 // center side border side
12909 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12910 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12911 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12912 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12914 static int touch_dir[4] =
12916 MV_LEFT | MV_RIGHT,
12921 int center_element = Feld[x][y]; // should always be non-moving!
12924 for (i = 0; i < NUM_DIRECTIONS; i++)
12926 int xx = x + xy[i][0];
12927 int yy = y + xy[i][1];
12928 int center_side = trigger_sides[i][0];
12929 int border_side = trigger_sides[i][1];
12930 int border_element;
12932 if (!IN_LEV_FIELD(xx, yy))
12935 if (IS_PLAYER(x, y)) // player found at center element
12937 struct PlayerInfo *player = PLAYERINFO(x, y);
12939 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12940 border_element = Feld[xx][yy]; // may be moving!
12941 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12942 border_element = Feld[xx][yy];
12943 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12944 border_element = MovingOrBlocked2Element(xx, yy);
12946 continue; // center and border element do not touch
12948 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12949 player->index_bit, border_side);
12950 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12951 CE_PLAYER_TOUCHES_X,
12952 player->index_bit, border_side);
12955 /* use player element that is initially defined in the level playfield,
12956 not the player element that corresponds to the runtime player number
12957 (example: a level that contains EL_PLAYER_3 as the only player would
12958 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12959 int player_element = PLAYERINFO(x, y)->initial_element;
12961 CheckElementChangeBySide(xx, yy, border_element, player_element,
12962 CE_TOUCHING_X, border_side);
12965 else if (IS_PLAYER(xx, yy)) // player found at border element
12967 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12969 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12971 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12972 continue; // center and border element do not touch
12975 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12976 player->index_bit, center_side);
12977 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12978 CE_PLAYER_TOUCHES_X,
12979 player->index_bit, center_side);
12982 /* use player element that is initially defined in the level playfield,
12983 not the player element that corresponds to the runtime player number
12984 (example: a level that contains EL_PLAYER_3 as the only player would
12985 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12986 int player_element = PLAYERINFO(xx, yy)->initial_element;
12988 CheckElementChangeBySide(x, y, center_element, player_element,
12989 CE_TOUCHING_X, center_side);
12997 void TestIfElementTouchesCustomElement(int x, int y)
12999 static int xy[4][2] =
13006 static int trigger_sides[4][2] =
13008 // center side border side
13009 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13010 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13011 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13012 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13014 static int touch_dir[4] =
13016 MV_LEFT | MV_RIGHT,
13021 boolean change_center_element = FALSE;
13022 int center_element = Feld[x][y]; // should always be non-moving!
13023 int border_element_old[NUM_DIRECTIONS];
13026 for (i = 0; i < NUM_DIRECTIONS; i++)
13028 int xx = x + xy[i][0];
13029 int yy = y + xy[i][1];
13030 int border_element;
13032 border_element_old[i] = -1;
13034 if (!IN_LEV_FIELD(xx, yy))
13037 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13038 border_element = Feld[xx][yy]; // may be moving!
13039 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13040 border_element = Feld[xx][yy];
13041 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13042 border_element = MovingOrBlocked2Element(xx, yy);
13044 continue; // center and border element do not touch
13046 border_element_old[i] = border_element;
13049 for (i = 0; i < NUM_DIRECTIONS; i++)
13051 int xx = x + xy[i][0];
13052 int yy = y + xy[i][1];
13053 int center_side = trigger_sides[i][0];
13054 int border_element = border_element_old[i];
13056 if (border_element == -1)
13059 // check for change of border element
13060 CheckElementChangeBySide(xx, yy, border_element, center_element,
13061 CE_TOUCHING_X, center_side);
13063 // (center element cannot be player, so we dont have to check this here)
13066 for (i = 0; i < NUM_DIRECTIONS; i++)
13068 int xx = x + xy[i][0];
13069 int yy = y + xy[i][1];
13070 int border_side = trigger_sides[i][1];
13071 int border_element = border_element_old[i];
13073 if (border_element == -1)
13076 // check for change of center element (but change it only once)
13077 if (!change_center_element)
13078 change_center_element =
13079 CheckElementChangeBySide(x, y, center_element, border_element,
13080 CE_TOUCHING_X, border_side);
13082 if (IS_PLAYER(xx, yy))
13084 /* use player element that is initially defined in the level playfield,
13085 not the player element that corresponds to the runtime player number
13086 (example: a level that contains EL_PLAYER_3 as the only player would
13087 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13088 int player_element = PLAYERINFO(xx, yy)->initial_element;
13090 CheckElementChangeBySide(x, y, center_element, player_element,
13091 CE_TOUCHING_X, border_side);
13096 void TestIfElementHitsCustomElement(int x, int y, int direction)
13098 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13099 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13100 int hitx = x + dx, hity = y + dy;
13101 int hitting_element = Feld[x][y];
13102 int touched_element;
13104 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13107 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13108 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13110 if (IN_LEV_FIELD(hitx, hity))
13112 int opposite_direction = MV_DIR_OPPOSITE(direction);
13113 int hitting_side = direction;
13114 int touched_side = opposite_direction;
13115 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13116 MovDir[hitx][hity] != direction ||
13117 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13123 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13124 CE_HITTING_X, touched_side);
13126 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13127 CE_HIT_BY_X, hitting_side);
13129 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13130 CE_HIT_BY_SOMETHING, opposite_direction);
13132 if (IS_PLAYER(hitx, hity))
13134 /* use player element that is initially defined in the level playfield,
13135 not the player element that corresponds to the runtime player number
13136 (example: a level that contains EL_PLAYER_3 as the only player would
13137 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13138 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13140 CheckElementChangeBySide(x, y, hitting_element, player_element,
13141 CE_HITTING_X, touched_side);
13146 // "hitting something" is also true when hitting the playfield border
13147 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13148 CE_HITTING_SOMETHING, direction);
13151 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13153 int i, kill_x = -1, kill_y = -1;
13155 int bad_element = -1;
13156 static int test_xy[4][2] =
13163 static int test_dir[4] =
13171 for (i = 0; i < NUM_DIRECTIONS; i++)
13173 int test_x, test_y, test_move_dir, test_element;
13175 test_x = good_x + test_xy[i][0];
13176 test_y = good_y + test_xy[i][1];
13178 if (!IN_LEV_FIELD(test_x, test_y))
13182 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13184 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13186 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13187 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13189 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13190 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13194 bad_element = test_element;
13200 if (kill_x != -1 || kill_y != -1)
13202 if (IS_PLAYER(good_x, good_y))
13204 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13206 if (player->shield_deadly_time_left > 0 &&
13207 !IS_INDESTRUCTIBLE(bad_element))
13208 Bang(kill_x, kill_y);
13209 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13210 KillPlayer(player);
13213 Bang(good_x, good_y);
13217 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13219 int i, kill_x = -1, kill_y = -1;
13220 int bad_element = Feld[bad_x][bad_y];
13221 static int test_xy[4][2] =
13228 static int touch_dir[4] =
13230 MV_LEFT | MV_RIGHT,
13235 static int test_dir[4] =
13243 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13246 for (i = 0; i < NUM_DIRECTIONS; i++)
13248 int test_x, test_y, test_move_dir, test_element;
13250 test_x = bad_x + test_xy[i][0];
13251 test_y = bad_y + test_xy[i][1];
13253 if (!IN_LEV_FIELD(test_x, test_y))
13257 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13259 test_element = Feld[test_x][test_y];
13261 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13262 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13264 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13265 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13267 // good thing is player or penguin that does not move away
13268 if (IS_PLAYER(test_x, test_y))
13270 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13272 if (bad_element == EL_ROBOT && player->is_moving)
13273 continue; // robot does not kill player if he is moving
13275 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13277 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13278 continue; // center and border element do not touch
13286 else if (test_element == EL_PENGUIN)
13296 if (kill_x != -1 || kill_y != -1)
13298 if (IS_PLAYER(kill_x, kill_y))
13300 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13302 if (player->shield_deadly_time_left > 0 &&
13303 !IS_INDESTRUCTIBLE(bad_element))
13304 Bang(bad_x, bad_y);
13305 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13306 KillPlayer(player);
13309 Bang(kill_x, kill_y);
13313 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13315 int bad_element = Feld[bad_x][bad_y];
13316 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13317 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13318 int test_x = bad_x + dx, test_y = bad_y + dy;
13319 int test_move_dir, test_element;
13320 int kill_x = -1, kill_y = -1;
13322 if (!IN_LEV_FIELD(test_x, test_y))
13326 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13328 test_element = Feld[test_x][test_y];
13330 if (test_move_dir != bad_move_dir)
13332 // good thing can be player or penguin that does not move away
13333 if (IS_PLAYER(test_x, test_y))
13335 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13337 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13338 player as being hit when he is moving towards the bad thing, because
13339 the "get hit by" condition would be lost after the player stops) */
13340 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13341 return; // player moves away from bad thing
13346 else if (test_element == EL_PENGUIN)
13353 if (kill_x != -1 || kill_y != -1)
13355 if (IS_PLAYER(kill_x, kill_y))
13357 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13359 if (player->shield_deadly_time_left > 0 &&
13360 !IS_INDESTRUCTIBLE(bad_element))
13361 Bang(bad_x, bad_y);
13362 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13363 KillPlayer(player);
13366 Bang(kill_x, kill_y);
13370 void TestIfPlayerTouchesBadThing(int x, int y)
13372 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13375 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13377 TestIfGoodThingHitsBadThing(x, y, move_dir);
13380 void TestIfBadThingTouchesPlayer(int x, int y)
13382 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13385 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13387 TestIfBadThingHitsGoodThing(x, y, move_dir);
13390 void TestIfFriendTouchesBadThing(int x, int y)
13392 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13395 void TestIfBadThingTouchesFriend(int x, int y)
13397 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13400 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13402 int i, kill_x = bad_x, kill_y = bad_y;
13403 static int xy[4][2] =
13411 for (i = 0; i < NUM_DIRECTIONS; i++)
13415 x = bad_x + xy[i][0];
13416 y = bad_y + xy[i][1];
13417 if (!IN_LEV_FIELD(x, y))
13420 element = Feld[x][y];
13421 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13422 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13430 if (kill_x != bad_x || kill_y != bad_y)
13431 Bang(bad_x, bad_y);
13434 void KillPlayer(struct PlayerInfo *player)
13436 int jx = player->jx, jy = player->jy;
13438 if (!player->active)
13442 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13443 player->killed, player->active, player->reanimated);
13446 /* the following code was introduced to prevent an infinite loop when calling
13448 -> CheckTriggeredElementChangeExt()
13449 -> ExecuteCustomElementAction()
13451 -> (infinitely repeating the above sequence of function calls)
13452 which occurs when killing the player while having a CE with the setting
13453 "kill player X when explosion of <player X>"; the solution using a new
13454 field "player->killed" was chosen for backwards compatibility, although
13455 clever use of the fields "player->active" etc. would probably also work */
13457 if (player->killed)
13461 player->killed = TRUE;
13463 // remove accessible field at the player's position
13464 Feld[jx][jy] = EL_EMPTY;
13466 // deactivate shield (else Bang()/Explode() would not work right)
13467 player->shield_normal_time_left = 0;
13468 player->shield_deadly_time_left = 0;
13471 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13472 player->killed, player->active, player->reanimated);
13478 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13479 player->killed, player->active, player->reanimated);
13482 if (player->reanimated) // killed player may have been reanimated
13483 player->killed = player->reanimated = FALSE;
13485 BuryPlayer(player);
13488 static void KillPlayerUnlessEnemyProtected(int x, int y)
13490 if (!PLAYER_ENEMY_PROTECTED(x, y))
13491 KillPlayer(PLAYERINFO(x, y));
13494 static void KillPlayerUnlessExplosionProtected(int x, int y)
13496 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13497 KillPlayer(PLAYERINFO(x, y));
13500 void BuryPlayer(struct PlayerInfo *player)
13502 int jx = player->jx, jy = player->jy;
13504 if (!player->active)
13507 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13508 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13510 RemovePlayer(player);
13512 player->buried = TRUE;
13514 if (game.all_players_gone)
13515 game.GameOver = TRUE;
13518 void RemovePlayer(struct PlayerInfo *player)
13520 int jx = player->jx, jy = player->jy;
13521 int i, found = FALSE;
13523 player->present = FALSE;
13524 player->active = FALSE;
13526 // required for some CE actions (even if the player is not active anymore)
13527 player->MovPos = 0;
13529 if (!ExplodeField[jx][jy])
13530 StorePlayer[jx][jy] = 0;
13532 if (player->is_moving)
13533 TEST_DrawLevelField(player->last_jx, player->last_jy);
13535 for (i = 0; i < MAX_PLAYERS; i++)
13536 if (stored_player[i].active)
13541 game.all_players_gone = TRUE;
13542 game.GameOver = TRUE;
13545 game.exit_x = game.robot_wheel_x = jx;
13546 game.exit_y = game.robot_wheel_y = jy;
13549 void ExitPlayer(struct PlayerInfo *player)
13551 DrawPlayer(player); // needed here only to cleanup last field
13552 RemovePlayer(player);
13554 if (game.players_still_needed > 0)
13555 game.players_still_needed--;
13558 static void setFieldForSnapping(int x, int y, int element, int direction)
13560 struct ElementInfo *ei = &element_info[element];
13561 int direction_bit = MV_DIR_TO_BIT(direction);
13562 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13563 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13564 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13566 Feld[x][y] = EL_ELEMENT_SNAPPING;
13567 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13569 ResetGfxAnimation(x, y);
13571 GfxElement[x][y] = element;
13572 GfxAction[x][y] = action;
13573 GfxDir[x][y] = direction;
13574 GfxFrame[x][y] = -1;
13578 =============================================================================
13579 checkDiagonalPushing()
13580 -----------------------------------------------------------------------------
13581 check if diagonal input device direction results in pushing of object
13582 (by checking if the alternative direction is walkable, diggable, ...)
13583 =============================================================================
13586 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13587 int x, int y, int real_dx, int real_dy)
13589 int jx, jy, dx, dy, xx, yy;
13591 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13594 // diagonal direction: check alternative direction
13599 xx = jx + (dx == 0 ? real_dx : 0);
13600 yy = jy + (dy == 0 ? real_dy : 0);
13602 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13606 =============================================================================
13608 -----------------------------------------------------------------------------
13609 x, y: field next to player (non-diagonal) to try to dig to
13610 real_dx, real_dy: direction as read from input device (can be diagonal)
13611 =============================================================================
13614 static int DigField(struct PlayerInfo *player,
13615 int oldx, int oldy, int x, int y,
13616 int real_dx, int real_dy, int mode)
13618 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13619 boolean player_was_pushing = player->is_pushing;
13620 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13621 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13622 int jx = oldx, jy = oldy;
13623 int dx = x - jx, dy = y - jy;
13624 int nextx = x + dx, nexty = y + dy;
13625 int move_direction = (dx == -1 ? MV_LEFT :
13626 dx == +1 ? MV_RIGHT :
13628 dy == +1 ? MV_DOWN : MV_NONE);
13629 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13630 int dig_side = MV_DIR_OPPOSITE(move_direction);
13631 int old_element = Feld[jx][jy];
13632 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13635 if (is_player) // function can also be called by EL_PENGUIN
13637 if (player->MovPos == 0)
13639 player->is_digging = FALSE;
13640 player->is_collecting = FALSE;
13643 if (player->MovPos == 0) // last pushing move finished
13644 player->is_pushing = FALSE;
13646 if (mode == DF_NO_PUSH) // player just stopped pushing
13648 player->is_switching = FALSE;
13649 player->push_delay = -1;
13651 return MP_NO_ACTION;
13655 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13656 old_element = Back[jx][jy];
13658 // in case of element dropped at player position, check background
13659 else if (Back[jx][jy] != EL_EMPTY &&
13660 game.engine_version >= VERSION_IDENT(2,2,0,0))
13661 old_element = Back[jx][jy];
13663 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13664 return MP_NO_ACTION; // field has no opening in this direction
13666 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13667 return MP_NO_ACTION; // field has no opening in this direction
13669 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13673 Feld[jx][jy] = player->artwork_element;
13674 InitMovingField(jx, jy, MV_DOWN);
13675 Store[jx][jy] = EL_ACID;
13676 ContinueMoving(jx, jy);
13677 BuryPlayer(player);
13679 return MP_DONT_RUN_INTO;
13682 if (player_can_move && DONT_RUN_INTO(element))
13684 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13686 return MP_DONT_RUN_INTO;
13689 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13690 return MP_NO_ACTION;
13692 collect_count = element_info[element].collect_count_initial;
13694 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13695 return MP_NO_ACTION;
13697 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13698 player_can_move = player_can_move_or_snap;
13700 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13701 game.engine_version >= VERSION_IDENT(2,2,0,0))
13703 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13704 player->index_bit, dig_side);
13705 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13706 player->index_bit, dig_side);
13708 if (element == EL_DC_LANDMINE)
13711 if (Feld[x][y] != element) // field changed by snapping
13714 return MP_NO_ACTION;
13717 if (player->gravity && is_player && !player->is_auto_moving &&
13718 canFallDown(player) && move_direction != MV_DOWN &&
13719 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13720 return MP_NO_ACTION; // player cannot walk here due to gravity
13722 if (player_can_move &&
13723 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13725 int sound_element = SND_ELEMENT(element);
13726 int sound_action = ACTION_WALKING;
13728 if (IS_RND_GATE(element))
13730 if (!player->key[RND_GATE_NR(element)])
13731 return MP_NO_ACTION;
13733 else if (IS_RND_GATE_GRAY(element))
13735 if (!player->key[RND_GATE_GRAY_NR(element)])
13736 return MP_NO_ACTION;
13738 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13740 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13741 return MP_NO_ACTION;
13743 else if (element == EL_EXIT_OPEN ||
13744 element == EL_EM_EXIT_OPEN ||
13745 element == EL_EM_EXIT_OPENING ||
13746 element == EL_STEEL_EXIT_OPEN ||
13747 element == EL_EM_STEEL_EXIT_OPEN ||
13748 element == EL_EM_STEEL_EXIT_OPENING ||
13749 element == EL_SP_EXIT_OPEN ||
13750 element == EL_SP_EXIT_OPENING)
13752 sound_action = ACTION_PASSING; // player is passing exit
13754 else if (element == EL_EMPTY)
13756 sound_action = ACTION_MOVING; // nothing to walk on
13759 // play sound from background or player, whatever is available
13760 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13761 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13763 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13765 else if (player_can_move &&
13766 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13768 if (!ACCESS_FROM(element, opposite_direction))
13769 return MP_NO_ACTION; // field not accessible from this direction
13771 if (CAN_MOVE(element)) // only fixed elements can be passed!
13772 return MP_NO_ACTION;
13774 if (IS_EM_GATE(element))
13776 if (!player->key[EM_GATE_NR(element)])
13777 return MP_NO_ACTION;
13779 else if (IS_EM_GATE_GRAY(element))
13781 if (!player->key[EM_GATE_GRAY_NR(element)])
13782 return MP_NO_ACTION;
13784 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13786 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13787 return MP_NO_ACTION;
13789 else if (IS_EMC_GATE(element))
13791 if (!player->key[EMC_GATE_NR(element)])
13792 return MP_NO_ACTION;
13794 else if (IS_EMC_GATE_GRAY(element))
13796 if (!player->key[EMC_GATE_GRAY_NR(element)])
13797 return MP_NO_ACTION;
13799 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13801 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13802 return MP_NO_ACTION;
13804 else if (element == EL_DC_GATE_WHITE ||
13805 element == EL_DC_GATE_WHITE_GRAY ||
13806 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13808 if (player->num_white_keys == 0)
13809 return MP_NO_ACTION;
13811 player->num_white_keys--;
13813 else if (IS_SP_PORT(element))
13815 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13816 element == EL_SP_GRAVITY_PORT_RIGHT ||
13817 element == EL_SP_GRAVITY_PORT_UP ||
13818 element == EL_SP_GRAVITY_PORT_DOWN)
13819 player->gravity = !player->gravity;
13820 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13821 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13822 element == EL_SP_GRAVITY_ON_PORT_UP ||
13823 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13824 player->gravity = TRUE;
13825 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13826 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13827 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13828 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13829 player->gravity = FALSE;
13832 // automatically move to the next field with double speed
13833 player->programmed_action = move_direction;
13835 if (player->move_delay_reset_counter == 0)
13837 player->move_delay_reset_counter = 2; // two double speed steps
13839 DOUBLE_PLAYER_SPEED(player);
13842 PlayLevelSoundAction(x, y, ACTION_PASSING);
13844 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13848 if (mode != DF_SNAP)
13850 GfxElement[x][y] = GFX_ELEMENT(element);
13851 player->is_digging = TRUE;
13854 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13856 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13857 player->index_bit, dig_side);
13859 if (mode == DF_SNAP)
13861 if (level.block_snap_field)
13862 setFieldForSnapping(x, y, element, move_direction);
13864 TestIfElementTouchesCustomElement(x, y); // for empty space
13866 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13867 player->index_bit, dig_side);
13870 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13874 if (is_player && mode != DF_SNAP)
13876 GfxElement[x][y] = element;
13877 player->is_collecting = TRUE;
13880 if (element == EL_SPEED_PILL)
13882 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13884 else if (element == EL_EXTRA_TIME && level.time > 0)
13886 TimeLeft += level.extra_time;
13888 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13890 DisplayGameControlValues();
13892 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13894 player->shield_normal_time_left += level.shield_normal_time;
13895 if (element == EL_SHIELD_DEADLY)
13896 player->shield_deadly_time_left += level.shield_deadly_time;
13898 else if (element == EL_DYNAMITE ||
13899 element == EL_EM_DYNAMITE ||
13900 element == EL_SP_DISK_RED)
13902 if (player->inventory_size < MAX_INVENTORY_SIZE)
13903 player->inventory_element[player->inventory_size++] = element;
13905 DrawGameDoorValues();
13907 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13909 player->dynabomb_count++;
13910 player->dynabombs_left++;
13912 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13914 player->dynabomb_size++;
13916 else if (element == EL_DYNABOMB_INCREASE_POWER)
13918 player->dynabomb_xl = TRUE;
13920 else if (IS_KEY(element))
13922 player->key[KEY_NR(element)] = TRUE;
13924 DrawGameDoorValues();
13926 else if (element == EL_DC_KEY_WHITE)
13928 player->num_white_keys++;
13930 // display white keys?
13931 // DrawGameDoorValues();
13933 else if (IS_ENVELOPE(element))
13935 player->show_envelope = element;
13937 else if (element == EL_EMC_LENSES)
13939 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13941 RedrawAllInvisibleElementsForLenses();
13943 else if (element == EL_EMC_MAGNIFIER)
13945 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13947 RedrawAllInvisibleElementsForMagnifier();
13949 else if (IS_DROPPABLE(element) ||
13950 IS_THROWABLE(element)) // can be collected and dropped
13954 if (collect_count == 0)
13955 player->inventory_infinite_element = element;
13957 for (i = 0; i < collect_count; i++)
13958 if (player->inventory_size < MAX_INVENTORY_SIZE)
13959 player->inventory_element[player->inventory_size++] = element;
13961 DrawGameDoorValues();
13963 else if (collect_count > 0)
13965 game.gems_still_needed -= collect_count;
13966 if (game.gems_still_needed < 0)
13967 game.gems_still_needed = 0;
13969 game.snapshot.collected_item = TRUE;
13971 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13973 DisplayGameControlValues();
13976 RaiseScoreElement(element);
13977 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13980 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13981 player->index_bit, dig_side);
13983 if (mode == DF_SNAP)
13985 if (level.block_snap_field)
13986 setFieldForSnapping(x, y, element, move_direction);
13988 TestIfElementTouchesCustomElement(x, y); // for empty space
13990 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13991 player->index_bit, dig_side);
13994 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13996 if (mode == DF_SNAP && element != EL_BD_ROCK)
13997 return MP_NO_ACTION;
13999 if (CAN_FALL(element) && dy)
14000 return MP_NO_ACTION;
14002 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14003 !(element == EL_SPRING && level.use_spring_bug))
14004 return MP_NO_ACTION;
14006 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14007 ((move_direction & MV_VERTICAL &&
14008 ((element_info[element].move_pattern & MV_LEFT &&
14009 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14010 (element_info[element].move_pattern & MV_RIGHT &&
14011 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14012 (move_direction & MV_HORIZONTAL &&
14013 ((element_info[element].move_pattern & MV_UP &&
14014 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14015 (element_info[element].move_pattern & MV_DOWN &&
14016 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14017 return MP_NO_ACTION;
14019 // do not push elements already moving away faster than player
14020 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14021 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14022 return MP_NO_ACTION;
14024 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14026 if (player->push_delay_value == -1 || !player_was_pushing)
14027 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14029 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14031 if (player->push_delay_value == -1)
14032 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14034 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14036 if (!player->is_pushing)
14037 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14040 player->is_pushing = TRUE;
14041 player->is_active = TRUE;
14043 if (!(IN_LEV_FIELD(nextx, nexty) &&
14044 (IS_FREE(nextx, nexty) ||
14045 (IS_SB_ELEMENT(element) &&
14046 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14047 (IS_CUSTOM_ELEMENT(element) &&
14048 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14049 return MP_NO_ACTION;
14051 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14052 return MP_NO_ACTION;
14054 if (player->push_delay == -1) // new pushing; restart delay
14055 player->push_delay = 0;
14057 if (player->push_delay < player->push_delay_value &&
14058 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14059 element != EL_SPRING && element != EL_BALLOON)
14061 // make sure that there is no move delay before next try to push
14062 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14063 player->move_delay = 0;
14065 return MP_NO_ACTION;
14068 if (IS_CUSTOM_ELEMENT(element) &&
14069 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14071 if (!DigFieldByCE(nextx, nexty, element))
14072 return MP_NO_ACTION;
14075 if (IS_SB_ELEMENT(element))
14077 boolean sokoban_task_solved = FALSE;
14079 if (element == EL_SOKOBAN_FIELD_FULL)
14081 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14083 IncrementSokobanFieldsNeeded();
14084 IncrementSokobanObjectsNeeded();
14087 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14089 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14091 DecrementSokobanFieldsNeeded();
14092 DecrementSokobanObjectsNeeded();
14094 // sokoban object was pushed from empty field to sokoban field
14095 if (Back[x][y] == EL_EMPTY)
14096 sokoban_task_solved = TRUE;
14099 Feld[x][y] = EL_SOKOBAN_OBJECT;
14101 if (Back[x][y] == Back[nextx][nexty])
14102 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14103 else if (Back[x][y] != 0)
14104 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14107 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14110 if (sokoban_task_solved &&
14111 game.sokoban_fields_still_needed == 0 &&
14112 game.sokoban_objects_still_needed == 0 &&
14113 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14115 game.players_still_needed = 0;
14119 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14123 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14125 InitMovingField(x, y, move_direction);
14126 GfxAction[x][y] = ACTION_PUSHING;
14128 if (mode == DF_SNAP)
14129 ContinueMoving(x, y);
14131 MovPos[x][y] = (dx != 0 ? dx : dy);
14133 Pushed[x][y] = TRUE;
14134 Pushed[nextx][nexty] = TRUE;
14136 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14137 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14139 player->push_delay_value = -1; // get new value later
14141 // check for element change _after_ element has been pushed
14142 if (game.use_change_when_pushing_bug)
14144 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14145 player->index_bit, dig_side);
14146 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14147 player->index_bit, dig_side);
14150 else if (IS_SWITCHABLE(element))
14152 if (PLAYER_SWITCHING(player, x, y))
14154 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14155 player->index_bit, dig_side);
14160 player->is_switching = TRUE;
14161 player->switch_x = x;
14162 player->switch_y = y;
14164 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14166 if (element == EL_ROBOT_WHEEL)
14168 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14170 game.robot_wheel_x = x;
14171 game.robot_wheel_y = y;
14172 game.robot_wheel_active = TRUE;
14174 TEST_DrawLevelField(x, y);
14176 else if (element == EL_SP_TERMINAL)
14180 SCAN_PLAYFIELD(xx, yy)
14182 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14186 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14188 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14190 ResetGfxAnimation(xx, yy);
14191 TEST_DrawLevelField(xx, yy);
14195 else if (IS_BELT_SWITCH(element))
14197 ToggleBeltSwitch(x, y);
14199 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14200 element == EL_SWITCHGATE_SWITCH_DOWN ||
14201 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14202 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14204 ToggleSwitchgateSwitch(x, y);
14206 else if (element == EL_LIGHT_SWITCH ||
14207 element == EL_LIGHT_SWITCH_ACTIVE)
14209 ToggleLightSwitch(x, y);
14211 else if (element == EL_TIMEGATE_SWITCH ||
14212 element == EL_DC_TIMEGATE_SWITCH)
14214 ActivateTimegateSwitch(x, y);
14216 else if (element == EL_BALLOON_SWITCH_LEFT ||
14217 element == EL_BALLOON_SWITCH_RIGHT ||
14218 element == EL_BALLOON_SWITCH_UP ||
14219 element == EL_BALLOON_SWITCH_DOWN ||
14220 element == EL_BALLOON_SWITCH_NONE ||
14221 element == EL_BALLOON_SWITCH_ANY)
14223 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14224 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14225 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14226 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14227 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14230 else if (element == EL_LAMP)
14232 Feld[x][y] = EL_LAMP_ACTIVE;
14233 game.lights_still_needed--;
14235 ResetGfxAnimation(x, y);
14236 TEST_DrawLevelField(x, y);
14238 else if (element == EL_TIME_ORB_FULL)
14240 Feld[x][y] = EL_TIME_ORB_EMPTY;
14242 if (level.time > 0 || level.use_time_orb_bug)
14244 TimeLeft += level.time_orb_time;
14245 game.no_time_limit = FALSE;
14247 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14249 DisplayGameControlValues();
14252 ResetGfxAnimation(x, y);
14253 TEST_DrawLevelField(x, y);
14255 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14256 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14260 game.ball_active = !game.ball_active;
14262 SCAN_PLAYFIELD(xx, yy)
14264 int e = Feld[xx][yy];
14266 if (game.ball_active)
14268 if (e == EL_EMC_MAGIC_BALL)
14269 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14270 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14271 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14275 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14276 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14277 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14278 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14283 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14284 player->index_bit, dig_side);
14286 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14287 player->index_bit, dig_side);
14289 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14290 player->index_bit, dig_side);
14296 if (!PLAYER_SWITCHING(player, x, y))
14298 player->is_switching = TRUE;
14299 player->switch_x = x;
14300 player->switch_y = y;
14302 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14303 player->index_bit, dig_side);
14304 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14305 player->index_bit, dig_side);
14307 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14308 player->index_bit, dig_side);
14309 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14310 player->index_bit, dig_side);
14313 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14314 player->index_bit, dig_side);
14315 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14316 player->index_bit, dig_side);
14318 return MP_NO_ACTION;
14321 player->push_delay = -1;
14323 if (is_player) // function can also be called by EL_PENGUIN
14325 if (Feld[x][y] != element) // really digged/collected something
14327 player->is_collecting = !player->is_digging;
14328 player->is_active = TRUE;
14335 static boolean DigFieldByCE(int x, int y, int digging_element)
14337 int element = Feld[x][y];
14339 if (!IS_FREE(x, y))
14341 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14342 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14345 // no element can dig solid indestructible elements
14346 if (IS_INDESTRUCTIBLE(element) &&
14347 !IS_DIGGABLE(element) &&
14348 !IS_COLLECTIBLE(element))
14351 if (AmoebaNr[x][y] &&
14352 (element == EL_AMOEBA_FULL ||
14353 element == EL_BD_AMOEBA ||
14354 element == EL_AMOEBA_GROWING))
14356 AmoebaCnt[AmoebaNr[x][y]]--;
14357 AmoebaCnt2[AmoebaNr[x][y]]--;
14360 if (IS_MOVING(x, y))
14361 RemoveMovingField(x, y);
14365 TEST_DrawLevelField(x, y);
14368 // if digged element was about to explode, prevent the explosion
14369 ExplodeField[x][y] = EX_TYPE_NONE;
14371 PlayLevelSoundAction(x, y, action);
14374 Store[x][y] = EL_EMPTY;
14376 // this makes it possible to leave the removed element again
14377 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14378 Store[x][y] = element;
14383 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14385 int jx = player->jx, jy = player->jy;
14386 int x = jx + dx, y = jy + dy;
14387 int snap_direction = (dx == -1 ? MV_LEFT :
14388 dx == +1 ? MV_RIGHT :
14390 dy == +1 ? MV_DOWN : MV_NONE);
14391 boolean can_continue_snapping = (level.continuous_snapping &&
14392 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14394 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14397 if (!player->active || !IN_LEV_FIELD(x, y))
14405 if (player->MovPos == 0)
14406 player->is_pushing = FALSE;
14408 player->is_snapping = FALSE;
14410 if (player->MovPos == 0)
14412 player->is_moving = FALSE;
14413 player->is_digging = FALSE;
14414 player->is_collecting = FALSE;
14420 // prevent snapping with already pressed snap key when not allowed
14421 if (player->is_snapping && !can_continue_snapping)
14424 player->MovDir = snap_direction;
14426 if (player->MovPos == 0)
14428 player->is_moving = FALSE;
14429 player->is_digging = FALSE;
14430 player->is_collecting = FALSE;
14433 player->is_dropping = FALSE;
14434 player->is_dropping_pressed = FALSE;
14435 player->drop_pressed_delay = 0;
14437 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14440 player->is_snapping = TRUE;
14441 player->is_active = TRUE;
14443 if (player->MovPos == 0)
14445 player->is_moving = FALSE;
14446 player->is_digging = FALSE;
14447 player->is_collecting = FALSE;
14450 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14451 TEST_DrawLevelField(player->last_jx, player->last_jy);
14453 TEST_DrawLevelField(x, y);
14458 static boolean DropElement(struct PlayerInfo *player)
14460 int old_element, new_element;
14461 int dropx = player->jx, dropy = player->jy;
14462 int drop_direction = player->MovDir;
14463 int drop_side = drop_direction;
14464 int drop_element = get_next_dropped_element(player);
14466 /* do not drop an element on top of another element; when holding drop key
14467 pressed without moving, dropped element must move away before the next
14468 element can be dropped (this is especially important if the next element
14469 is dynamite, which can be placed on background for historical reasons) */
14470 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14473 if (IS_THROWABLE(drop_element))
14475 dropx += GET_DX_FROM_DIR(drop_direction);
14476 dropy += GET_DY_FROM_DIR(drop_direction);
14478 if (!IN_LEV_FIELD(dropx, dropy))
14482 old_element = Feld[dropx][dropy]; // old element at dropping position
14483 new_element = drop_element; // default: no change when dropping
14485 // check if player is active, not moving and ready to drop
14486 if (!player->active || player->MovPos || player->drop_delay > 0)
14489 // check if player has anything that can be dropped
14490 if (new_element == EL_UNDEFINED)
14493 // only set if player has anything that can be dropped
14494 player->is_dropping_pressed = TRUE;
14496 // check if drop key was pressed long enough for EM style dynamite
14497 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14500 // check if anything can be dropped at the current position
14501 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14504 // collected custom elements can only be dropped on empty fields
14505 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14508 if (old_element != EL_EMPTY)
14509 Back[dropx][dropy] = old_element; // store old element on this field
14511 ResetGfxAnimation(dropx, dropy);
14512 ResetRandomAnimationValue(dropx, dropy);
14514 if (player->inventory_size > 0 ||
14515 player->inventory_infinite_element != EL_UNDEFINED)
14517 if (player->inventory_size > 0)
14519 player->inventory_size--;
14521 DrawGameDoorValues();
14523 if (new_element == EL_DYNAMITE)
14524 new_element = EL_DYNAMITE_ACTIVE;
14525 else if (new_element == EL_EM_DYNAMITE)
14526 new_element = EL_EM_DYNAMITE_ACTIVE;
14527 else if (new_element == EL_SP_DISK_RED)
14528 new_element = EL_SP_DISK_RED_ACTIVE;
14531 Feld[dropx][dropy] = new_element;
14533 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14534 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14535 el2img(Feld[dropx][dropy]), 0);
14537 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14539 // needed if previous element just changed to "empty" in the last frame
14540 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14542 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14543 player->index_bit, drop_side);
14544 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14546 player->index_bit, drop_side);
14548 TestIfElementTouchesCustomElement(dropx, dropy);
14550 else // player is dropping a dyna bomb
14552 player->dynabombs_left--;
14554 Feld[dropx][dropy] = new_element;
14556 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14557 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14558 el2img(Feld[dropx][dropy]), 0);
14560 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14563 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14564 InitField_WithBug1(dropx, dropy, FALSE);
14566 new_element = Feld[dropx][dropy]; // element might have changed
14568 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14569 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14571 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14572 MovDir[dropx][dropy] = drop_direction;
14574 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14576 // do not cause impact style collision by dropping elements that can fall
14577 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14580 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14581 player->is_dropping = TRUE;
14583 player->drop_pressed_delay = 0;
14584 player->is_dropping_pressed = FALSE;
14586 player->drop_x = dropx;
14587 player->drop_y = dropy;
14592 // ----------------------------------------------------------------------------
14593 // game sound playing functions
14594 // ----------------------------------------------------------------------------
14596 static int *loop_sound_frame = NULL;
14597 static int *loop_sound_volume = NULL;
14599 void InitPlayLevelSound(void)
14601 int num_sounds = getSoundListSize();
14603 checked_free(loop_sound_frame);
14604 checked_free(loop_sound_volume);
14606 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14607 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14610 static void PlayLevelSound(int x, int y, int nr)
14612 int sx = SCREENX(x), sy = SCREENY(y);
14613 int volume, stereo_position;
14614 int max_distance = 8;
14615 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14617 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14618 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14621 if (!IN_LEV_FIELD(x, y) ||
14622 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14623 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14626 volume = SOUND_MAX_VOLUME;
14628 if (!IN_SCR_FIELD(sx, sy))
14630 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14631 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14633 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14636 stereo_position = (SOUND_MAX_LEFT +
14637 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14638 (SCR_FIELDX + 2 * max_distance));
14640 if (IS_LOOP_SOUND(nr))
14642 /* This assures that quieter loop sounds do not overwrite louder ones,
14643 while restarting sound volume comparison with each new game frame. */
14645 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14648 loop_sound_volume[nr] = volume;
14649 loop_sound_frame[nr] = FrameCounter;
14652 PlaySoundExt(nr, volume, stereo_position, type);
14655 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14657 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14658 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14659 y < LEVELY(BY1) ? LEVELY(BY1) :
14660 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14664 static void PlayLevelSoundAction(int x, int y, int action)
14666 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14669 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14671 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14673 if (sound_effect != SND_UNDEFINED)
14674 PlayLevelSound(x, y, sound_effect);
14677 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14680 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14682 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14683 PlayLevelSound(x, y, sound_effect);
14686 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14688 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14690 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14691 PlayLevelSound(x, y, sound_effect);
14694 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14696 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14698 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14699 StopSound(sound_effect);
14702 static int getLevelMusicNr(void)
14704 if (levelset.music[level_nr] != MUS_UNDEFINED)
14705 return levelset.music[level_nr]; // from config file
14707 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14710 static void FadeLevelSounds(void)
14715 static void FadeLevelMusic(void)
14717 int music_nr = getLevelMusicNr();
14718 char *curr_music = getCurrentlyPlayingMusicFilename();
14719 char *next_music = getMusicInfoEntryFilename(music_nr);
14721 if (!strEqual(curr_music, next_music))
14725 void FadeLevelSoundsAndMusic(void)
14731 static void PlayLevelMusic(void)
14733 int music_nr = getLevelMusicNr();
14734 char *curr_music = getCurrentlyPlayingMusicFilename();
14735 char *next_music = getMusicInfoEntryFilename(music_nr);
14737 if (!strEqual(curr_music, next_music))
14738 PlayMusicLoop(music_nr);
14741 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14743 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14744 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14745 int x = xx - 1 - offset;
14746 int y = yy - 1 - offset;
14751 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14755 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14759 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14763 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14767 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14771 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14775 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14778 case SOUND_android_clone:
14779 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14782 case SOUND_android_move:
14783 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14787 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14791 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14795 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14798 case SOUND_eater_eat:
14799 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14803 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14806 case SOUND_collect:
14807 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14810 case SOUND_diamond:
14811 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14815 // !!! CHECK THIS !!!
14817 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14819 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14823 case SOUND_wonderfall:
14824 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14828 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14832 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14836 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14840 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14844 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14848 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14852 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14856 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14859 case SOUND_exit_open:
14860 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14863 case SOUND_exit_leave:
14864 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14867 case SOUND_dynamite:
14868 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14872 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14876 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14880 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14884 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14888 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14892 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14896 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14901 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14903 int element = map_element_SP_to_RND(element_sp);
14904 int action = map_action_SP_to_RND(action_sp);
14905 int offset = (setup.sp_show_border_elements ? 0 : 1);
14906 int x = xx - offset;
14907 int y = yy - offset;
14909 PlayLevelSoundElementAction(x, y, element, action);
14912 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14914 int element = map_element_MM_to_RND(element_mm);
14915 int action = map_action_MM_to_RND(action_mm);
14917 int x = xx - offset;
14918 int y = yy - offset;
14920 if (!IS_MM_ELEMENT(element))
14921 element = EL_MM_DEFAULT;
14923 PlayLevelSoundElementAction(x, y, element, action);
14926 void PlaySound_MM(int sound_mm)
14928 int sound = map_sound_MM_to_RND(sound_mm);
14930 if (sound == SND_UNDEFINED)
14936 void PlaySoundLoop_MM(int sound_mm)
14938 int sound = map_sound_MM_to_RND(sound_mm);
14940 if (sound == SND_UNDEFINED)
14943 PlaySoundLoop(sound);
14946 void StopSound_MM(int sound_mm)
14948 int sound = map_sound_MM_to_RND(sound_mm);
14950 if (sound == SND_UNDEFINED)
14956 void RaiseScore(int value)
14958 game.score += value;
14960 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14962 DisplayGameControlValues();
14965 void RaiseScoreElement(int element)
14970 case EL_BD_DIAMOND:
14971 case EL_EMERALD_YELLOW:
14972 case EL_EMERALD_RED:
14973 case EL_EMERALD_PURPLE:
14974 case EL_SP_INFOTRON:
14975 RaiseScore(level.score[SC_EMERALD]);
14978 RaiseScore(level.score[SC_DIAMOND]);
14981 RaiseScore(level.score[SC_CRYSTAL]);
14984 RaiseScore(level.score[SC_PEARL]);
14987 case EL_BD_BUTTERFLY:
14988 case EL_SP_ELECTRON:
14989 RaiseScore(level.score[SC_BUG]);
14992 case EL_BD_FIREFLY:
14993 case EL_SP_SNIKSNAK:
14994 RaiseScore(level.score[SC_SPACESHIP]);
14997 case EL_DARK_YAMYAM:
14998 RaiseScore(level.score[SC_YAMYAM]);
15001 RaiseScore(level.score[SC_ROBOT]);
15004 RaiseScore(level.score[SC_PACMAN]);
15007 RaiseScore(level.score[SC_NUT]);
15010 case EL_EM_DYNAMITE:
15011 case EL_SP_DISK_RED:
15012 case EL_DYNABOMB_INCREASE_NUMBER:
15013 case EL_DYNABOMB_INCREASE_SIZE:
15014 case EL_DYNABOMB_INCREASE_POWER:
15015 RaiseScore(level.score[SC_DYNAMITE]);
15017 case EL_SHIELD_NORMAL:
15018 case EL_SHIELD_DEADLY:
15019 RaiseScore(level.score[SC_SHIELD]);
15021 case EL_EXTRA_TIME:
15022 RaiseScore(level.extra_time_score);
15036 case EL_DC_KEY_WHITE:
15037 RaiseScore(level.score[SC_KEY]);
15040 RaiseScore(element_info[element].collect_score);
15045 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15047 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15049 // closing door required in case of envelope style request dialogs
15052 // prevent short reactivation of overlay buttons while closing door
15053 SetOverlayActive(FALSE);
15055 CloseDoor(DOOR_CLOSE_1);
15058 if (network.enabled)
15059 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15063 FadeSkipNextFadeIn();
15065 SetGameStatus(GAME_MODE_MAIN);
15070 else // continue playing the game
15072 if (tape.playing && tape.deactivate_display)
15073 TapeDeactivateDisplayOff(TRUE);
15075 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15077 if (tape.playing && tape.deactivate_display)
15078 TapeDeactivateDisplayOn();
15082 void RequestQuitGame(boolean ask_if_really_quit)
15084 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15085 boolean skip_request = game.all_players_gone || quick_quit;
15087 RequestQuitGameExt(skip_request, quick_quit,
15088 "Do you really want to quit the game?");
15091 void RequestRestartGame(char *message)
15093 game.restart_game_message = NULL;
15095 boolean has_started_game = hasStartedNetworkGame();
15096 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15098 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15100 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15104 SetGameStatus(GAME_MODE_MAIN);
15110 void CheckGameOver(void)
15112 static boolean last_game_over = FALSE;
15113 static int game_over_delay = 0;
15114 int game_over_delay_value = 50;
15115 boolean game_over = checkGameFailed();
15117 // do not handle game over if request dialog is already active
15118 if (game.request_active)
15121 // do not ask to play again if game was never actually played
15122 if (!game.GamePlayed)
15127 last_game_over = FALSE;
15128 game_over_delay = game_over_delay_value;
15133 if (game_over_delay > 0)
15140 if (last_game_over != game_over)
15141 game.restart_game_message = (hasStartedNetworkGame() ?
15142 "Game over! Play it again?" :
15145 last_game_over = game_over;
15148 boolean checkGameSolved(void)
15150 // set for all game engines if level was solved
15151 return game.LevelSolved_GameEnd;
15154 boolean checkGameFailed(void)
15156 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15157 return (game_em.game_over && !game_em.level_solved);
15158 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15159 return (game_sp.game_over && !game_sp.level_solved);
15160 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15161 return (game_mm.game_over && !game_mm.level_solved);
15162 else // GAME_ENGINE_TYPE_RND
15163 return (game.GameOver && !game.LevelSolved);
15166 boolean checkGameEnded(void)
15168 return (checkGameSolved() || checkGameFailed());
15172 // ----------------------------------------------------------------------------
15173 // random generator functions
15174 // ----------------------------------------------------------------------------
15176 unsigned int InitEngineRandom_RND(int seed)
15178 game.num_random_calls = 0;
15180 return InitEngineRandom(seed);
15183 unsigned int RND(int max)
15187 game.num_random_calls++;
15189 return GetEngineRandom(max);
15196 // ----------------------------------------------------------------------------
15197 // game engine snapshot handling functions
15198 // ----------------------------------------------------------------------------
15200 struct EngineSnapshotInfo
15202 // runtime values for custom element collect score
15203 int collect_score[NUM_CUSTOM_ELEMENTS];
15205 // runtime values for group element choice position
15206 int choice_pos[NUM_GROUP_ELEMENTS];
15208 // runtime values for belt position animations
15209 int belt_graphic[4][NUM_BELT_PARTS];
15210 int belt_anim_mode[4][NUM_BELT_PARTS];
15213 static struct EngineSnapshotInfo engine_snapshot_rnd;
15214 static char *snapshot_level_identifier = NULL;
15215 static int snapshot_level_nr = -1;
15217 static void SaveEngineSnapshotValues_RND(void)
15219 static int belt_base_active_element[4] =
15221 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15222 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15223 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15224 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15228 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15230 int element = EL_CUSTOM_START + i;
15232 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15235 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15237 int element = EL_GROUP_START + i;
15239 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15242 for (i = 0; i < 4; i++)
15244 for (j = 0; j < NUM_BELT_PARTS; j++)
15246 int element = belt_base_active_element[i] + j;
15247 int graphic = el2img(element);
15248 int anim_mode = graphic_info[graphic].anim_mode;
15250 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15251 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15256 static void LoadEngineSnapshotValues_RND(void)
15258 unsigned int num_random_calls = game.num_random_calls;
15261 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15263 int element = EL_CUSTOM_START + i;
15265 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15268 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15270 int element = EL_GROUP_START + i;
15272 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15275 for (i = 0; i < 4; i++)
15277 for (j = 0; j < NUM_BELT_PARTS; j++)
15279 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15280 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15282 graphic_info[graphic].anim_mode = anim_mode;
15286 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15288 InitRND(tape.random_seed);
15289 for (i = 0; i < num_random_calls; i++)
15293 if (game.num_random_calls != num_random_calls)
15295 Error(ERR_INFO, "number of random calls out of sync");
15296 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15297 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15298 Error(ERR_EXIT, "this should not happen -- please debug");
15302 void FreeEngineSnapshotSingle(void)
15304 FreeSnapshotSingle();
15306 setString(&snapshot_level_identifier, NULL);
15307 snapshot_level_nr = -1;
15310 void FreeEngineSnapshotList(void)
15312 FreeSnapshotList();
15315 static ListNode *SaveEngineSnapshotBuffers(void)
15317 ListNode *buffers = NULL;
15319 // copy some special values to a structure better suited for the snapshot
15321 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15322 SaveEngineSnapshotValues_RND();
15323 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15324 SaveEngineSnapshotValues_EM();
15325 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15326 SaveEngineSnapshotValues_SP(&buffers);
15327 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15328 SaveEngineSnapshotValues_MM(&buffers);
15330 // save values stored in special snapshot structure
15332 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15333 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15334 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15335 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15336 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15338 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15341 // save further RND engine values
15343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15344 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15345 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15348 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15349 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15351 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15353 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15355 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15357 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15362 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15363 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15364 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15365 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15366 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15367 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15368 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15369 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15370 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15371 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15372 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15373 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15374 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15375 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15376 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15377 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15378 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15379 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15381 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15382 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15384 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15385 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15386 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15388 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15389 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15391 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15392 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15393 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15394 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15395 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15397 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15398 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15401 ListNode *node = engine_snapshot_list_rnd;
15404 while (node != NULL)
15406 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15411 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15417 void SaveEngineSnapshotSingle(void)
15419 ListNode *buffers = SaveEngineSnapshotBuffers();
15421 // finally save all snapshot buffers to single snapshot
15422 SaveSnapshotSingle(buffers);
15424 // save level identification information
15425 setString(&snapshot_level_identifier, leveldir_current->identifier);
15426 snapshot_level_nr = level_nr;
15429 boolean CheckSaveEngineSnapshotToList(void)
15431 boolean save_snapshot =
15432 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15433 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15434 game.snapshot.changed_action) ||
15435 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15436 game.snapshot.collected_item));
15438 game.snapshot.changed_action = FALSE;
15439 game.snapshot.collected_item = FALSE;
15440 game.snapshot.save_snapshot = save_snapshot;
15442 return save_snapshot;
15445 void SaveEngineSnapshotToList(void)
15447 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15451 ListNode *buffers = SaveEngineSnapshotBuffers();
15453 // finally save all snapshot buffers to snapshot list
15454 SaveSnapshotToList(buffers);
15457 void SaveEngineSnapshotToListInitial(void)
15459 FreeEngineSnapshotList();
15461 SaveEngineSnapshotToList();
15464 static void LoadEngineSnapshotValues(void)
15466 // restore special values from snapshot structure
15468 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15469 LoadEngineSnapshotValues_RND();
15470 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15471 LoadEngineSnapshotValues_EM();
15472 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15473 LoadEngineSnapshotValues_SP();
15474 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15475 LoadEngineSnapshotValues_MM();
15478 void LoadEngineSnapshotSingle(void)
15480 LoadSnapshotSingle();
15482 LoadEngineSnapshotValues();
15485 static void LoadEngineSnapshot_Undo(int steps)
15487 LoadSnapshotFromList_Older(steps);
15489 LoadEngineSnapshotValues();
15492 static void LoadEngineSnapshot_Redo(int steps)
15494 LoadSnapshotFromList_Newer(steps);
15496 LoadEngineSnapshotValues();
15499 boolean CheckEngineSnapshotSingle(void)
15501 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15502 snapshot_level_nr == level_nr);
15505 boolean CheckEngineSnapshotList(void)
15507 return CheckSnapshotList();
15511 // ---------- new game button stuff -------------------------------------------
15518 boolean *setup_value;
15519 boolean allowed_on_tape;
15520 boolean is_touch_button;
15522 } gamebutton_info[NUM_GAME_BUTTONS] =
15525 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15526 GAME_CTRL_ID_STOP, NULL,
15527 TRUE, FALSE, "stop game"
15530 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15531 GAME_CTRL_ID_PAUSE, NULL,
15532 TRUE, FALSE, "pause game"
15535 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15536 GAME_CTRL_ID_PLAY, NULL,
15537 TRUE, FALSE, "play game"
15540 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15541 GAME_CTRL_ID_UNDO, NULL,
15542 TRUE, FALSE, "undo step"
15545 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15546 GAME_CTRL_ID_REDO, NULL,
15547 TRUE, FALSE, "redo step"
15550 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15551 GAME_CTRL_ID_SAVE, NULL,
15552 TRUE, FALSE, "save game"
15555 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15556 GAME_CTRL_ID_PAUSE2, NULL,
15557 TRUE, FALSE, "pause game"
15560 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15561 GAME_CTRL_ID_LOAD, NULL,
15562 TRUE, FALSE, "load game"
15565 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15566 GAME_CTRL_ID_PANEL_STOP, NULL,
15567 FALSE, FALSE, "stop game"
15570 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15571 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15572 FALSE, FALSE, "pause game"
15575 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15576 GAME_CTRL_ID_PANEL_PLAY, NULL,
15577 FALSE, FALSE, "play game"
15580 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15581 GAME_CTRL_ID_TOUCH_STOP, NULL,
15582 FALSE, TRUE, "stop game"
15585 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15586 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15587 FALSE, TRUE, "pause game"
15590 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15591 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15592 TRUE, FALSE, "background music on/off"
15595 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15596 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15597 TRUE, FALSE, "sound loops on/off"
15600 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15601 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15602 TRUE, FALSE, "normal sounds on/off"
15605 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15606 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15607 FALSE, FALSE, "background music on/off"
15610 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15611 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15612 FALSE, FALSE, "sound loops on/off"
15615 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15616 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15617 FALSE, FALSE, "normal sounds on/off"
15621 void CreateGameButtons(void)
15625 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15627 int graphic = gamebutton_info[i].graphic;
15628 struct GraphicInfo *gfx = &graphic_info[graphic];
15629 struct XY *pos = gamebutton_info[i].pos;
15630 struct GadgetInfo *gi;
15633 unsigned int event_mask;
15634 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15635 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15636 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15637 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15638 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15639 int gd_x = gfx->src_x;
15640 int gd_y = gfx->src_y;
15641 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15642 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15643 int gd_xa = gfx->src_x + gfx->active_xoffset;
15644 int gd_ya = gfx->src_y + gfx->active_yoffset;
15645 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15646 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15647 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15648 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15651 if (gfx->bitmap == NULL)
15653 game_gadget[id] = NULL;
15658 if (id == GAME_CTRL_ID_STOP ||
15659 id == GAME_CTRL_ID_PANEL_STOP ||
15660 id == GAME_CTRL_ID_TOUCH_STOP ||
15661 id == GAME_CTRL_ID_PLAY ||
15662 id == GAME_CTRL_ID_PANEL_PLAY ||
15663 id == GAME_CTRL_ID_SAVE ||
15664 id == GAME_CTRL_ID_LOAD)
15666 button_type = GD_TYPE_NORMAL_BUTTON;
15668 event_mask = GD_EVENT_RELEASED;
15670 else if (id == GAME_CTRL_ID_UNDO ||
15671 id == GAME_CTRL_ID_REDO)
15673 button_type = GD_TYPE_NORMAL_BUTTON;
15675 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15679 button_type = GD_TYPE_CHECK_BUTTON;
15680 checked = (gamebutton_info[i].setup_value != NULL ?
15681 *gamebutton_info[i].setup_value : FALSE);
15682 event_mask = GD_EVENT_PRESSED;
15685 gi = CreateGadget(GDI_CUSTOM_ID, id,
15686 GDI_IMAGE_ID, graphic,
15687 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15690 GDI_WIDTH, gfx->width,
15691 GDI_HEIGHT, gfx->height,
15692 GDI_TYPE, button_type,
15693 GDI_STATE, GD_BUTTON_UNPRESSED,
15694 GDI_CHECKED, checked,
15695 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15696 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15697 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15698 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15699 GDI_DIRECT_DRAW, FALSE,
15700 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15701 GDI_EVENT_MASK, event_mask,
15702 GDI_CALLBACK_ACTION, HandleGameButtons,
15706 Error(ERR_EXIT, "cannot create gadget");
15708 game_gadget[id] = gi;
15712 void FreeGameButtons(void)
15716 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15717 FreeGadget(game_gadget[i]);
15720 static void UnmapGameButtonsAtSamePosition(int id)
15724 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15726 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15727 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15728 UnmapGadget(game_gadget[i]);
15731 static void UnmapGameButtonsAtSamePosition_All(void)
15733 if (setup.show_snapshot_buttons)
15735 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15736 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15737 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15741 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15742 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15743 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15745 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15746 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15747 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15751 static void MapGameButtonsAtSamePosition(int id)
15755 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15757 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15758 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15759 MapGadget(game_gadget[i]);
15761 UnmapGameButtonsAtSamePosition_All();
15764 void MapUndoRedoButtons(void)
15766 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15767 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15769 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15770 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15773 void UnmapUndoRedoButtons(void)
15775 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15776 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15778 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15779 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15782 void ModifyPauseButtons(void)
15786 GAME_CTRL_ID_PAUSE,
15787 GAME_CTRL_ID_PAUSE2,
15788 GAME_CTRL_ID_PANEL_PAUSE,
15789 GAME_CTRL_ID_TOUCH_PAUSE,
15794 for (i = 0; ids[i] > -1; i++)
15795 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15798 static void MapGameButtonsExt(boolean on_tape)
15802 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15803 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15804 i != GAME_CTRL_ID_UNDO &&
15805 i != GAME_CTRL_ID_REDO)
15806 MapGadget(game_gadget[i]);
15808 UnmapGameButtonsAtSamePosition_All();
15810 RedrawGameButtons();
15813 static void UnmapGameButtonsExt(boolean on_tape)
15817 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15818 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15819 UnmapGadget(game_gadget[i]);
15822 static void RedrawGameButtonsExt(boolean on_tape)
15826 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15827 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15828 RedrawGadget(game_gadget[i]);
15831 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15836 gi->checked = state;
15839 static void RedrawSoundButtonGadget(int id)
15841 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15842 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15843 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15844 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15845 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15846 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15849 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15850 RedrawGadget(game_gadget[id2]);
15853 void MapGameButtons(void)
15855 MapGameButtonsExt(FALSE);
15858 void UnmapGameButtons(void)
15860 UnmapGameButtonsExt(FALSE);
15863 void RedrawGameButtons(void)
15865 RedrawGameButtonsExt(FALSE);
15868 void MapGameButtonsOnTape(void)
15870 MapGameButtonsExt(TRUE);
15873 void UnmapGameButtonsOnTape(void)
15875 UnmapGameButtonsExt(TRUE);
15878 void RedrawGameButtonsOnTape(void)
15880 RedrawGameButtonsExt(TRUE);
15883 static void GameUndoRedoExt(void)
15885 ClearPlayerAction();
15887 tape.pausing = TRUE;
15890 UpdateAndDisplayGameControlValues();
15892 DrawCompleteVideoDisplay();
15893 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15894 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15895 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15900 static void GameUndo(int steps)
15902 if (!CheckEngineSnapshotList())
15905 LoadEngineSnapshot_Undo(steps);
15910 static void GameRedo(int steps)
15912 if (!CheckEngineSnapshotList())
15915 LoadEngineSnapshot_Redo(steps);
15920 static void HandleGameButtonsExt(int id, int button)
15922 static boolean game_undo_executed = FALSE;
15923 int steps = BUTTON_STEPSIZE(button);
15924 boolean handle_game_buttons =
15925 (game_status == GAME_MODE_PLAYING ||
15926 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15928 if (!handle_game_buttons)
15933 case GAME_CTRL_ID_STOP:
15934 case GAME_CTRL_ID_PANEL_STOP:
15935 case GAME_CTRL_ID_TOUCH_STOP:
15936 if (game_status == GAME_MODE_MAIN)
15942 RequestQuitGame(TRUE);
15946 case GAME_CTRL_ID_PAUSE:
15947 case GAME_CTRL_ID_PAUSE2:
15948 case GAME_CTRL_ID_PANEL_PAUSE:
15949 case GAME_CTRL_ID_TOUCH_PAUSE:
15950 if (network.enabled && game_status == GAME_MODE_PLAYING)
15953 SendToServer_ContinuePlaying();
15955 SendToServer_PausePlaying();
15958 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15960 game_undo_executed = FALSE;
15964 case GAME_CTRL_ID_PLAY:
15965 case GAME_CTRL_ID_PANEL_PLAY:
15966 if (game_status == GAME_MODE_MAIN)
15968 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15970 else if (tape.pausing)
15972 if (network.enabled)
15973 SendToServer_ContinuePlaying();
15975 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15979 case GAME_CTRL_ID_UNDO:
15980 // Important: When using "save snapshot when collecting an item" mode,
15981 // load last (current) snapshot for first "undo" after pressing "pause"
15982 // (else the last-but-one snapshot would be loaded, because the snapshot
15983 // pointer already points to the last snapshot when pressing "pause",
15984 // which is fine for "every step/move" mode, but not for "every collect")
15985 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15986 !game_undo_executed)
15989 game_undo_executed = TRUE;
15994 case GAME_CTRL_ID_REDO:
15998 case GAME_CTRL_ID_SAVE:
16002 case GAME_CTRL_ID_LOAD:
16006 case SOUND_CTRL_ID_MUSIC:
16007 case SOUND_CTRL_ID_PANEL_MUSIC:
16008 if (setup.sound_music)
16010 setup.sound_music = FALSE;
16014 else if (audio.music_available)
16016 setup.sound = setup.sound_music = TRUE;
16018 SetAudioMode(setup.sound);
16020 if (game_status == GAME_MODE_PLAYING)
16024 RedrawSoundButtonGadget(id);
16028 case SOUND_CTRL_ID_LOOPS:
16029 case SOUND_CTRL_ID_PANEL_LOOPS:
16030 if (setup.sound_loops)
16031 setup.sound_loops = FALSE;
16032 else if (audio.loops_available)
16034 setup.sound = setup.sound_loops = TRUE;
16036 SetAudioMode(setup.sound);
16039 RedrawSoundButtonGadget(id);
16043 case SOUND_CTRL_ID_SIMPLE:
16044 case SOUND_CTRL_ID_PANEL_SIMPLE:
16045 if (setup.sound_simple)
16046 setup.sound_simple = FALSE;
16047 else if (audio.sound_available)
16049 setup.sound = setup.sound_simple = TRUE;
16051 SetAudioMode(setup.sound);
16054 RedrawSoundButtonGadget(id);
16063 static void HandleGameButtons(struct GadgetInfo *gi)
16065 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16068 void HandleSoundButtonKeys(Key key)
16070 if (key == setup.shortcut.sound_simple)
16071 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16072 else if (key == setup.shortcut.sound_loops)
16073 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16074 else if (key == setup.shortcut.sound_music)
16075 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);