fixed bug with screen relocation on even sized playfield screen
[rocksndiamonds.git] / src / game.c
index 1f4d8a01a378252d1d5e1b75f6d4edd756cc2668..b68f6be1e470eb6295ba830bc6f8c2611fc4f6e2 100644 (file)
@@ -1,15 +1,13 @@
-/***********************************************************
-* Rocks'n'Diamonds -- McDuffin Strikes Back!               *
-*----------------------------------------------------------*
-* (c) 1995-2002 Artsoft Entertainment                      *
-*               Holger Schemel                             *
-*               Detmolder Strasse 189                      *
-*               33604 Bielefeld                            *
-*               Germany                                    *
-*               e-mail: info@artsoft.org                   *
-*----------------------------------------------------------*
-* game.c                                                   *
-***********************************************************/
+// ============================================================================
+// Rocks'n'Diamonds - McDuffin Strikes Back!
+// ----------------------------------------------------------------------------
+// (c) 1995-2014 by Artsoft Entertainment
+//                         Holger Schemel
+//                 info@artsoft.org
+//                 http://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// game.c
+// ============================================================================
 
 #include "libgame/libgame.h"
 
 #include "init.h"
 #include "tools.h"
 #include "screens.h"
+#include "events.h"
 #include "files.h"
 #include "tape.h"
 #include "network.h"
+#include "anim.h"
 
-/* EXPERIMENTAL STUFF */
+
+// DEBUG SETTINGS
+#define DEBUG_INIT_PLAYER      1
+#define DEBUG_PLAYER_ACTIONS   0
+
+// EXPERIMENTAL STUFF
 #define USE_NEW_AMOEBA_CODE    FALSE
 
-/* EXPERIMENTAL STUFF */
-#define USE_NEW_STUFF                  (                         1)
+// EXPERIMENTAL STUFF
+#define USE_QUICKSAND_BD_ROCK_BUGFIX   0
+#define USE_QUICKSAND_IMPACT_BUGFIX    0
+#define USE_DELAYED_GFX_REDRAW         0
+#define USE_NEW_PLAYER_ASSIGNMENTS     1
+
+#if USE_DELAYED_GFX_REDRAW
+#define TEST_DrawLevelField(x, y)                              \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE
+#define TEST_DrawLevelFieldCrumbled(x, y)                      \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
+#define TEST_DrawLevelFieldCrumbledNeighbours(x, y)            \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
+#define TEST_DrawTwinkleOnField(x, y)                          \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
+#else
+#define TEST_DrawLevelField(x, y)                              \
+            DrawLevelField(x, y)
+#define TEST_DrawLevelFieldCrumbled(x, y)                      \
+            DrawLevelFieldCrumbled(x, y)
+#define TEST_DrawLevelFieldCrumbledNeighbours(x, y)            \
+            DrawLevelFieldCrumbledNeighbours(x, y)
+#define TEST_DrawTwinkleOnField(x, y)                          \
+            DrawTwinkleOnField(x, y)
+#endif
 
-#define USE_NEW_SP_SLIPPERY            (USE_NEW_STUFF          * 1)
-#define USE_NEW_CUSTOM_VALUE           (USE_NEW_STUFF          * 1)
-#define USE_NEW_PLAYER_ANIM            (USE_NEW_STUFF          * 1)
-#define USE_NEW_ALL_SLIPPERY           (USE_NEW_STUFF          * 1)
-#define USE_NEW_PLAYER_SPEED           (USE_NEW_STUFF          * 1)
-#define USE_NEW_DELAYED_ACTION         (USE_NEW_STUFF          * 1)
-#define USE_NEW_SNAP_DELAY             (USE_NEW_STUFF          * 1)
-#define USE_ONLY_ONE_CHANGE_PER_FRAME  (USE_NEW_STUFF          * 1)
-#define USE_QUICKSAND_IMPACT_BUGFIX    (USE_NEW_STUFF          * 0)
 
-/* for DigField() */
+// for DigField()
 #define DF_NO_PUSH             0
 #define DF_DIG                 1
 #define DF_SNAP                        2
 
-/* for MovePlayer() */
-#define MF_NO_ACTION           0
-#define MF_MOVING              1
-#define MF_ACTION              2
+// for MovePlayer()
+#define MP_NO_ACTION           0
+#define MP_MOVING              1
+#define MP_ACTION              2
+#define MP_DONT_RUN_INTO       (MP_MOVING | MP_ACTION)
 
-/* for ScrollPlayer() */
+// for ScrollPlayer()
 #define SCROLL_INIT            0
 #define SCROLL_GO_ON           1
 
-/* for Bang()/Explode() */
+// for Bang()/Explode()
 #define EX_PHASE_START         0
 #define EX_TYPE_NONE           0
 #define EX_TYPE_NORMAL         (1 << 0)
 #define EX_TYPE_DYNA           (1 << 4)
 #define EX_TYPE_SINGLE_TILE    (EX_TYPE_CENTER | EX_TYPE_BORDER)
 
-/* special positions in the game control window (relative to control window) */
-#define XX_LEVEL               37
-#define YY_LEVEL               20
-#define XX_EMERALDS            29
-#define YY_EMERALDS            54
-#define XX_DYNAMITE            29
-#define YY_DYNAMITE            89
-#define XX_KEYS                        18
-#define YY_KEYS                        123
-#define XX_SCORE               15
-#define YY_SCORE               159
-#define XX_TIME1               29
-#define XX_TIME2               30
-#define YY_TIME                        194
-
-/* special positions in the game control window (relative to main window) */
-#define DX_LEVEL               (DX + XX_LEVEL)
-#define DY_LEVEL               (DY + YY_LEVEL)
-#define DX_EMERALDS            (DX + XX_EMERALDS)
-#define DY_EMERALDS            (DY + YY_EMERALDS)
-#define DX_DYNAMITE            (DX + XX_DYNAMITE)
-#define DY_DYNAMITE            (DY + YY_DYNAMITE)
-#define DX_KEYS                        (DX + XX_KEYS)
-#define DY_KEYS                        (DY + YY_KEYS)
-#define DX_SCORE               (DX + XX_SCORE)
-#define DY_SCORE               (DY + YY_SCORE)
-#define DX_TIME1               (DX + XX_TIME1)
-#define DX_TIME2               (DX + XX_TIME2)
-#define DY_TIME                        (DY + YY_TIME)
-
-/* values for initial player move delay (initial delay counter value) */
-#define INITIAL_MOVE_DELAY_OFF -1
-#define INITIAL_MOVE_DELAY_ON  0
-
-/* values for player movement speed (which is in fact a delay value) */
-#define MOVE_DELAY_MIN_SPEED   32
-#define MOVE_DELAY_NORMAL_SPEED        8
-#define MOVE_DELAY_HIGH_SPEED  4
-#define MOVE_DELAY_MAX_SPEED   1
-
-#if 0
-#define DOUBLE_MOVE_DELAY(x)   (x = (x <= MOVE_DELAY_HIGH_SPEED ? x * 2 : x))
-#define HALVE_MOVE_DELAY(x)    (x = (x >= MOVE_DELAY_HIGH_SPEED ? x / 2 : x))
-#else
-#define DOUBLE_MOVE_DELAY(x)   (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
-#define HALVE_MOVE_DELAY(x)    (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
-#endif
-#define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY((p)->move_delay_value))
-#define HALVE_PLAYER_SPEED(p)  (DOUBLE_MOVE_DELAY((p)->move_delay_value))
-
-/* values for other actions */
-#define MOVE_STEPSIZE_NORMAL   (TILEX / MOVE_DELAY_NORMAL_SPEED)
-#define MOVE_STEPSIZE_MIN      (1)
-#define MOVE_STEPSIZE_MAX      (TILEX)
-
-#define GET_DX_FROM_DIR(d)     ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
-#define GET_DY_FROM_DIR(d)     ((d) == MV_UP   ? -1 : (d) == MV_DOWN  ? 1 : 0)
-
-#define        INIT_GFX_RANDOM()       (SimpleRND(1000000))
-
-#define GET_NEW_PUSH_DELAY(e)  (   (element_info[e].push_delay_fixed) + \
-                                RND(element_info[e].push_delay_random))
-#define GET_NEW_DROP_DELAY(e)  (   (element_info[e].drop_delay_fixed) + \
-                                RND(element_info[e].drop_delay_random))
-#define GET_NEW_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
-                                RND(element_info[e].move_delay_random))
-#define GET_MAX_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
-                                   (element_info[e].move_delay_random))
-#define GET_NEW_CUSTOM_VALUE(e)        (   (element_info[e].ce_value_fixed_initial) +\
-                                RND(element_info[e].ce_value_random_initial))
-#define GET_CHANGE_DELAY(c)    (   ((c)->delay_fixed  * (c)->delay_frames) + \
-                                RND((c)->delay_random * (c)->delay_frames))
-#define GET_CE_DELAY_VALUE(c)  (   ((c)->delay_fixed) + \
-                                RND((c)->delay_random))
-
-#define GET_TARGET_ELEMENT(e, ch)                                      \
-       ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element :     \
-        (e) == EL_TRIGGER_PLAYER  ? (ch)->actual_trigger_player : (e))
-
-#define CAN_GROW_INTO(e)                                               \
-       ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
-
-#define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition)                        \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (condition)))
-
-#define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition)             \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       (condition)))
-
-#define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition)             \
-               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       (condition)))
-
-#define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition)             \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (condition) ||                  \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       (DONT_COLLIDE_WITH(e) &&        \
-                                        IS_PLAYER(x, y) &&             \
-                                        !PLAYER_ENEMY_PROTECTED(x, y))))
-
-#define ELEMENT_CAN_ENTER_FIELD(e, x, y)                               \
-       ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
-
-#define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
-
-#define ENEMY_CAN_ENTER_FIELD(e, x, y)                                 \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
-
-#define YAMYAM_CAN_ENTER_FIELD(e, x, y)                                        \
-       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
-
-#define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y)                           \
-       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
-
-#define PACMAN_CAN_ENTER_FIELD(e, x, y)                                        \
-       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
-
-#define PIG_CAN_ENTER_FIELD(e, x, y)                                   \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
-
-#define PENGUIN_CAN_ENTER_FIELD(e, x, y)                               \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN ||\
-                                                IS_FOOD_PENGUIN(Feld[x][y])))
-#define DRAGON_CAN_ENTER_FIELD(e, x, y)                                        \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
-
-#define MOLE_CAN_ENTER_FIELD(e, x, y, condition)                       \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
-
-#define SPRING_CAN_ENTER_FIELD(e, x, y)                                        \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
-
-#define GROUP_NR(e)            ((e) - EL_GROUP_START)
-#define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
-#define IS_IN_GROUP(e, nr)     (element_info[e].in_group[nr] == TRUE)
-#define IS_IN_GROUP_EL(e, ge)  (IS_IN_GROUP(e, (ge) - EL_GROUP_START))
-
-#define IS_EQUAL_OR_IN_GROUP(e, ge)                                    \
-       (IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge))
-
-#define CE_ENTER_FIELD_COND(e, x, y)                                   \
-               (!IS_PLAYER(x, y) &&                                    \
-                IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
-
-#define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y)                                \
-       ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
-
-#define IN_LEV_FIELD_AND_IS_FREE(x, y)  (IN_LEV_FIELD(x, y) &&  IS_FREE(x, y))
-#define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
-
-#define ACCESS_FROM(e, d)              (element_info[e].access_direction &(d))
-#define IS_WALKABLE_FROM(e, d)         (IS_WALKABLE(e)   && ACCESS_FROM(e, d))
-#define IS_PASSABLE_FROM(e, d)         (IS_PASSABLE(e)   && ACCESS_FROM(e, d))
-#define IS_ACCESSIBLE_FROM(e, d)       (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
-
-/* game button identifiers */
-#define GAME_CTRL_ID_STOP              0
-#define GAME_CTRL_ID_PAUSE             1
-#define GAME_CTRL_ID_PLAY              2
-#define SOUND_CTRL_ID_MUSIC            3
-#define SOUND_CTRL_ID_LOOPS            4
-#define SOUND_CTRL_ID_SIMPLE           5
-
-#define NUM_GAME_BUTTONS               6
-
-
-/* forward declaration for internal use */
-
-static void SetPlayerWaiting(struct PlayerInfo *, boolean);
-static void AdvanceFrameAndPlayerCounters(int);
-
-static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
-static boolean MovePlayer(struct PlayerInfo *, int, int);
-static void ScrollPlayer(struct PlayerInfo *, int);
-static void ScrollScreen(struct PlayerInfo *, int);
-
-int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
-
-static void InitBeltMovement(void);
-static void CloseAllOpenTimegates(void);
-static void CheckGravityMovement(struct PlayerInfo *);
-static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
-static void KillPlayerUnlessEnemyProtected(int, int);
-static void KillPlayerUnlessExplosionProtected(int, int);
-
-static void TestIfPlayerTouchesCustomElement(int, int);
-static void TestIfElementTouchesCustomElement(int, int);
-static void TestIfElementHitsCustomElement(int, int, int);
-#if 0
-static void TestIfElementSmashesCustomElement(int, int, int);
-#endif
-
-static void ChangeElement(int, int, int);
-
-static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
-#define CheckTriggeredElementChange(x, y, e, ev)                       \
-       CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
-#define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s)         \
-       CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
-#define CheckTriggeredElementChangeBySide(x, y, e, ev, s)              \
-       CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
-#define CheckTriggeredElementChangeByPage(x, y, e, ev, p)              \
-       CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
-
-static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
-#define CheckElementChange(x, y, e, te, ev)                            \
-       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
-#define CheckElementChangeByPlayer(x, y, e, ev, p, s)                  \
-       CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
-#define CheckElementChangeBySide(x, y, e, te, ev, s)                   \
-       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
-
-static void PlayLevelSound(int, int, int);
-static void PlayLevelSoundNearest(int, int, int);
-static void PlayLevelSoundAction(int, int, int);
-static void PlayLevelSoundElementAction(int, int, int, int);
-static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
-static void PlayLevelSoundActionIfLoop(int, int, int);
-static void StopLevelSoundActionIfLoop(int, int, int);
-static void PlayLevelMusic();
-
-static void MapGameButtons();
-static void HandleGameButtons(struct GadgetInfo *);
-
-static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
-
-
-/* ------------------------------------------------------------------------- */
-/* definition of elements that automatically change to other elements after  */
-/* a specified time, eventually calling a function when changing             */
-/* ------------------------------------------------------------------------- */
+#define PANEL_OFF()            (game.panel.active == FALSE)
+#define        PANEL_DEACTIVATED(p)    ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
+#define PANEL_XPOS(p)          (DX + ALIGNED_TEXT_XPOS(p))
+#define PANEL_YPOS(p)          (DY + ALIGNED_TEXT_YPOS(p))
+
+// game panel display and control definitions
+#define GAME_PANEL_LEVEL_NUMBER                        0
+#define GAME_PANEL_GEMS                                1
+#define GAME_PANEL_INVENTORY_COUNT             2
+#define GAME_PANEL_INVENTORY_FIRST_1           3
+#define GAME_PANEL_INVENTORY_FIRST_2           4
+#define GAME_PANEL_INVENTORY_FIRST_3           5
+#define GAME_PANEL_INVENTORY_FIRST_4           6
+#define GAME_PANEL_INVENTORY_FIRST_5           7
+#define GAME_PANEL_INVENTORY_FIRST_6           8
+#define GAME_PANEL_INVENTORY_FIRST_7           9
+#define GAME_PANEL_INVENTORY_FIRST_8           10
+#define GAME_PANEL_INVENTORY_LAST_1            11
+#define GAME_PANEL_INVENTORY_LAST_2            12
+#define GAME_PANEL_INVENTORY_LAST_3            13
+#define GAME_PANEL_INVENTORY_LAST_4            14
+#define GAME_PANEL_INVENTORY_LAST_5            15
+#define GAME_PANEL_INVENTORY_LAST_6            16
+#define GAME_PANEL_INVENTORY_LAST_7            17
+#define GAME_PANEL_INVENTORY_LAST_8            18
+#define GAME_PANEL_KEY_1                       19
+#define GAME_PANEL_KEY_2                       20
+#define GAME_PANEL_KEY_3                       21
+#define GAME_PANEL_KEY_4                       22
+#define GAME_PANEL_KEY_5                       23
+#define GAME_PANEL_KEY_6                       24
+#define GAME_PANEL_KEY_7                       25
+#define GAME_PANEL_KEY_8                       26
+#define GAME_PANEL_KEY_WHITE                   27
+#define GAME_PANEL_KEY_WHITE_COUNT             28
+#define GAME_PANEL_SCORE                       29
+#define GAME_PANEL_HIGHSCORE                   30
+#define GAME_PANEL_TIME                                31
+#define GAME_PANEL_TIME_HH                     32
+#define GAME_PANEL_TIME_MM                     33
+#define GAME_PANEL_TIME_SS                     34
+#define GAME_PANEL_TIME_ANIM                   35
+#define GAME_PANEL_HEALTH                      36
+#define GAME_PANEL_HEALTH_ANIM                 37
+#define GAME_PANEL_FRAME                       38
+#define GAME_PANEL_SHIELD_NORMAL               39
+#define GAME_PANEL_SHIELD_NORMAL_TIME          40
+#define GAME_PANEL_SHIELD_DEADLY               41
+#define GAME_PANEL_SHIELD_DEADLY_TIME          42
+#define GAME_PANEL_EXIT                                43
+#define GAME_PANEL_EMC_MAGIC_BALL              44
+#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH       45
+#define GAME_PANEL_LIGHT_SWITCH                        46
+#define GAME_PANEL_LIGHT_SWITCH_TIME           47
+#define GAME_PANEL_TIMEGATE_SWITCH             48
+#define GAME_PANEL_TIMEGATE_SWITCH_TIME                49
+#define GAME_PANEL_SWITCHGATE_SWITCH           50
+#define GAME_PANEL_EMC_LENSES                  51
+#define GAME_PANEL_EMC_LENSES_TIME             52
+#define GAME_PANEL_EMC_MAGNIFIER               53
+#define GAME_PANEL_EMC_MAGNIFIER_TIME          54
+#define GAME_PANEL_BALLOON_SWITCH              55
+#define GAME_PANEL_DYNABOMB_NUMBER             56
+#define GAME_PANEL_DYNABOMB_SIZE               57
+#define GAME_PANEL_DYNABOMB_POWER              58
+#define GAME_PANEL_PENGUINS                    59
+#define GAME_PANEL_SOKOBAN_OBJECTS             60
+#define GAME_PANEL_SOKOBAN_FIELDS              61
+#define GAME_PANEL_ROBOT_WHEEL                 62
+#define GAME_PANEL_CONVEYOR_BELT_1             63
+#define GAME_PANEL_CONVEYOR_BELT_2             64
+#define GAME_PANEL_CONVEYOR_BELT_3             65
+#define GAME_PANEL_CONVEYOR_BELT_4             66
+#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH      67
+#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH      68
+#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH      69
+#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH      70
+#define GAME_PANEL_MAGIC_WALL                  71
+#define GAME_PANEL_MAGIC_WALL_TIME             72
+#define GAME_PANEL_GRAVITY_STATE               73
+#define GAME_PANEL_GRAPHIC_1                   74
+#define GAME_PANEL_GRAPHIC_2                   75
+#define GAME_PANEL_GRAPHIC_3                   76
+#define GAME_PANEL_GRAPHIC_4                   77
+#define GAME_PANEL_GRAPHIC_5                   78
+#define GAME_PANEL_GRAPHIC_6                   79
+#define GAME_PANEL_GRAPHIC_7                   80
+#define GAME_PANEL_GRAPHIC_8                   81
+#define GAME_PANEL_ELEMENT_1                   82
+#define GAME_PANEL_ELEMENT_2                   83
+#define GAME_PANEL_ELEMENT_3                   84
+#define GAME_PANEL_ELEMENT_4                   85
+#define GAME_PANEL_ELEMENT_5                   86
+#define GAME_PANEL_ELEMENT_6                   87
+#define GAME_PANEL_ELEMENT_7                   88
+#define GAME_PANEL_ELEMENT_8                   89
+#define GAME_PANEL_ELEMENT_COUNT_1             90
+#define GAME_PANEL_ELEMENT_COUNT_2             91
+#define GAME_PANEL_ELEMENT_COUNT_3             92
+#define GAME_PANEL_ELEMENT_COUNT_4             93
+#define GAME_PANEL_ELEMENT_COUNT_5             94
+#define GAME_PANEL_ELEMENT_COUNT_6             95
+#define GAME_PANEL_ELEMENT_COUNT_7             96
+#define GAME_PANEL_ELEMENT_COUNT_8             97
+#define GAME_PANEL_CE_SCORE_1                  98
+#define GAME_PANEL_CE_SCORE_2                  99
+#define GAME_PANEL_CE_SCORE_3                  100
+#define GAME_PANEL_CE_SCORE_4                  101
+#define GAME_PANEL_CE_SCORE_5                  102
+#define GAME_PANEL_CE_SCORE_6                  103
+#define GAME_PANEL_CE_SCORE_7                  104
+#define GAME_PANEL_CE_SCORE_8                  105
+#define GAME_PANEL_CE_SCORE_1_ELEMENT          106
+#define GAME_PANEL_CE_SCORE_2_ELEMENT          107
+#define GAME_PANEL_CE_SCORE_3_ELEMENT          108
+#define GAME_PANEL_CE_SCORE_4_ELEMENT          109
+#define GAME_PANEL_CE_SCORE_5_ELEMENT          110
+#define GAME_PANEL_CE_SCORE_6_ELEMENT          111
+#define GAME_PANEL_CE_SCORE_7_ELEMENT          112
+#define GAME_PANEL_CE_SCORE_8_ELEMENT          113
+#define GAME_PANEL_PLAYER_NAME                 114
+#define GAME_PANEL_LEVEL_NAME                  115
+#define GAME_PANEL_LEVEL_AUTHOR                        116
+
+#define NUM_GAME_PANEL_CONTROLS                        117
+
+struct GamePanelOrderInfo
+{
+  int nr;
+  int sort_priority;
+};
 
-/* forward declaration for changer functions */
-static void InitBuggyBase(int x, int y);
-static void WarnBuggyBase(int x, int y);
+static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
 
-static void InitTrap(int x, int y);
-static void ActivateTrap(int x, int y);
-static void ChangeActiveTrap(int x, int y);
+struct GamePanelControlInfo
+{
+  int nr;
 
-static void InitRobotWheel(int x, int y);
-static void RunRobotWheel(int x, int y);
-static void StopRobotWheel(int x, int y);
+  struct TextPosInfo *pos;
+  int type;
 
-static void InitTimegateWheel(int x, int y);
-static void RunTimegateWheel(int x, int y);
+  int graphic, graphic_active;
 
-struct ChangingElementInfo
-{
-  int element;
-  int target_element;
-  int change_delay;
-  void (*pre_change_function)(int x, int y);
-  void (*change_function)(int x, int y);
-  void (*post_change_function)(int x, int y);
+  int value, last_value;
+  int frame, last_frame;
+  int gfx_frame;
+  int gfx_random;
 };
 
-static struct ChangingElementInfo change_delay_list[] =
+static struct GamePanelControlInfo game_panel_controls[] =
 {
   {
-    EL_NUT_BREAKING,
-    EL_EMERALD,
-    6,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_LEVEL_NUMBER,
+    &game.panel.level_number,
+    TYPE_INTEGER,
   },
   {
-    EL_PEARL_BREAKING,
-    EL_EMPTY,
-    8,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_GEMS,
+    &game.panel.gems,
+    TYPE_INTEGER,
   },
   {
-    EL_EXIT_OPENING,
-    EL_EXIT_OPEN,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_COUNT,
+    &game.panel.inventory_count,
+    TYPE_INTEGER,
   },
   {
-    EL_EXIT_CLOSING,
-    EL_EXIT_CLOSED,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_1,
+    &game.panel.inventory_first[0],
+    TYPE_ELEMENT,
   },
   {
-    EL_SP_EXIT_OPENING,
-    EL_SP_EXIT_OPEN,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_2,
+    &game.panel.inventory_first[1],
+    TYPE_ELEMENT,
   },
   {
-    EL_SP_EXIT_CLOSING,
-    EL_SP_EXIT_CLOSED,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_3,
+    &game.panel.inventory_first[2],
+    TYPE_ELEMENT,
   },
   {
-    EL_SWITCHGATE_OPENING,
-    EL_SWITCHGATE_OPEN,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_4,
+    &game.panel.inventory_first[3],
+    TYPE_ELEMENT,
   },
   {
-    EL_SWITCHGATE_CLOSING,
-    EL_SWITCHGATE_CLOSED,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_5,
+    &game.panel.inventory_first[4],
+    TYPE_ELEMENT,
   },
   {
-    EL_TIMEGATE_OPENING,
-    EL_TIMEGATE_OPEN,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_6,
+    &game.panel.inventory_first[5],
+    TYPE_ELEMENT,
   },
   {
-    EL_TIMEGATE_CLOSING,
-    EL_TIMEGATE_CLOSED,
-    29,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_7,
+    &game.panel.inventory_first[6],
+    TYPE_ELEMENT,
   },
-
   {
-    EL_ACID_SPLASH_LEFT,
-    EL_EMPTY,
-    8,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_FIRST_8,
+    &game.panel.inventory_first[7],
+    TYPE_ELEMENT,
   },
   {
-    EL_ACID_SPLASH_RIGHT,
-    EL_EMPTY,
-    8,
-    NULL,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_LAST_1,
+    &game.panel.inventory_last[0],
+    TYPE_ELEMENT,
   },
   {
-    EL_SP_BUGGY_BASE,
-    EL_SP_BUGGY_BASE_ACTIVATING,
-    0,
-    InitBuggyBase,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_LAST_2,
+    &game.panel.inventory_last[1],
+    TYPE_ELEMENT,
   },
   {
-    EL_SP_BUGGY_BASE_ACTIVATING,
-    EL_SP_BUGGY_BASE_ACTIVE,
-    0,
-    InitBuggyBase,
-    NULL,
-    NULL
+    GAME_PANEL_INVENTORY_LAST_3,
+    &game.panel.inventory_last[2],
+    TYPE_ELEMENT,
   },
   {
-    EL_SP_BUGGY_BASE_ACTIVE,
-    EL_SP_BUGGY_BASE,
-    0,
-    InitBuggyBase,
-    WarnBuggyBase,
-    NULL
+    GAME_PANEL_INVENTORY_LAST_4,
+    &game.panel.inventory_last[3],
+    TYPE_ELEMENT,
   },
   {
-    EL_TRAP,
-    EL_TRAP_ACTIVE,
-    0,
-    InitTrap,
-    NULL,
-    ActivateTrap
+    GAME_PANEL_INVENTORY_LAST_5,
+    &game.panel.inventory_last[4],
+    TYPE_ELEMENT,
   },
   {
-    EL_TRAP_ACTIVE,
-    EL_TRAP,
-    31,
-    NULL,
-    ChangeActiveTrap,
-    NULL
+    GAME_PANEL_INVENTORY_LAST_6,
+    &game.panel.inventory_last[5],
+    TYPE_ELEMENT,
   },
   {
-    EL_ROBOT_WHEEL_ACTIVE,
-    EL_ROBOT_WHEEL,
-    0,
-    InitRobotWheel,
-    RunRobotWheel,
-    StopRobotWheel
+    GAME_PANEL_INVENTORY_LAST_7,
+    &game.panel.inventory_last[6],
+    TYPE_ELEMENT,
   },
   {
-    EL_TIMEGATE_SWITCH_ACTIVE,
-    EL_TIMEGATE_SWITCH,
-    0,
-    InitTimegateWheel,
-    RunTimegateWheel,
-    NULL
+    GAME_PANEL_INVENTORY_LAST_8,
+    &game.panel.inventory_last[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_1,
+    &game.panel.key[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_2,
+    &game.panel.key[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_3,
+    &game.panel.key[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_4,
+    &game.panel.key[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_5,
+    &game.panel.key[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_6,
+    &game.panel.key[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_7,
+    &game.panel.key[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_8,
+    &game.panel.key[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_WHITE,
+    &game.panel.key_white,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_WHITE_COUNT,
+    &game.panel.key_white_count,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SCORE,
+    &game.panel.score,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_HIGHSCORE,
+    &game.panel.highscore,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME,
+    &game.panel.time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME_HH,
+    &game.panel.time_hh,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME_MM,
+    &game.panel.time_mm,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME_SS,
+    &game.panel.time_ss,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME_ANIM,
+    &game.panel.time_anim,
+    TYPE_GRAPHIC,
+
+    IMG_GFX_GAME_PANEL_TIME_ANIM,
+    IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
+  },
+  {
+    GAME_PANEL_HEALTH,
+    &game.panel.health,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_HEALTH_ANIM,
+    &game.panel.health_anim,
+    TYPE_GRAPHIC,
+
+    IMG_GFX_GAME_PANEL_HEALTH_ANIM,
+    IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
+  },
+  {
+    GAME_PANEL_FRAME,
+    &game.panel.frame,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SHIELD_NORMAL,
+    &game.panel.shield_normal,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_SHIELD_NORMAL_TIME,
+    &game.panel.shield_normal_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SHIELD_DEADLY,
+    &game.panel.shield_deadly,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_SHIELD_DEADLY_TIME,
+    &game.panel.shield_deadly_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_EXIT,
+    &game.panel.exit,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_MAGIC_BALL,
+    &game.panel.emc_magic_ball,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
+    &game.panel.emc_magic_ball_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_LIGHT_SWITCH,
+    &game.panel.light_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_LIGHT_SWITCH_TIME,
+    &game.panel.light_switch_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIMEGATE_SWITCH,
+    &game.panel.timegate_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_TIMEGATE_SWITCH_TIME,
+    &game.panel.timegate_switch_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SWITCHGATE_SWITCH,
+    &game.panel.switchgate_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_LENSES,
+    &game.panel.emc_lenses,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_LENSES_TIME,
+    &game.panel.emc_lenses_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_EMC_MAGNIFIER,
+    &game.panel.emc_magnifier,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_MAGNIFIER_TIME,
+    &game.panel.emc_magnifier_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_BALLOON_SWITCH,
+    &game.panel.balloon_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_DYNABOMB_NUMBER,
+    &game.panel.dynabomb_number,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_DYNABOMB_SIZE,
+    &game.panel.dynabomb_size,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_DYNABOMB_POWER,
+    &game.panel.dynabomb_power,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_PENGUINS,
+    &game.panel.penguins,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SOKOBAN_OBJECTS,
+    &game.panel.sokoban_objects,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SOKOBAN_FIELDS,
+    &game.panel.sokoban_fields,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ROBOT_WHEEL,
+    &game.panel.robot_wheel,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_1,
+    &game.panel.conveyor_belt[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_2,
+    &game.panel.conveyor_belt[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_3,
+    &game.panel.conveyor_belt[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_4,
+    &game.panel.conveyor_belt[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
+    &game.panel.conveyor_belt_switch[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
+    &game.panel.conveyor_belt_switch[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
+    &game.panel.conveyor_belt_switch[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
+    &game.panel.conveyor_belt_switch[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_MAGIC_WALL,
+    &game.panel.magic_wall,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_MAGIC_WALL_TIME,
+    &game.panel.magic_wall_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_GRAVITY_STATE,
+    &game.panel.gravity_state,
+    TYPE_STRING,
+  },
+  {
+    GAME_PANEL_GRAPHIC_1,
+    &game.panel.graphic[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_2,
+    &game.panel.graphic[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_3,
+    &game.panel.graphic[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_4,
+    &game.panel.graphic[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_5,
+    &game.panel.graphic[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_6,
+    &game.panel.graphic[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_7,
+    &game.panel.graphic[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_8,
+    &game.panel.graphic[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_1,
+    &game.panel.element[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_2,
+    &game.panel.element[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_3,
+    &game.panel.element[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_4,
+    &game.panel.element[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_5,
+    &game.panel.element[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_6,
+    &game.panel.element[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_7,
+    &game.panel.element[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_8,
+    &game.panel.element[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_1,
+    &game.panel.element_count[0],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_2,
+    &game.panel.element_count[1],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_3,
+    &game.panel.element_count[2],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_4,
+    &game.panel.element_count[3],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_5,
+    &game.panel.element_count[4],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_6,
+    &game.panel.element_count[5],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_7,
+    &game.panel.element_count[6],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_8,
+    &game.panel.element_count[7],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_1,
+    &game.panel.ce_score[0],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_2,
+    &game.panel.ce_score[1],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_3,
+    &game.panel.ce_score[2],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_4,
+    &game.panel.ce_score[3],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_5,
+    &game.panel.ce_score[4],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_6,
+    &game.panel.ce_score[5],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_7,
+    &game.panel.ce_score[6],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_8,
+    &game.panel.ce_score[7],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_1_ELEMENT,
+    &game.panel.ce_score_element[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_2_ELEMENT,
+    &game.panel.ce_score_element[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_3_ELEMENT,
+    &game.panel.ce_score_element[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_4_ELEMENT,
+    &game.panel.ce_score_element[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_5_ELEMENT,
+    &game.panel.ce_score_element[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_6_ELEMENT,
+    &game.panel.ce_score_element[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_7_ELEMENT,
+    &game.panel.ce_score_element[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_8_ELEMENT,
+    &game.panel.ce_score_element[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_PLAYER_NAME,
+    &game.panel.player_name,
+    TYPE_STRING,
+  },
+  {
+    GAME_PANEL_LEVEL_NAME,
+    &game.panel.level_name,
+    TYPE_STRING,
+  },
+  {
+    GAME_PANEL_LEVEL_AUTHOR,
+    &game.panel.level_author,
+    TYPE_STRING,
   },
 
   {
-    EL_UNDEFINED,
-    EL_UNDEFINED,
     -1,
     NULL,
-    NULL,
-    NULL
+    -1,
   }
 };
 
-struct
-{
-  int element;
-  int push_delay_fixed, push_delay_random;
-}
-push_delay_list[] =
-{
-  { EL_SPRING,                 0, 0 },
-  { EL_BALLOON,                        0, 0 },
+// values for delayed check of falling and moving elements and for collision
+#define CHECK_DELAY_MOVING     3
+#define CHECK_DELAY_FALLING    CHECK_DELAY_MOVING
+#define CHECK_DELAY_COLLISION  2
+#define CHECK_DELAY_IMPACT     CHECK_DELAY_COLLISION
 
-  { EL_SOKOBAN_OBJECT,         2, 0 },
-  { EL_SOKOBAN_FIELD_FULL,     2, 0 },
-  { EL_SATELLITE,              2, 0 },
-  { EL_SP_DISK_YELLOW,         2, 0 },
+// values for initial player move delay (initial delay counter value)
+#define INITIAL_MOVE_DELAY_OFF -1
+#define INITIAL_MOVE_DELAY_ON  0
 
-  { EL_UNDEFINED,              0, 0 },
-};
+// values for player movement speed (which is in fact a delay value)
+#define MOVE_DELAY_MIN_SPEED   32
+#define MOVE_DELAY_NORMAL_SPEED        8
+#define MOVE_DELAY_HIGH_SPEED  4
+#define MOVE_DELAY_MAX_SPEED   1
 
-struct
-{
-  int element;
-  int move_stepsize;
-}
-move_stepsize_list[] =
-{
-  { EL_AMOEBA_DROP,            2 },
-  { EL_AMOEBA_DROPPING,                2 },
-  { EL_QUICKSAND_FILLING,      1 },
-  { EL_QUICKSAND_EMPTYING,     1 },
-  { EL_MAGIC_WALL_FILLING,     2 },
-  { EL_BD_MAGIC_WALL_FILLING,  2 },
-  { EL_MAGIC_WALL_EMPTYING,    2 },
-  { EL_BD_MAGIC_WALL_EMPTYING, 2 },
+#define DOUBLE_MOVE_DELAY(x)   (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
+#define HALVE_MOVE_DELAY(x)    (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
 
-  { EL_UNDEFINED,              0 },
-};
+#define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
+#define HALVE_PLAYER_SPEED(p)  (DOUBLE_MOVE_DELAY((p)->move_delay_value))
 
-struct
-{
-  int element;
-  int count;
-}
-collect_count_list[] =
-{
-  { EL_EMERALD,                        1 },
-  { EL_BD_DIAMOND,             1 },
-  { EL_EMERALD_YELLOW,         1 },
-  { EL_EMERALD_RED,            1 },
-  { EL_EMERALD_PURPLE,         1 },
-  { EL_DIAMOND,                        3 },
-  { EL_SP_INFOTRON,            1 },
-  { EL_PEARL,                  5 },
-  { EL_CRYSTAL,                        8 },
+// values for scroll positions
+#define SCROLL_POSITION_X(x)   ((x) < SBX_Left  + MIDPOSX ? SBX_Left : \
+                                (x) > SBX_Right + MIDPOSX ? SBX_Right :\
+                                (x) - MIDPOSX)
+#define SCROLL_POSITION_Y(y)   ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
+                                (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
+                                (y) - MIDPOSY)
 
-  { EL_UNDEFINED,              0 },
-};
+// values for other actions
+#define MOVE_STEPSIZE_NORMAL   (TILEX / MOVE_DELAY_NORMAL_SPEED)
+#define MOVE_STEPSIZE_MIN      (1)
+#define MOVE_STEPSIZE_MAX      (TILEX)
 
-struct
-{
-  int element;
-  int direction;
-}
-access_direction_list[] =
-{
-  { EL_TUBE_ANY,                       MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
-  { EL_TUBE_VERTICAL,                                       MV_UP | MV_DOWN },
-  { EL_TUBE_HORIZONTAL,                        MV_LEFT | MV_RIGHT                   },
-  { EL_TUBE_VERTICAL_LEFT,             MV_LEFT |            MV_UP | MV_DOWN },
-  { EL_TUBE_VERTICAL_RIGHT,                      MV_RIGHT | MV_UP | MV_DOWN },
-  { EL_TUBE_HORIZONTAL_UP,             MV_LEFT | MV_RIGHT | MV_UP           },
-  { EL_TUBE_HORIZONTAL_DOWN,           MV_LEFT | MV_RIGHT |         MV_DOWN },
-  { EL_TUBE_LEFT_UP,                   MV_LEFT |            MV_UP           },
-  { EL_TUBE_LEFT_DOWN,                 MV_LEFT |                    MV_DOWN },
-  { EL_TUBE_RIGHT_UP,                            MV_RIGHT | MV_UP           },
-  { EL_TUBE_RIGHT_DOWN,                                  MV_RIGHT |         MV_DOWN },
+#define GET_DX_FROM_DIR(d)     ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
+#define GET_DY_FROM_DIR(d)     ((d) == MV_UP   ? -1 : (d) == MV_DOWN  ? 1 : 0)
 
-  { EL_SP_PORT_LEFT,                             MV_RIGHT                   },
-  { EL_SP_PORT_RIGHT,                  MV_LEFT                              },
-  { EL_SP_PORT_UP,                                                  MV_DOWN },
-  { EL_SP_PORT_DOWN,                                        MV_UP           },
-  { EL_SP_PORT_HORIZONTAL,             MV_LEFT | MV_RIGHT                   },
-  { EL_SP_PORT_VERTICAL,                                    MV_UP | MV_DOWN },
-  { EL_SP_PORT_ANY,                    MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
-  { EL_SP_GRAVITY_PORT_LEFT,                     MV_RIGHT                   },
-  { EL_SP_GRAVITY_PORT_RIGHT,          MV_LEFT                              },
-  { EL_SP_GRAVITY_PORT_UP,                                          MV_DOWN },
-  { EL_SP_GRAVITY_PORT_DOWN,                                MV_UP           },
-  { EL_SP_GRAVITY_ON_PORT_LEFT,                          MV_RIGHT                   },
-  { EL_SP_GRAVITY_ON_PORT_RIGHT,       MV_LEFT                              },
-  { EL_SP_GRAVITY_ON_PORT_UP,                                       MV_DOWN },
-  { EL_SP_GRAVITY_ON_PORT_DOWN,                                     MV_UP           },
-  { EL_SP_GRAVITY_OFF_PORT_LEFT,                 MV_RIGHT                   },
-  { EL_SP_GRAVITY_OFF_PORT_RIGHT,      MV_LEFT                              },
-  { EL_SP_GRAVITY_OFF_PORT_UP,                                      MV_DOWN },
-  { EL_SP_GRAVITY_OFF_PORT_DOWN,                            MV_UP           },
+#define        INIT_GFX_RANDOM()       (GetSimpleRandom(1000000))
 
-  { EL_UNDEFINED,                      MV_NONE                              }
-};
+#define GET_NEW_PUSH_DELAY(e)  (   (element_info[e].push_delay_fixed) + \
+                                RND(element_info[e].push_delay_random))
+#define GET_NEW_DROP_DELAY(e)  (   (element_info[e].drop_delay_fixed) + \
+                                RND(element_info[e].drop_delay_random))
+#define GET_NEW_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
+                                RND(element_info[e].move_delay_random))
+#define GET_MAX_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
+                                   (element_info[e].move_delay_random))
+#define GET_NEW_CE_VALUE(e)    (   (element_info[e].ce_value_fixed_initial) +\
+                                RND(element_info[e].ce_value_random_initial))
+#define GET_CE_SCORE(e)                (   (element_info[e].collect_score))
+#define GET_CHANGE_DELAY(c)    (   ((c)->delay_fixed  * (c)->delay_frames) + \
+                                RND((c)->delay_random * (c)->delay_frames))
+#define GET_CE_DELAY_VALUE(c)  (   ((c)->delay_fixed) + \
+                                RND((c)->delay_random))
 
-static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
 
-#define IS_AUTO_CHANGING(e)    (element_info[e].has_change_event[CE_DELAY])
-#define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
-#define IS_CHANGING(x, y)      (IS_AUTO_CHANGING(Feld[x][y]) || \
-                                IS_JUST_CHANGING(x, y))
+#define GET_VALID_RUNTIME_ELEMENT(e)                                   \
+        ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
 
-#define CE_PAGE(e, ce)         (element_info[e].event_page[ce])
+#define RESOLVED_REFERENCE_ELEMENT(be, e)                              \
+       ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START :     \
+        (be) + (e) - EL_SELF > EL_CUSTOM_END   ? EL_CUSTOM_END :       \
+        (be) + (e) - EL_SELF)
 
-/* static variables for playfield scan mode (scanning forward or backward) */
-static int playfield_scan_start_x = 0;
-static int playfield_scan_start_y = 0;
-static int playfield_scan_delta_x = 1;
-static int playfield_scan_delta_y = 1;
+#define GET_PLAYER_FROM_BITS(p)                                                \
+       (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
 
-#define SCAN_PLAYFIELD(x, y)   for ((y) = playfield_scan_start_y;      \
-                                    (y) >= 0 && (y) <= lev_fieldy - 1; \
-                                    (y) += playfield_scan_delta_y)     \
-                               for ((x) = playfield_scan_start_x;      \
-                                    (x) >= 0 && (x) <= lev_fieldx - 1; \
-                                    (x) += playfield_scan_delta_x)     \
+#define GET_TARGET_ELEMENT(be, e, ch, cv, cs)                          \
+       ((e) == EL_TRIGGER_PLAYER   ? (ch)->actual_trigger_player    :  \
+        (e) == EL_TRIGGER_ELEMENT  ? (ch)->actual_trigger_element   :  \
+        (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value  :  \
+        (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score  :  \
+        (e) == EL_CURRENT_CE_VALUE ? (cv) :                            \
+        (e) == EL_CURRENT_CE_SCORE ? (cs) :                            \
+        (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ?                   \
+        RESOLVED_REFERENCE_ELEMENT(be, e) :                            \
+        (e))
 
-static void InitPlayfieldScanModeVars()
-{
-  if (game.use_reverse_scan_direction)
-  {
-    playfield_scan_start_x = lev_fieldx - 1;
-    playfield_scan_start_y = lev_fieldy - 1;
+#define CAN_GROW_INTO(e)                                               \
+       ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
 
-    playfield_scan_delta_x = -1;
-    playfield_scan_delta_y = -1;
-  }
-  else
-  {
-    playfield_scan_start_x = 0;
-    playfield_scan_start_y = 0;
+#define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition)                        \
+               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
+                                       (condition)))
 
-    playfield_scan_delta_x = 1;
-    playfield_scan_delta_y = 1;
-  }
-}
+#define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition)             \
+               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
+                                        Feld[x][y] == EL_ACID) ||      \
+                                       (condition)))
 
-static void InitPlayfieldScanMode(int mode)
-{
-  game.use_reverse_scan_direction =
-    (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
+#define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition)             \
+               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
+                                        Feld[x][y] == EL_ACID) ||      \
+                                       (condition)))
 
-  InitPlayfieldScanModeVars();
-}
+#define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition)             \
+               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
+                                       (condition) ||                  \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
+                                        Feld[x][y] == EL_ACID) ||      \
+                                       (DONT_COLLIDE_WITH(e) &&        \
+                                        IS_PLAYER(x, y) &&             \
+                                        !PLAYER_ENEMY_PROTECTED(x, y))))
 
-void GetPlayerConfig()
-{
-  if (!audio.sound_available)
-    setup.sound_simple = FALSE;
+#define ELEMENT_CAN_ENTER_FIELD(e, x, y)                               \
+       ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
 
-  if (!audio.loops_available)
-    setup.sound_loops = FALSE;
+#define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
 
-  if (!audio.music_available)
-    setup.sound_music = FALSE;
+#define ANDROID_CAN_ENTER_FIELD(e, x, y)                               \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
 
-  if (!video.fullscreen_available)
-    setup.fullscreen = FALSE;
+#define ANDROID_CAN_CLONE_FIELD(x, y)                                  \
+       (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
+                               CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
 
-  setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
+#define ENEMY_CAN_ENTER_FIELD(e, x, y)                                 \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
-  SetAudioMode(setup.sound);
-  InitJoysticks();
-}
+#define YAMYAM_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
 
-static int getBeltNrFromBeltElement(int element)
-{
-  return (element < EL_CONVEYOR_BELT_2_LEFT ? 0 :
-         element < EL_CONVEYOR_BELT_3_LEFT ? 1 :
-         element < EL_CONVEYOR_BELT_4_LEFT ? 2 : 3);
-}
+#define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y)                           \
+       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
 
-static int getBeltNrFromBeltActiveElement(int element)
-{
-  return (element < EL_CONVEYOR_BELT_2_LEFT_ACTIVE ? 0 :
-         element < EL_CONVEYOR_BELT_3_LEFT_ACTIVE ? 1 :
-         element < EL_CONVEYOR_BELT_4_LEFT_ACTIVE ? 2 : 3);
-}
+#define PACMAN_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
 
-static int getBeltNrFromBeltSwitchElement(int element)
-{
-  return (element < EL_CONVEYOR_BELT_2_SWITCH_LEFT ? 0 :
-         element < EL_CONVEYOR_BELT_3_SWITCH_LEFT ? 1 :
-         element < EL_CONVEYOR_BELT_4_SWITCH_LEFT ? 2 : 3);
-}
+#define PIG_CAN_ENTER_FIELD(e, x, y)                                   \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
 
-static int getBeltDirNrFromBeltSwitchElement(int element)
-{
-  static int belt_base_element[4] =
-  {
-    EL_CONVEYOR_BELT_1_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_2_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_3_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_4_SWITCH_LEFT
-  };
+#define PENGUIN_CAN_ENTER_FIELD(e, x, y)                               \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
+                                                Feld[x][y] == EL_EM_EXIT_OPEN || \
+                                                Feld[x][y] == EL_STEEL_EXIT_OPEN || \
+                                                Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
+                                                IS_FOOD_PENGUIN(Feld[x][y])))
+#define DRAGON_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
-  int belt_nr = getBeltNrFromBeltSwitchElement(element);
-  int belt_dir_nr = element - belt_base_element[belt_nr];
+#define MOLE_CAN_ENTER_FIELD(e, x, y, condition)                       \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
 
-  return (belt_dir_nr % 3);
-}
+#define SPRING_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
-static int getBeltDirFromBeltSwitchElement(int element)
-{
-  static int belt_move_dir[3] =
-  {
-    MV_LEFT,
-    MV_NONE,
-    MV_RIGHT
-  };
+#define SPRING_CAN_BUMP_FROM_FIELD(x, y)                               \
+       (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER ||   \
+                               Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
 
-  int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(element);
+#define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
 
-  return belt_move_dir[belt_dir_nr];
-}
+#define CE_ENTER_FIELD_COND(e, x, y)                                   \
+               (!IS_PLAYER(x, y) &&                                    \
+                IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
 
-static void InitPlayerField(int x, int y, int element, boolean init_game)
-{
-  if (element == EL_SP_MURPHY)
-  {
-    if (init_game)
-    {
-      if (stored_player[0].present)
-      {
-       Feld[x][y] = EL_SP_MURPHY_CLONE;
+#define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y)                                \
+       ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
 
-       return;
-      }
-      else
-      {
-       stored_player[0].use_murphy = TRUE;
-      }
+#define IN_LEV_FIELD_AND_IS_FREE(x, y)  (IN_LEV_FIELD(x, y) &&  IS_FREE(x, y))
+#define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
 
-      Feld[x][y] = EL_PLAYER_1;
-    }
-  }
-
-  if (init_game)
-  {
-    struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
-    int jx = player->jx, jy = player->jy;
-
-    player->present = TRUE;
-
-    player->block_last_field = (element == EL_SP_MURPHY ?
-                               level.sp_block_last_field :
-                               level.block_last_field);
-
-    /* ---------- initialize player's last field block delay --------------- */
-
-    /* always start with reliable default value (no adjustment needed) */
-    player->block_delay_adjustment = 0;
-
-    /* special case 1: in Supaplex, Murphy blocks last field one more frame */
-    if (player->block_last_field && element == EL_SP_MURPHY)
-      player->block_delay_adjustment = 1;
-
-    /* special case 2: in game engines before 3.1.1, blocking was different */
-    if (game.use_block_last_field_bug)
-      player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
-
-    if (!options.network || player->connected)
-    {
-      player->active = TRUE;
-
-      /* remove potentially duplicate players */
-      if (StorePlayer[jx][jy] == Feld[x][y])
-       StorePlayer[jx][jy] = 0;
-
-      StorePlayer[x][y] = Feld[x][y];
-
-      if (options.debug)
-      {
-       printf("Player %d activated.\n", player->element_nr);
-       printf("[Local player is %d and currently %s.]\n",
-              local_player->element_nr,
-              local_player->active ? "active" : "not active");
-      }
-    }
-
-    Feld[x][y] = EL_EMPTY;
-
-    player->jx = player->last_jx = x;
-    player->jy = player->last_jy = y;
-  }
-}
+#define ACCESS_FROM(e, d)              (element_info[e].access_direction &(d))
+#define IS_WALKABLE_FROM(e, d)         (IS_WALKABLE(e)   && ACCESS_FROM(e, d))
+#define IS_PASSABLE_FROM(e, d)         (IS_PASSABLE(e)   && ACCESS_FROM(e, d))
+#define IS_ACCESSIBLE_FROM(e, d)       (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
 
-static void InitField(int x, int y, boolean init_game)
-{
-  int element = Feld[x][y];
+#define MM_HEALTH(x)           (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
 
-  switch (element)
-  {
-    case EL_SP_MURPHY:
-    case EL_PLAYER_1:
-    case EL_PLAYER_2:
-    case EL_PLAYER_3:
-    case EL_PLAYER_4:
-      InitPlayerField(x, y, element, init_game);
-      break;
+// game button identifiers
+#define GAME_CTRL_ID_STOP              0
+#define GAME_CTRL_ID_PAUSE             1
+#define GAME_CTRL_ID_PLAY              2
+#define GAME_CTRL_ID_UNDO              3
+#define GAME_CTRL_ID_REDO              4
+#define GAME_CTRL_ID_SAVE              5
+#define GAME_CTRL_ID_PAUSE2            6
+#define GAME_CTRL_ID_LOAD              7
+#define GAME_CTRL_ID_PANEL_STOP                8
+#define GAME_CTRL_ID_PANEL_PAUSE       9
+#define GAME_CTRL_ID_PANEL_PLAY                10
+#define SOUND_CTRL_ID_MUSIC            11
+#define SOUND_CTRL_ID_LOOPS            12
+#define SOUND_CTRL_ID_SIMPLE           13
+#define SOUND_CTRL_ID_PANEL_MUSIC      14
+#define SOUND_CTRL_ID_PANEL_LOOPS      15
+#define SOUND_CTRL_ID_PANEL_SIMPLE     16
 
-    case EL_SOKOBAN_FIELD_PLAYER:
-      element = Feld[x][y] = EL_PLAYER_1;
-      InitField(x, y, init_game);
+#define NUM_GAME_BUTTONS               17
 
-      element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
-      InitField(x, y, init_game);
-      break;
 
-    case EL_SOKOBAN_FIELD_EMPTY:
-      local_player->sokobanfields_still_needed++;
-      break;
+// forward declaration for internal use
 
-    case EL_STONEBLOCK:
-      if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
-       Feld[x][y] = EL_ACID_POOL_TOPLEFT;
-      else if (x > 0 && Feld[x-1][y] == EL_ACID)
-       Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
-      else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
-       Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
-      else if (y > 0 && Feld[x][y-1] == EL_ACID)
-       Feld[x][y] = EL_ACID_POOL_BOTTOM;
-      else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
-       Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
-      break;
+static void CreateField(int, int, int);
 
-    case EL_BUG_RIGHT:
-    case EL_BUG_UP:
-    case EL_BUG_LEFT:
-    case EL_BUG_DOWN:
-    case EL_BUG:
-    case EL_SPACESHIP_RIGHT:
-    case EL_SPACESHIP_UP:
-    case EL_SPACESHIP_LEFT:
-    case EL_SPACESHIP_DOWN:
-    case EL_SPACESHIP:
-    case EL_BD_BUTTERFLY_RIGHT:
-    case EL_BD_BUTTERFLY_UP:
-    case EL_BD_BUTTERFLY_LEFT:
-    case EL_BD_BUTTERFLY_DOWN:
-    case EL_BD_BUTTERFLY:
-    case EL_BD_FIREFLY_RIGHT:
-    case EL_BD_FIREFLY_UP:
-    case EL_BD_FIREFLY_LEFT:
-    case EL_BD_FIREFLY_DOWN:
-    case EL_BD_FIREFLY:
-    case EL_PACMAN_RIGHT:
-    case EL_PACMAN_UP:
-    case EL_PACMAN_LEFT:
-    case EL_PACMAN_DOWN:
-    case EL_YAMYAM:
-    case EL_DARK_YAMYAM:
-    case EL_ROBOT:
-    case EL_PACMAN:
-    case EL_SP_SNIKSNAK:
-    case EL_SP_ELECTRON:
-    case EL_MOLE_LEFT:
-    case EL_MOLE_RIGHT:
-    case EL_MOLE_UP:
-    case EL_MOLE_DOWN:
-    case EL_MOLE:
-      InitMovDir(x, y);
-      break;
+static void ResetGfxAnimation(int, int);
 
-    case EL_AMOEBA_FULL:
-    case EL_BD_AMOEBA:
-      InitAmoebaNr(x, y);
-      break;
+static void SetPlayerWaiting(struct PlayerInfo *, boolean);
+static void AdvanceFrameAndPlayerCounters(int);
 
-    case EL_AMOEBA_DROP:
-      if (y == lev_fieldy - 1)
-      {
-       Feld[x][y] = EL_AMOEBA_GROWING;
-       Store[x][y] = EL_AMOEBA_WET;
-      }
-      break;
+static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
+static boolean MovePlayer(struct PlayerInfo *, int, int);
+static void ScrollPlayer(struct PlayerInfo *, int);
+static void ScrollScreen(struct PlayerInfo *, int);
 
-    case EL_DYNAMITE_ACTIVE:
-    case EL_SP_DISK_RED_ACTIVE:
-    case EL_DYNABOMB_PLAYER_1_ACTIVE:
-    case EL_DYNABOMB_PLAYER_2_ACTIVE:
-    case EL_DYNABOMB_PLAYER_3_ACTIVE:
-    case EL_DYNABOMB_PLAYER_4_ACTIVE:
-      MovDelay[x][y] = 96;
-      break;
+static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
+static boolean DigFieldByCE(int, int, int);
+static boolean SnapField(struct PlayerInfo *, int, int);
+static boolean DropElement(struct PlayerInfo *);
 
-    case EL_LAMP:
-      local_player->lights_still_needed++;
-      break;
+static void InitBeltMovement(void);
+static void CloseAllOpenTimegates(void);
+static void CheckGravityMovement(struct PlayerInfo *);
+static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
+static void KillPlayerUnlessEnemyProtected(int, int);
+static void KillPlayerUnlessExplosionProtected(int, int);
 
-    case EL_PENGUIN:
-      local_player->friends_still_needed++;
-      break;
+static void TestIfPlayerTouchesCustomElement(int, int);
+static void TestIfElementTouchesCustomElement(int, int);
+static void TestIfElementHitsCustomElement(int, int, int);
 
-    case EL_PIG:
-    case EL_DRAGON:
-      GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
-      break;
+static void HandleElementChange(int, int, int);
+static void ExecuteCustomElementAction(int, int, int, int);
+static boolean ChangeElement(int, int, int, int);
 
-    case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
-    case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
-    case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
-    case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
-    case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
-    case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
-    case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
-    case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
-    case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
-    case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
-    case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
-    case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
-      if (init_game)
-      {
-       int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
-       int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
-       int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
+static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
+#define CheckTriggeredElementChange(x, y, e, ev)                       \
+       CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
+#define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s)         \
+       CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
+#define CheckTriggeredElementChangeBySide(x, y, e, ev, s)              \
+       CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
+#define CheckTriggeredElementChangeByPage(x, y, e, ev, p)              \
+       CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
 
-       if (game.belt_dir_nr[belt_nr] == 3)     /* initial value */
-       {
-         game.belt_dir[belt_nr] = belt_dir;
-         game.belt_dir_nr[belt_nr] = belt_dir_nr;
-       }
-       else    /* more than one switch -- set it like the first switch */
-       {
-         Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
-       }
-      }
-      break;
+static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
+#define CheckElementChange(x, y, e, te, ev)                            \
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
+#define CheckElementChangeByPlayer(x, y, e, ev, p, s)                  \
+       CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
+#define CheckElementChangeBySide(x, y, e, te, ev, s)                   \
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
 
-    case EL_SWITCHGATE_SWITCH_DOWN:    /* always start with same switch pos */
-      if (init_game)
-       Feld[x][y] = EL_SWITCHGATE_SWITCH_UP;
-      break;
+static void PlayLevelSound(int, int, int);
+static void PlayLevelSoundNearest(int, int, int);
+static void PlayLevelSoundAction(int, int, int);
+static void PlayLevelSoundElementAction(int, int, int, int);
+static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
+static void PlayLevelSoundActionIfLoop(int, int, int);
+static void StopLevelSoundActionIfLoop(int, int, int);
+static void PlayLevelMusic(void);
+static void FadeLevelSoundsAndMusic(void);
 
-    case EL_LIGHT_SWITCH_ACTIVE:
-      if (init_game)
-       game.light_time_left = level.time_light * FRAMES_PER_SECOND;
-      break;
+static void HandleGameButtons(struct GadgetInfo *);
 
-    default:
-#if 1
-      if (IS_CUSTOM_ELEMENT(element))
-      {
-       if (CAN_MOVE(element))
-         InitMovDir(x, y);
+int AmoebeNachbarNr(int, int);
+void AmoebeUmwandeln(int, int);
+void ContinueMoving(int, int);
+void Bang(int, int);
+void InitMovDir(int, int);
+void InitAmoebaNr(int, int);
+int NewHiScore(int);
+
+void TestIfGoodThingHitsBadThing(int, int, int);
+void TestIfBadThingHitsGoodThing(int, int, int);
+void TestIfPlayerTouchesBadThing(int, int);
+void TestIfPlayerRunsIntoBadThing(int, int, int);
+void TestIfBadThingTouchesPlayer(int, int);
+void TestIfBadThingRunsIntoPlayer(int, int, int);
+void TestIfFriendTouchesBadThing(int, int);
+void TestIfBadThingTouchesFriend(int, int);
+void TestIfBadThingTouchesOtherBadThing(int, int);
+void TestIfGoodThingGetsHitByBadThing(int, int, int);
+
+void KillPlayer(struct PlayerInfo *);
+void BuryPlayer(struct PlayerInfo *);
+void RemovePlayer(struct PlayerInfo *);
+void ExitPlayer(struct PlayerInfo *);
+
+static int getInvisibleActiveFromInvisibleElement(int);
+static int getInvisibleFromInvisibleActiveElement(int);
 
-#if USE_NEW_CUSTOM_VALUE
-       if (!element_info[element].use_last_ce_value || init_game)
-         CustomValue[x][y] = GET_NEW_CUSTOM_VALUE(Feld[x][y]);
-#endif
-      }
-#else
-      if (IS_CUSTOM_ELEMENT(element) && CAN_MOVE(element))
-       InitMovDir(x, y);
-#endif
-      else if (IS_GROUP_ELEMENT(element))
-      {
-       struct ElementGroupInfo *group = element_info[element].group;
-       int last_anim_random_frame = gfx.anim_random_frame;
-       int element_pos;
+static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 
-       if (group->choice_mode == ANIM_RANDOM)
-         gfx.anim_random_frame = RND(group->num_elements_resolved);
+// for detection of endless loops, caused by custom element programming
+// (using maximal playfield width x 10 is just a rough approximation)
+#define MAX_ELEMENT_CHANGE_RECURSION_DEPTH     (MAX_PLAYFIELD_WIDTH * 10)
 
-       element_pos = getAnimationFrame(group->num_elements_resolved, 1,
-                                       group->choice_mode, 0,
-                                       group->choice_pos);
+#define RECURSION_LOOP_DETECTION_START(e, rc)                          \
+{                                                                      \
+  if (recursion_loop_detected)                                         \
+    return (rc);                                                       \
+                                                                       \
+  if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH)       \
+  {                                                                    \
+    recursion_loop_detected = TRUE;                                    \
+    recursion_loop_element = (e);                                      \
+  }                                                                    \
+                                                                       \
+  recursion_loop_depth++;                                              \
+}
 
-       if (group->choice_mode == ANIM_RANDOM)
-         gfx.anim_random_frame = last_anim_random_frame;
+#define RECURSION_LOOP_DETECTION_END()                                 \
+{                                                                      \
+  recursion_loop_depth--;                                              \
+}
 
-       group->choice_pos++;
+static int recursion_loop_depth;
+static boolean recursion_loop_detected;
+static boolean recursion_loop_element;
 
-       Feld[x][y] = group->element_resolved[element_pos];
+static int map_player_action[MAX_PLAYERS];
 
-       InitField(x, y, init_game);
-      }
-      break;
-  }
 
-#if 1
-  if (!init_game)
-    CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
-#endif
+// ----------------------------------------------------------------------------
+// definition of elements that automatically change to other elements after
+// a specified time, eventually calling a function when changing
+// ----------------------------------------------------------------------------
 
-#if 0
+// forward declaration for changer functions
+static void InitBuggyBase(int, int);
+static void WarnBuggyBase(int, int);
 
-#if USE_NEW_CUSTOM_VALUE
+static void InitTrap(int, int);
+static void ActivateTrap(int, int);
+static void ChangeActiveTrap(int, int);
 
-#if 1
-  CustomValue[x][y] = GET_NEW_CUSTOM_VALUE(Feld[x][y]);
-#else
-  CustomValue[x][y] = element_info[Feld[x][y]].custom_value_initial;
-#endif
+static void InitRobotWheel(int, int);
+static void RunRobotWheel(int, int);
+static void StopRobotWheel(int, int);
 
-#endif
+static void InitTimegateWheel(int, int);
+static void RunTimegateWheel(int, int);
 
-#endif
-}
+static void InitMagicBallDelay(int, int);
+static void ActivateMagicBall(int, int);
 
-static inline void InitField_WithBug1(int x, int y, boolean init_game)
+struct ChangingElementInfo
 {
-  InitField(x, y, init_game);
-
-  /* not needed to call InitMovDir() -- already done by InitField()! */
-  if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
-      CAN_MOVE(Feld[x][y]))
-    InitMovDir(x, y);
-}
+  int element;
+  int target_element;
+  int change_delay;
+  void (*pre_change_function)(int x, int y);
+  void (*change_function)(int x, int y);
+  void (*post_change_function)(int x, int y);
+};
+
+static struct ChangingElementInfo change_delay_list[] =
+{
+  {
+    EL_NUT_BREAKING,
+    EL_EMERALD,
+    6,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_PEARL_BREAKING,
+    EL_EMPTY,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EXIT_OPENING,
+    EL_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EXIT_CLOSING,
+    EL_EXIT_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_STEEL_EXIT_OPENING,
+    EL_STEEL_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_STEEL_EXIT_CLOSING,
+    EL_STEEL_EXIT_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_EXIT_OPENING,
+    EL_EM_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_EXIT_CLOSING,
+    EL_EMPTY,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_STEEL_EXIT_OPENING,
+    EL_EM_STEEL_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_STEEL_EXIT_CLOSING,
+    EL_STEELWALL,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_EXIT_OPENING,
+    EL_SP_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_EXIT_CLOSING,
+    EL_SP_EXIT_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SWITCHGATE_OPENING,
+    EL_SWITCHGATE_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SWITCHGATE_CLOSING,
+    EL_SWITCHGATE_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_TIMEGATE_OPENING,
+    EL_TIMEGATE_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_TIMEGATE_CLOSING,
+    EL_TIMEGATE_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+
+  {
+    EL_ACID_SPLASH_LEFT,
+    EL_EMPTY,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_ACID_SPLASH_RIGHT,
+    EL_EMPTY,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_BUGGY_BASE,
+    EL_SP_BUGGY_BASE_ACTIVATING,
+    0,
+    InitBuggyBase,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_BUGGY_BASE_ACTIVATING,
+    EL_SP_BUGGY_BASE_ACTIVE,
+    0,
+    InitBuggyBase,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_BUGGY_BASE_ACTIVE,
+    EL_SP_BUGGY_BASE,
+    0,
+    InitBuggyBase,
+    WarnBuggyBase,
+    NULL
+  },
+  {
+    EL_TRAP,
+    EL_TRAP_ACTIVE,
+    0,
+    InitTrap,
+    NULL,
+    ActivateTrap
+  },
+  {
+    EL_TRAP_ACTIVE,
+    EL_TRAP,
+    31,
+    NULL,
+    ChangeActiveTrap,
+    NULL
+  },
+  {
+    EL_ROBOT_WHEEL_ACTIVE,
+    EL_ROBOT_WHEEL,
+    0,
+    InitRobotWheel,
+    RunRobotWheel,
+    StopRobotWheel
+  },
+  {
+    EL_TIMEGATE_SWITCH_ACTIVE,
+    EL_TIMEGATE_SWITCH,
+    0,
+    InitTimegateWheel,
+    RunTimegateWheel,
+    NULL
+  },
+  {
+    EL_DC_TIMEGATE_SWITCH_ACTIVE,
+    EL_DC_TIMEGATE_SWITCH,
+    0,
+    InitTimegateWheel,
+    RunTimegateWheel,
+    NULL
+  },
+  {
+    EL_EMC_MAGIC_BALL_ACTIVE,
+    EL_EMC_MAGIC_BALL_ACTIVE,
+    0,
+    InitMagicBallDelay,
+    NULL,
+    ActivateMagicBall
+  },
+  {
+    EL_EMC_SPRING_BUMPER_ACTIVE,
+    EL_EMC_SPRING_BUMPER,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_DIAGONAL_SHRINKING,
+    EL_UNDEFINED,
+    0,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_DIAGONAL_GROWING,
+    EL_UNDEFINED,
+    0,
+    NULL,
+    NULL,
+    NULL,
+  },
+
+  {
+    EL_UNDEFINED,
+    EL_UNDEFINED,
+    -1,
+    NULL,
+    NULL,
+    NULL
+  }
+};
+
+struct
+{
+  int element;
+  int push_delay_fixed, push_delay_random;
+}
+push_delay_list[] =
+{
+  { EL_SPRING,                 0, 0 },
+  { EL_BALLOON,                        0, 0 },
+
+  { EL_SOKOBAN_OBJECT,         2, 0 },
+  { EL_SOKOBAN_FIELD_FULL,     2, 0 },
+  { EL_SATELLITE,              2, 0 },
+  { EL_SP_DISK_YELLOW,         2, 0 },
+
+  { EL_UNDEFINED,              0, 0 },
+};
+
+struct
+{
+  int element;
+  int move_stepsize;
+}
+move_stepsize_list[] =
+{
+  { EL_AMOEBA_DROP,            2 },
+  { EL_AMOEBA_DROPPING,                2 },
+  { EL_QUICKSAND_FILLING,      1 },
+  { EL_QUICKSAND_EMPTYING,     1 },
+  { EL_QUICKSAND_FAST_FILLING, 2 },
+  { EL_QUICKSAND_FAST_EMPTYING,        2 },
+  { EL_MAGIC_WALL_FILLING,     2 },
+  { EL_MAGIC_WALL_EMPTYING,    2 },
+  { EL_BD_MAGIC_WALL_FILLING,  2 },
+  { EL_BD_MAGIC_WALL_EMPTYING, 2 },
+  { EL_DC_MAGIC_WALL_FILLING,  2 },
+  { EL_DC_MAGIC_WALL_EMPTYING, 2 },
+
+  { EL_UNDEFINED,              0 },
+};
+
+struct
+{
+  int element;
+  int count;
+}
+collect_count_list[] =
+{
+  { EL_EMERALD,                        1 },
+  { EL_BD_DIAMOND,             1 },
+  { EL_EMERALD_YELLOW,         1 },
+  { EL_EMERALD_RED,            1 },
+  { EL_EMERALD_PURPLE,         1 },
+  { EL_DIAMOND,                        3 },
+  { EL_SP_INFOTRON,            1 },
+  { EL_PEARL,                  5 },
+  { EL_CRYSTAL,                        8 },
+
+  { EL_UNDEFINED,              0 },
+};
+
+struct
+{
+  int element;
+  int direction;
+}
+access_direction_list[] =
+{
+  { EL_TUBE_ANY,                       MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
+  { EL_TUBE_VERTICAL,                                       MV_UP | MV_DOWN },
+  { EL_TUBE_HORIZONTAL,                        MV_LEFT | MV_RIGHT                   },
+  { EL_TUBE_VERTICAL_LEFT,             MV_LEFT |            MV_UP | MV_DOWN },
+  { EL_TUBE_VERTICAL_RIGHT,                      MV_RIGHT | MV_UP | MV_DOWN },
+  { EL_TUBE_HORIZONTAL_UP,             MV_LEFT | MV_RIGHT | MV_UP           },
+  { EL_TUBE_HORIZONTAL_DOWN,           MV_LEFT | MV_RIGHT |         MV_DOWN },
+  { EL_TUBE_LEFT_UP,                   MV_LEFT |            MV_UP           },
+  { EL_TUBE_LEFT_DOWN,                 MV_LEFT |                    MV_DOWN },
+  { EL_TUBE_RIGHT_UP,                            MV_RIGHT | MV_UP           },
+  { EL_TUBE_RIGHT_DOWN,                                  MV_RIGHT |         MV_DOWN },
+
+  { EL_SP_PORT_LEFT,                             MV_RIGHT                   },
+  { EL_SP_PORT_RIGHT,                  MV_LEFT                              },
+  { EL_SP_PORT_UP,                                                  MV_DOWN },
+  { EL_SP_PORT_DOWN,                                        MV_UP           },
+  { EL_SP_PORT_HORIZONTAL,             MV_LEFT | MV_RIGHT                   },
+  { EL_SP_PORT_VERTICAL,                                    MV_UP | MV_DOWN },
+  { EL_SP_PORT_ANY,                    MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
+  { EL_SP_GRAVITY_PORT_LEFT,                     MV_RIGHT                   },
+  { EL_SP_GRAVITY_PORT_RIGHT,          MV_LEFT                              },
+  { EL_SP_GRAVITY_PORT_UP,                                          MV_DOWN },
+  { EL_SP_GRAVITY_PORT_DOWN,                                MV_UP           },
+  { EL_SP_GRAVITY_ON_PORT_LEFT,                          MV_RIGHT                   },
+  { EL_SP_GRAVITY_ON_PORT_RIGHT,       MV_LEFT                              },
+  { EL_SP_GRAVITY_ON_PORT_UP,                                       MV_DOWN },
+  { EL_SP_GRAVITY_ON_PORT_DOWN,                                     MV_UP           },
+  { EL_SP_GRAVITY_OFF_PORT_LEFT,                 MV_RIGHT                   },
+  { EL_SP_GRAVITY_OFF_PORT_RIGHT,      MV_LEFT                              },
+  { EL_SP_GRAVITY_OFF_PORT_UP,                                      MV_DOWN },
+  { EL_SP_GRAVITY_OFF_PORT_DOWN,                            MV_UP           },
+
+  { EL_UNDEFINED,                      MV_NONE                              }
+};
+
+static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
+
+#define IS_AUTO_CHANGING(e)    (element_info[e].has_change_event[CE_DELAY])
+#define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
+#define IS_CHANGING(x, y)      (IS_AUTO_CHANGING(Feld[x][y]) || \
+                                IS_JUST_CHANGING(x, y))
+
+#define CE_PAGE(e, ce)         (element_info[e].event_page[ce])
+
+// static variables for playfield scan mode (scanning forward or backward)
+static int playfield_scan_start_x = 0;
+static int playfield_scan_start_y = 0;
+static int playfield_scan_delta_x = 1;
+static int playfield_scan_delta_y = 1;
+
+#define SCAN_PLAYFIELD(x, y)   for ((y) = playfield_scan_start_y;      \
+                                    (y) >= 0 && (y) <= lev_fieldy - 1; \
+                                    (y) += playfield_scan_delta_y)     \
+                               for ((x) = playfield_scan_start_x;      \
+                                    (x) >= 0 && (x) <= lev_fieldx - 1; \
+                                    (x) += playfield_scan_delta_x)
+
+#ifdef DEBUG
+void DEBUG_SetMaximumDynamite(void)
+{
+  int i;
+
+  for (i = 0; i < MAX_INVENTORY_SIZE; i++)
+    if (local_player->inventory_size < MAX_INVENTORY_SIZE)
+      local_player->inventory_element[local_player->inventory_size++] =
+       EL_DYNAMITE;
+}
+#endif
+
+static void InitPlayfieldScanModeVars(void)
+{
+  if (game.use_reverse_scan_direction)
+  {
+    playfield_scan_start_x = lev_fieldx - 1;
+    playfield_scan_start_y = lev_fieldy - 1;
+
+    playfield_scan_delta_x = -1;
+    playfield_scan_delta_y = -1;
+  }
+  else
+  {
+    playfield_scan_start_x = 0;
+    playfield_scan_start_y = 0;
+
+    playfield_scan_delta_x = 1;
+    playfield_scan_delta_y = 1;
+  }
+}
+
+static void InitPlayfieldScanMode(int mode)
+{
+  game.use_reverse_scan_direction =
+    (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
+
+  InitPlayfieldScanModeVars();
+}
+
+static int get_move_delay_from_stepsize(int move_stepsize)
+{
+  move_stepsize =
+    MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
+
+  // make sure that stepsize value is always a power of 2
+  move_stepsize = (1 << log_2(move_stepsize));
+
+  return TILEX / move_stepsize;
+}
+
+static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
+                              boolean init_game)
+{
+  int player_nr = player->index_nr;
+  int move_delay = get_move_delay_from_stepsize(move_stepsize);
+  boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
+
+  // do no immediately change move delay -- the player might just be moving
+  player->move_delay_value_next = move_delay;
+
+  // information if player can move must be set separately
+  player->cannot_move = cannot_move;
+
+  if (init_game)
+  {
+    player->move_delay       = game.initial_move_delay[player_nr];
+    player->move_delay_value = game.initial_move_delay_value[player_nr];
+
+    player->move_delay_value_next = -1;
+
+    player->move_delay_reset_counter = 0;
+  }
+}
+
+void GetPlayerConfig(void)
+{
+  GameFrameDelay = setup.game_frame_delay;
+
+  if (!audio.sound_available)
+    setup.sound_simple = FALSE;
+
+  if (!audio.loops_available)
+    setup.sound_loops = FALSE;
+
+  if (!audio.music_available)
+    setup.sound_music = FALSE;
+
+  if (!video.fullscreen_available)
+    setup.fullscreen = FALSE;
+
+  setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
+
+  SetAudioMode(setup.sound);
+}
+
+int GetElementFromGroupElement(int element)
+{
+  if (IS_GROUP_ELEMENT(element))
+  {
+    struct ElementGroupInfo *group = element_info[element].group;
+    int last_anim_random_frame = gfx.anim_random_frame;
+    int element_pos;
+
+    if (group->choice_mode == ANIM_RANDOM)
+      gfx.anim_random_frame = RND(group->num_elements_resolved);
+
+    element_pos = getAnimationFrame(group->num_elements_resolved, 1,
+                                   group->choice_mode, 0,
+                                   group->choice_pos);
+
+    if (group->choice_mode == ANIM_RANDOM)
+      gfx.anim_random_frame = last_anim_random_frame;
+
+    group->choice_pos++;
+
+    element = group->element_resolved[element_pos];
+  }
+
+  return element;
+}
+
+static void IncrementSokobanFieldsNeeded(void)
+{
+  if (level.sb_fields_needed)
+    game.sokoban_fields_still_needed++;
+}
+
+static void IncrementSokobanObjectsNeeded(void)
+{
+  if (level.sb_objects_needed)
+    game.sokoban_objects_still_needed++;
+}
+
+static void DecrementSokobanFieldsNeeded(void)
+{
+  if (game.sokoban_fields_still_needed > 0)
+    game.sokoban_fields_still_needed--;
+}
+
+static void DecrementSokobanObjectsNeeded(void)
+{
+  if (game.sokoban_objects_still_needed > 0)
+    game.sokoban_objects_still_needed--;
+}
+
+static void InitPlayerField(int x, int y, int element, boolean init_game)
+{
+  if (element == EL_SP_MURPHY)
+  {
+    if (init_game)
+    {
+      if (stored_player[0].present)
+      {
+       Feld[x][y] = EL_SP_MURPHY_CLONE;
+
+       return;
+      }
+      else
+      {
+       stored_player[0].initial_element = element;
+       stored_player[0].use_murphy = TRUE;
+
+       if (!level.use_artwork_element[0])
+         stored_player[0].artwork_element = EL_SP_MURPHY;
+      }
+
+      Feld[x][y] = EL_PLAYER_1;
+    }
+  }
+
+  if (init_game)
+  {
+    struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
+    int jx = player->jx, jy = player->jy;
+
+    player->present = TRUE;
+
+    player->block_last_field = (element == EL_SP_MURPHY ?
+                               level.sp_block_last_field :
+                               level.block_last_field);
+
+    // ---------- initialize player's last field block delay ------------------
+
+    // always start with reliable default value (no adjustment needed)
+    player->block_delay_adjustment = 0;
+
+    // special case 1: in Supaplex, Murphy blocks last field one more frame
+    if (player->block_last_field && element == EL_SP_MURPHY)
+      player->block_delay_adjustment = 1;
+
+    // special case 2: in game engines before 3.1.1, blocking was different
+    if (game.use_block_last_field_bug)
+      player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
+
+    if (!network.enabled || player->connected_network)
+    {
+      player->active = TRUE;
+
+      // remove potentially duplicate players
+      if (StorePlayer[jx][jy] == Feld[x][y])
+       StorePlayer[jx][jy] = 0;
+
+      StorePlayer[x][y] = Feld[x][y];
+
+#if DEBUG_INIT_PLAYER
+      if (options.debug)
+      {
+       printf("- player element %d activated", player->element_nr);
+       printf(" (local player is %d and currently %s)\n",
+              local_player->element_nr,
+              local_player->active ? "active" : "not active");
+      }
+    }
+#endif
+
+    Feld[x][y] = EL_EMPTY;
+
+    player->jx = player->last_jx = x;
+    player->jy = player->last_jy = y;
+  }
+
+  if (!init_game)
+  {
+    int player_nr = GET_PLAYER_NR(element);
+    struct PlayerInfo *player = &stored_player[player_nr];
+
+    if (player->active && player->killed)
+      player->reanimated = TRUE; // if player was just killed, reanimate him
+  }
+}
+
+static void InitField(int x, int y, boolean init_game)
+{
+  int element = Feld[x][y];
+
+  switch (element)
+  {
+    case EL_SP_MURPHY:
+    case EL_PLAYER_1:
+    case EL_PLAYER_2:
+    case EL_PLAYER_3:
+    case EL_PLAYER_4:
+      InitPlayerField(x, y, element, init_game);
+      break;
+
+    case EL_SOKOBAN_FIELD_PLAYER:
+      element = Feld[x][y] = EL_PLAYER_1;
+      InitField(x, y, init_game);
+
+      element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
+      InitField(x, y, init_game);
+      break;
+
+    case EL_SOKOBAN_FIELD_EMPTY:
+      IncrementSokobanFieldsNeeded();
+      break;
+
+    case EL_SOKOBAN_OBJECT:
+      IncrementSokobanObjectsNeeded();
+      break;
+
+    case EL_STONEBLOCK:
+      if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
+       Feld[x][y] = EL_ACID_POOL_TOPLEFT;
+      else if (x > 0 && Feld[x-1][y] == EL_ACID)
+       Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
+      else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
+       Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
+      else if (y > 0 && Feld[x][y-1] == EL_ACID)
+       Feld[x][y] = EL_ACID_POOL_BOTTOM;
+      else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
+       Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
+      break;
+
+    case EL_BUG:
+    case EL_BUG_RIGHT:
+    case EL_BUG_UP:
+    case EL_BUG_LEFT:
+    case EL_BUG_DOWN:
+    case EL_SPACESHIP:
+    case EL_SPACESHIP_RIGHT:
+    case EL_SPACESHIP_UP:
+    case EL_SPACESHIP_LEFT:
+    case EL_SPACESHIP_DOWN:
+    case EL_BD_BUTTERFLY:
+    case EL_BD_BUTTERFLY_RIGHT:
+    case EL_BD_BUTTERFLY_UP:
+    case EL_BD_BUTTERFLY_LEFT:
+    case EL_BD_BUTTERFLY_DOWN:
+    case EL_BD_FIREFLY:
+    case EL_BD_FIREFLY_RIGHT:
+    case EL_BD_FIREFLY_UP:
+    case EL_BD_FIREFLY_LEFT:
+    case EL_BD_FIREFLY_DOWN:
+    case EL_PACMAN_RIGHT:
+    case EL_PACMAN_UP:
+    case EL_PACMAN_LEFT:
+    case EL_PACMAN_DOWN:
+    case EL_YAMYAM:
+    case EL_YAMYAM_LEFT:
+    case EL_YAMYAM_RIGHT:
+    case EL_YAMYAM_UP:
+    case EL_YAMYAM_DOWN:
+    case EL_DARK_YAMYAM:
+    case EL_ROBOT:
+    case EL_PACMAN:
+    case EL_SP_SNIKSNAK:
+    case EL_SP_ELECTRON:
+    case EL_MOLE:
+    case EL_MOLE_LEFT:
+    case EL_MOLE_RIGHT:
+    case EL_MOLE_UP:
+    case EL_MOLE_DOWN:
+      InitMovDir(x, y);
+      break;
+
+    case EL_AMOEBA_FULL:
+    case EL_BD_AMOEBA:
+      InitAmoebaNr(x, y);
+      break;
+
+    case EL_AMOEBA_DROP:
+      if (y == lev_fieldy - 1)
+      {
+       Feld[x][y] = EL_AMOEBA_GROWING;
+       Store[x][y] = EL_AMOEBA_WET;
+      }
+      break;
+
+    case EL_DYNAMITE_ACTIVE:
+    case EL_SP_DISK_RED_ACTIVE:
+    case EL_DYNABOMB_PLAYER_1_ACTIVE:
+    case EL_DYNABOMB_PLAYER_2_ACTIVE:
+    case EL_DYNABOMB_PLAYER_3_ACTIVE:
+    case EL_DYNABOMB_PLAYER_4_ACTIVE:
+      MovDelay[x][y] = 96;
+      break;
+
+    case EL_EM_DYNAMITE_ACTIVE:
+      MovDelay[x][y] = 32;
+      break;
+
+    case EL_LAMP:
+      game.lights_still_needed++;
+      break;
+
+    case EL_PENGUIN:
+      game.friends_still_needed++;
+      break;
+
+    case EL_PIG:
+    case EL_DRAGON:
+      GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
+      break;
+
+    case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
+    case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
+    case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
+    case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
+    case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
+    case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
+    case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
+    case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
+    case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
+    case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
+    case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
+    case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
+      if (init_game)
+      {
+       int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
+       int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
+       int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
+
+       if (game.belt_dir_nr[belt_nr] == 3)     // initial value
+       {
+         game.belt_dir[belt_nr] = belt_dir;
+         game.belt_dir_nr[belt_nr] = belt_dir_nr;
+       }
+       else    // more than one switch -- set it like the first switch
+       {
+         Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
+       }
+      }
+      break;
+
+    case EL_LIGHT_SWITCH_ACTIVE:
+      if (init_game)
+       game.light_time_left = level.time_light * FRAMES_PER_SECOND;
+      break;
+
+    case EL_INVISIBLE_STEELWALL:
+    case EL_INVISIBLE_WALL:
+    case EL_INVISIBLE_SAND:
+      if (game.light_time_left > 0 ||
+         game.lenses_time_left > 0)
+        Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+      break;
+
+    case EL_EMC_MAGIC_BALL:
+      if (game.ball_state)
+       Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
+      break;
+
+    case EL_EMC_MAGIC_BALL_SWITCH:
+      if (game.ball_state)
+       Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
+      break;
+
+    case EL_TRIGGER_PLAYER:
+    case EL_TRIGGER_ELEMENT:
+    case EL_TRIGGER_CE_VALUE:
+    case EL_TRIGGER_CE_SCORE:
+    case EL_SELF:
+    case EL_ANY_ELEMENT:
+    case EL_CURRENT_CE_VALUE:
+    case EL_CURRENT_CE_SCORE:
+    case EL_PREV_CE_1:
+    case EL_PREV_CE_2:
+    case EL_PREV_CE_3:
+    case EL_PREV_CE_4:
+    case EL_PREV_CE_5:
+    case EL_PREV_CE_6:
+    case EL_PREV_CE_7:
+    case EL_PREV_CE_8:
+    case EL_NEXT_CE_1:
+    case EL_NEXT_CE_2:
+    case EL_NEXT_CE_3:
+    case EL_NEXT_CE_4:
+    case EL_NEXT_CE_5:
+    case EL_NEXT_CE_6:
+    case EL_NEXT_CE_7:
+    case EL_NEXT_CE_8:
+      // reference elements should not be used on the playfield
+      Feld[x][y] = EL_EMPTY;
+      break;
+
+    default:
+      if (IS_CUSTOM_ELEMENT(element))
+      {
+       if (CAN_MOVE(element))
+         InitMovDir(x, y);
+
+       if (!element_info[element].use_last_ce_value || init_game)
+         CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
+      }
+      else if (IS_GROUP_ELEMENT(element))
+      {
+       Feld[x][y] = GetElementFromGroupElement(element);
+
+       InitField(x, y, init_game);
+      }
+
+      break;
+  }
+
+  if (!init_game)
+    CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
+}
+
+static void InitField_WithBug1(int x, int y, boolean init_game)
+{
+  InitField(x, y, init_game);
+
+  // not needed to call InitMovDir() -- already done by InitField()!
+  if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+      CAN_MOVE(Feld[x][y]))
+    InitMovDir(x, y);
+}
+
+static void InitField_WithBug2(int x, int y, boolean init_game)
+{
+  int old_element = Feld[x][y];
+
+  InitField(x, y, init_game);
+
+  // not needed to call InitMovDir() -- already done by InitField()!
+  if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+      CAN_MOVE(old_element) &&
+      (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
+    InitMovDir(x, y);
+
+  /* this case is in fact a combination of not less than three bugs:
+     first, it calls InitMovDir() for elements that can move, although this is
+     already done by InitField(); then, it checks the element that was at this
+     field _before_ the call to InitField() (which can change it); lastly, it
+     was not called for "mole with direction" elements, which were treated as
+     "cannot move" due to (fixed) wrong element initialization in "src/init.c"
+  */
+}
+
+static int get_key_element_from_nr(int key_nr)
+{
+  int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
+                         level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                         EL_EM_KEY_1 : EL_KEY_1);
+
+  return key_base_element + key_nr;
+}
+
+static int get_next_dropped_element(struct PlayerInfo *player)
+{
+  return (player->inventory_size > 0 ?
+         player->inventory_element[player->inventory_size - 1] :
+         player->inventory_infinite_element != EL_UNDEFINED ?
+         player->inventory_infinite_element :
+         player->dynabombs_left > 0 ?
+         EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
+         EL_UNDEFINED);
+}
+
+static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
+{
+  // pos >= 0: get element from bottom of the stack;
+  // pos <  0: get element from top of the stack
+
+  if (pos < 0)
+  {
+    int min_inventory_size = -pos;
+    int inventory_pos = player->inventory_size - min_inventory_size;
+    int min_dynabombs_left = min_inventory_size - player->inventory_size;
+
+    return (player->inventory_size >= min_inventory_size ?
+           player->inventory_element[inventory_pos] :
+           player->inventory_infinite_element != EL_UNDEFINED ?
+           player->inventory_infinite_element :
+           player->dynabombs_left >= min_dynabombs_left ?
+           EL_DYNABOMB_PLAYER_1 + player->index_nr :
+           EL_UNDEFINED);
+  }
+  else
+  {
+    int min_dynabombs_left = pos + 1;
+    int min_inventory_size = pos + 1 - player->dynabombs_left;
+    int inventory_pos = pos - player->dynabombs_left;
+
+    return (player->inventory_infinite_element != EL_UNDEFINED ?
+           player->inventory_infinite_element :
+           player->dynabombs_left >= min_dynabombs_left ?
+           EL_DYNABOMB_PLAYER_1 + player->index_nr :
+           player->inventory_size >= min_inventory_size ?
+           player->inventory_element[inventory_pos] :
+           EL_UNDEFINED);
+  }
+}
+
+static int compareGamePanelOrderInfo(const void *object1, const void *object2)
+{
+  const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
+  const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
+  int compare_result;
+
+  if (gpo1->sort_priority != gpo2->sort_priority)
+    compare_result = gpo1->sort_priority - gpo2->sort_priority;
+  else
+    compare_result = gpo1->nr - gpo2->nr;
+
+  return compare_result;
+}
+
+int getPlayerInventorySize(int player_nr)
+{
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    return level.native_em_level->ply[player_nr]->dynamite;
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    return level.native_sp_level->game_sp->red_disk_count;
+  else
+    return stored_player[player_nr].inventory_size;
+}
+
+static void InitGameControlValues(void)
+{
+  int i;
+
+  for (i = 0; game_panel_controls[i].nr != -1; i++)
+  {
+    struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+    struct GamePanelOrderInfo *gpo = &game_panel_order[i];
+    struct TextPosInfo *pos = gpc->pos;
+    int nr = gpc->nr;
+    int type = gpc->type;
+
+    if (nr != i)
+    {
+      Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
+      Error(ERR_EXIT, "this should not happen -- please debug");
+    }
+
+    // force update of game controls after initialization
+    gpc->value = gpc->last_value = -1;
+    gpc->frame = gpc->last_frame = -1;
+    gpc->gfx_frame = -1;
+
+    // determine panel value width for later calculation of alignment
+    if (type == TYPE_INTEGER || type == TYPE_STRING)
+    {
+      pos->width = pos->size * getFontWidth(pos->font);
+      pos->height = getFontHeight(pos->font);
+    }
+    else if (type == TYPE_ELEMENT)
+    {
+      pos->width = pos->size;
+      pos->height = pos->size;
+    }
+
+    // fill structure for game panel draw order
+    gpo->nr = gpc->nr;
+    gpo->sort_priority = pos->sort_priority;
+  }
+
+  // sort game panel controls according to sort_priority and control number
+  qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
+       sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
+}
+
+static void UpdatePlayfieldElementCount(void)
+{
+  boolean use_element_count = FALSE;
+  int i, j, x, y;
+
+  // first check if it is needed at all to calculate playfield element count
+  for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
+    if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
+      use_element_count = TRUE;
+
+  if (!use_element_count)
+    return;
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    element_info[i].element_count = 0;
+
+  SCAN_PLAYFIELD(x, y)
+  {
+    element_info[Feld[x][y]].element_count++;
+  }
+
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+    for (j = 0; j < MAX_NUM_ELEMENTS; j++)
+      if (IS_IN_GROUP(j, i))
+       element_info[EL_GROUP_START + i].element_count +=
+         element_info[j].element_count;
+}
+
+static void UpdateGameControlValues(void)
+{
+  int i, k;
+  int time = (game.LevelSolved ?
+             game.LevelSolved_CountingTime :
+             level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+             level.native_em_level->lev->time :
+             level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+             level.native_sp_level->game_sp->time_played :
+             level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+             game_mm.energy_left :
+             game.no_time_limit ? TimePlayed : TimeLeft);
+  int score = (game.LevelSolved ?
+              game.LevelSolved_CountingScore :
+              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+              level.native_em_level->lev->score :
+              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+              level.native_sp_level->game_sp->score :
+              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+              game_mm.score :
+              game.score);
+  int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+             level.native_em_level->lev->required :
+             level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+             level.native_sp_level->game_sp->infotrons_still_needed :
+             level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+             game_mm.kettles_still_needed :
+             game.gems_still_needed);
+  int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                    level.native_em_level->lev->required > 0 :
+                    level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+                    level.native_sp_level->game_sp->infotrons_still_needed > 0 :
+                    level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                    game_mm.kettles_still_needed > 0 ||
+                    game_mm.lights_still_needed > 0 :
+                    game.gems_still_needed > 0 ||
+                    game.sokoban_fields_still_needed > 0 ||
+                    game.sokoban_objects_still_needed > 0 ||
+                    game.lights_still_needed > 0);
+  int health = (game.LevelSolved ?
+               game.LevelSolved_CountingHealth :
+               level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+               MM_HEALTH(game_mm.laser_overload_value) :
+               game.health);
+
+  UpdatePlayfieldElementCount();
+
+  // update game panel control values
+
+  // used instead of "level_nr" (for network games)
+  game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
+  game_panel_controls[GAME_PANEL_GEMS].value = gems;
+
+  game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
+  for (i = 0; i < MAX_NUM_KEYS; i++)
+    game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
+  game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
+  game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
+
+  if (game.centered_player_nr == -1)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      // only one player in Supaplex game engine
+      if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
+       break;
+
+      for (k = 0; k < MAX_NUM_KEYS; k++)
+      {
+       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+       {
+         if (level.native_em_level->ply[i]->keys & (1 << k))
+           game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+             get_key_element_from_nr(k);
+       }
+       else if (stored_player[i].key[k])
+         game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+           get_key_element_from_nr(k);
+      }
+
+      game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+       getPlayerInventorySize(i);
+
+      if (stored_player[i].num_white_keys > 0)
+       game_panel_controls[GAME_PANEL_KEY_WHITE].value =
+         EL_DC_KEY_WHITE;
+
+      game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+       stored_player[i].num_white_keys;
+    }
+  }
+  else
+  {
+    int player_nr = game.centered_player_nr;
+
+    for (k = 0; k < MAX_NUM_KEYS; k++)
+    {
+      if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+      {
+       if (level.native_em_level->ply[player_nr]->keys & (1 << k))
+         game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+           get_key_element_from_nr(k);
+      }
+      else if (stored_player[player_nr].key[k])
+       game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+         get_key_element_from_nr(k);
+    }
+
+    game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+      getPlayerInventorySize(player_nr);
+
+    if (stored_player[player_nr].num_white_keys > 0)
+      game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
+
+    game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+      stored_player[player_nr].num_white_keys;
+  }
+
+  for (i = 0; i < NUM_PANEL_INVENTORY; i++)
+  {
+    game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
+      get_inventory_element_from_pos(local_player, i);
+    game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
+      get_inventory_element_from_pos(local_player, -i - 1);
+  }
+
+  game_panel_controls[GAME_PANEL_SCORE].value = score;
+  game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
+
+  game_panel_controls[GAME_PANEL_TIME].value = time;
+
+  game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
+  game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
+  game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
+
+  if (level.time == 0)
+    game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
+  else
+    game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
+
+  game_panel_controls[GAME_PANEL_HEALTH].value = health;
+  game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
+
+  game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
+
+  game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
+    (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
+     EL_EMPTY);
+  game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
+    local_player->shield_normal_time_left;
+  game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
+    (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
+     EL_EMPTY);
+  game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
+    local_player->shield_deadly_time_left;
+
+  game_panel_controls[GAME_PANEL_EXIT].value =
+    (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
+
+  game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
+    (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
+  game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
+    (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
+     EL_EMC_MAGIC_BALL_SWITCH);
+
+  game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
+    (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
+  game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
+    game.light_time_left;
+
+  game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
+    (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
+  game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
+    game.timegate_time_left;
+
+  game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
+    EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
+
+  game_panel_controls[GAME_PANEL_EMC_LENSES].value =
+    (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
+  game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
+    game.lenses_time_left;
+
+  game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
+    (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
+  game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
+    game.magnify_time_left;
+
+  game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
+    (game.wind_direction == MV_LEFT  ? EL_BALLOON_SWITCH_LEFT  :
+     game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
+     game.wind_direction == MV_UP    ? EL_BALLOON_SWITCH_UP    :
+     game.wind_direction == MV_DOWN  ? EL_BALLOON_SWITCH_DOWN  :
+     EL_BALLOON_SWITCH_NONE);
+
+  game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
+    local_player->dynabomb_count;
+  game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
+    local_player->dynabomb_size;
+  game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
+    (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
+
+  game_panel_controls[GAME_PANEL_PENGUINS].value =
+    game.friends_still_needed;
+
+  game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
+    game.sokoban_objects_still_needed;
+  game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
+    game.sokoban_fields_still_needed;
+
+  game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
+    (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
+
+  for (i = 0; i < NUM_BELTS; i++)
+  {
+    game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
+      (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
+       EL_CONVEYOR_BELT_1_MIDDLE) + i;
+    game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
+      getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
+  }
+
+  game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
+    (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
+  game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
+    game.magic_wall_time_left;
+
+  game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
+    local_player->gravity;
+
+  for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
+    game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
+
+  for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+    game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
+      (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
+       game.panel.element[i].id : EL_UNDEFINED);
+
+  for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+    game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
+      (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
+       element_info[game.panel.element_count[i].id].element_count : 0);
+
+  for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+    game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
+      (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
+       element_info[game.panel.ce_score[i].id].collect_score : 0);
+
+  for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+    game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
+      (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
+       element_info[game.panel.ce_score_element[i].id].collect_score :
+       EL_UNDEFINED);
+
+  game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
+  game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
+  game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
+
+  // update game panel control frames
+
+  for (i = 0; game_panel_controls[i].nr != -1; i++)
+  {
+    struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+
+    if (gpc->type == TYPE_ELEMENT)
+    {
+      if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
+      {
+       int last_anim_random_frame = gfx.anim_random_frame;
+       int element = gpc->value;
+       int graphic = el2panelimg(element);
+
+       if (gpc->value != gpc->last_value)
+       {
+         gpc->gfx_frame = 0;
+         gpc->gfx_random = INIT_GFX_RANDOM();
+       }
+       else
+       {
+         gpc->gfx_frame++;
+
+         if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+             IS_NEXT_FRAME(gpc->gfx_frame, graphic))
+           gpc->gfx_random = INIT_GFX_RANDOM();
+       }
+
+       if (ANIM_MODE(graphic) == ANIM_RANDOM)
+         gfx.anim_random_frame = gpc->gfx_random;
+
+       if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+         gpc->gfx_frame = element_info[element].collect_score;
+
+       gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
+                                             gpc->gfx_frame);
+
+       if (ANIM_MODE(graphic) == ANIM_RANDOM)
+         gfx.anim_random_frame = last_anim_random_frame;
+      }
+    }
+    else if (gpc->type == TYPE_GRAPHIC)
+    {
+      if (gpc->graphic != IMG_UNDEFINED)
+      {
+       int last_anim_random_frame = gfx.anim_random_frame;
+       int graphic = gpc->graphic;
+
+       if (gpc->value != gpc->last_value)
+       {
+         gpc->gfx_frame = 0;
+         gpc->gfx_random = INIT_GFX_RANDOM();
+       }
+       else
+       {
+         gpc->gfx_frame++;
+
+         if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+             IS_NEXT_FRAME(gpc->gfx_frame, graphic))
+           gpc->gfx_random = INIT_GFX_RANDOM();
+       }
+
+       if (ANIM_MODE(graphic) == ANIM_RANDOM)
+         gfx.anim_random_frame = gpc->gfx_random;
+
+       gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
+
+       if (ANIM_MODE(graphic) == ANIM_RANDOM)
+         gfx.anim_random_frame = last_anim_random_frame;
+      }
+    }
+  }
+}
+
+static void DisplayGameControlValues(void)
+{
+  boolean redraw_panel = FALSE;
+  int i;
+
+  for (i = 0; game_panel_controls[i].nr != -1; i++)
+  {
+    struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+
+    if (PANEL_DEACTIVATED(gpc->pos))
+      continue;
+
+    if (gpc->value == gpc->last_value &&
+       gpc->frame == gpc->last_frame)
+      continue;
+
+    redraw_panel = TRUE;
+  }
+
+  if (!redraw_panel)
+    return;
+
+  // copy default game door content to main double buffer
+
+  // !!! CHECK AGAIN !!!
+  SetPanelBackground();
+  // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+  DrawBackground(DX, DY, DXSIZE, DYSIZE);
+
+  // redraw game control buttons
+  RedrawGameButtons();
+
+  SetGameStatus(GAME_MODE_PSEUDO_PANEL);
+
+  for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
+  {
+    int nr = game_panel_order[i].nr;
+    struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
+    struct TextPosInfo *pos = gpc->pos;
+    int type = gpc->type;
+    int value = gpc->value;
+    int frame = gpc->frame;
+    int size = pos->size;
+    int font = pos->font;
+    boolean draw_masked = pos->draw_masked;
+    int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
+
+    if (PANEL_DEACTIVATED(pos))
+      continue;
+
+    gpc->last_value = value;
+    gpc->last_frame = frame;
+
+    if (type == TYPE_INTEGER)
+    {
+      if (nr == GAME_PANEL_LEVEL_NUMBER ||
+         nr == GAME_PANEL_TIME)
+      {
+       boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
+
+       if (use_dynamic_size)           // use dynamic number of digits
+       {
+         int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
+         int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
+         int size2 = size1 + 1;
+         int font1 = pos->font;
+         int font2 = pos->font_alt;
+
+         size = (value < value_change ? size1 : size2);
+         font = (value < value_change ? font1 : font2);
+       }
+      }
+
+      // correct text size if "digits" is zero or less
+      if (size <= 0)
+       size = strlen(int2str(value, size));
+
+      // dynamically correct text alignment
+      pos->width = size * getFontWidth(font);
+
+      DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+                 int2str(value, size), font, mask_mode);
+    }
+    else if (type == TYPE_ELEMENT)
+    {
+      int element, graphic;
+      Bitmap *src_bitmap;
+      int src_x, src_y;
+      int width, height;
+      int dst_x = PANEL_XPOS(pos);
+      int dst_y = PANEL_YPOS(pos);
+
+      if (value != EL_UNDEFINED && value != EL_EMPTY)
+      {
+       element = value;
+       graphic = el2panelimg(value);
+
+       // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
+
+       if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
+         size = TILESIZE;
+
+       getSizedGraphicSource(graphic, frame, size, &src_bitmap,
+                             &src_x, &src_y);
+
+       width  = graphic_info[graphic].width  * size / TILESIZE;
+       height = graphic_info[graphic].height * size / TILESIZE;
+
+       if (draw_masked)
+         BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+                          dst_x, dst_y);
+       else
+         BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+                    dst_x, dst_y);
+      }
+    }
+    else if (type == TYPE_GRAPHIC)
+    {
+      int graphic        = gpc->graphic;
+      int graphic_active = gpc->graphic_active;
+      Bitmap *src_bitmap;
+      int src_x, src_y;
+      int width, height;
+      int dst_x = PANEL_XPOS(pos);
+      int dst_y = PANEL_YPOS(pos);
+      boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
+                     level.game_engine_type != GAME_ENGINE_TYPE_MM);
+
+      if (graphic != IMG_UNDEFINED && !skip)
+      {
+       if (pos->style == STYLE_REVERSE)
+         value = 100 - value;
+
+       getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
+
+       if (pos->direction & MV_HORIZONTAL)
+       {
+         width  = graphic_info[graphic_active].width * value / 100;
+         height = graphic_info[graphic_active].height;
+
+         if (pos->direction == MV_LEFT)
+         {
+           src_x += graphic_info[graphic_active].width - width;
+           dst_x += graphic_info[graphic_active].width - width;
+         }
+       }
+       else
+       {
+         width  = graphic_info[graphic_active].width;
+         height = graphic_info[graphic_active].height * value / 100;
+
+         if (pos->direction == MV_UP)
+         {
+           src_y += graphic_info[graphic_active].height - height;
+           dst_y += graphic_info[graphic_active].height - height;
+         }
+       }
+
+       if (draw_masked)
+         BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+                          dst_x, dst_y);
+       else
+         BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+                    dst_x, dst_y);
+
+       getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+       if (pos->direction & MV_HORIZONTAL)
+       {
+         if (pos->direction == MV_RIGHT)
+         {
+           src_x += width;
+           dst_x += width;
+         }
+         else
+         {
+           dst_x = PANEL_XPOS(pos);
+         }
+
+         width = graphic_info[graphic].width - width;
+       }
+       else
+       {
+         if (pos->direction == MV_DOWN)
+         {
+           src_y += height;
+           dst_y += height;
+         }
+         else
+         {
+           dst_y = PANEL_YPOS(pos);
+         }
+
+         height = graphic_info[graphic].height - height;
+       }
+
+       if (draw_masked)
+         BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+                          dst_x, dst_y);
+       else
+         BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+                    dst_x, dst_y);
+      }
+    }
+    else if (type == TYPE_STRING)
+    {
+      boolean active = (value != 0);
+      char *state_normal = "off";
+      char *state_active = "on";
+      char *state = (active ? state_active : state_normal);
+      char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
+                nr == GAME_PANEL_PLAYER_NAME   ? setup.player_name :
+                nr == GAME_PANEL_LEVEL_NAME    ? level.name :
+                nr == GAME_PANEL_LEVEL_AUTHOR  ? level.author : NULL);
+
+      if (nr == GAME_PANEL_GRAVITY_STATE)
+      {
+       int font1 = pos->font;          // (used for normal state)
+       int font2 = pos->font_alt;      // (used for active state)
+
+       font = (active ? font2 : font1);
+      }
+
+      if (s != NULL)
+      {
+       char *s_cut;
+
+       if (size <= 0)
+       {
+         // don't truncate output if "chars" is zero or less
+         size = strlen(s);
+
+         // dynamically correct text alignment
+         pos->width = size * getFontWidth(font);
+       }
+
+       s_cut = getStringCopyN(s, size);
+
+       DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+                   s_cut, font, mask_mode);
+
+       free(s_cut);
+      }
+    }
+
+    redraw_mask |= REDRAW_DOOR_1;
+  }
+
+  SetGameStatus(GAME_MODE_PLAYING);
+}
+
+void UpdateAndDisplayGameControlValues(void)
+{
+  if (tape.deactivate_display)
+    return;
+
+  UpdateGameControlValues();
+  DisplayGameControlValues();
+}
+
+#if 0
+static void UpdateGameDoorValues(void)
+{
+  UpdateGameControlValues();
+}
+#endif
+
+void DrawGameDoorValues(void)
+{
+  DisplayGameControlValues();
+}
+
+
+// ============================================================================
+// InitGameEngine()
+// ----------------------------------------------------------------------------
+// initialize game engine due to level / tape version number
+// ============================================================================
+
+static void InitGameEngine(void)
+{
+  int i, j, k, l, x, y;
+
+  // set game engine from tape file when re-playing, else from level file
+  game.engine_version = (tape.playing ? tape.engine_version :
+                        level.game_version);
+
+  // set single or multi-player game mode (needed for re-playing tapes)
+  game.team_mode = setup.team_mode;
+
+  if (tape.playing)
+  {
+    int num_players = 0;
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (tape.player_participates[i])
+       num_players++;
+
+    // multi-player tapes contain input data for more than one player
+    game.team_mode = (num_players > 1);
+  }
+
+  // --------------------------------------------------------------------------
+  // set flags for bugs and changes according to active game engine version
+  // --------------------------------------------------------------------------
+
+  /*
+    Summary of bugfix/change:
+    Fixed handling for custom elements that change when pushed by the player.
+
+    Fixed/changed in version:
+    3.1.0
+
+    Description:
+    Before 3.1.0, custom elements that "change when pushing" changed directly
+    after the player started pushing them (until then handled in "DigField()").
+    Since 3.1.0, these custom elements are not changed until the "pushing"
+    move of the element is finished (now handled in "ContinueMoving()").
+
+    Affected levels/tapes:
+    The first condition is generally needed for all levels/tapes before version
+    3.1.0, which might use the old behaviour before it was changed; known tapes
+    that are affected are some tapes from the level set "Walpurgis Gardens" by
+    Jamie Cullen.
+    The second condition is an exception from the above case and is needed for
+    the special case of tapes recorded with game (not engine!) version 3.1.0 or
+    above (including some development versions of 3.1.0), but before it was
+    known that this change would break tapes like the above and was fixed in
+    3.1.1, so that the changed behaviour was active although the engine version
+    while recording maybe was before 3.1.0. There is at least one tape that is
+    affected by this exception, which is the tape for the one-level set "Bug
+    Machine" by Juergen Bonhagen.
+  */
+
+  game.use_change_when_pushing_bug =
+    (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+     !(tape.playing &&
+       tape.game_version >= VERSION_IDENT(3,1,0,0) &&
+       tape.game_version <  VERSION_IDENT(3,1,1,0)));
+
+  /*
+    Summary of bugfix/change:
+    Fixed handling for blocking the field the player leaves when moving.
+
+    Fixed/changed in version:
+    3.1.1
+
+    Description:
+    Before 3.1.1, when "block last field when moving" was enabled, the field
+    the player is leaving when moving was blocked for the time of the move,
+    and was directly unblocked afterwards. This resulted in the last field
+    being blocked for exactly one less than the number of frames of one player
+    move. Additionally, even when blocking was disabled, the last field was
+    blocked for exactly one frame.
+    Since 3.1.1, due to changes in player movement handling, the last field
+    is not blocked at all when blocking is disabled. When blocking is enabled,
+    the last field is blocked for exactly the number of frames of one player
+    move. Additionally, if the player is Murphy, the hero of Supaplex, the
+    last field is blocked for exactly one more than the number of frames of
+    one player move.
+
+    Affected levels/tapes:
+    (!!! yet to be determined -- probably many !!!)
+  */
+
+  game.use_block_last_field_bug =
+    (game.engine_version < VERSION_IDENT(3,1,1,0));
+
+  game_em.use_single_button =
+    (game.engine_version > VERSION_IDENT(4,0,0,2));
+
+  game_em.use_snap_key_bug =
+    (game.engine_version < VERSION_IDENT(4,0,1,0));
+
+  // --------------------------------------------------------------------------
+
+  // set maximal allowed number of custom element changes per game frame
+  game.max_num_changes_per_frame = 1;
+
+  // default scan direction: scan playfield from top/left to bottom/right
+  InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
+
+  // dynamically adjust element properties according to game engine version
+  InitElementPropertiesEngine(game.engine_version);
+
+#if 0
+  printf("level %d: level version == %06d\n", level_nr, level.game_version);
+  printf("          tape version == %06d [%s] [file: %06d]\n",
+        tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
+        tape.file_version);
+  printf("       => game.engine_version == %06d\n", game.engine_version);
+#endif
+
+  // ---------- initialize player's initial move delay ------------------------
+
+  // dynamically adjust player properties according to level information
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.initial_move_delay_value[i] =
+      get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
+
+  // dynamically adjust player properties according to game engine version
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.initial_move_delay[i] =
+      (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
+       game.initial_move_delay_value[i] : 0);
+
+  // ---------- initialize player's initial push delay ------------------------
+
+  // dynamically adjust player properties according to game engine version
+  game.initial_push_delay_value =
+    (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
+
+  // ---------- initialize changing elements ----------------------------------
+
+  // initialize changing elements information
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    struct ElementInfo *ei = &element_info[i];
+
+    // this pointer might have been changed in the level editor
+    ei->change = &ei->change_page[0];
+
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      ei->change->target_element = EL_EMPTY_SPACE;
+      ei->change->delay_fixed = 0;
+      ei->change->delay_random = 0;
+      ei->change->delay_frames = 1;
+    }
+
+    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+    {
+      ei->has_change_event[j] = FALSE;
+
+      ei->event_page_nr[j] = 0;
+      ei->event_page[j] = &ei->change_page[0];
+    }
+  }
+
+  // add changing elements from pre-defined list
+  for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
+  {
+    struct ChangingElementInfo *ch_delay = &change_delay_list[i];
+    struct ElementInfo *ei = &element_info[ch_delay->element];
+
+    ei->change->target_element       = ch_delay->target_element;
+    ei->change->delay_fixed          = ch_delay->change_delay;
+
+    ei->change->pre_change_function  = ch_delay->pre_change_function;
+    ei->change->change_function      = ch_delay->change_function;
+    ei->change->post_change_function = ch_delay->post_change_function;
+
+    ei->change->can_change = TRUE;
+    ei->change->can_change_or_has_action = TRUE;
+
+    ei->has_change_event[CE_DELAY] = TRUE;
+
+    SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
+    SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
+  }
+
+  // ---------- initialize internal run-time variables ------------------------
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      ei->change_page[j].can_change_or_has_action =
+       (ei->change_page[j].can_change |
+        ei->change_page[j].has_action);
+    }
+  }
+
+  // add change events from custom element configuration
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      if (!ei->change_page[j].can_change_or_has_action)
+       continue;
+
+      for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+      {
+       // only add event page for the first page found with this event
+       if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
+       {
+         ei->has_change_event[k] = TRUE;
+
+         ei->event_page_nr[k] = j;
+         ei->event_page[k] = &ei->change_page[j];
+       }
+      }
+    }
+  }
+
+  // ---------- initialize reference elements in change conditions ------------
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+    struct ElementInfo *ei = &element_info[element];
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      int trigger_element = ei->change_page[j].initial_trigger_element;
+
+      if (trigger_element >= EL_PREV_CE_8 &&
+         trigger_element <= EL_NEXT_CE_8)
+       trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
+
+      ei->change_page[j].trigger_element = trigger_element;
+    }
+  }
+
+  // ---------- initialize run-time trigger player and element ----------------
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      ei->change_page[j].actual_trigger_element = EL_EMPTY;
+      ei->change_page[j].actual_trigger_player = EL_EMPTY;
+      ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
+      ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
+      ei->change_page[j].actual_trigger_ce_value = 0;
+      ei->change_page[j].actual_trigger_ce_score = 0;
+    }
+  }
+
+  // ---------- initialize trigger events -------------------------------------
+
+  // initialize trigger events information
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+      trigger_events[i][j] = FALSE;
+
+  // add trigger events from element change event properties
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    struct ElementInfo *ei = &element_info[i];
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      if (!ei->change_page[j].can_change_or_has_action)
+       continue;
+
+      if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
+      {
+       int trigger_element = ei->change_page[j].trigger_element;
+
+       for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+       {
+         if (ei->change_page[j].has_event[k])
+         {
+           if (IS_GROUP_ELEMENT(trigger_element))
+           {
+             struct ElementGroupInfo *group =
+               element_info[trigger_element].group;
+
+             for (l = 0; l < group->num_elements_resolved; l++)
+               trigger_events[group->element_resolved[l]][k] = TRUE;
+           }
+           else if (trigger_element == EL_ANY_ELEMENT)
+             for (l = 0; l < MAX_NUM_ELEMENTS; l++)
+               trigger_events[l][k] = TRUE;
+           else
+             trigger_events[trigger_element][k] = TRUE;
+         }
+       }
+      }
+    }
+  }
+
+  // ---------- initialize push delay -----------------------------------------
+
+  // initialize push delay values to default
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      // set default push delay values (corrected since version 3.0.7-1)
+      if (game.engine_version < VERSION_IDENT(3,0,7,1))
+      {
+       element_info[i].push_delay_fixed = 2;
+       element_info[i].push_delay_random = 8;
+      }
+      else
+      {
+       element_info[i].push_delay_fixed = 8;
+       element_info[i].push_delay_random = 8;
+      }
+    }
+  }
+
+  // set push delay value for certain elements from pre-defined list
+  for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
+  {
+    int e = push_delay_list[i].element;
+
+    element_info[e].push_delay_fixed  = push_delay_list[i].push_delay_fixed;
+    element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
+  }
+
+  // set push delay value for Supaplex elements for newer engine versions
+  if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+  {
+    for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    {
+      if (IS_SP_ELEMENT(i))
+      {
+       // set SP push delay to just enough to push under a falling zonk
+       int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
+
+       element_info[i].push_delay_fixed  = delay;
+       element_info[i].push_delay_random = 0;
+      }
+    }
+  }
+
+  // ---------- initialize move stepsize --------------------------------------
+
+  // initialize move stepsize values to default
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    if (!IS_CUSTOM_ELEMENT(i))
+      element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
+
+  // set move stepsize value for certain elements from pre-defined list
+  for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
+  {
+    int e = move_stepsize_list[i].element;
+
+    element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
+  }
+
+  // ---------- initialize collect score --------------------------------------
+
+  // initialize collect score values for custom elements from initial value
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    if (IS_CUSTOM_ELEMENT(i))
+      element_info[i].collect_score = element_info[i].collect_score_initial;
+
+  // ---------- initialize collect count --------------------------------------
+
+  // initialize collect count values for non-custom elements
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    if (!IS_CUSTOM_ELEMENT(i))
+      element_info[i].collect_count_initial = 0;
+
+  // add collect count values for all elements from pre-defined list
+  for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
+    element_info[collect_count_list[i].element].collect_count_initial =
+      collect_count_list[i].count;
+
+  // ---------- initialize access direction -----------------------------------
+
+  // initialize access direction values to default (access from every side)
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    if (!IS_CUSTOM_ELEMENT(i))
+      element_info[i].access_direction = MV_ALL_DIRECTIONS;
+
+  // set access direction value for certain elements from pre-defined list
+  for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
+    element_info[access_direction_list[i].element].access_direction =
+      access_direction_list[i].direction;
+
+  // ---------- initialize explosion content ----------------------------------
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (IS_CUSTOM_ELEMENT(i))
+      continue;
+
+    for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
+    {
+      // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
+
+      element_info[i].content.e[x][y] =
+       (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
+        i == EL_PLAYER_2 ? EL_EMERALD_RED :
+        i == EL_PLAYER_3 ? EL_EMERALD :
+        i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
+        i == EL_MOLE ? EL_EMERALD_RED :
+        i == EL_PENGUIN ? EL_EMERALD_PURPLE :
+        i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
+        i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
+        i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
+        i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
+        i == EL_WALL_EMERALD ? EL_EMERALD :
+        i == EL_WALL_DIAMOND ? EL_DIAMOND :
+        i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
+        i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
+        i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
+        i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
+        i == EL_WALL_PEARL ? EL_PEARL :
+        i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
+        EL_EMPTY);
+    }
+  }
+
+  // ---------- initialize recursion detection --------------------------------
+  recursion_loop_depth = 0;
+  recursion_loop_detected = FALSE;
+  recursion_loop_element = EL_UNDEFINED;
+
+  // ---------- initialize graphics engine ------------------------------------
+  game.scroll_delay_value =
+    (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
+     setup.scroll_delay                   ? setup.scroll_delay_value       : 0);
+  game.scroll_delay_value =
+    MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
+
+  // ---------- initialize game engine snapshots ------------------------------
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.snapshot.last_action[i] = 0;
+  game.snapshot.changed_action = FALSE;
+  game.snapshot.collected_item = FALSE;
+  game.snapshot.mode =
+    (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
+     SNAPSHOT_MODE_EVERY_STEP :
+     strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
+     SNAPSHOT_MODE_EVERY_MOVE :
+     strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
+     SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
+  game.snapshot.save_snapshot = FALSE;
+
+  // ---------- initialize level time for Supaplex engine ---------------------
+  // Supaplex levels with time limit currently unsupported -- should be added
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    level.time = 0;
+}
+
+static int get_num_special_action(int element, int action_first,
+                                 int action_last)
+{
+  int num_special_action = 0;
+  int i, j;
+
+  for (i = action_first; i <= action_last; i++)
+  {
+    boolean found = FALSE;
+
+    for (j = 0; j < NUM_DIRECTIONS; j++)
+      if (el_act_dir2img(element, i, j) !=
+         el_act_dir2img(element, ACTION_DEFAULT, j))
+       found = TRUE;
+
+    if (found)
+      num_special_action++;
+    else
+      break;
+  }
+
+  return num_special_action;
+}
+
+
+// ============================================================================
+// InitGame()
+// ----------------------------------------------------------------------------
+// initialize and start new game
+// ============================================================================
+
+#if DEBUG_INIT_PLAYER
+static void DebugPrintPlayerStatus(char *message)
+{
+  int i;
+
+  if (!options.debug)
+    return;
+
+  printf("%s:\n", message);
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
+          i + 1,
+          player->present,
+          player->connected,
+          player->connected_locally,
+          player->connected_network,
+          player->active);
+
+    if (local_player == player)
+      printf(" (local player)");
+
+    printf("\n");
+  }
+}
+#endif
+
+void InitGame(void)
+{
+  int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
+  int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
+  int fade_mask = REDRAW_FIELD;
+
+  boolean emulate_bd = TRUE;   // unless non-BOULDERDASH elements found
+  boolean emulate_sb = TRUE;   // unless non-SOKOBAN     elements found
+  boolean emulate_sp = TRUE;   // unless non-SUPAPLEX    elements found
+  int initial_move_dir = MV_DOWN;
+  int i, j, x, y;
+
+  // required here to update video display before fading (FIX THIS)
+  DrawMaskedBorder(REDRAW_DOOR_2);
+
+  if (!game.restart_level)
+    CloseDoor(DOOR_CLOSE_1);
+
+  SetGameStatus(GAME_MODE_PLAYING);
+
+  if (level_editor_test_game)
+    FadeSkipNextFadeIn();
+  else
+    FadeSetEnterScreen();
+
+  if (CheckFadeAll())
+    fade_mask = REDRAW_ALL;
+
+  FadeLevelSoundsAndMusic();
+
+  ExpireSoundLoops(TRUE);
+
+  if (!level_editor_test_game)
+    FadeOut(fade_mask);
+
+  // needed if different viewport properties defined for playing
+  ChangeViewportPropertiesIfNeeded();
+
+  ClearField();
+
+  DrawCompleteVideoDisplay();
+
+  OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
+
+  InitGameEngine();
+  InitGameControlValues();
+
+  // don't play tapes over network
+  network_playing = (network.enabled && !tape.playing);
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    player->index_nr = i;
+    player->index_bit = (1 << i);
+    player->element_nr = EL_PLAYER_1 + i;
+
+    player->present = FALSE;
+    player->active = FALSE;
+    player->mapped = FALSE;
+
+    player->killed = FALSE;
+    player->reanimated = FALSE;
+    player->buried = FALSE;
+
+    player->action = 0;
+    player->effective_action = 0;
+    player->programmed_action = 0;
+    player->snap_action = 0;
+
+    player->mouse_action.lx = 0;
+    player->mouse_action.ly = 0;
+    player->mouse_action.button = 0;
+    player->mouse_action.button_hint = 0;
+
+    player->effective_mouse_action.lx = 0;
+    player->effective_mouse_action.ly = 0;
+    player->effective_mouse_action.button = 0;
+    player->effective_mouse_action.button_hint = 0;
+
+    for (j = 0; j < MAX_NUM_KEYS; j++)
+      player->key[j] = FALSE;
+
+    player->num_white_keys = 0;
+
+    player->dynabomb_count = 0;
+    player->dynabomb_size = 1;
+    player->dynabombs_left = 0;
+    player->dynabomb_xl = FALSE;
+
+    player->MovDir = initial_move_dir;
+    player->MovPos = 0;
+    player->GfxPos = 0;
+    player->GfxDir = initial_move_dir;
+    player->GfxAction = ACTION_DEFAULT;
+    player->Frame = 0;
+    player->StepFrame = 0;
+
+    player->initial_element = player->element_nr;
+    player->artwork_element =
+      (level.use_artwork_element[i] ? level.artwork_element[i] :
+       player->element_nr);
+    player->use_murphy = FALSE;
+
+    player->block_last_field = FALSE;  // initialized in InitPlayerField()
+    player->block_delay_adjustment = 0;        // initialized in InitPlayerField()
+
+    player->gravity = level.initial_player_gravity[i];
+
+    player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
+
+    player->actual_frame_counter = 0;
+
+    player->step_counter = 0;
+
+    player->last_move_dir = initial_move_dir;
+
+    player->is_active = FALSE;
+
+    player->is_waiting = FALSE;
+    player->is_moving = FALSE;
+    player->is_auto_moving = FALSE;
+    player->is_digging = FALSE;
+    player->is_snapping = FALSE;
+    player->is_collecting = FALSE;
+    player->is_pushing = FALSE;
+    player->is_switching = FALSE;
+    player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
+
+    player->is_bored = FALSE;
+    player->is_sleeping = FALSE;
+
+    player->was_waiting = TRUE;
+    player->was_moving = FALSE;
+    player->was_snapping = FALSE;
+    player->was_dropping = FALSE;
+
+    player->force_dropping = FALSE;
+
+    player->frame_counter_bored = -1;
+    player->frame_counter_sleeping = -1;
+
+    player->anim_delay_counter = 0;
+    player->post_delay_counter = 0;
+
+    player->dir_waiting = initial_move_dir;
+    player->action_waiting = ACTION_DEFAULT;
+    player->last_action_waiting = ACTION_DEFAULT;
+    player->special_action_bored = ACTION_DEFAULT;
+    player->special_action_sleeping = ACTION_DEFAULT;
+
+    player->switch_x = -1;
+    player->switch_y = -1;
+
+    player->drop_x = -1;
+    player->drop_y = -1;
+
+    player->show_envelope = 0;
+
+    SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
+
+    player->push_delay       = -1;     // initialized when pushing starts
+    player->push_delay_value = game.initial_push_delay_value;
+
+    player->drop_delay = 0;
+    player->drop_pressed_delay = 0;
+
+    player->last_jx = -1;
+    player->last_jy = -1;
+    player->jx = -1;
+    player->jy = -1;
+
+    player->shield_normal_time_left = 0;
+    player->shield_deadly_time_left = 0;
+
+    player->inventory_infinite_element = EL_UNDEFINED;
+    player->inventory_size = 0;
+
+    if (level.use_initial_inventory[i])
+    {
+      for (j = 0; j < level.initial_inventory_size[i]; j++)
+      {
+       int element = level.initial_inventory_content[i][j];
+       int collect_count = element_info[element].collect_count_initial;
+       int k;
+
+       if (!IS_CUSTOM_ELEMENT(element))
+         collect_count = 1;
+
+       if (collect_count == 0)
+         player->inventory_infinite_element = element;
+       else
+         for (k = 0; k < collect_count; k++)
+           if (player->inventory_size < MAX_INVENTORY_SIZE)
+             player->inventory_element[player->inventory_size++] = element;
+      }
+    }
+
+    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
+    SnapField(player, 0, 0);
+
+    map_player_action[i] = i;
+  }
+
+  network_player_action_received = FALSE;
+
+  // initial null action
+  if (network_playing)
+    SendToServer_MovePlayer(MV_NONE);
+
+  FrameCounter = 0;
+  TimeFrames = 0;
+  TimePlayed = 0;
+  TimeLeft = level.time;
+  TapeTime = 0;
+
+  ScreenMovDir = MV_NONE;
+  ScreenMovPos = 0;
+  ScreenGfxPos = 0;
+
+  ScrollStepSize = 0;  // will be correctly initialized by ScrollScreen()
+
+  game.robot_wheel_x = -1;
+  game.robot_wheel_y = -1;
+
+  game.exit_x = -1;
+  game.exit_y = -1;
+
+  game.all_players_gone = FALSE;
+
+  game.LevelSolved = FALSE;
+  game.GameOver = FALSE;
+
+  game.GamePlayed = !tape.playing;
+
+  game.LevelSolved_GameWon = FALSE;
+  game.LevelSolved_GameEnd = FALSE;
+  game.LevelSolved_SaveTape = FALSE;
+  game.LevelSolved_SaveScore = FALSE;
+
+  game.LevelSolved_CountingTime = 0;
+  game.LevelSolved_CountingScore = 0;
+  game.LevelSolved_CountingHealth = 0;
+
+  game.panel.active = TRUE;
+
+  game.no_time_limit = (level.time == 0);
+
+  game.yamyam_content_nr = 0;
+  game.robot_wheel_active = FALSE;
+  game.magic_wall_active = FALSE;
+  game.magic_wall_time_left = 0;
+  game.light_time_left = 0;
+  game.timegate_time_left = 0;
+  game.switchgate_pos = 0;
+  game.wind_direction = level.wind_direction_initial;
+
+  game.score = 0;
+  game.score_final = 0;
+
+  game.health = MAX_HEALTH;
+  game.health_final = MAX_HEALTH;
+
+  game.gems_still_needed = level.gems_needed;
+  game.sokoban_fields_still_needed = 0;
+  game.sokoban_objects_still_needed = 0;
+  game.lights_still_needed = 0;
+  game.players_still_needed = 0;
+  game.friends_still_needed = 0;
+
+  game.lenses_time_left = 0;
+  game.magnify_time_left = 0;
+
+  game.ball_state = level.ball_state_initial;
+  game.ball_content_nr = 0;
+
+  game.explosions_delayed = TRUE;
+
+  game.envelope_active = FALSE;
+
+  for (i = 0; i < NUM_BELTS; i++)
+  {
+    game.belt_dir[i] = MV_NONE;
+    game.belt_dir_nr[i] = 3;           // not moving, next moving left
+  }
+
+  for (i = 0; i < MAX_NUM_AMOEBA; i++)
+    AmoebaCnt[i] = AmoebaCnt2[i] = 0;
+
+#if DEBUG_INIT_PLAYER
+  DebugPrintPlayerStatus("Player status at level initialization");
+#endif
+
+  SCAN_PLAYFIELD(x, y)
+  {
+    Feld[x][y] = Last[x][y] = level.field[x][y];
+    MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
+    ChangeDelay[x][y] = 0;
+    ChangePage[x][y] = -1;
+    CustomValue[x][y] = 0;             // initialized in InitField()
+    Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
+    AmoebaNr[x][y] = 0;
+    WasJustMoving[x][y] = 0;
+    WasJustFalling[x][y] = 0;
+    CheckCollision[x][y] = 0;
+    CheckImpact[x][y] = 0;
+    Stop[x][y] = FALSE;
+    Pushed[x][y] = FALSE;
+
+    ChangeCount[x][y] = 0;
+    ChangeEvent[x][y] = -1;
+
+    ExplodePhase[x][y] = 0;
+    ExplodeDelay[x][y] = 0;
+    ExplodeField[x][y] = EX_TYPE_NONE;
+
+    RunnerVisit[x][y] = 0;
+    PlayerVisit[x][y] = 0;
+
+    GfxFrame[x][y] = 0;
+    GfxRandom[x][y] = INIT_GFX_RANDOM();
+    GfxElement[x][y] = EL_UNDEFINED;
+    GfxAction[x][y] = ACTION_DEFAULT;
+    GfxDir[x][y] = MV_NONE;
+    GfxRedraw[x][y] = GFX_REDRAW_NONE;
+  }
+
+  SCAN_PLAYFIELD(x, y)
+  {
+    if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
+      emulate_bd = FALSE;
+    if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
+      emulate_sb = FALSE;
+    if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
+      emulate_sp = FALSE;
+
+    InitField(x, y, TRUE);
+
+    ResetGfxAnimation(x, y);
+  }
+
+  InitBeltMovement();
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    // set number of special actions for bored and sleeping animation
+    player->num_special_action_bored =
+      get_num_special_action(player->artwork_element,
+                            ACTION_BORING_1, ACTION_BORING_LAST);
+    player->num_special_action_sleeping =
+      get_num_special_action(player->artwork_element,
+                            ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+  }
+
+  game.emulation = (emulate_bd ? EMU_BOULDERDASH :
+                   emulate_sb ? EMU_SOKOBAN :
+                   emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
+
+  // initialize type of slippery elements
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      // default: elements slip down either to the left or right randomly
+      element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
+
+      // SP style elements prefer to slip down on the left side
+      if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
+       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+
+      // BD style elements prefer to slip down on the left side
+      if (game.emulation == EMU_BOULDERDASH)
+       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+    }
+  }
+
+  // initialize explosion and ignition delay
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      int num_phase = 8;
+      int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
+                   game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
+                  game.emulation == EMU_SUPAPLEX ? 3 : 2);
+      int last_phase = (num_phase + 1) * delay;
+      int half_phase = (num_phase / 2) * delay;
+
+      element_info[i].explosion_delay = last_phase - 1;
+      element_info[i].ignition_delay = half_phase;
+
+      if (i == EL_BLACK_ORB)
+       element_info[i].ignition_delay = 1;
+    }
+  }
+
+  // correct non-moving belts to start moving left
+  for (i = 0; i < NUM_BELTS; i++)
+    if (game.belt_dir[i] == MV_NONE)
+      game.belt_dir_nr[i] = 3;         // not moving, next moving left
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+  // use preferred player also in local single-player mode
+  if (!network.enabled && !game.team_mode)
+  {
+    int old_index_nr = local_player->index_nr;
+    int new_index_nr = setup.network_player_nr;
+
+    if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
+    {
+      stored_player[old_index_nr].connected_locally = FALSE;
+      stored_player[new_index_nr].connected_locally = TRUE;
+    }
+  }
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    stored_player[i].connected = FALSE;
+
+    // in network game mode, the local player might not be the first player
+    if (stored_player[i].connected_locally)
+      local_player = &stored_player[i];
+  }
+
+  if (!network.enabled)
+    local_player->connected = TRUE;
+
+  if (tape.playing)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].connected = tape.player_participates[i];
+  }
+  else if (network.enabled)
+  {
+    // add team mode players connected over the network (needed for correct
+    // assignment of player figures from level to locally playing players)
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (stored_player[i].connected_network)
+       stored_player[i].connected = TRUE;
+  }
+  else if (game.team_mode)
+  {
+    // try to guess locally connected team mode players (needed for correct
+    // assignment of player figures from level to locally playing players)
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (setup.input[i].use_joystick ||
+         setup.input[i].key.left != KSYM_UNDEFINED)
+       stored_player[i].connected = TRUE;
+  }
+
+#if DEBUG_INIT_PLAYER
+  DebugPrintPlayerStatus("Player status after level initialization");
+#endif
+
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+    printf("Reassigning players ...\n");
+#endif
+
+  // check if any connected player was not found in playfield
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    if (player->connected && !player->present)
+    {
+      struct PlayerInfo *field_player = NULL;
+
+#if DEBUG_INIT_PLAYER
+      if (options.debug)
+       printf("- looking for field player for player %d ...\n", i + 1);
+#endif
+
+      // assign first free player found that is present in the playfield
+
+      // first try: look for unmapped playfield player that is not connected
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (field_player == NULL &&
+           stored_player[j].present &&
+           !stored_player[j].mapped &&
+           !stored_player[j].connected)
+         field_player = &stored_player[j];
+
+      // second try: look for *any* unmapped playfield player
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (field_player == NULL &&
+           stored_player[j].present &&
+           !stored_player[j].mapped)
+         field_player = &stored_player[j];
+
+      if (field_player != NULL)
+      {
+       int jx = field_player->jx, jy = field_player->jy;
+
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("- found player %d\n", field_player->index_nr + 1);
+#endif
+
+       player->present = FALSE;
+       player->active = FALSE;
+
+       field_player->present = TRUE;
+       field_player->active = TRUE;
+
+       /*
+       player->initial_element = field_player->initial_element;
+       player->artwork_element = field_player->artwork_element;
+
+       player->block_last_field       = field_player->block_last_field;
+       player->block_delay_adjustment = field_player->block_delay_adjustment;
+       */
+
+       StorePlayer[jx][jy] = field_player->element_nr;
+
+       field_player->jx = field_player->last_jx = jx;
+       field_player->jy = field_player->last_jy = jy;
+
+       if (local_player == player)
+         local_player = field_player;
+
+       map_player_action[field_player->index_nr] = i;
+
+       field_player->mapped = TRUE;
+
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("- map_player_action[%d] == %d\n",
+                field_player->index_nr + 1, i + 1);
+#endif
+      }
+    }
+
+    if (player->connected && player->present)
+      player->mapped = TRUE;
+  }
+
+#if DEBUG_INIT_PLAYER
+  DebugPrintPlayerStatus("Player status after player assignment (first stage)");
+#endif
+
+#else
+
+  // check if any connected player was not found in playfield
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    if (player->connected && !player->present)
+    {
+      for (j = 0; j < MAX_PLAYERS; j++)
+      {
+       struct PlayerInfo *field_player = &stored_player[j];
+       int jx = field_player->jx, jy = field_player->jy;
+
+       // assign first free player found that is present in the playfield
+       if (field_player->present && !field_player->connected)
+       {
+         player->present = TRUE;
+         player->active = TRUE;
+
+         field_player->present = FALSE;
+         field_player->active = FALSE;
+
+         player->initial_element = field_player->initial_element;
+         player->artwork_element = field_player->artwork_element;
+
+         player->block_last_field       = field_player->block_last_field;
+         player->block_delay_adjustment = field_player->block_delay_adjustment;
+
+         StorePlayer[jx][jy] = player->element_nr;
+
+         player->jx = player->last_jx = jx;
+         player->jy = player->last_jy = jy;
+
+         break;
+       }
+      }
+    }
+  }
+#endif
+
+#if 0
+  printf("::: local_player->present == %d\n", local_player->present);
+#endif
+
+  // set focus to local player for network games, else to all players
+  game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
+  game.centered_player_nr_next = game.centered_player_nr;
+  game.set_centered_player = FALSE;
+
+  if (network_playing && tape.recording)
+  {
+    // store client dependent player focus when recording network games
+    tape.centered_player_nr_next = game.centered_player_nr_next;
+    tape.set_centered_player = TRUE;
+  }
+
+  if (tape.playing)
+  {
+    // when playing a tape, eliminate all players who do not participate
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+
+    if (!game.team_mode)
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (stored_player[i].active &&
+           !tape.player_participates[map_player_action[i]])
+       {
+         struct PlayerInfo *player = &stored_player[i];
+         int jx = player->jx, jy = player->jy;
+
+#if DEBUG_INIT_PLAYER
+         if (options.debug)
+           printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
+#endif
+
+         player->active = FALSE;
+         StorePlayer[jx][jy] = 0;
+         Feld[jx][jy] = EL_EMPTY;
+       }
+      }
+    }
+
+#else
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      if (stored_player[i].active &&
+         !tape.player_participates[i])
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int jx = player->jx, jy = player->jy;
+
+       player->active = FALSE;
+       StorePlayer[jx][jy] = 0;
+       Feld[jx][jy] = EL_EMPTY;
+      }
+    }
+#endif
+  }
+  else if (!network.enabled && !game.team_mode)                // && !tape.playing
+  {
+    // when in single player mode, eliminate all but the local player
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
 
-static inline void InitField_WithBug2(int x, int y, boolean init_game)
-{
-  int old_element = Feld[x][y];
+      if (player->active && player != local_player)
+      {
+       int jx = player->jx, jy = player->jy;
 
-  InitField(x, y, init_game);
+       player->active = FALSE;
+       player->present = FALSE;
 
-  /* not needed to call InitMovDir() -- already done by InitField()! */
-  if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
-      CAN_MOVE(old_element) &&
-      (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
-    InitMovDir(x, y);
+       StorePlayer[jx][jy] = 0;
+       Feld[jx][jy] = EL_EMPTY;
+      }
+    }
+  }
 
-  /* this case is in fact a combination of not less than three bugs:
-     first, it calls InitMovDir() for elements that can move, although this is
-     already done by InitField(); then, it checks the element that was at this
-     field _before_ the call to InitField() (which can change it); lastly, it
-     was not called for "mole with direction" elements, which were treated as
-     "cannot move" due to (fixed) wrong element initialization in "src/init.c"
-  */
-}
+  for (i = 0; i < MAX_PLAYERS; i++)
+    if (stored_player[i].active)
+      game.players_still_needed++;
 
-inline void DrawGameValue_Emeralds(int value)
-{
-  DrawText(DX_EMERALDS, DY_EMERALDS, int2str(value, 3), FONT_TEXT_2);
-}
+  if (level.solved_by_one_player)
+    game.players_still_needed = 1;
 
-inline void DrawGameValue_Dynamite(int value)
-{
-  DrawText(DX_DYNAMITE, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2);
-}
+  // when recording the game, store which players take part in the game
+  if (tape.recording)
+  {
+#if USE_NEW_PLAYER_ASSIGNMENTS
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (stored_player[i].connected)
+       tape.player_participates[i] = TRUE;
+#else
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (stored_player[i].active)
+       tape.player_participates[i] = TRUE;
+#endif
+  }
 
-inline void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
-{
-  int i;
+#if DEBUG_INIT_PLAYER
+  DebugPrintPlayerStatus("Player status after player assignment (final stage)");
+#endif
 
-  /* currently only 4 of 8 possible keys are displayed */
-  for (i = 0; i < STD_NUM_KEYS; i++)
+  if (BorderElement == EL_EMPTY)
   {
-    if (key[i])
-      DrawMiniGraphicExt(drawto, DX_KEYS + i * MINI_TILEX, DY_KEYS,
-                        el2edimg(EL_KEY_1 + i));
-    else
-      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
-                DOOR_GFX_PAGEX5 + XX_KEYS + i * MINI_TILEX, YY_KEYS,
-                MINI_TILEX, MINI_TILEY, DX_KEYS + i * MINI_TILEX, DY_KEYS);
+    SBX_Left = 0;
+    SBX_Right = lev_fieldx - SCR_FIELDX;
+    SBY_Upper = 0;
+    SBY_Lower = lev_fieldy - SCR_FIELDY;
+  }
+  else
+  {
+    SBX_Left = -1;
+    SBX_Right = lev_fieldx - SCR_FIELDX + 1;
+    SBY_Upper = -1;
+    SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
   }
-}
 
-inline void DrawGameValue_Score(int value)
-{
-  DrawText(DX_SCORE, DY_SCORE, int2str(value, 5), FONT_TEXT_2);
-}
+  if (full_lev_fieldx <= SCR_FIELDX)
+    SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
+  if (full_lev_fieldy <= SCR_FIELDY)
+    SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
 
-inline void DrawGameValue_Time(int value)
-{
-  if (value < 1000)
-    DrawText(DX_TIME1, DY_TIME, int2str(value, 3), FONT_TEXT_2);
-  else
-    DrawText(DX_TIME2, DY_TIME, int2str(value, 4), FONT_LEVEL_NUMBER);
-}
+  if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
+    SBX_Left--;
+  if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
+    SBY_Upper--;
 
-inline void DrawGameValue_Level(int value)
-{
-  if (level_nr < 100)
-    DrawText(DX_LEVEL, DY_LEVEL, int2str(value, 2), FONT_TEXT_2);
-  else
+  // if local player not found, look for custom element that might create
+  // the player (make some assumptions about the right custom element)
+  if (!local_player->present)
   {
-    /* misuse area for displaying emeralds to draw bigger level number */
-    DrawTextExt(drawto, DX_EMERALDS, DY_EMERALDS,
-               int2str(value, 3), FONT_LEVEL_NUMBER, BLIT_OPAQUE);
+    int start_x = 0, start_y = 0;
+    int found_rating = 0;
+    int found_element = EL_UNDEFINED;
+    int player_nr = local_player->index_nr;
 
-    /* now copy it to the area for displaying level number */
-    BlitBitmap(drawto, drawto,
-              DX_EMERALDS, DY_EMERALDS + 1,
-              getFontWidth(FONT_LEVEL_NUMBER) * 3,
-              getFontHeight(FONT_LEVEL_NUMBER) - 1,
-              DX_LEVEL - 1, DY_LEVEL + 1);
+    SCAN_PLAYFIELD(x, y)
+    {
+      int element = Feld[x][y];
+      int content;
+      int xx, yy;
+      boolean is_player;
 
-    /* restore the area for displaying emeralds */
-    DrawGameValue_Emeralds(local_player->gems_still_needed);
+      if (level.use_start_element[player_nr] &&
+         level.start_element[player_nr] == element &&
+         found_rating < 4)
+      {
+       start_x = x;
+       start_y = y;
 
-    /* yes, this is all really ugly :-) */
-  }
-}
+       found_rating = 4;
+       found_element = element;
+      }
 
-void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
-                      int key_bits)
-{
-  int key[MAX_NUM_KEYS];
-  int i;
+      if (!IS_CUSTOM_ELEMENT(element))
+       continue;
 
-  for (i = 0; i < MAX_NUM_KEYS; i++)
-    key[i] = key_bits & (1 << i);
+      if (CAN_CHANGE(element))
+      {
+       for (i = 0; i < element_info[element].num_change_pages; i++)
+       {
+         // check for player created from custom element as single target
+         content = element_info[element].change_page[i].target_element;
+         is_player = ELEM_IS_PLAYER(content);
 
-  DrawGameValue_Level(level_nr);
+         if (is_player && (found_rating < 3 ||
+                           (found_rating == 3 && element < found_element)))
+         {
+           start_x = x;
+           start_y = y;
 
-  DrawGameValue_Emeralds(emeralds);
-  DrawGameValue_Dynamite(dynamite);
-  DrawGameValue_Score(score);
-  DrawGameValue_Time(time);
+           found_rating = 3;
+           found_element = element;
+         }
+       }
+      }
 
-  DrawGameValue_Keys(key);
-}
+      for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
+      {
+       // check for player created from custom element as explosion content
+       content = element_info[element].content.e[xx][yy];
+       is_player = ELEM_IS_PLAYER(content);
 
-void DrawGameDoorValues()
-{
-  int i;
+       if (is_player && (found_rating < 2 ||
+                         (found_rating == 2 && element < found_element)))
+       {
+         start_x = x + xx - 1;
+         start_y = y + yy - 1;
+
+         found_rating = 2;
+         found_element = element;
+       }
+
+       if (!CAN_CHANGE(element))
+         continue;
+
+       for (i = 0; i < element_info[element].num_change_pages; i++)
+       {
+         // check for player created from custom element as extended target
+         content =
+           element_info[element].change_page[i].target_content.e[xx][yy];
 
+         is_player = ELEM_IS_PLAYER(content);
+
+         if (is_player && (found_rating < 1 ||
+                           (found_rating == 1 && element < found_element)))
+         {
+           start_x = x + xx - 1;
+           start_y = y + yy - 1;
+
+           found_rating = 1;
+           found_element = element;
+         }
+       }
+      }
+    }
+
+    scroll_x = SCROLL_POSITION_X(start_x);
+    scroll_y = SCROLL_POSITION_Y(start_y);
+  }
+  else
+  {
+    scroll_x = SCROLL_POSITION_X(local_player->jx);
+    scroll_y = SCROLL_POSITION_Y(local_player->jy);
+  }
+
+  // !!! FIX THIS (START) !!!
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
-    DrawGameDoorValues_EM();
+    InitGameEngine_EM();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    InitGameEngine_SP();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+  {
+    InitGameEngine_MM();
+  }
+  else
+  {
+    DrawLevel(REDRAW_FIELD);
+    DrawAllPlayers();
 
-    return;
+    // after drawing the level, correct some elements
+    if (game.timegate_time_left == 0)
+      CloseAllOpenTimegates();
   }
 
-  DrawGameValue_Level(level_nr);
+  // blit playfield from scroll buffer to normal back buffer for fading in
+  BlitScreenToBitmap(backbuffer);
+  // !!! FIX THIS (END) !!!
 
-  DrawGameValue_Emeralds(local_player->gems_still_needed);
-  DrawGameValue_Dynamite(local_player->inventory_size);
-  DrawGameValue_Score(local_player->score);
-  DrawGameValue_Time(TimeLeft);
+  DrawMaskedBorder(fade_mask);
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    DrawGameValue_Keys(stored_player[i].key);
-}
+  FadeIn(fade_mask);
 
-static void resolve_group_element(int group_element, int recursion_depth)
-{
-  static int group_nr;
-  static struct ElementGroupInfo *group;
-  struct ElementGroupInfo *actual_group = element_info[group_element].group;
-  int i;
+#if 1
+  // full screen redraw is required at this point in the following cases:
+  // - special editor door undrawn when game was started from level editor
+  // - drawing area (playfield) was changed and has to be removed completely
+  redraw_mask = REDRAW_ALL;
+  BackToFront();
+#endif
 
-  if (recursion_depth > NUM_GROUP_ELEMENTS)    /* recursion too deep */
+  if (!game.restart_level)
   {
-    Error(ERR_WARN, "recursion too deep when resolving group element %d",
-         group_element - EL_GROUP_START + 1);
+    // copy default game door content to main double buffer
 
-    /* replace element which caused too deep recursion by question mark */
-    group->element_resolved[group->num_elements_resolved++] = EL_UNKNOWN;
-
-    return;
+    // !!! CHECK AGAIN !!!
+    SetPanelBackground();
+    // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+    DrawBackground(DX, DY, DXSIZE, DYSIZE);
   }
 
-  if (recursion_depth == 0)                    /* initialization */
+  SetPanelBackground();
+  SetDrawBackgroundMask(REDRAW_DOOR_1);
+
+  UpdateAndDisplayGameControlValues();
+
+  if (!game.restart_level)
   {
-    group = element_info[group_element].group;
-    group_nr = group_element - EL_GROUP_START;
+    UnmapGameButtons();
+    UnmapTapeButtons();
+
+    FreeGameButtons();
+    CreateGameButtons();
+
+    MapGameButtons();
+    MapTapeButtons();
+
+    // copy actual game door content to door double buffer for OpenDoor()
+    BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
+
+    OpenDoor(DOOR_OPEN_ALL);
 
-    group->num_elements_resolved = 0;
-    group->choice_pos = 0;
+    KeyboardAutoRepeatOffUnlessAutoplay();
+
+#if DEBUG_INIT_PLAYER
+    DebugPrintPlayerStatus("Player status (final)");
+#endif
   }
 
-  for (i = 0; i < actual_group->num_elements; i++)
-  {
-    int element = actual_group->element[i];
+  UnmapAllGadgets();
 
-    if (group->num_elements_resolved == NUM_FILE_ELEMENTS)
-      break;
+  MapGameButtons();
+  MapTapeButtons();
 
-    if (IS_GROUP_ELEMENT(element))
-      resolve_group_element(element, recursion_depth + 1);
-    else
-    {
-      group->element_resolved[group->num_elements_resolved++] = element;
-      element_info[element].in_group[group_nr] = TRUE;
-    }
+  if (!game.restart_level && !tape.playing)
+  {
+    LevelStats_incPlayed(level_nr);
+
+    SaveLevelSetup_SeriesInfo();
   }
-}
 
+  game.restart_level = FALSE;
+  game.restart_game_message = NULL;
+  game.request_active = FALSE;
 
-/*
-  =============================================================================
-  InitGameEngine()
-  -----------------------------------------------------------------------------
-  initialize game engine due to level / tape version number
-  =============================================================================
-*/
+  if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+    InitGameActions_MM();
 
-static void InitGameEngine()
-{
-  int i, j, k, l, x, y;
+  SaveEngineSnapshotToListInitial();
 
-  /* set game engine from tape file when re-playing, else from level file */
-  game.engine_version = (tape.playing ? tape.engine_version :
-                        level.game_version);
+  if (!game.restart_level)
+  {
+    PlaySound(SND_GAME_STARTING);
 
-  /* ---------------------------------------------------------------------- */
-  /* set flags for bugs and changes according to active game engine version */
-  /* ---------------------------------------------------------------------- */
+    if (setup.sound_music)
+      PlayLevelMusic();
+  }
+}
 
-  /*
-    Summary of bugfix/change:
-    Fixed handling for custom elements that change when pushed by the player.
+void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
+                       int actual_player_x, int actual_player_y)
+{
+  // this is used for non-R'n'D game engines to update certain engine values
 
-    Fixed/changed in version:
-    3.1.0
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    actual_player_x = correctLevelPosX_EM(actual_player_x);
+    actual_player_y = correctLevelPosY_EM(actual_player_y);
+  }
 
-    Description:
-    Before 3.1.0, custom elements that "change when pushing" changed directly
-    after the player started pushing them (until then handled in "DigField()").
-    Since 3.1.0, these custom elements are not changed until the "pushing"
-    move of the element is finished (now handled in "ContinueMoving()").
+  // needed to determine if sounds are played within the visible screen area
+  scroll_x = actual_scroll_x;
+  scroll_y = actual_scroll_y;
 
-    Affected levels/tapes:
-    The first condition is generally needed for all levels/tapes before version
-    3.1.0, which might use the old behaviour before it was changed; known tapes
-    that are affected are some tapes from the level set "Walpurgis Gardens" by
-    Jamie Cullen.
-    The second condition is an exception from the above case and is needed for
-    the special case of tapes recorded with game (not engine!) version 3.1.0 or
-    above (including some development versions of 3.1.0), but before it was
-    known that this change would break tapes like the above and was fixed in
-    3.1.1, so that the changed behaviour was active although the engine version
-    while recording maybe was before 3.1.0. There is at least one tape that is
-    affected by this exception, which is the tape for the one-level set "Bug
-    Machine" by Juergen Bonhagen.
-  */
+  // needed to get player position for "follow finger" playing input method
+  local_player->jx = actual_player_x;
+  local_player->jy = actual_player_y;
+}
 
-  game.use_change_when_pushing_bug =
-    (game.engine_version < VERSION_IDENT(3,1,0,0) &&
-     !(tape.playing &&
-       tape.game_version >= VERSION_IDENT(3,1,0,0) &&
-       tape.game_version <  VERSION_IDENT(3,1,1,0)));
+void InitMovDir(int x, int y)
+{
+  int i, element = Feld[x][y];
+  static int xy[4][2] =
+  {
+    {  0, +1 },
+    { +1,  0 },
+    {  0, -1 },
+    { -1,  0 }
+  };
+  static int direction[3][4] =
+  {
+    { MV_RIGHT, MV_UP,   MV_LEFT,  MV_DOWN },
+    { MV_LEFT,  MV_DOWN, MV_RIGHT, MV_UP },
+    { MV_LEFT,  MV_RIGHT, MV_UP, MV_DOWN }
+  };
 
-  /*
-    Summary of bugfix/change:
-    Fixed handling for blocking the field the player leaves when moving.
+  switch (element)
+  {
+    case EL_BUG_RIGHT:
+    case EL_BUG_UP:
+    case EL_BUG_LEFT:
+    case EL_BUG_DOWN:
+      Feld[x][y] = EL_BUG;
+      MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
+      break;
 
-    Fixed/changed in version:
-    3.1.1
+    case EL_SPACESHIP_RIGHT:
+    case EL_SPACESHIP_UP:
+    case EL_SPACESHIP_LEFT:
+    case EL_SPACESHIP_DOWN:
+      Feld[x][y] = EL_SPACESHIP;
+      MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
+      break;
 
-    Description:
-    Before 3.1.1, when "block last field when moving" was enabled, the field
-    the player is leaving when moving was blocked for the time of the move,
-    and was directly unblocked afterwards. This resulted in the last field
-    being blocked for exactly one less than the number of frames of one player
-    move. Additionally, even when blocking was disabled, the last field was
-    blocked for exactly one frame.
-    Since 3.1.1, due to changes in player movement handling, the last field
-    is not blocked at all when blocking is disabled. When blocking is enabled,
-    the last field is blocked for exactly the number of frames of one player
-    move. Additionally, if the player is Murphy, the hero of Supaplex, the
-    last field is blocked for exactly one more than the number of frames of
-    one player move.
+    case EL_BD_BUTTERFLY_RIGHT:
+    case EL_BD_BUTTERFLY_UP:
+    case EL_BD_BUTTERFLY_LEFT:
+    case EL_BD_BUTTERFLY_DOWN:
+      Feld[x][y] = EL_BD_BUTTERFLY;
+      MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
+      break;
 
-    Affected levels/tapes:
-    (!!! yet to be determined -- probably many !!!)
-  */
+    case EL_BD_FIREFLY_RIGHT:
+    case EL_BD_FIREFLY_UP:
+    case EL_BD_FIREFLY_LEFT:
+    case EL_BD_FIREFLY_DOWN:
+      Feld[x][y] = EL_BD_FIREFLY;
+      MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
+      break;
 
-  game.use_block_last_field_bug =
-    (game.engine_version < VERSION_IDENT(3,1,1,0));
+    case EL_PACMAN_RIGHT:
+    case EL_PACMAN_UP:
+    case EL_PACMAN_LEFT:
+    case EL_PACMAN_DOWN:
+      Feld[x][y] = EL_PACMAN;
+      MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
+      break;
 
-  /*
-    Summary of bugfix/change:
-    Changed behaviour of CE changes with multiple changes per single frame.
+    case EL_YAMYAM_LEFT:
+    case EL_YAMYAM_RIGHT:
+    case EL_YAMYAM_UP:
+    case EL_YAMYAM_DOWN:
+      Feld[x][y] = EL_YAMYAM;
+      MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
+      break;
 
-    Fixed/changed in version:
-    3.2.0-6
+    case EL_SP_SNIKSNAK:
+      MovDir[x][y] = MV_UP;
+      break;
 
-    Description:
-    Before 3.2.0-6, only one single CE change was allowed in each engine frame.
-    This resulted in race conditions where CEs seem to behave strange in some
-    situations (where triggered CE changes were just skipped because there was
-    already a CE change on that tile in the playfield in that engine frame).
-    Since 3.2.0-6, this was changed to allow up to MAX_NUM_CHANGES_PER_FRAME.
-    (The number of changes per frame must be limited in any case, because else
-    it is easily possible to define CE changes that would result in an infinite
-    loop, causing the whole game to freeze. The MAX_NUM_CHANGES_PER_FRAME value
-    should be set large enough so that it would only be reached in cases where
-    the corresponding CE change conditions run into a loop. Therefore, it seems
-    to be reasonable to set MAX_NUM_CHANGES_PER_FRAME to the same value as the
-    maximal number of change pages for custom elements.)
+    case EL_SP_ELECTRON:
+      MovDir[x][y] = MV_LEFT;
+      break;
 
-    Affected levels/tapes:
-    Probably many.
-  */
+    case EL_MOLE_LEFT:
+    case EL_MOLE_RIGHT:
+    case EL_MOLE_UP:
+    case EL_MOLE_DOWN:
+      Feld[x][y] = EL_MOLE;
+      MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
+      break;
 
-#if USE_ONLY_ONE_CHANGE_PER_FRAME
-  game.max_num_changes_per_frame = 1;
-#else
-  game.max_num_changes_per_frame =
-    (game.engine_version < VERSION_IDENT(3,2,0,6) ? 1 : 32);
-#endif
+    default:
+      if (IS_CUSTOM_ELEMENT(element))
+      {
+       struct ElementInfo *ei = &element_info[element];
+       int move_direction_initial = ei->move_direction_initial;
+       int move_pattern = ei->move_pattern;
 
-  /* ---------------------------------------------------------------------- */
+       if (move_direction_initial == MV_START_PREVIOUS)
+       {
+         if (MovDir[x][y] != MV_NONE)
+           return;
 
-  /* default scan direction: scan playfield from top/left to bottom/right */
-  InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
+         move_direction_initial = MV_START_AUTOMATIC;
+       }
 
-  /* dynamically adjust element properties according to game engine version */
-  InitElementPropertiesEngine(game.engine_version);
+       if (move_direction_initial == MV_START_RANDOM)
+         MovDir[x][y] = 1 << RND(4);
+       else if (move_direction_initial & MV_ANY_DIRECTION)
+         MovDir[x][y] = move_direction_initial;
+       else if (move_pattern == MV_ALL_DIRECTIONS ||
+                move_pattern == MV_TURNING_LEFT ||
+                move_pattern == MV_TURNING_RIGHT ||
+                move_pattern == MV_TURNING_LEFT_RIGHT ||
+                move_pattern == MV_TURNING_RIGHT_LEFT ||
+                move_pattern == MV_TURNING_RANDOM)
+         MovDir[x][y] = 1 << RND(4);
+       else if (move_pattern == MV_HORIZONTAL)
+         MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
+       else if (move_pattern == MV_VERTICAL)
+         MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
+       else if (move_pattern & MV_ANY_DIRECTION)
+         MovDir[x][y] = element_info[element].move_pattern;
+       else if (move_pattern == MV_ALONG_LEFT_SIDE ||
+                move_pattern == MV_ALONG_RIGHT_SIDE)
+       {
+         // use random direction as default start direction
+         if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+           MovDir[x][y] = 1 << RND(4);
 
-#if 0
-  printf("level %d: level version == %06d\n", level_nr, level.game_version);
-  printf("          tape version == %06d [%s] [file: %06d]\n",
-        tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
-        tape.file_version);
-  printf("       => game.engine_version == %06d\n", game.engine_version);
-#endif
+         for (i = 0; i < NUM_DIRECTIONS; i++)
+         {
+           int x1 = x + xy[i][0];
+           int y1 = y + xy[i][1];
 
-  /* ---------- recursively resolve group elements ------------------------- */
+           if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
+           {
+             if (move_pattern == MV_ALONG_RIGHT_SIDE)
+               MovDir[x][y] = direction[0][i];
+             else
+               MovDir[x][y] = direction[1][i];
 
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    for (j = 0; j < NUM_GROUP_ELEMENTS; j++)
-      element_info[i].in_group[j] = FALSE;
+             break;
+           }
+         }
+       }                
+      }
+      else
+      {
+       MovDir[x][y] = 1 << RND(4);
 
-  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
-    resolve_group_element(EL_GROUP_START + i, 0);
+       if (element != EL_BUG &&
+           element != EL_SPACESHIP &&
+           element != EL_BD_BUTTERFLY &&
+           element != EL_BD_FIREFLY)
+         break;
 
-  /* ---------- initialize player's initial move delay --------------------- */
+       for (i = 0; i < NUM_DIRECTIONS; i++)
+       {
+         int x1 = x + xy[i][0];
+         int y1 = y + xy[i][1];
 
-  /* dynamically adjust player properties according to level information */
-  game.initial_move_delay_value =
-    (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
+         if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
+         {
+           if (element == EL_BUG || element == EL_BD_BUTTERFLY)
+           {
+             MovDir[x][y] = direction[0][i];
+             break;
+           }
+           else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
+                    element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
+           {
+             MovDir[x][y] = direction[1][i];
+             break;
+           }
+         }
+       }
+      }
+      break;
+  }
 
-  /* dynamically adjust player properties according to game engine version */
-  game.initial_move_delay = (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
-                            game.initial_move_delay_value : 0);
+  GfxDir[x][y] = MovDir[x][y];
+}
 
-  /* ---------- initialize player's initial push delay --------------------- */
+void InitAmoebaNr(int x, int y)
+{
+  int i;
+  int group_nr = AmoebeNachbarNr(x, y);
 
-  /* dynamically adjust player properties according to game engine version */
-  game.initial_push_delay_value =
-    (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
+  if (group_nr == 0)
+  {
+    for (i = 1; i < MAX_NUM_AMOEBA; i++)
+    {
+      if (AmoebaCnt[i] == 0)
+      {
+       group_nr = i;
+       break;
+      }
+    }
+  }
 
-  /* ---------- initialize changing elements ------------------------------- */
+  AmoebaNr[x][y] = group_nr;
+  AmoebaCnt[group_nr]++;
+  AmoebaCnt2[group_nr]++;
+}
 
-  /* initialize changing elements information */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-  {
-    struct ElementInfo *ei = &element_info[i];
+static void LevelSolved(void)
+{
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
+      game.players_still_needed > 0)
+    return;
 
-    /* this pointer might have been changed in the level editor */
-    ei->change = &ei->change_page[0];
+  game.LevelSolved = TRUE;
+  game.GameOver = TRUE;
 
-    if (!IS_CUSTOM_ELEMENT(i))
-    {
-      ei->change->target_element = EL_EMPTY_SPACE;
-      ei->change->delay_fixed = 0;
-      ei->change->delay_random = 0;
-      ei->change->delay_frames = 1;
-    }
+  game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                     level.native_em_level->lev->score :
+                     level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                     game_mm.score :
+                     game.score);
+  game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                      MM_HEALTH(game_mm.laser_overload_value) :
+                      game.health);
 
-    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
-    {
-      ei->has_change_event[j] = FALSE;
+  game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
+  game.LevelSolved_CountingScore = game.score_final;
+  game.LevelSolved_CountingHealth = game.health_final;
+}
 
-      ei->event_page_nr[j] = 0;
-      ei->event_page[j] = &ei->change_page[0];
-    }
-  }
+void GameWon(void)
+{
+  static int time_count_steps;
+  static int time, time_final;
+  static int score, score_final;
+  static int health, health_final;
+  static int game_over_delay_1 = 0;
+  static int game_over_delay_2 = 0;
+  static int game_over_delay_3 = 0;
+  int game_over_delay_value_1 = 50;
+  int game_over_delay_value_2 = 25;
+  int game_over_delay_value_3 = 50;
 
-  /* add changing elements from pre-defined list */
-  for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
+  if (!game.LevelSolved_GameWon)
   {
-    struct ChangingElementInfo *ch_delay = &change_delay_list[i];
-    struct ElementInfo *ei = &element_info[ch_delay->element];
+    int i;
 
-    ei->change->target_element       = ch_delay->target_element;
-    ei->change->delay_fixed          = ch_delay->change_delay;
+    // do not start end game actions before the player stops moving (to exit)
+    if (local_player->active && local_player->MovPos)
+      return;
 
-    ei->change->pre_change_function  = ch_delay->pre_change_function;
-    ei->change->change_function      = ch_delay->change_function;
-    ei->change->post_change_function = ch_delay->post_change_function;
+    game.LevelSolved_GameWon = TRUE;
+    game.LevelSolved_SaveTape = tape.recording;
+    game.LevelSolved_SaveScore = !tape.playing;
 
-    ei->change->can_change = TRUE;
-    ei->change->can_change_or_has_action = TRUE;
+    if (!tape.playing)
+    {
+      LevelStats_incSolved(level_nr);
 
-    ei->has_change_event[CE_DELAY] = TRUE;
+      SaveLevelSetup_SeriesInfo();
+    }
 
-    SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
-    SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
-  }
+    if (tape.auto_play)                // tape might already be stopped here
+      tape.auto_play_level_solved = TRUE;
 
-  /* ---------- initialize internal run-time variables ------------- */
+    TapeStop();
 
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
-  {
-    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+    game_over_delay_1 = 0;
+    game_over_delay_2 = 0;
+    game_over_delay_3 = game_over_delay_value_3;
 
-    for (j = 0; j < ei->num_change_pages; j++)
+    time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
+    score = score_final = game.score_final;
+    health = health_final = game.health_final;
+
+    if (level.score[SC_TIME_BONUS] > 0)
     {
-      ei->change_page[j].can_change_or_has_action =
-       (ei->change_page[j].can_change |
-        ei->change_page[j].has_action);
-    }
-  }
+      if (TimeLeft > 0)
+      {
+       time_final = 0;
+       score_final += TimeLeft * level.score[SC_TIME_BONUS];
+      }
+      else if (game.no_time_limit && TimePlayed < 999)
+      {
+       time_final = 999;
+       score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
+      }
 
-  /* add change events from custom element configuration */
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
-  {
-    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+      time_count_steps = MAX(1, ABS(time_final - time) / 100);
 
-    for (j = 0; j < ei->num_change_pages; j++)
-    {
-      if (!ei->change_page[j].can_change_or_has_action)
-       continue;
+      game_over_delay_1 = game_over_delay_value_1;
 
-      for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+      if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
       {
-       /* only add event page for the first page found with this event */
-       if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
-       {
-         ei->has_change_event[k] = TRUE;
+       health_final = 0;
+       score_final += health * level.score[SC_TIME_BONUS];
 
-         ei->event_page_nr[k] = j;
-         ei->event_page[k] = &ei->change_page[j];
-       }
+       game_over_delay_2 = game_over_delay_value_2;
       }
+
+      game.score_final = score_final;
+      game.health_final = health_final;
     }
-  }
 
-  /* ---------- initialize run-time trigger player and element ------------- */
+    if (level_editor_test_game)
+    {
+      time = time_final;
+      score = score_final;
 
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
-  {
-    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+      game.LevelSolved_CountingTime = time;
+      game.LevelSolved_CountingScore = score;
 
-    for (j = 0; j < ei->num_change_pages; j++)
-    {
-      ei->change_page[j].actual_trigger_element = EL_EMPTY;
-      ei->change_page[j].actual_trigger_player = EL_PLAYER_1;
-      ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
-      ei->change_page[j].actual_trigger_ce_value = 0;
+      game_panel_controls[GAME_PANEL_TIME].value = time;
+      game_panel_controls[GAME_PANEL_SCORE].value = score;
+
+      DisplayGameControlValues();
     }
-  }
 
-  /* ---------- initialize trigger events ---------------------------------- */
+    if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    {
+      // check if last player has left the level
+      if (game.exit_x >= 0 &&
+         game.exit_y >= 0)
+      {
+       int x = game.exit_x;
+       int y = game.exit_y;
+       int element = Feld[x][y];
 
-  /* initialize trigger events information */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
-      trigger_events[i][j] = FALSE;
+       // close exit door after last player
+       if ((game.all_players_gone &&
+            (element == EL_EXIT_OPEN ||
+             element == EL_SP_EXIT_OPEN ||
+             element == EL_STEEL_EXIT_OPEN)) ||
+           element == EL_EM_EXIT_OPEN ||
+           element == EL_EM_STEEL_EXIT_OPEN)
+       {
 
-  /* add trigger events from element change event properties */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-  {
-    struct ElementInfo *ei = &element_info[i];
+         Feld[x][y] =
+           (element == EL_EXIT_OPEN            ? EL_EXIT_CLOSING :
+            element == EL_EM_EXIT_OPEN         ? EL_EM_EXIT_CLOSING :
+            element == EL_SP_EXIT_OPEN         ? EL_SP_EXIT_CLOSING:
+            element == EL_STEEL_EXIT_OPEN      ? EL_STEEL_EXIT_CLOSING:
+            EL_EM_STEEL_EXIT_CLOSING);
 
-    for (j = 0; j < ei->num_change_pages; j++)
-    {
-      if (!ei->change_page[j].can_change_or_has_action)
-       continue;
+         PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
+       }
 
-      if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
+       // player disappears
+       DrawLevelField(x, y);
+      }
+
+      for (i = 0; i < MAX_PLAYERS; i++)
       {
-       int trigger_element = ei->change_page[j].trigger_element;
+       struct PlayerInfo *player = &stored_player[i];
 
-       for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+       if (player->present)
        {
-         if (ei->change_page[j].has_event[k])
-         {
-           if (IS_GROUP_ELEMENT(trigger_element))
-           {
-             struct ElementGroupInfo *group =
-               element_info[trigger_element].group;
+         RemovePlayer(player);
 
-             for (l = 0; l < group->num_elements_resolved; l++)
-               trigger_events[group->element_resolved[l]][k] = TRUE;
-           }
-           else
-             trigger_events[trigger_element][k] = TRUE;
-         }
+         // player disappears
+         DrawLevelField(player->jx, player->jy);
        }
       }
     }
-  }
-
-  /* ---------- initialize push delay -------------------------------------- */
 
-  /* initialize push delay values to default */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-  {
-    if (!IS_CUSTOM_ELEMENT(i))
-    {
-      element_info[i].push_delay_fixed  = game.default_push_delay_fixed;
-      element_info[i].push_delay_random = game.default_push_delay_random;
-    }
+    PlaySound(SND_GAME_WINNING);
   }
 
-  /* set push delay value for certain elements from pre-defined list */
-  for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
+  if (game_over_delay_1 > 0)
   {
-    int e = push_delay_list[i].element;
+    game_over_delay_1--;
 
-    element_info[e].push_delay_fixed  = push_delay_list[i].push_delay_fixed;
-    element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
+    return;
   }
 
-  /* set push delay value for Supaplex elements for newer engine versions */
-  if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+  if (time != time_final)
   {
-    for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    {
-      if (IS_SP_ELEMENT(i))
-      {
-       /* set SP push delay to just enough to push under a falling zonk */
-       int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
+    int time_to_go = ABS(time_final - time);
+    int time_count_dir = (time < time_final ? +1 : -1);
 
-       element_info[i].push_delay_fixed  = delay;
-       element_info[i].push_delay_random = 0;
-      }
-    }
-  }
+    if (time_to_go < time_count_steps)
+      time_count_steps = 1;
 
-  /* ---------- initialize move stepsize ----------------------------------- */
+    time  += time_count_steps * time_count_dir;
+    score += time_count_steps * level.score[SC_TIME_BONUS];
 
-  /* initialize move stepsize values to default */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    if (!IS_CUSTOM_ELEMENT(i))
-      element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
+    game.LevelSolved_CountingTime = time;
+    game.LevelSolved_CountingScore = score;
 
-  /* set move stepsize value for certain elements from pre-defined list */
-  for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
+    game_panel_controls[GAME_PANEL_TIME].value = time;
+    game_panel_controls[GAME_PANEL_SCORE].value = score;
+
+    DisplayGameControlValues();
+
+    if (time == time_final)
+      StopSound(SND_GAME_LEVELTIME_BONUS);
+    else if (setup.sound_loops)
+      PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
+    else
+      PlaySound(SND_GAME_LEVELTIME_BONUS);
+
+    return;
+  }
+
+  if (game_over_delay_2 > 0)
   {
-    int e = move_stepsize_list[i].element;
+    game_over_delay_2--;
 
-    element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
+    return;
   }
 
-  /* ---------- initialize collect score ----------------------------------- */
+  if (health != health_final)
+  {
+    int health_count_dir = (health < health_final ? +1 : -1);
 
-  /* initialize collect score values for custom elements from initial value */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    if (IS_CUSTOM_ELEMENT(i))
-      element_info[i].collect_score = element_info[i].collect_score_initial;
+    health += health_count_dir;
+    score  += level.score[SC_TIME_BONUS];
 
-  /* ---------- initialize collect count ----------------------------------- */
+    game.LevelSolved_CountingHealth = health;
+    game.LevelSolved_CountingScore = score;
 
-  /* initialize collect count values for non-custom elements */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    if (!IS_CUSTOM_ELEMENT(i))
-      element_info[i].collect_count_initial = 0;
+    game_panel_controls[GAME_PANEL_HEALTH].value = health;
+    game_panel_controls[GAME_PANEL_SCORE].value = score;
 
-  /* add collect count values for all elements from pre-defined list */
-  for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
-    element_info[collect_count_list[i].element].collect_count_initial =
-      collect_count_list[i].count;
+    DisplayGameControlValues();
 
-  /* ---------- initialize access direction -------------------------------- */
+    if (health == health_final)
+      StopSound(SND_GAME_LEVELTIME_BONUS);
+    else if (setup.sound_loops)
+      PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
+    else
+      PlaySound(SND_GAME_LEVELTIME_BONUS);
 
-  /* initialize access direction values to default (access from every side) */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    if (!IS_CUSTOM_ELEMENT(i))
-      element_info[i].access_direction = MV_ALL_DIRECTIONS;
+    return;
+  }
 
-  /* set access direction value for certain elements from pre-defined list */
-  for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
-    element_info[access_direction_list[i].element].access_direction =
-      access_direction_list[i].direction;
+  game.panel.active = FALSE;
 
-  /* ---------- initialize explosion content ------------------------------- */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  if (game_over_delay_3 > 0)
   {
-    if (IS_CUSTOM_ELEMENT(i))
-      continue;
-
-    for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
-    {
-      /* (content for EL_YAMYAM set at run-time with game.yamyam_content_nr) */
+    game_over_delay_3--;
 
-      element_info[i].content.e[x][y] =
-       (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
-        i == EL_PLAYER_2 ? EL_EMERALD_RED :
-        i == EL_PLAYER_3 ? EL_EMERALD :
-        i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
-        i == EL_MOLE ? EL_EMERALD_RED :
-        i == EL_PENGUIN ? EL_EMERALD_PURPLE :
-        i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
-        i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
-        i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
-        i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
-        i == EL_WALL_EMERALD ? EL_EMERALD :
-        i == EL_WALL_DIAMOND ? EL_DIAMOND :
-        i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
-        i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
-        i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
-        i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
-        i == EL_WALL_PEARL ? EL_PEARL :
-        i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
-        EL_EMPTY);
-    }
+    return;
   }
+
+  GameEnd();
 }
 
-int get_num_special_action(int element, int action_first, int action_last)
+void GameEnd(void)
 {
-  int num_special_action = 0;
-  int i, j;
+  // used instead of "level_nr" (needed for network games)
+  int last_level_nr = levelset.level_nr;
+  int hi_pos;
 
-  for (i = action_first; i <= action_last; i++)
+  game.LevelSolved_GameEnd = TRUE;
+
+  if (game.LevelSolved_SaveTape)
   {
-    boolean found = FALSE;
+    // make sure that request dialog to save tape does not open door again
+    if (!global.use_envelope_request)
+      CloseDoor(DOOR_CLOSE_1);
 
-    for (j = 0; j < NUM_DIRECTIONS; j++)
-      if (el_act_dir2img(element, i, j) !=
-         el_act_dir2img(element, ACTION_DEFAULT, j))
-       found = TRUE;
+    SaveTapeChecked_LevelSolved(tape.level_nr);                // ask to save tape
+  }
 
-    if (found)
-      num_special_action++;
-    else
-      break;
+  // if no tape is to be saved, close both doors simultaneously
+  CloseDoor(DOOR_CLOSE_ALL);
+
+  if (level_editor_test_game)
+  {
+    SetGameStatus(GAME_MODE_MAIN);
+
+    DrawMainMenu();
+
+    return;
   }
 
-  return num_special_action;
-}
+  if (!game.LevelSolved_SaveScore)
+  {
+    SetGameStatus(GAME_MODE_MAIN);
 
-/*
-  =============================================================================
-  InitGame()
-  -----------------------------------------------------------------------------
-  initialize and start new game
-  =============================================================================
-*/
+    DrawMainMenu();
 
-void InitGame()
-{
-  boolean emulate_bd = TRUE;   /* unless non-BOULDERDASH elements found */
-  boolean emulate_sb = TRUE;   /* unless non-SOKOBAN     elements found */
-  boolean emulate_sp = TRUE;   /* unless non-SUPAPLEX    elements found */
-  int i, j, x, y;
+    return;
+  }
 
-  InitGameEngine();
+  if (level_nr == leveldir_current->handicap_level)
+  {
+    leveldir_current->handicap_level++;
 
-  /* don't play tapes over network */
-  network_playing = (options.network && !tape.playing);
+    SaveLevelSetup_SeriesInfo();
+  }
 
-  for (i = 0; i < MAX_PLAYERS; i++)
+  if (setup.increment_levels &&
+      level_nr < leveldir_current->last_level &&
+      !network_playing)
   {
-    struct PlayerInfo *player = &stored_player[i];
+    level_nr++;                // advance to next level
+    TapeErase();       // start with empty tape
 
-    player->index_nr = i;
-    player->index_bit = (1 << i);
-    player->element_nr = EL_PLAYER_1 + i;
+    if (setup.auto_play_next_level)
+    {
+      LoadLevel(level_nr);
 
-    player->present = FALSE;
-    player->active = FALSE;
+      SaveLevelSetup_SeriesInfo();
+    }
+  }
 
-    player->action = 0;
-    player->effective_action = 0;
-    player->programmed_action = 0;
+  hi_pos = NewHiScore(last_level_nr);
 
-    player->score = 0;
-    player->gems_still_needed = level.gems_needed;
-    player->sokobanfields_still_needed = 0;
-    player->lights_still_needed = 0;
-    player->friends_still_needed = 0;
+  if (hi_pos >= 0 && !setup.skip_scores_after_game)
+  {
+    SetGameStatus(GAME_MODE_SCORES);
 
-    for (j = 0; j < MAX_NUM_KEYS; j++)
-      player->key[j] = FALSE;
+    DrawHallOfFame(last_level_nr, hi_pos);
+  }
+  else if (setup.auto_play_next_level && setup.increment_levels &&
+          last_level_nr < leveldir_current->last_level &&
+          !network_playing)
+  {
+    StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+  }
+  else
+  {
+    SetGameStatus(GAME_MODE_MAIN);
 
-    player->dynabomb_count = 0;
-    player->dynabomb_size = 1;
-    player->dynabombs_left = 0;
-    player->dynabomb_xl = FALSE;
+    DrawMainMenu();
+  }
+}
 
-    player->MovDir = MV_NONE;
-    player->MovPos = 0;
-    player->GfxPos = 0;
-    player->GfxDir = MV_NONE;
-    player->GfxAction = ACTION_DEFAULT;
-    player->Frame = 0;
-    player->StepFrame = 0;
+int NewHiScore(int level_nr)
+{
+  int k, l;
+  int position = -1;
+  boolean one_score_entry_per_name = !program.many_scores_per_name;
 
-    player->use_murphy = FALSE;
-    player->artwork_element =
-      (level.use_artwork_element[i] ? level.artwork_element[i] :
-       player->element_nr);
+  LoadScore(level_nr);
 
-    player->block_last_field = FALSE;  /* initialized in InitPlayerField() */
-    player->block_delay_adjustment = 0;        /* initialized in InitPlayerField() */
+  if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
+      game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
+    return -1;
 
-    player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
+  for (k = 0; k < MAX_SCORE_ENTRIES; k++)
+  {
+    if (game.score_final > highscore[k].Score)
+    {
+      // player has made it to the hall of fame
 
-    player->actual_frame_counter = 0;
+      if (k < MAX_SCORE_ENTRIES - 1)
+      {
+       int m = MAX_SCORE_ENTRIES - 1;
 
-    player->step_counter = 0;
+       if (one_score_entry_per_name)
+       {
+         for (l = k; l < MAX_SCORE_ENTRIES; l++)
+           if (strEqual(setup.player_name, highscore[l].Name))
+             m = l;
 
-    player->last_move_dir = MV_NONE;
+         if (m == k)   // player's new highscore overwrites his old one
+           goto put_into_list;
+       }
 
-    player->is_waiting = FALSE;
-    player->is_moving = FALSE;
-    player->is_auto_moving = FALSE;
-    player->is_digging = FALSE;
-    player->is_snapping = FALSE;
-    player->is_collecting = FALSE;
-    player->is_pushing = FALSE;
-    player->is_switching = FALSE;
-    player->is_dropping = FALSE;
+       for (l = m; l > k; l--)
+       {
+         strcpy(highscore[l].Name, highscore[l - 1].Name);
+         highscore[l].Score = highscore[l - 1].Score;
+       }
+      }
 
-    player->is_bored = FALSE;
-    player->is_sleeping = FALSE;
+      put_into_list:
 
-    player->cannot_move = FALSE;
+      strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
+      highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
+      highscore[k].Score = game.score_final;
+      position = k;
 
-    player->frame_counter_bored = -1;
-    player->frame_counter_sleeping = -1;
+      break;
+    }
+    else if (one_score_entry_per_name &&
+            !strncmp(setup.player_name, highscore[k].Name,
+                     MAX_PLAYER_NAME_LEN))
+      break;   // player already there with a higher score
+  }
 
-    player->anim_delay_counter = 0;
-    player->post_delay_counter = 0;
+  if (position >= 0) 
+    SaveScore(level_nr);
 
-    player->action_waiting = ACTION_DEFAULT;
-    player->last_action_waiting = ACTION_DEFAULT;
-    player->special_action_bored = ACTION_DEFAULT;
-    player->special_action_sleeping = ACTION_DEFAULT;
+  return position;
+}
 
-    /* set number of special actions for bored and sleeping animation */
-    player->num_special_action_bored =
-      get_num_special_action(player->artwork_element,
-                            ACTION_BORING_1, ACTION_BORING_LAST);
-    player->num_special_action_sleeping =
-      get_num_special_action(player->artwork_element,
-                            ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+static int getElementMoveStepsizeExt(int x, int y, int direction)
+{
+  int element = Feld[x][y];
+  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
+  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
+  int horiz_move = (dx != 0);
+  int sign = (horiz_move ? dx : dy);
+  int step = sign * element_info[element].move_stepsize;
 
-    player->switch_x = -1;
-    player->switch_y = -1;
+  // special values for move stepsize for spring and things on conveyor belt
+  if (horiz_move)
+  {
+    if (CAN_FALL(element) &&
+       y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
+      step = sign * MOVE_STEPSIZE_NORMAL / 2;
+    else if (element == EL_SPRING)
+      step = sign * MOVE_STEPSIZE_NORMAL * 2;
+  }
 
-    player->drop_x = -1;
-    player->drop_y = -1;
+  return step;
+}
 
-    player->show_envelope = 0;
+static int getElementMoveStepsize(int x, int y)
+{
+  return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
+}
 
-    player->move_delay       = game.initial_move_delay;
-    player->move_delay_value = game.initial_move_delay_value;
+void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
+{
+  if (player->GfxAction != action || player->GfxDir != dir)
+  {
+    player->GfxAction = action;
+    player->GfxDir = dir;
+    player->Frame = 0;
+    player->StepFrame = 0;
+  }
+}
 
-    player->move_delay_value_next = -1;
+static void ResetGfxFrame(int x, int y)
+{
+  // profiling showed that "autotest" spends 10~20% of its time in this function
+  if (DrawingDeactivatedField())
+    return;
 
-    player->move_delay_reset_counter = 0;
+  int element = Feld[x][y];
+  int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
 
-    player->push_delay       = -1;     /* initialized when pushing starts */
-    player->push_delay_value = game.initial_push_delay_value;
+  if (graphic_info[graphic].anim_global_sync)
+    GfxFrame[x][y] = FrameCounter;
+  else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+    GfxFrame[x][y] = CustomValue[x][y];
+  else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+    GfxFrame[x][y] = element_info[element].collect_score;
+  else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+    GfxFrame[x][y] = ChangeDelay[x][y];
+}
 
-    player->drop_delay = 0;
+static void ResetGfxAnimation(int x, int y)
+{
+  GfxAction[x][y] = ACTION_DEFAULT;
+  GfxDir[x][y] = MovDir[x][y];
+  GfxFrame[x][y] = 0;
 
-    player->last_jx = player->last_jy = 0;
-    player->jx = player->jy = 0;
+  ResetGfxFrame(x, y);
+}
 
-    player->shield_normal_time_left = 0;
-    player->shield_deadly_time_left = 0;
+static void ResetRandomAnimationValue(int x, int y)
+{
+  GfxRandom[x][y] = INIT_GFX_RANDOM();
+}
 
-    player->inventory_infinite_element = EL_UNDEFINED;
-    player->inventory_size = 0;
+static void InitMovingField(int x, int y, int direction)
+{
+  int element = Feld[x][y];
+  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
+  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
+  int newx = x + dx;
+  int newy = y + dy;
+  boolean is_moving_before, is_moving_after;
 
-    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
-    SnapField(player, 0, 0);
+  // check if element was/is moving or being moved before/after mode change
+  is_moving_before = (WasJustMoving[x][y] != 0);
+  is_moving_after  = (getElementMoveStepsizeExt(x, y, direction)    != 0);
 
-    player->LevelSolved = FALSE;
-    player->GameOver = FALSE;
-  }
+  // reset animation only for moving elements which change direction of moving
+  // or which just started or stopped moving
+  // (else CEs with property "can move" / "not moving" are reset each frame)
+  if (is_moving_before != is_moving_after ||
+      direction != MovDir[x][y])
+    ResetGfxAnimation(x, y);
 
-  network_player_action_received = FALSE;
+  MovDir[x][y] = direction;
+  GfxDir[x][y] = direction;
 
-#if defined(NETWORK_AVALIABLE)
-  /* initial null action */
-  if (network_playing)
-    SendToServer_MovePlayer(MV_NONE);
-#endif
+  GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
+                    direction == MV_DOWN && CAN_FALL(element) ?
+                    ACTION_FALLING : ACTION_MOVING);
 
-  ZX = ZY = -1;
-  ExitX = ExitY = -1;
+  // this is needed for CEs with property "can move" / "not moving"
 
-  FrameCounter = 0;
-  TimeFrames = 0;
-  TimePlayed = 0;
-  TimeLeft = level.time;
-  TapeTime = 0;
+  if (is_moving_after)
+  {
+    if (Feld[newx][newy] == EL_EMPTY)
+      Feld[newx][newy] = EL_BLOCKED;
 
-  ScreenMovDir = MV_NONE;
-  ScreenMovPos = 0;
-  ScreenGfxPos = 0;
+    MovDir[newx][newy] = MovDir[x][y];
 
-  ScrollStepSize = 0;  /* will be correctly initialized by ScrollScreen() */
+    CustomValue[newx][newy] = CustomValue[x][y];
 
-  AllPlayersGone = FALSE;
+    GfxFrame[newx][newy] = GfxFrame[x][y];
+    GfxRandom[newx][newy] = GfxRandom[x][y];
+    GfxAction[newx][newy] = GfxAction[x][y];
+    GfxDir[newx][newy] = GfxDir[x][y];
+  }
+}
 
-  game.yamyam_content_nr = 0;
-  game.magic_wall_active = FALSE;
-  game.magic_wall_time_left = 0;
-  game.light_time_left = 0;
-  game.timegate_time_left = 0;
-  game.switchgate_pos = 0;
-  game.wind_direction = level.wind_direction_initial;
-  game.gravity = level.initial_gravity;
-  game.explosions_delayed = TRUE;
+void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
+{
+  int direction = MovDir[x][y];
+  int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
+  int newy = y + (direction & MV_UP   ? -1 : direction & MV_DOWN  ? +1 : 0);
 
-  game.lenses_time_left = 0;
-  game.magnify_time_left = 0;
+  *goes_to_x = newx;
+  *goes_to_y = newy;
+}
 
-  game.envelope_active = FALSE;
+void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
+{
+  int oldx = x, oldy = y;
+  int direction = MovDir[x][y];
 
-  for (i = 0; i < NUM_BELTS; i++)
+  if (direction == MV_LEFT)
+    oldx++;
+  else if (direction == MV_RIGHT)
+    oldx--;
+  else if (direction == MV_UP)
+    oldy++;
+  else if (direction == MV_DOWN)
+    oldy--;
+
+  *comes_from_x = oldx;
+  *comes_from_y = oldy;
+}
+
+static int MovingOrBlocked2Element(int x, int y)
+{
+  int element = Feld[x][y];
+
+  if (element == EL_BLOCKED)
   {
-    game.belt_dir[i] = MV_NONE;
-    game.belt_dir_nr[i] = 3;           /* not moving, next moving left */
+    int oldx, oldy;
+
+    Blocked2Moving(x, y, &oldx, &oldy);
+    return Feld[oldx][oldy];
   }
+  else
+    return element;
+}
 
-  for (i = 0; i < MAX_NUM_AMOEBA; i++)
-    AmoebaCnt[i] = AmoebaCnt2[i] = 0;
+static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
+{
+  // like MovingOrBlocked2Element(), but if element is moving
+  // and (x,y) is the field the moving element is just leaving,
+  // return EL_BLOCKED instead of the element value
+  int element = Feld[x][y];
 
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (x = 0; x < lev_fieldx; x++) for (y = 0; y < lev_fieldy; y++)
-#endif
+  if (IS_MOVING(x, y))
   {
-    Feld[x][y] = level.field[x][y];
-    MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
-    ChangeDelay[x][y] = 0;
-    ChangePage[x][y] = -1;
-#if USE_NEW_CUSTOM_VALUE
-    CustomValue[x][y] = 0;             /* initialized in InitField() */
-#endif
-    Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
-    AmoebaNr[x][y] = 0;
-    WasJustMoving[x][y] = 0;
-    WasJustFalling[x][y] = 0;
-    CheckCollision[x][y] = 0;
-    Stop[x][y] = FALSE;
-    Pushed[x][y] = FALSE;
+    if (element == EL_BLOCKED)
+    {
+      int oldx, oldy;
 
-    ChangeCount[x][y] = 0;
-    ChangeEvent[x][y] = -1;
+      Blocked2Moving(x, y, &oldx, &oldy);
+      return Feld[oldx][oldy];
+    }
+    else
+      return EL_BLOCKED;
+  }
+  else
+    return element;
+}
 
-    ExplodePhase[x][y] = 0;
-    ExplodeDelay[x][y] = 0;
-    ExplodeField[x][y] = EX_TYPE_NONE;
+static void RemoveField(int x, int y)
+{
+  Feld[x][y] = EL_EMPTY;
 
-    RunnerVisit[x][y] = 0;
-    PlayerVisit[x][y] = 0;
+  MovPos[x][y] = 0;
+  MovDir[x][y] = 0;
+  MovDelay[x][y] = 0;
 
-    GfxFrame[x][y] = 0;
-    GfxRandom[x][y] = INIT_GFX_RANDOM();
-    GfxElement[x][y] = EL_UNDEFINED;
-    GfxAction[x][y] = ACTION_DEFAULT;
-    GfxDir[x][y] = MV_NONE;
-  }
+  CustomValue[x][y] = 0;
 
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
-  {
-    if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
-      emulate_bd = FALSE;
-    if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
-      emulate_sb = FALSE;
-    if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
-      emulate_sp = FALSE;
+  AmoebaNr[x][y] = 0;
+  ChangeDelay[x][y] = 0;
+  ChangePage[x][y] = -1;
+  Pushed[x][y] = FALSE;
 
-    InitField(x, y, TRUE);
-  }
+  GfxElement[x][y] = EL_UNDEFINED;
+  GfxAction[x][y] = ACTION_DEFAULT;
+  GfxDir[x][y] = MV_NONE;
+}
 
-  InitBeltMovement();
+static void RemoveMovingField(int x, int y)
+{
+  int oldx = x, oldy = y, newx = x, newy = y;
+  int element = Feld[x][y];
+  int next_element = EL_UNDEFINED;
 
-  game.emulation = (emulate_bd ? EMU_BOULDERDASH :
-                   emulate_sb ? EMU_SOKOBAN :
-                   emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
+  if (element != EL_BLOCKED && !IS_MOVING(x, y))
+    return;
 
-#if USE_NEW_ALL_SLIPPERY
-  /* initialize type of slippery elements */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  if (IS_MOVING(x, y))
   {
-    if (!IS_CUSTOM_ELEMENT(i))
+    Moving2Blocked(x, y, &newx, &newy);
+
+    if (Feld[newx][newy] != EL_BLOCKED)
     {
-      /* default: elements slip down either to the left or right randomly */
-      element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
+      // element is moving, but target field is not free (blocked), but
+      // already occupied by something different (example: acid pool);
+      // in this case, only remove the moving field, but not the target
 
-      /* SP style elements prefer to slip down on the left side */
-      if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
-       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+      RemoveField(oldx, oldy);
 
-      /* BD style elements prefer to slip down on the left side */
-      if (game.emulation == EMU_BOULDERDASH)
-       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+      Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+
+      TEST_DrawLevelField(oldx, oldy);
+
+      return;
     }
   }
-#endif
-
-  /* initialize explosion and ignition delay */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  else if (element == EL_BLOCKED)
   {
-    if (!IS_CUSTOM_ELEMENT(i))
-    {
-      int num_phase = 8;
-      int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
-                   game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
-                  game.emulation == EMU_SUPAPLEX ? 3 : 2);
-      int last_phase = (num_phase + 1) * delay;
-      int half_phase = (num_phase / 2) * delay;
+    Blocked2Moving(x, y, &oldx, &oldy);
+    if (!IS_MOVING(oldx, oldy))
+      return;
+  }
 
-      element_info[i].explosion_delay = last_phase - 1;
-      element_info[i].ignition_delay = half_phase;
+  if (element == EL_BLOCKED &&
+      (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
+       Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
+       Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
+       Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
+       Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
+       Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
+    next_element = get_next_element(Feld[oldx][oldy]);
 
-      if (i == EL_BLACK_ORB)
-       element_info[i].ignition_delay = 1;
-    }
+  RemoveField(oldx, oldy);
+  RemoveField(newx, newy);
 
-#if 0
-    if (element_info[i].explosion_delay < 1)   /* !!! check again !!! */
-      element_info[i].explosion_delay = 1;
+  Store[oldx][oldy] = Store2[oldx][oldy] = 0;
 
-    if (element_info[i].ignition_delay < 1)    /* !!! check again !!! */
-      element_info[i].ignition_delay = 1;
-#endif
-  }
+  if (next_element != EL_UNDEFINED)
+    Feld[oldx][oldy] = next_element;
 
-  /* correct non-moving belts to start moving left */
-  for (i = 0; i < NUM_BELTS; i++)
-    if (game.belt_dir[i] == MV_NONE)
-      game.belt_dir_nr[i] = 3;         /* not moving, next moving left */
+  TEST_DrawLevelField(oldx, oldy);
+  TEST_DrawLevelField(newx, newy);
+}
 
-  /* check if any connected player was not found in playfield */
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    struct PlayerInfo *player = &stored_player[i];
+void DrawDynamite(int x, int y)
+{
+  int sx = SCREENX(x), sy = SCREENY(y);
+  int graphic = el2img(Feld[x][y]);
+  int frame;
 
-    if (player->connected && !player->present)
-    {
-      for (j = 0; j < MAX_PLAYERS; j++)
-      {
-       struct PlayerInfo *some_player = &stored_player[j];
-       int jx = some_player->jx, jy = some_player->jy;
+  if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
+    return;
 
-       /* assign first free player found that is present in the playfield */
-       if (some_player->present && !some_player->connected)
-       {
-         player->present = TRUE;
-         player->active = TRUE;
+  if (IS_WALKABLE_INSIDE(Back[x][y]))
+    return;
 
-         some_player->present = FALSE;
-         some_player->active = FALSE;
+  if (Back[x][y])
+    DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
+  else if (Store[x][y])
+    DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
 
-#if 0
-         player->element_nr = some_player->element_nr;
-#endif
+  frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
-         player->artwork_element = some_player->artwork_element;
+  if (Back[x][y] || Store[x][y])
+    DrawGraphicThruMask(sx, sy, graphic, frame);
+  else
+    DrawGraphic(sx, sy, graphic, frame);
+}
 
-         player->block_last_field       = some_player->block_last_field;
-         player->block_delay_adjustment = some_player->block_delay_adjustment;
+static void CheckDynamite(int x, int y)
+{
+  if (MovDelay[x][y] != 0)     // dynamite is still waiting to explode
+  {
+    MovDelay[x][y]--;
 
-         StorePlayer[jx][jy] = player->element_nr;
-         player->jx = player->last_jx = jx;
-         player->jy = player->last_jy = jy;
+    if (MovDelay[x][y] != 0)
+    {
+      DrawDynamite(x, y);
+      PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
 
-         break;
-       }
-      }
+      return;
     }
   }
 
-  if (tape.playing)
-  {
-    /* when playing a tape, eliminate all players which do not participate */
+  StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
 
-    for (i = 0; i < MAX_PLAYERS; i++)
+  Bang(x, y);
+}
+
+static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
+{
+  boolean num_checked_players = 0;
+  int i;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    if (stored_player[i].active)
     {
-      if (stored_player[i].active && !tape.player_participates[i])
-      {
-       struct PlayerInfo *player = &stored_player[i];
-       int jx = player->jx, jy = player->jy;
+      int sx = stored_player[i].jx;
+      int sy = stored_player[i].jy;
 
-       player->active = FALSE;
-       StorePlayer[jx][jy] = 0;
-       Feld[jx][jy] = EL_EMPTY;
+      if (num_checked_players == 0)
+      {
+       *sx1 = *sx2 = sx;
+       *sy1 = *sy2 = sy;
+      }
+      else
+      {
+       *sx1 = MIN(*sx1, sx);
+       *sy1 = MIN(*sy1, sy);
+       *sx2 = MAX(*sx2, sx);
+       *sy2 = MAX(*sy2, sy);
       }
+
+      num_checked_players++;
     }
   }
-  else if (!options.network && !setup.team_mode)       /* && !tape.playing */
-  {
-    /* when in single player mode, eliminate all but the first active player */
+}
 
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      if (stored_player[i].active)
-      {
-       for (j = i + 1; j < MAX_PLAYERS; j++)
-       {
-         if (stored_player[j].active)
-         {
-           struct PlayerInfo *player = &stored_player[j];
-           int jx = player->jx, jy = player->jy;
+static boolean checkIfAllPlayersFitToScreen_RND(void)
+{
+  int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
 
-           player->active = FALSE;
-           player->present = FALSE;
+  setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
 
-           StorePlayer[jx][jy] = 0;
-           Feld[jx][jy] = EL_EMPTY;
-         }
-       }
-      }
-    }
-  }
+  return (sx2 - sx1 < SCR_FIELDX &&
+         sy2 - sy1 < SCR_FIELDY);
+}
 
-  /* when recording the game, store which players take part in the game */
-  if (tape.recording)
-  {
-    for (i = 0; i < MAX_PLAYERS; i++)
-      if (stored_player[i].active)
-       tape.player_participates[i] = TRUE;
-  }
+static void setScreenCenteredToAllPlayers(int *sx, int *sy)
+{
+  int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
 
-  if (options.debug)
+  setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+  *sx = (sx1 + sx2) / 2;
+  *sy = (sy1 + sy2) / 2;
+}
+
+static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
+                              boolean center_screen, boolean quick_relocation)
+{
+  unsigned int frame_delay_value_old = GetVideoFrameDelay();
+  boolean ffwd_delay = (tape.playing && tape.fast_forward);
+  boolean no_delay = (tape.warp_forward);
+  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+  int new_scroll_x, new_scroll_y;
+
+  if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
   {
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      struct PlayerInfo *player = &stored_player[i];
+    // case 1: quick relocation inside visible screen (without scrolling)
 
-      printf("Player %d: present == %d, connected == %d, active == %d.\n",
-            i+1,
-            player->present,
-            player->connected,
-            player->active);
-      if (local_player == player)
-       printf("Player  %d is local player.\n", i+1);
-    }
+    RedrawPlayfield();
+
+    return;
   }
 
-  if (BorderElement == EL_EMPTY)
+  if (!level.shifted_relocation || center_screen)
   {
-    SBX_Left = 0;
-    SBX_Right = lev_fieldx - SCR_FIELDX;
-    SBY_Upper = 0;
-    SBY_Lower = lev_fieldy - SCR_FIELDY;
+    // relocation _with_ centering of screen
+
+    new_scroll_x = SCROLL_POSITION_X(x);
+    new_scroll_y = SCROLL_POSITION_Y(y);
   }
   else
   {
-    SBX_Left = -1;
-    SBX_Right = lev_fieldx - SCR_FIELDX + 1;
-    SBY_Upper = -1;
-    SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
-  }
+    // relocation _without_ centering of screen
 
-  if (lev_fieldx + (SBX_Left == -1 ? 2 : 0) <= SCR_FIELDX)
-    SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
+    int center_scroll_x = SCROLL_POSITION_X(old_x);
+    int center_scroll_y = SCROLL_POSITION_Y(old_y);
+    int offset_x = x + (scroll_x - center_scroll_x);
+    int offset_y = y + (scroll_y - center_scroll_y);
 
-  if (lev_fieldy + (SBY_Upper == -1 ? 2 : 0) <= SCR_FIELDY)
-    SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
+    // for new screen position, apply previous offset to center position
+    new_scroll_x = SCROLL_POSITION_X(offset_x);
+    new_scroll_y = SCROLL_POSITION_Y(offset_y);
+  }
 
-  /* if local player not found, look for custom element that might create
-     the player (make some assumptions about the right custom element) */
-  if (!local_player->present)
+  if (quick_relocation)
   {
-    int start_x = 0, start_y = 0;
-    int found_rating = 0;
-    int found_element = EL_UNDEFINED;
-    int player_nr = local_player->index_nr;
+    // case 2: quick relocation (redraw without visible scrolling)
 
-#if 1
-    SCAN_PLAYFIELD(x, y)
-#else
-    for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
-    {
-      int element = Feld[x][y];
-      int content;
-      int xx, yy;
-      boolean is_player;
+    scroll_x = new_scroll_x;
+    scroll_y = new_scroll_y;
 
-      if (level.use_start_element[player_nr] &&
-         level.start_element[player_nr] == element &&
-         found_rating < 4)
-      {
-       start_x = x;
-       start_y = y;
+    RedrawPlayfield();
 
-       found_rating = 4;
-       found_element = element;
-      }
+    return;
+  }
 
-      if (!IS_CUSTOM_ELEMENT(element))
-       continue;
+  // case 3: visible relocation (with scrolling to new position)
 
-      if (CAN_CHANGE(element))
-      {
-       for (i = 0; i < element_info[element].num_change_pages; i++)
-       {
-         /* check for player created from custom element as single target */
-         content = element_info[element].change_page[i].target_element;
-         is_player = ELEM_IS_PLAYER(content);
+  ScrollScreen(NULL, SCROLL_GO_ON);    // scroll last frame to full tile
 
-         if (is_player && (found_rating < 3 || element < found_element))
-         {
-           start_x = x;
-           start_y = y;
+  SetVideoFrameDelay(wait_delay_value);
 
-           found_rating = 3;
-           found_element = element;
-         }
-       }
-      }
+  while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
+  {
+    int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
+    int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
 
-      for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
-      {
-       /* check for player created from custom element as explosion content */
-       content = element_info[element].content.e[xx][yy];
-       is_player = ELEM_IS_PLAYER(content);
+    if (dx == 0 && dy == 0)            // no scrolling needed at all
+      break;
 
-       if (is_player && (found_rating < 2 || element < found_element))
-       {
-         start_x = x + xx - 1;
-         start_y = y + yy - 1;
+    scroll_x -= dx;
+    scroll_y -= dy;
 
-         found_rating = 2;
-         found_element = element;
-       }
+    // set values for horizontal/vertical screen scrolling (half tile size)
+    int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
+    int dir_y = (dy != 0 ? MV_VERTICAL   : 0);
+    int pos_x = dx * TILEX / 2;
+    int pos_y = dy * TILEY / 2;
+    int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
+    int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
 
-       if (!CAN_CHANGE(element))
-         continue;
+    ScrollLevel(dx, dy);
+    DrawAllPlayers();
 
-       for (i = 0; i < element_info[element].num_change_pages; i++)
-       {
-         /* check for player created from custom element as extended target */
-         content =
-           element_info[element].change_page[i].target_content.e[xx][yy];
+    // scroll in two steps of half tile size to make things smoother
+    BlitScreenToBitmapExt_RND(window, fx, fy);
 
-         is_player = ELEM_IS_PLAYER(content);
+    // scroll second step to align at full tile size
+    BlitScreenToBitmap(window);
+  }
 
-         if (is_player && (found_rating < 1 || element < found_element))
-         {
-           start_x = x + xx - 1;
-           start_y = y + yy - 1;
+  DrawAllPlayers();
+  BackToFront();
 
-           found_rating = 1;
-           found_element = element;
-         }
-       }
-      }
-    }
+  SetVideoFrameDelay(frame_delay_value_old);
+}
 
-    scroll_x = (start_x < SBX_Left  + MIDPOSX ? SBX_Left :
-               start_x > SBX_Right + MIDPOSX ? SBX_Right :
-               start_x - MIDPOSX);
+static void RelocatePlayer(int jx, int jy, int el_player_raw)
+{
+  int el_player = GET_PLAYER_ELEMENT(el_player_raw);
+  int player_nr = GET_PLAYER_NR(el_player);
+  struct PlayerInfo *player = &stored_player[player_nr];
+  boolean ffwd_delay = (tape.playing && tape.fast_forward);
+  boolean no_delay = (tape.warp_forward);
+  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+  int old_jx = player->jx;
+  int old_jy = player->jy;
+  int old_element = Feld[old_jx][old_jy];
+  int element = Feld[jx][jy];
+  boolean player_relocated = (old_jx != jx || old_jy != jy);
 
-    scroll_y = (start_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-               start_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-               start_y - MIDPOSY);
-  }
-  else
-  {
-    scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-               local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-               local_player->jx - MIDPOSX);
+  int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
+  int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
+  int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
+  int enter_side_vert  = MV_DIR_OPPOSITE(move_dir_vert);
+  int leave_side_horiz = move_dir_horiz;
+  int leave_side_vert  = move_dir_vert;
+  int enter_side = enter_side_horiz | enter_side_vert;
+  int leave_side = leave_side_horiz | leave_side_vert;
 
-    scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
-               local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
-               local_player->jy - MIDPOSY);
-  }
+  if (player->buried)          // do not reanimate dead player
+    return;
 
-  if (!game.restart_level)
-    CloseDoor(DOOR_CLOSE_1);
+  if (!player_relocated)       // no need to relocate the player
+    return;
 
-  /* !!! FIX THIS (START) !!! */
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (IS_PLAYER(jx, jy))       // player already placed at new position
   {
-    InitGameEngine_EM();
+    RemoveField(jx, jy);       // temporarily remove newly placed player
+    DrawLevelField(jx, jy);
   }
-  else
+
+  if (player->present)
   {
-    DrawLevel();
-    DrawAllPlayers();
+    while (player->MovPos)
+    {
+      ScrollPlayer(player, SCROLL_GO_ON);
+      ScrollScreen(NULL, SCROLL_GO_ON);
 
-    /* after drawing the level, correct some elements */
-    if (game.timegate_time_left == 0)
-      CloseAllOpenTimegates();
+      AdvanceFrameAndPlayerCounters(player->index_nr);
 
-    if (setup.soft_scrolling)
-      BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
+      DrawPlayer(player);
 
-    redraw_mask |= REDRAW_FROM_BACKBUFFER;
-    FadeToFront();
-  }
-  /* !!! FIX THIS (END) !!! */
+      BackToFront_WithFrameDelay(wait_delay_value);
+    }
 
-  if (!game.restart_level)
-  {
-    /* copy default game door content to main double buffer */
-    BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
-              DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
+    DrawPlayer(player);                // needed here only to cleanup last field
+    DrawLevelField(player->jx, player->jy);    // remove player graphic
+
+    player->is_moving = FALSE;
   }
 
-  DrawGameDoorValues();
+  if (IS_CUSTOM_ELEMENT(old_element))
+    CheckElementChangeByPlayer(old_jx, old_jy, old_element,
+                              CE_LEFT_BY_PLAYER,
+                              player->index_bit, leave_side);
+
+  CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
+                                     CE_PLAYER_LEAVES_X,
+                                     player->index_bit, leave_side);
 
-  if (!game.restart_level)
+  Feld[jx][jy] = el_player;
+  InitPlayerField(jx, jy, el_player, TRUE);
+
+  /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
+     possible that the relocation target field did not contain a player element,
+     but a walkable element, to which the new player was relocated -- in this
+     case, restore that (already initialized!) element on the player field */
+  if (!ELEM_IS_PLAYER(element))        // player may be set on walkable element
   {
-    UnmapGameButtons();
-    UnmapTapeButtons();
-    game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
-    game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
-    game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
-    MapGameButtons();
-    MapTapeButtons();
+    Feld[jx][jy] = element;    // restore previously existing element
+  }
 
-    /* copy actual game door content to door double buffer for OpenDoor() */
-    BlitBitmap(drawto, bitmap_db_door,
-              DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
+  // only visually relocate centered player
+  DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
+                    FALSE, level.instant_relocation);
 
-    OpenDoor(DOOR_OPEN_ALL);
+  TestIfPlayerTouchesBadThing(jx, jy);
+  TestIfPlayerTouchesCustomElement(jx, jy);
 
-    PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
+  if (IS_CUSTOM_ELEMENT(element))
+    CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
+                              player->index_bit, enter_side);
 
-    if (setup.sound_music)
-      PlayLevelMusic();
+  CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
+                                     player->index_bit, enter_side);
 
-    KeyboardAutoRepeatOffUnlessAutoplay();
+  if (player->is_switching)
+  {
+    /* ensure that relocation while still switching an element does not cause
+       a new element to be treated as also switched directly after relocation
+       (this is important for teleporter switches that teleport the player to
+       a place where another teleporter switch is in the same direction, which
+       would then incorrectly be treated as immediately switched before the
+       direction key that caused the switch was released) */
 
-    if (options.debug)
-    {
-      for (i = 0; i < MAX_PLAYERS; i++)
-       printf("Player %d %sactive.\n",
-              i + 1, (stored_player[i].active ? "" : "not "));
-    }
+    player->switch_x += jx - old_jx;
+    player->switch_y += jy - old_jy;
   }
-
-  game.restart_level = FALSE;
 }
 
-void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y)
+static void Explode(int ex, int ey, int phase, int mode)
 {
-  /* this is used for non-R'n'D game engines to update certain engine values */
+  int x, y;
+  int last_phase;
+  int border_element;
 
-  /* needed to determine if sounds are played within the visible screen area */
-  scroll_x = actual_scroll_x;
-  scroll_y = actual_scroll_y;
-}
+  // !!! eliminate this variable !!!
+  int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
 
-void InitMovDir(int x, int y)
-{
-  int i, element = Feld[x][y];
-  static int xy[4][2] =
-  {
-    {  0, +1 },
-    { +1,  0 },
-    {  0, -1 },
-    { -1,  0 }
-  };
-  static int direction[3][4] =
+  if (game.explosions_delayed)
   {
-    { MV_RIGHT, MV_UP,   MV_LEFT,  MV_DOWN },
-    { MV_LEFT,  MV_DOWN, MV_RIGHT, MV_UP },
-    { MV_LEFT,  MV_RIGHT, MV_UP, MV_DOWN }
-  };
+    ExplodeField[ex][ey] = mode;
+    return;
+  }
 
-  switch(element)
+  if (phase == EX_PHASE_START)         // initialize 'Store[][]' field
   {
-    case EL_BUG_RIGHT:
-    case EL_BUG_UP:
-    case EL_BUG_LEFT:
-    case EL_BUG_DOWN:
-      Feld[x][y] = EL_BUG;
-      MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
-      break;
+    int center_element = Feld[ex][ey];
+    int artwork_element, explosion_element;    // set these values later
 
-    case EL_SPACESHIP_RIGHT:
-    case EL_SPACESHIP_UP:
-    case EL_SPACESHIP_LEFT:
-    case EL_SPACESHIP_DOWN:
-      Feld[x][y] = EL_SPACESHIP;
-      MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
-      break;
+    // remove things displayed in background while burning dynamite
+    if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
+      Back[ex][ey] = 0;
 
-    case EL_BD_BUTTERFLY_RIGHT:
-    case EL_BD_BUTTERFLY_UP:
-    case EL_BD_BUTTERFLY_LEFT:
-    case EL_BD_BUTTERFLY_DOWN:
-      Feld[x][y] = EL_BD_BUTTERFLY;
-      MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
-      break;
+    if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+    {
+      // put moving element to center field (and let it explode there)
+      center_element = MovingOrBlocked2Element(ex, ey);
+      RemoveMovingField(ex, ey);
+      Feld[ex][ey] = center_element;
+    }
 
-    case EL_BD_FIREFLY_RIGHT:
-    case EL_BD_FIREFLY_UP:
-    case EL_BD_FIREFLY_LEFT:
-    case EL_BD_FIREFLY_DOWN:
-      Feld[x][y] = EL_BD_FIREFLY;
-      MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
-      break;
+    // now "center_element" is finally determined -- set related values now
+    artwork_element = center_element;          // for custom player artwork
+    explosion_element = center_element;                // for custom player artwork
 
-    case EL_PACMAN_RIGHT:
-    case EL_PACMAN_UP:
-    case EL_PACMAN_LEFT:
-    case EL_PACMAN_DOWN:
-      Feld[x][y] = EL_PACMAN;
-      MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
-      break;
+    if (IS_PLAYER(ex, ey))
+    {
+      int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
 
-    case EL_SP_SNIKSNAK:
-      MovDir[x][y] = MV_UP;
-      break;
+      artwork_element = stored_player[player_nr].artwork_element;
 
-    case EL_SP_ELECTRON:
-      MovDir[x][y] = MV_LEFT;
-      break;
+      if (level.use_explosion_element[player_nr])
+      {
+       explosion_element = level.explosion_element[player_nr];
+       artwork_element = explosion_element;
+      }
+    }
 
-    case EL_MOLE_LEFT:
-    case EL_MOLE_RIGHT:
-    case EL_MOLE_UP:
-    case EL_MOLE_DOWN:
-      Feld[x][y] = EL_MOLE;
-      MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
-      break;
+    if (mode == EX_TYPE_NORMAL ||
+       mode == EX_TYPE_CENTER ||
+       mode == EX_TYPE_CROSS)
+      PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
 
-    default:
-      if (IS_CUSTOM_ELEMENT(element))
-      {
-       struct ElementInfo *ei = &element_info[element];
-       int move_direction_initial = ei->move_direction_initial;
-       int move_pattern = ei->move_pattern;
+    last_phase = element_info[explosion_element].explosion_delay + 1;
 
-       if (move_direction_initial == MV_START_PREVIOUS)
-       {
-         if (MovDir[x][y] != MV_NONE)
-           return;
+    for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
+    {
+      int xx = x - ex + 1;
+      int yy = y - ey + 1;
+      int element;
 
-         move_direction_initial = MV_START_AUTOMATIC;
-       }
+      if (!IN_LEV_FIELD(x, y) ||
+         (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
+         (mode == EX_TYPE_CROSS      && (x != ex && y != ey)))
+       continue;
 
-       if (move_direction_initial == MV_START_RANDOM)
-         MovDir[x][y] = 1 << RND(4);
-       else if (move_direction_initial & MV_ANY_DIRECTION)
-         MovDir[x][y] = move_direction_initial;
-       else if (move_pattern == MV_ALL_DIRECTIONS ||
-                move_pattern == MV_TURNING_LEFT ||
-                move_pattern == MV_TURNING_RIGHT ||
-                move_pattern == MV_TURNING_LEFT_RIGHT ||
-                move_pattern == MV_TURNING_RIGHT_LEFT ||
-                move_pattern == MV_TURNING_RANDOM)
-         MovDir[x][y] = 1 << RND(4);
-       else if (move_pattern == MV_HORIZONTAL)
-         MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
-       else if (move_pattern == MV_VERTICAL)
-         MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
-       else if (move_pattern & MV_ANY_DIRECTION)
-         MovDir[x][y] = element_info[element].move_pattern;
-       else if (move_pattern == MV_ALONG_LEFT_SIDE ||
-                move_pattern == MV_ALONG_RIGHT_SIDE)
-       {
-         /* use random direction as default start direction */
-         if (game.engine_version >= VERSION_IDENT(3,1,0,0))
-           MovDir[x][y] = 1 << RND(4);
+      element = Feld[x][y];
 
-         for (i = 0; i < NUM_DIRECTIONS; i++)
-         {
-           int x1 = x + xy[i][0];
-           int y1 = y + xy[i][1];
+      if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+      {
+       element = MovingOrBlocked2Element(x, y);
 
-           if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
-           {
-             if (move_pattern == MV_ALONG_RIGHT_SIDE)
-               MovDir[x][y] = direction[0][i];
-             else
-               MovDir[x][y] = direction[1][i];
+       if (!IS_EXPLOSION_PROOF(element))
+         RemoveMovingField(x, y);
+      }
 
-             break;
-           }
-         }
-       }                
+      // indestructible elements can only explode in center (but not flames)
+      if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
+                                          mode == EX_TYPE_BORDER)) ||
+         element == EL_FLAMES)
+       continue;
+
+      /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
+        behaviour, for example when touching a yamyam that explodes to rocks
+        with active deadly shield, a rock is created under the player !!! */
+      // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
+#if 0
+      if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
+         (game.engine_version < VERSION_IDENT(3,1,0,0) ||
+          (x == ex && y == ey && mode != EX_TYPE_BORDER)))
+#else
+      if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
+#endif
+      {
+       if (IS_ACTIVE_BOMB(element))
+       {
+         // re-activate things under the bomb like gate or penguin
+         Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
+         Back[x][y] = 0;
+       }
+
+       continue;
       }
-      else
-      {
-       MovDir[x][y] = 1 << RND(4);
 
-       if (element != EL_BUG &&
-           element != EL_SPACESHIP &&
-           element != EL_BD_BUTTERFLY &&
-           element != EL_BD_FIREFLY)
-         break;
+      // save walkable background elements while explosion on same tile
+      if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
+         (x != ex || y != ey || mode == EX_TYPE_BORDER))
+       Back[x][y] = element;
 
-       for (i = 0; i < NUM_DIRECTIONS; i++)
-       {
-         int x1 = x + xy[i][0];
-         int y1 = y + xy[i][1];
+      // ignite explodable elements reached by other explosion
+      if (element == EL_EXPLOSION)
+       element = Store2[x][y];
 
-         if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
-         {
-           if (element == EL_BUG || element == EL_BD_BUTTERFLY)
-           {
-             MovDir[x][y] = direction[0][i];
-             break;
-           }
-           else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
-                    element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
-           {
-             MovDir[x][y] = direction[1][i];
-             break;
-           }
-         }
-       }
+      if (AmoebaNr[x][y] &&
+         (element == EL_AMOEBA_FULL ||
+          element == EL_BD_AMOEBA ||
+          element == EL_AMOEBA_GROWING))
+      {
+       AmoebaCnt[AmoebaNr[x][y]]--;
+       AmoebaCnt2[AmoebaNr[x][y]]--;
       }
-      break;
-  }
-
-  GfxDir[x][y] = MovDir[x][y];
-}
 
-void InitAmoebaNr(int x, int y)
-{
-  int i;
-  int group_nr = AmoebeNachbarNr(x, y);
+      RemoveField(x, y);
 
-  if (group_nr == 0)
-  {
-    for (i = 1; i < MAX_NUM_AMOEBA; i++)
-    {
-      if (AmoebaCnt[i] == 0)
+      if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
       {
-       group_nr = i;
-       break;
+       int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
+
+       Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
+
+       if (PLAYERINFO(ex, ey)->use_murphy)
+         Store[x][y] = EL_EMPTY;
       }
-    }
-  }
 
-  AmoebaNr[x][y] = group_nr;
-  AmoebaCnt[group_nr]++;
-  AmoebaCnt2[group_nr]++;
-}
+      // !!! check this case -- currently needed for rnd_rado_negundo_v,
+      // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
+      else if (ELEM_IS_PLAYER(center_element))
+       Store[x][y] = EL_EMPTY;
+      else if (center_element == EL_YAMYAM)
+       Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
+      else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
+       Store[x][y] = element_info[center_element].content.e[xx][yy];
+#if 1
+      // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
+      // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
+      // otherwise) -- FIX THIS !!!
+      else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
+       Store[x][y] = element_info[element].content.e[1][1];
+#else
+      else if (!CAN_EXPLODE(element))
+       Store[x][y] = element_info[element].content.e[1][1];
+#endif
+      else
+       Store[x][y] = EL_EMPTY;
 
-void GameWon()
-{
-  int hi_pos;
-  boolean raise_level = FALSE;
+      if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
+         center_element == EL_AMOEBA_TO_DIAMOND)
+       Store2[x][y] = element;
+
+      Feld[x][y] = EL_EXPLOSION;
+      GfxElement[x][y] = artwork_element;
+
+      ExplodePhase[x][y] = 1;
+      ExplodeDelay[x][y] = last_phase;
+
+      Stop[x][y] = TRUE;
+    }
+
+    if (center_element == EL_YAMYAM)
+      game.yamyam_content_nr =
+       (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
 
-  if (local_player->MovPos)
     return;
+  }
 
-  if (tape.auto_play)          /* tape might already be stopped here */
-    tape.auto_play_level_solved = TRUE;
+  if (Stop[ex][ey])
+    return;
 
-  local_player->LevelSolved = FALSE;
+  x = ex;
+  y = ey;
 
-  PlaySoundStereo(SND_GAME_WINNING, SOUND_MIDDLE);
+  if (phase == 1)
+    GfxFrame[x][y] = 0;                // restart explosion animation
 
-  if (TimeLeft)
-  {
-    if (!tape.playing && setup.sound_loops)
-      PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
-                  SND_CTRL_PLAY_LOOP);
+  last_phase = ExplodeDelay[x][y];
 
-    while (TimeLeft > 0)
-    {
-      if (!tape.playing && !setup.sound_loops)
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
+  ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
 
-      if (TimeLeft > 100 && TimeLeft % 10 == 0)
-      {
-       TimeLeft -= 10;
-       RaiseScore(level.score[SC_TIME_BONUS] * 10);
-      }
-      else
-      {
-       TimeLeft--;
-       RaiseScore(level.score[SC_TIME_BONUS]);
-      }
+  // this can happen if the player leaves an explosion just in time
+  if (GfxElement[x][y] == EL_UNDEFINED)
+    GfxElement[x][y] = EL_EMPTY;
 
-      DrawGameValue_Time(TimeLeft);
+  border_element = Store2[x][y];
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
+    border_element = StorePlayer[x][y];
 
-      BackToFront();
+  if (phase == element_info[border_element].ignition_delay ||
+      phase == last_phase)
+  {
+    boolean border_explosion = FALSE;
 
-      if (!tape.playing)
-       Delay(10);
+    if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
+       !PLAYER_EXPLOSION_PROTECTED(x, y))
+    {
+      KillPlayerUnlessExplosionProtected(x, y);
+      border_explosion = TRUE;
+    }
+    else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
+    {
+      Feld[x][y] = Store2[x][y];
+      Store2[x][y] = 0;
+      Bang(x, y);
+      border_explosion = TRUE;
+    }
+    else if (border_element == EL_AMOEBA_TO_DIAMOND)
+    {
+      AmoebeUmwandeln(x, y);
+      Store2[x][y] = 0;
+      border_explosion = TRUE;
     }
 
-    if (!tape.playing && setup.sound_loops)
-      StopSound(SND_GAME_LEVELTIME_BONUS);
+    // if an element just explodes due to another explosion (chain-reaction),
+    // do not immediately end the new explosion when it was the last frame of
+    // the explosion (as it would be done in the following "if"-statement!)
+    if (border_explosion && phase == last_phase)
+      return;
   }
-  else if (level.time == 0)            /* level without time limit */
-  {
-    if (!tape.playing && setup.sound_loops)
-      PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
-                  SND_CTRL_PLAY_LOOP);
 
-    while (TimePlayed < 999)
-    {
-      if (!tape.playing && !setup.sound_loops)
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
+  if (phase == last_phase)
+  {
+    int element;
 
-      if (TimePlayed < 900 && TimePlayed % 10 == 0)
-      {
-       TimePlayed += 10;
-       RaiseScore(level.score[SC_TIME_BONUS] * 10);
-      }
-      else
-      {
-       TimePlayed++;
-       RaiseScore(level.score[SC_TIME_BONUS]);
-      }
+    element = Feld[x][y] = Store[x][y];
+    Store[x][y] = Store2[x][y] = 0;
+    GfxElement[x][y] = EL_UNDEFINED;
 
-      DrawGameValue_Time(TimePlayed);
+    // player can escape from explosions and might therefore be still alive
+    if (element >= EL_PLAYER_IS_EXPLODING_1 &&
+       element <= EL_PLAYER_IS_EXPLODING_4)
+    {
+      int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
+      int explosion_element = EL_PLAYER_1 + player_nr;
+      int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
+      int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
 
-      BackToFront();
+      if (level.use_explosion_element[player_nr])
+       explosion_element = level.explosion_element[player_nr];
 
-      if (!tape.playing)
-       Delay(10);
+      Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
+                   element_info[explosion_element].content.e[xx][yy]);
     }
 
-    if (!tape.playing && setup.sound_loops)
-      StopSound(SND_GAME_LEVELTIME_BONUS);
-  }
+    // restore probably existing indestructible background element
+    if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
+      element = Feld[x][y] = Back[x][y];
+    Back[x][y] = 0;
 
-  /* close exit door after last player */
-  if (AllPlayersGone && ExitX >= 0 && ExitY >= 0 &&
-      (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
-       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN))
-  {
-    int element = Feld[ExitX][ExitY];
+    MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
+    GfxDir[x][y] = MV_NONE;
+    ChangeDelay[x][y] = 0;
+    ChangePage[x][y] = -1;
 
-    Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
-                         EL_SP_EXIT_CLOSING);
+    CustomValue[x][y] = 0;
 
-    PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
-  }
+    InitField_WithBug2(x, y, FALSE);
 
-  /* player disappears */
-  if (ExitX >= 0 && ExitY >= 0)
-    DrawLevelField(ExitX, ExitY);
+    TEST_DrawLevelField(x, y);
 
-  BackToFront();
+    TestIfElementTouchesCustomElement(x, y);
 
-  if (tape.playing)
-    return;
+    if (GFX_CRUMBLED(element))
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
-  CloseDoor(DOOR_CLOSE_1);
+    if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
+      StorePlayer[x][y] = 0;
 
-  if (tape.recording)
+    if (ELEM_IS_PLAYER(element))
+      RelocatePlayer(x, y, element);
+  }
+  else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
   {
-    TapeStop();
-    SaveTape(tape.level_nr);           /* Ask to save tape */
+    int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
+    int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+
+    if (phase == delay)
+      TEST_DrawLevelFieldCrumbled(x, y);
+
+    if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
+    {
+      DrawLevelElement(x, y, Back[x][y]);
+      DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
+    }
+    else if (IS_WALKABLE_UNDER(Back[x][y]))
+    {
+      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+      DrawLevelElementThruMask(x, y, Back[x][y]);
+    }
+    else if (!IS_WALKABLE_INSIDE(Back[x][y]))
+      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
   }
+}
 
-  if (level_nr == leveldir_current->handicap_level)
+static void DynaExplode(int ex, int ey)
+{
+  int i, j;
+  int dynabomb_element = Feld[ex][ey];
+  int dynabomb_size = 1;
+  boolean dynabomb_xl = FALSE;
+  struct PlayerInfo *player;
+  static int xy[4][2] =
   {
-    leveldir_current->handicap_level++;
-    SaveLevelSetup_SeriesInfo();
+    { 0, -1 },
+    { -1, 0 },
+    { +1, 0 },
+    { 0, +1 }
+  };
+
+  if (IS_ACTIVE_BOMB(dynabomb_element))
+  {
+    player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
+    dynabomb_size = player->dynabomb_size;
+    dynabomb_xl = player->dynabomb_xl;
+    player->dynabombs_left++;
   }
 
-  if (level_editor_test_game)
-    local_player->score = -1;  /* no highscore when playing from editor */
-  else if (level_nr < leveldir_current->last_level)
-    raise_level = TRUE;                /* advance to next level */
+  Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
+
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    for (j = 1; j <= dynabomb_size; j++)
+    {
+      int x = ex + j * xy[i][0];
+      int y = ey + j * xy[i][1];
+      int element;
+
+      if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
+       break;
+
+      element = Feld[x][y];
+
+      // do not restart explosions of fields with active bombs
+      if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
+       continue;
+
+      Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
 
-  if ((hi_pos = NewHiScore()) >= 0) 
-  {
-    game_status = GAME_MODE_SCORES;
-    DrawHallOfFame(hi_pos);
-    if (raise_level)
-    {
-      level_nr++;
-      TapeErase();
-    }
-  }
-  else
-  {
-    game_status = GAME_MODE_MAIN;
-    if (raise_level)
-    {
-      level_nr++;
-      TapeErase();
+      if (element != EL_EMPTY && element != EL_EXPLOSION &&
+         !IS_DIGGABLE(element) && !dynabomb_xl)
+       break;
     }
-    DrawMainMenu();
   }
-
-  BackToFront();
 }
 
-int NewHiScore()
+void Bang(int x, int y)
 {
-  int k, l;
-  int position = -1;
+  int element = MovingOrBlocked2Element(x, y);
+  int explosion_type = EX_TYPE_NORMAL;
 
-  LoadScore(level_nr);
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
+  {
+    struct PlayerInfo *player = PLAYERINFO(x, y);
 
-  if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
-      local_player->score < highscore[MAX_SCORE_ENTRIES - 1].Score) 
-    return -1;
+    element = Feld[x][y] = player->initial_element;
 
-  for (k = 0; k < MAX_SCORE_ENTRIES; k++) 
-  {
-    if (local_player->score > highscore[k].Score)
+    if (level.use_explosion_element[player->index_nr])
     {
-      /* player has made it to the hall of fame */
+      int explosion_element = level.explosion_element[player->index_nr];
 
-      if (k < MAX_SCORE_ENTRIES - 1)
-      {
-       int m = MAX_SCORE_ENTRIES - 1;
+      if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
+       explosion_type = EX_TYPE_CROSS;
+      else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
+       explosion_type = EX_TYPE_CENTER;
+    }
+  }
 
-#ifdef ONE_PER_NAME
-       for (l = k; l < MAX_SCORE_ENTRIES; l++)
-         if (!strcmp(setup.player_name, highscore[l].Name))
-           m = l;
-       if (m == k)     /* player's new highscore overwrites his old one */
-         goto put_into_list;
-#endif
+  switch (element)
+  {
+    case EL_BUG:
+    case EL_SPACESHIP:
+    case EL_BD_BUTTERFLY:
+    case EL_BD_FIREFLY:
+    case EL_YAMYAM:
+    case EL_DARK_YAMYAM:
+    case EL_ROBOT:
+    case EL_PACMAN:
+    case EL_MOLE:
+      RaiseScoreElement(element);
+      break;
 
-       for (l = m; l > k; l--)
-       {
-         strcpy(highscore[l].Name, highscore[l - 1].Name);
-         highscore[l].Score = highscore[l - 1].Score;
-       }
-      }
+    case EL_DYNABOMB_PLAYER_1_ACTIVE:
+    case EL_DYNABOMB_PLAYER_2_ACTIVE:
+    case EL_DYNABOMB_PLAYER_3_ACTIVE:
+    case EL_DYNABOMB_PLAYER_4_ACTIVE:
+    case EL_DYNABOMB_INCREASE_NUMBER:
+    case EL_DYNABOMB_INCREASE_SIZE:
+    case EL_DYNABOMB_INCREASE_POWER:
+      explosion_type = EX_TYPE_DYNA;
+      break;
 
-#ifdef ONE_PER_NAME
-      put_into_list:
-#endif
-      strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
-      highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
-      highscore[k].Score = local_player->score; 
-      position = k;
+    case EL_DC_LANDMINE:
+      explosion_type = EX_TYPE_CENTER;
       break;
-    }
 
-#ifdef ONE_PER_NAME
-    else if (!strncmp(setup.player_name, highscore[k].Name,
-                     MAX_PLAYER_NAME_LEN))
-      break;   /* player already there with a higher score */
-#endif
+    case EL_PENGUIN:
+    case EL_LAMP:
+    case EL_LAMP_ACTIVE:
+    case EL_AMOEBA_TO_DIAMOND:
+      if (!IS_PLAYER(x, y))    // penguin and player may be at same field
+       explosion_type = EX_TYPE_CENTER;
+      break;
 
+    default:
+      if (element_info[element].explosion_type == EXPLODES_CROSS)
+       explosion_type = EX_TYPE_CROSS;
+      else if (element_info[element].explosion_type == EXPLODES_1X1)
+       explosion_type = EX_TYPE_CENTER;
+      break;
   }
 
-  if (position >= 0) 
-    SaveScore(level_nr);
+  if (explosion_type == EX_TYPE_DYNA)
+    DynaExplode(x, y);
+  else
+    Explode(x, y, EX_PHASE_START, explosion_type);
 
-  return position;
+  CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
 }
 
-inline static int getElementMoveStepsize(int x, int y)
+static void SplashAcid(int x, int y)
 {
-  int element = Feld[x][y];
-  int direction = MovDir[x][y];
-  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-  int horiz_move = (dx != 0);
-  int sign = (horiz_move ? dx : dy);
-  int step = sign * element_info[element].move_stepsize;
+  if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
+      (!IN_LEV_FIELD(x - 1, y - 2) ||
+       !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
+    Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
 
-  /* special values for move stepsize for spring and things on conveyor belt */
-  if (horiz_move)
-  {
-#if 0
-    if (element == EL_SPRING)
-      step = sign * MOVE_STEPSIZE_NORMAL * 2;
-    else if (CAN_FALL(element) && !CAN_MOVE(element) &&
-            y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
-      step = sign * MOVE_STEPSIZE_NORMAL / 2;
-#else
-    if (CAN_FALL(element) &&
-       y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
-      step = sign * MOVE_STEPSIZE_NORMAL / 2;
-    else if (element == EL_SPRING)
-      step = sign * MOVE_STEPSIZE_NORMAL * 2;
-#endif
-  }
+  if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
+      (!IN_LEV_FIELD(x + 1, y - 2) ||
+       !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
+    Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
 
-  return step;
+  PlayLevelSound(x, y, SND_ACID_SPLASHING);
 }
 
-void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
+static void InitBeltMovement(void)
 {
-  if (player->GfxAction != action || player->GfxDir != dir)
+  static int belt_base_element[4] =
   {
-#if 0
-    printf("Player frame reset! (%d => %d, %d => %d)\n",
-          player->GfxAction, action, player->GfxDir, dir);
-#endif
-
-    player->GfxAction = action;
-    player->GfxDir = dir;
-    player->Frame = 0;
-    player->StepFrame = 0;
-  }
-}
-
-static void ResetRandomAnimationValue(int x, int y)
-{
-  GfxRandom[x][y] = INIT_GFX_RANDOM();
-}
-
-static void ResetGfxAnimation(int x, int y)
-{
-  GfxFrame[x][y] = 0;
-  GfxAction[x][y] = ACTION_DEFAULT;
-  GfxDir[x][y] = MovDir[x][y];
-}
+    EL_CONVEYOR_BELT_1_LEFT,
+    EL_CONVEYOR_BELT_2_LEFT,
+    EL_CONVEYOR_BELT_3_LEFT,
+    EL_CONVEYOR_BELT_4_LEFT
+  };
+  static int belt_base_active_element[4] =
+  {
+    EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+  };
 
-void InitMovingField(int x, int y, int direction)
-{
-  int element = Feld[x][y];
-  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-  int newx = x + dx;
-  int newy = y + dy;
+  int x, y, i, j;
 
-  if (!WasJustMoving[x][y] || direction != MovDir[x][y])
-    ResetGfxAnimation(x, y);
+  // set frame order for belt animation graphic according to belt direction
+  for (i = 0; i < NUM_BELTS; i++)
+  {
+    int belt_nr = i;
 
-  MovDir[x][y] = direction;
-  GfxDir[x][y] = direction;
-  GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
-                    ACTION_FALLING : ACTION_MOVING);
+    for (j = 0; j < NUM_BELT_PARTS; j++)
+    {
+      int element = belt_base_active_element[belt_nr] + j;
+      int graphic_1 = el2img(element);
+      int graphic_2 = el2panelimg(element);
 
-  /* this is needed for CEs with property "can move" / "not moving" */
+      if (game.belt_dir[i] == MV_LEFT)
+      {
+       graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+       graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
+      }
+      else
+      {
+       graphic_info[graphic_1].anim_mode |=  ANIM_REVERSE;
+       graphic_info[graphic_2].anim_mode |=  ANIM_REVERSE;
+      }
+    }
+  }
 
-  if (getElementMoveStepsize(x, y) != 0)       /* moving or being moved */
+  SCAN_PLAYFIELD(x, y)
   {
-    if (Feld[newx][newy] == EL_EMPTY)
-      Feld[newx][newy] = EL_BLOCKED;
+    int element = Feld[x][y];
 
-    MovDir[newx][newy] = MovDir[x][y];
+    for (i = 0; i < NUM_BELTS; i++)
+    {
+      if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
+      {
+       int e_belt_nr = getBeltNrFromBeltElement(element);
+       int belt_nr = i;
 
-#if USE_NEW_CUSTOM_VALUE
-    CustomValue[newx][newy] = CustomValue[x][y];
-#endif
+       if (e_belt_nr == belt_nr)
+       {
+         int belt_part = Feld[x][y] - belt_base_element[belt_nr];
 
-    GfxFrame[newx][newy] = GfxFrame[x][y];
-    GfxRandom[newx][newy] = GfxRandom[x][y];
-    GfxAction[newx][newy] = GfxAction[x][y];
-    GfxDir[newx][newy] = GfxDir[x][y];
+         Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
+       }
+      }
+    }
   }
 }
 
-void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
-{
-  int direction = MovDir[x][y];
-  int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-
-  *goes_to_x = newx;
-  *goes_to_y = newy;
-}
-
-void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
+static void ToggleBeltSwitch(int x, int y)
 {
-  int oldx = x, oldy = y;
-  int direction = MovDir[x][y];
-
-  if (direction == MV_LEFT)
-    oldx++;
-  else if (direction == MV_RIGHT)
-    oldx--;
-  else if (direction == MV_UP)
-    oldy++;
-  else if (direction == MV_DOWN)
-    oldy--;
-
-  *comes_from_x = oldx;
-  *comes_from_y = oldy;
-}
+  static int belt_base_element[4] =
+  {
+    EL_CONVEYOR_BELT_1_LEFT,
+    EL_CONVEYOR_BELT_2_LEFT,
+    EL_CONVEYOR_BELT_3_LEFT,
+    EL_CONVEYOR_BELT_4_LEFT
+  };
+  static int belt_base_active_element[4] =
+  {
+    EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+  };
+  static int belt_base_switch_element[4] =
+  {
+    EL_CONVEYOR_BELT_1_SWITCH_LEFT,
+    EL_CONVEYOR_BELT_2_SWITCH_LEFT,
+    EL_CONVEYOR_BELT_3_SWITCH_LEFT,
+    EL_CONVEYOR_BELT_4_SWITCH_LEFT
+  };
+  static int belt_move_dir[4] =
+  {
+    MV_LEFT,
+    MV_NONE,
+    MV_RIGHT,
+    MV_NONE,
+  };
 
-int MovingOrBlocked2Element(int x, int y)
-{
   int element = Feld[x][y];
+  int belt_nr = getBeltNrFromBeltSwitchElement(element);
+  int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
+  int belt_dir = belt_move_dir[belt_dir_nr];
+  int xx, yy, i;
 
-  if (element == EL_BLOCKED)
-  {
-    int oldx, oldy;
+  if (!IS_BELT_SWITCH(element))
+    return;
 
-    Blocked2Moving(x, y, &oldx, &oldy);
-    return Feld[oldx][oldy];
-  }
-  else
-    return element;
-}
+  game.belt_dir_nr[belt_nr] = belt_dir_nr;
+  game.belt_dir[belt_nr] = belt_dir;
 
-static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
-{
-  /* like MovingOrBlocked2Element(), but if element is moving
-     and (x,y) is the field the moving element is just leaving,
-     return EL_BLOCKED instead of the element value */
-  int element = Feld[x][y];
+  if (belt_dir_nr == 3)
+    belt_dir_nr = 1;
 
-  if (IS_MOVING(x, y))
+  // set frame order for belt animation graphic according to belt direction
+  for (i = 0; i < NUM_BELT_PARTS; i++)
   {
-    if (element == EL_BLOCKED)
-    {
-      int oldx, oldy;
+    int element = belt_base_active_element[belt_nr] + i;
+    int graphic_1 = el2img(element);
+    int graphic_2 = el2panelimg(element);
 
-      Blocked2Moving(x, y, &oldx, &oldy);
-      return Feld[oldx][oldy];
+    if (belt_dir == MV_LEFT)
+    {
+      graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+      graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
     }
     else
-      return EL_BLOCKED;
+    {
+      graphic_info[graphic_1].anim_mode |=  ANIM_REVERSE;
+      graphic_info[graphic_2].anim_mode |=  ANIM_REVERSE;
+    }
   }
-  else
-    return element;
-}
 
-static void RemoveField(int x, int y)
-{
-  Feld[x][y] = EL_EMPTY;
+  SCAN_PLAYFIELD(xx, yy)
+  {
+    int element = Feld[xx][yy];
 
-  MovPos[x][y] = 0;
-  MovDir[x][y] = 0;
-  MovDelay[x][y] = 0;
+    if (IS_BELT_SWITCH(element))
+    {
+      int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
 
-#if USE_NEW_CUSTOM_VALUE
-  CustomValue[x][y] = 0;
-#endif
+      if (e_belt_nr == belt_nr)
+      {
+       Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
+       TEST_DrawLevelField(xx, yy);
+      }
+    }
+    else if (IS_BELT(element) && belt_dir != MV_NONE)
+    {
+      int e_belt_nr = getBeltNrFromBeltElement(element);
 
-  AmoebaNr[x][y] = 0;
-  ChangeDelay[x][y] = 0;
-  ChangePage[x][y] = -1;
-  Pushed[x][y] = FALSE;
+      if (e_belt_nr == belt_nr)
+      {
+       int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
 
-#if 0
-  ExplodeField[x][y] = EX_TYPE_NONE;
-#endif
+       Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
+       TEST_DrawLevelField(xx, yy);
+      }
+    }
+    else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
+    {
+      int e_belt_nr = getBeltNrFromBeltActiveElement(element);
 
-  GfxElement[x][y] = EL_UNDEFINED;
-  GfxAction[x][y] = ACTION_DEFAULT;
-  GfxDir[x][y] = MV_NONE;
+      if (e_belt_nr == belt_nr)
+      {
+       int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
+
+       Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
+       TEST_DrawLevelField(xx, yy);
+      }
+    }
+  }
 }
 
-void RemoveMovingField(int x, int y)
+static void ToggleSwitchgateSwitch(int x, int y)
 {
-  int oldx = x, oldy = y, newx = x, newy = y;
-  int element = Feld[x][y];
-  int next_element = EL_UNDEFINED;
+  int xx, yy;
 
-  if (element != EL_BLOCKED && !IS_MOVING(x, y))
-    return;
+  game.switchgate_pos = !game.switchgate_pos;
 
-  if (IS_MOVING(x, y))
+  SCAN_PLAYFIELD(xx, yy)
   {
-    Moving2Blocked(x, y, &newx, &newy);
+    int element = Feld[xx][yy];
 
-    if (Feld[newx][newy] != EL_BLOCKED)
+    if (element == EL_SWITCHGATE_SWITCH_UP)
     {
-      /* element is moving, but target field is not free (blocked), but
-        already occupied by something different (example: acid pool);
-        in this case, only remove the moving field, but not the target */
-
-      RemoveField(oldx, oldy);
-
-      Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+      Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_SWITCHGATE_SWITCH_DOWN)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
+    {
+      Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+    {
+      Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_SWITCHGATE_OPEN ||
+            element == EL_SWITCHGATE_OPENING)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
 
-      DrawLevelField(oldx, oldy);
+      PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
+    }
+    else if (element == EL_SWITCHGATE_CLOSED ||
+            element == EL_SWITCHGATE_CLOSING)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_OPENING;
 
-      return;
+      PlayLevelSoundAction(xx, yy, ACTION_OPENING);
     }
   }
-  else if (element == EL_BLOCKED)
-  {
-    Blocked2Moving(x, y, &oldx, &oldy);
-    if (!IS_MOVING(oldx, oldy))
-      return;
-  }
-
-  if (element == EL_BLOCKED &&
-      (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
-       Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
-       Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
-       Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
-    next_element = get_next_element(Feld[oldx][oldy]);
-
-  RemoveField(oldx, oldy);
-  RemoveField(newx, newy);
-
-  Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+}
 
-  if (next_element != EL_UNDEFINED)
-    Feld[oldx][oldy] = next_element;
+static int getInvisibleActiveFromInvisibleElement(int element)
+{
+  return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
+         element == EL_INVISIBLE_WALL      ? EL_INVISIBLE_WALL_ACTIVE :
+         element == EL_INVISIBLE_SAND      ? EL_INVISIBLE_SAND_ACTIVE :
+         element);
+}
 
-  DrawLevelField(oldx, oldy);
-  DrawLevelField(newx, newy);
+static int getInvisibleFromInvisibleActiveElement(int element)
+{
+  return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
+         element == EL_INVISIBLE_WALL_ACTIVE      ? EL_INVISIBLE_WALL :
+         element == EL_INVISIBLE_SAND_ACTIVE      ? EL_INVISIBLE_SAND :
+         element);
 }
 
-void DrawDynamite(int x, int y)
+static void RedrawAllLightSwitchesAndInvisibleElements(void)
 {
-  int sx = SCREENX(x), sy = SCREENY(y);
-  int graphic = el2img(Feld[x][y]);
-  int frame;
+  int x, y;
 
-  if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
-    return;
+  SCAN_PLAYFIELD(x, y)
+  {
+    int element = Feld[x][y];
 
-  if (IS_WALKABLE_INSIDE(Back[x][y]))
-    return;
+    if (element == EL_LIGHT_SWITCH &&
+       game.light_time_left > 0)
+    {
+      Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_LIGHT_SWITCH_ACTIVE &&
+            game.light_time_left == 0)
+    {
+      Feld[x][y] = EL_LIGHT_SWITCH;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_DRIPPER &&
+            game.light_time_left > 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_DRIPPER_ACTIVE &&
+            game.light_time_left == 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL ||
+            element == EL_INVISIBLE_WALL ||
+            element == EL_INVISIBLE_SAND)
+    {
+      if (game.light_time_left > 0)
+       Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
 
-  if (Back[x][y])
-    DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
-  else if (Store[x][y])
-    DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
+      TEST_DrawLevelField(x, y);
 
-  frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+      // uncrumble neighbour fields, if needed
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+            element == EL_INVISIBLE_WALL_ACTIVE ||
+            element == EL_INVISIBLE_SAND_ACTIVE)
+    {
+      if (game.light_time_left == 0)
+       Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
 
-  if (Back[x][y] || Store[x][y])
-    DrawGraphicThruMask(sx, sy, graphic, frame);
-  else
-    DrawGraphic(sx, sy, graphic, frame);
+      TEST_DrawLevelField(x, y);
+
+      // re-crumble neighbour fields, if needed
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+    }
+  }
 }
 
-void CheckDynamite(int x, int y)
+static void RedrawAllInvisibleElementsForLenses(void)
 {
-  if (MovDelay[x][y] != 0)     /* dynamite is still waiting to explode */
+  int x, y;
+
+  SCAN_PLAYFIELD(x, y)
   {
-    MovDelay[x][y]--;
+    int element = Feld[x][y];
+
+    if (element == EL_EMC_DRIPPER &&
+       game.lenses_time_left > 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_DRIPPER_ACTIVE &&
+            game.lenses_time_left == 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL ||
+            element == EL_INVISIBLE_WALL ||
+            element == EL_INVISIBLE_SAND)
+    {
+      if (game.lenses_time_left > 0)
+       Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+
+      TEST_DrawLevelField(x, y);
+
+      // uncrumble neighbour fields, if needed
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+            element == EL_INVISIBLE_WALL_ACTIVE ||
+            element == EL_INVISIBLE_SAND_ACTIVE)
+    {
+      if (game.lenses_time_left == 0)
+       Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
 
-    if (MovDelay[x][y] != 0)
-    {
-      DrawDynamite(x, y);
-      PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+      TEST_DrawLevelField(x, y);
 
-      return;
+      // re-crumble neighbour fields, if needed
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
   }
-
-  StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
-
-  Bang(x, y);
 }
 
-void DrawRelocatePlayer(struct PlayerInfo *player)
+static void RedrawAllInvisibleElementsForMagnifier(void)
 {
-  boolean ffwd_delay = (tape.playing && tape.fast_forward);
-  boolean no_delay = (tape.warp_forward);
-  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
-  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
-  int jx = player->jx;
-  int jy = player->jy;
+  int x, y;
 
-  if (level.instant_relocation)
+  SCAN_PLAYFIELD(x, y)
   {
-    int offset = (setup.scroll_delay ? 3 : 0);
+    int element = Feld[x][y];
 
-    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+    if (element == EL_EMC_FAKE_GRASS &&
+       game.magnify_time_left > 0)
     {
-      scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-                 local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-                 local_player->jx - MIDPOSX);
-
-      scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
-                 local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
-                 local_player->jy - MIDPOSY);
+      Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
+      TEST_DrawLevelField(x, y);
     }
-    else
+    else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
+            game.magnify_time_left == 0)
     {
-      if ((player->MovDir == MV_LEFT  && scroll_x > jx - MIDPOSX + offset) ||
-         (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
-       scroll_x = jx - MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
-
-      if ((player->MovDir == MV_UP  && scroll_y > jy - MIDPOSY + offset) ||
-         (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
-       scroll_y = jy - MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
-
-      /* don't scroll over playfield boundaries */
-      if (scroll_x < SBX_Left || scroll_x > SBX_Right)
-       scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
-
-      /* don't scroll over playfield boundaries */
-      if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
-       scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+      Feld[x][y] = EL_EMC_FAKE_GRASS;
+      TEST_DrawLevelField(x, y);
     }
-
-    RedrawPlayfield(TRUE, 0,0,0,0);
-  }
-  else
-  {
-    int scroll_xx = -999, scroll_yy = -999;
-
-    ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
-
-    while (scroll_xx != scroll_x || scroll_yy != scroll_y)
+    else if (IS_GATE_GRAY(element) &&
+            game.magnify_time_left > 0)
     {
-      int dx = 0, dy = 0;
-      int fx = FX, fy = FY;
-
-      scroll_xx = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-                  local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-                  local_player->jx - MIDPOSX);
+      Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
+                   element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
+                   IS_EM_GATE_GRAY(element) ?
+                   element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
+                   IS_EMC_GATE_GRAY(element) ?
+                   element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
+                   IS_DC_GATE_GRAY(element) ?
+                   EL_DC_GATE_WHITE_GRAY_ACTIVE :
+                   element);
+      TEST_DrawLevelField(x, y);
+    }
+    else if (IS_GATE_GRAY_ACTIVE(element) &&
+            game.magnify_time_left == 0)
+    {
+      Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
+                   element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
+                   IS_EM_GATE_GRAY_ACTIVE(element) ?
+                   element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
+                   IS_EMC_GATE_GRAY_ACTIVE(element) ?
+                   element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
+                   IS_DC_GATE_GRAY_ACTIVE(element) ?
+                   EL_DC_GATE_WHITE_GRAY :
+                   element);
+      TEST_DrawLevelField(x, y);
+    }
+  }
+}
 
-      scroll_yy = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
-                  local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
-                  local_player->jy - MIDPOSY);
+static void ToggleLightSwitch(int x, int y)
+{
+  int element = Feld[x][y];
 
-      dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
-      dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
+  game.light_time_left =
+    (element == EL_LIGHT_SWITCH ?
+     level.time_light * FRAMES_PER_SECOND : 0);
 
-      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
-       break;
+  RedrawAllLightSwitchesAndInvisibleElements();
+}
 
-      scroll_x -= dx;
-      scroll_y -= dy;
+static void ActivateTimegateSwitch(int x, int y)
+{
+  int xx, yy;
 
-      fx += dx * TILEX / 2;
-      fy += dy * TILEY / 2;
+  game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
 
-      ScrollLevel(dx, dy);
-      DrawAllPlayers();
+  SCAN_PLAYFIELD(xx, yy)
+  {
+    int element = Feld[xx][yy];
 
-      /* scroll in two steps of half tile size to make things smoother */
-      BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
-      FlushDisplay();
-      Delay(wait_delay_value);
+    if (element == EL_TIMEGATE_CLOSED ||
+       element == EL_TIMEGATE_CLOSING)
+    {
+      Feld[xx][yy] = EL_TIMEGATE_OPENING;
+      PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
+    }
 
-      /* scroll second step to align at full tile size */
-      BackToFront();
-      Delay(wait_delay_value);
+    /*
+    else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
+    {
+      Feld[xx][yy] = EL_TIMEGATE_SWITCH;
+      TEST_DrawLevelField(xx, yy);
     }
+    */
 
-    DrawPlayer(player);
-    BackToFront();
-    Delay(wait_delay_value);
   }
+
+  Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
+               EL_DC_TIMEGATE_SWITCH_ACTIVE);
 }
 
-void RelocatePlayer(int jx, int jy, int el_player_raw)
+static void Impact(int x, int y)
 {
-  int el_player = GET_PLAYER_ELEMENT(el_player_raw);
-  int player_nr = GET_PLAYER_NR(el_player);
-  struct PlayerInfo *player = &stored_player[player_nr];
-  boolean ffwd_delay = (tape.playing && tape.fast_forward);
-  boolean no_delay = (tape.warp_forward);
-  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
-  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
-  int old_jx = player->jx;
-  int old_jy = player->jy;
-  int old_element = Feld[old_jx][old_jy];
-  int element = Feld[jx][jy];
-  boolean player_relocated = (old_jx != jx || old_jy != jy);
-
-  int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
-  int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
-  int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
-  int enter_side_vert  = MV_DIR_OPPOSITE(move_dir_vert);
-  int leave_side_horiz = move_dir_horiz;
-  int leave_side_vert  = move_dir_vert;
-  int enter_side = enter_side_horiz | enter_side_vert;
-  int leave_side = leave_side_horiz | leave_side_vert;
+  boolean last_line = (y == lev_fieldy - 1);
+  boolean object_hit = FALSE;
+  boolean impact = (last_line || object_hit);
+  int element = Feld[x][y];
+  int smashed = EL_STEELWALL;
 
-  if (player->GameOver)                /* do not reanimate dead player */
-    return;
+  if (!last_line)      // check if element below was hit
+  {
+    if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
+      return;
 
-  if (!player_relocated)       /* no need to relocate the player */
-    return;
+    object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
+                                        MovDir[x][y + 1] != MV_DOWN ||
+                                        MovPos[x][y + 1] <= TILEY / 2));
 
-  if (IS_PLAYER(jx, jy))       /* player already placed at new position */
-  {
-    RemoveField(jx, jy);       /* temporarily remove newly placed player */
-    DrawLevelField(jx, jy);
-  }
+    // do not smash moving elements that left the smashed field in time
+    if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
+       ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
+      object_hit = FALSE;
 
-  if (player->present)
-  {
-    while (player->MovPos)
+#if USE_QUICKSAND_IMPACT_BUGFIX
+    if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
     {
-      ScrollPlayer(player, SCROLL_GO_ON);
-      ScrollScreen(NULL, SCROLL_GO_ON);
+      RemoveMovingField(x, y + 1);
+      Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
+      Feld[x][y + 2] = EL_ROCK;
+      TEST_DrawLevelField(x, y + 2);
 
-      AdvanceFrameAndPlayerCounters(player->index_nr);
+      object_hit = TRUE;
+    }
 
-      DrawPlayer(player);
+    if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
+    {
+      RemoveMovingField(x, y + 1);
+      Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
+      Feld[x][y + 2] = EL_ROCK;
+      TEST_DrawLevelField(x, y + 2);
 
-      BackToFront();
-      Delay(wait_delay_value);
+      object_hit = TRUE;
     }
+#endif
 
-    DrawPlayer(player);                /* needed here only to cleanup last field */
-    DrawLevelField(player->jx, player->jy);    /* remove player graphic */
+    if (object_hit)
+      smashed = MovingOrBlocked2Element(x, y + 1);
 
-    player->is_moving = FALSE;
+    impact = (last_line || object_hit);
   }
 
-  if (IS_CUSTOM_ELEMENT(old_element))
-    CheckElementChangeByPlayer(old_jx, old_jy, old_element,
-                              CE_LEFT_BY_PLAYER,
-                              player->index_bit, leave_side);
-
-  CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
-                                     CE_PLAYER_LEAVES_X,
-                                     player->index_bit, leave_side);
-
-  Feld[jx][jy] = el_player;
-  InitPlayerField(jx, jy, el_player, TRUE);
-
-  if (!ELEM_IS_PLAYER(element))        /* player may be set on walkable element */
+  if (!last_line && smashed == EL_ACID)        // element falls into acid
   {
-    Feld[jx][jy] = element;
-    InitField(jx, jy, FALSE);
+    SplashAcid(x, y + 1);
+    return;
   }
 
-  if (player == local_player)  /* only visually relocate local player */
-    DrawRelocatePlayer(player);
-
-  TestIfPlayerTouchesBadThing(jx, jy);
-  TestIfPlayerTouchesCustomElement(jx, jy);
-
-  if (IS_CUSTOM_ELEMENT(element))
-    CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
-                              player->index_bit, enter_side);
-
-  CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
-                                     player->index_bit, enter_side);
-}
-
-void Explode(int ex, int ey, int phase, int mode)
-{
-  int x, y;
-  int last_phase;
-  int border_element;
-
-  /* !!! eliminate this variable !!! */
-  int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+  // !!! not sufficient for all cases -- see EL_PEARL below !!!
+  // only reset graphic animation if graphic really changes after impact
+  if (impact &&
+      el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
+  {
+    ResetGfxAnimation(x, y);
+    TEST_DrawLevelField(x, y);
+  }
 
-  if (game.explosions_delayed)
+  if (impact && CAN_EXPLODE_IMPACT(element))
   {
-    ExplodeField[ex][ey] = mode;
+    Bang(x, y);
     return;
   }
-
-  if (phase == EX_PHASE_START)         /* initialize 'Store[][]' field */
+  else if (impact && element == EL_PEARL &&
+          smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
   {
-    int center_element = Feld[ex][ey];
-    int artwork_element, explosion_element;    /* set these values later */
-
-#if 0
-    /* --- This is only really needed (and now handled) in "Impact()". --- */
-    /* do not explode moving elements that left the explode field in time */
-    if (game.engine_version >= VERSION_IDENT(2,2,0,7) &&
-       center_element == EL_EMPTY &&
-       (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER))
-      return;
-#endif
-
-#if 0
-    /* !!! at this place, the center element may be EL_BLOCKED !!! */
-    if (mode == EX_TYPE_NORMAL ||
-       mode == EX_TYPE_CENTER ||
-       mode == EX_TYPE_CROSS)
-      PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
-#endif
-
-    /* remove things displayed in background while burning dynamite */
-    if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
-      Back[ex][ey] = 0;
-
-    if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
-    {
-      /* put moving element to center field (and let it explode there) */
-      center_element = MovingOrBlocked2Element(ex, ey);
-      RemoveMovingField(ex, ey);
-      Feld[ex][ey] = center_element;
-    }
-
-    /* now "center_element" is finally determined -- set related values now */
-    artwork_element = center_element;          /* for custom player artwork */
-    explosion_element = center_element;                /* for custom player artwork */
-
-    if (IS_PLAYER(ex, ey))
-    {
-      int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
-
-      artwork_element = stored_player[player_nr].artwork_element;
-
-      if (level.use_explosion_element[player_nr])
-      {
-       explosion_element = level.explosion_element[player_nr];
-       artwork_element = explosion_element;
-      }
-    }
+    ResetGfxAnimation(x, y);
 
-#if 1
-    if (mode == EX_TYPE_NORMAL ||
-       mode == EX_TYPE_CENTER ||
-       mode == EX_TYPE_CROSS)
-      PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
-#endif
+    Feld[x][y] = EL_PEARL_BREAKING;
+    PlayLevelSound(x, y, SND_PEARL_BREAKING);
+    return;
+  }
+  else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
+  {
+    PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
 
-#if 1
-    last_phase = element_info[explosion_element].explosion_delay + 1;
-#else
-    last_phase = element_info[center_element].explosion_delay + 1;
-#endif
+    return;
+  }
 
-    for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
+  if (impact && element == EL_AMOEBA_DROP)
+  {
+    if (object_hit && IS_PLAYER(x, y + 1))
+      KillPlayerUnlessEnemyProtected(x, y + 1);
+    else if (object_hit && smashed == EL_PENGUIN)
+      Bang(x, y + 1);
+    else
     {
-      int xx = x - ex + 1;
-      int yy = y - ey + 1;
-      int element;
+      Feld[x][y] = EL_AMOEBA_GROWING;
+      Store[x][y] = EL_AMOEBA_WET;
 
-      if (!IN_LEV_FIELD(x, y) ||
-         (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
-         (mode == EX_TYPE_CROSS      && (x != ex && y != ey)))
-       continue;
+      ResetRandomAnimationValue(x, y);
+    }
+    return;
+  }
 
-      element = Feld[x][y];
+  if (object_hit)              // check which object was hit
+  {
+    if ((CAN_PASS_MAGIC_WALL(element) && 
+        (smashed == EL_MAGIC_WALL ||
+         smashed == EL_BD_MAGIC_WALL)) ||
+       (CAN_PASS_DC_MAGIC_WALL(element) &&
+        smashed == EL_DC_MAGIC_WALL))
+    {
+      int xx, yy;
+      int activated_magic_wall =
+       (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
+        smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
+        EL_DC_MAGIC_WALL_ACTIVE);
 
-      if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+      // activate magic wall / mill
+      SCAN_PLAYFIELD(xx, yy)
       {
-       element = MovingOrBlocked2Element(x, y);
-
-       if (!IS_EXPLOSION_PROOF(element))
-         RemoveMovingField(x, y);
+       if (Feld[xx][yy] == smashed)
+         Feld[xx][yy] = activated_magic_wall;
       }
 
-      /* indestructible elements can only explode in center (but not flames) */
-      if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
-                                          mode == EX_TYPE_BORDER)) ||
-         element == EL_FLAMES)
-       continue;
+      game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
+      game.magic_wall_active = TRUE;
 
-      /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
-        behaviour, for example when touching a yamyam that explodes to rocks
-        with active deadly shield, a rock is created under the player !!! */
-      /* (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8) */
-#if 0
-      if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
-         (game.engine_version < VERSION_IDENT(3,1,0,0) ||
-          (x == ex && y == ey && mode != EX_TYPE_BORDER)))
-#else
-      if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
-#endif
-      {
-       if (IS_ACTIVE_BOMB(element))
-       {
-         /* re-activate things under the bomb like gate or penguin */
-         Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
-         Back[x][y] = 0;
-       }
+      PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
+                           SND_MAGIC_WALL_ACTIVATING :
+                           smashed == EL_BD_MAGIC_WALL ?
+                           SND_BD_MAGIC_WALL_ACTIVATING :
+                           SND_DC_MAGIC_WALL_ACTIVATING));
+    }
 
-       continue;
+    if (IS_PLAYER(x, y + 1))
+    {
+      if (CAN_SMASH_PLAYER(element))
+      {
+       KillPlayerUnlessEnemyProtected(x, y + 1);
+       return;
       }
-
-      /* save walkable background elements while explosion on same tile */
-      if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
-         (x != ex || y != ey || mode == EX_TYPE_BORDER))
-       Back[x][y] = element;
-
-      /* ignite explodable elements reached by other explosion */
-      if (element == EL_EXPLOSION)
-       element = Store2[x][y];
-
-      if (AmoebaNr[x][y] &&
-         (element == EL_AMOEBA_FULL ||
-          element == EL_BD_AMOEBA ||
-          element == EL_AMOEBA_GROWING))
+    }
+    else if (smashed == EL_PENGUIN)
+    {
+      if (CAN_SMASH_PLAYER(element))
       {
-       AmoebaCnt[AmoebaNr[x][y]]--;
-       AmoebaCnt2[AmoebaNr[x][y]]--;
+       Bang(x, y + 1);
+       return;
       }
-
-      RemoveField(x, y);
-
-      if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
+    }
+    else if (element == EL_BD_DIAMOND)
+    {
+      if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
       {
-#if 1
-       int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
+       Bang(x, y + 1);
+       return;
+      }
+    }
+    else if (((element == EL_SP_INFOTRON ||
+              element == EL_SP_ZONK) &&
+             (smashed == EL_SP_SNIKSNAK ||
+              smashed == EL_SP_ELECTRON ||
+              smashed == EL_SP_DISK_ORANGE)) ||
+            (element == EL_SP_INFOTRON &&
+             smashed == EL_SP_DISK_YELLOW))
+    {
+      Bang(x, y + 1);
+      return;
+    }
+    else if (CAN_SMASH_EVERYTHING(element))
+    {
+      if (IS_CLASSIC_ENEMY(smashed) ||
+         CAN_EXPLODE_SMASHED(smashed))
+      {
+       Bang(x, y + 1);
+       return;
+      }
+      else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
+      {
+       if (smashed == EL_LAMP ||
+           smashed == EL_LAMP_ACTIVE)
+       {
+         Bang(x, y + 1);
+         return;
+       }
+       else if (smashed == EL_NUT)
+       {
+         Feld[x][y + 1] = EL_NUT_BREAKING;
+         PlayLevelSound(x, y, SND_NUT_BREAKING);
+         RaiseScoreElement(EL_NUT);
+         return;
+       }
+       else if (smashed == EL_PEARL)
+       {
+         ResetGfxAnimation(x, y);
 
-       Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
-#else
-       switch(StorePlayer[ex][ey])
+         Feld[x][y + 1] = EL_PEARL_BREAKING;
+         PlayLevelSound(x, y, SND_PEARL_BREAKING);
+         return;
+       }
+       else if (smashed == EL_DIAMOND)
        {
-         case EL_PLAYER_2:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_2;
-           break;
-         case EL_PLAYER_3:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_3;
-           break;
-         case EL_PLAYER_4:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_4;
-           break;
-         case EL_PLAYER_1:
-         default:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_1;
-           break;
+         Feld[x][y + 1] = EL_DIAMOND_BREAKING;
+         PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
+         return;
        }
-#endif
+       else if (IS_BELT_SWITCH(smashed))
+       {
+         ToggleBeltSwitch(x, y + 1);
+       }
+       else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
+                smashed == EL_SWITCHGATE_SWITCH_DOWN ||
+                smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
+                smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
+       {
+         ToggleSwitchgateSwitch(x, y + 1);
+       }
+       else if (smashed == EL_LIGHT_SWITCH ||
+                smashed == EL_LIGHT_SWITCH_ACTIVE)
+       {
+         ToggleLightSwitch(x, y + 1);
+       }
+       else
+       {
+         CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
 
-       if (PLAYERINFO(ex, ey)->use_murphy)
-         Store[x][y] = EL_EMPTY;
+         CheckElementChangeBySide(x, y + 1, smashed, element,
+                                  CE_SWITCHED, CH_SIDE_TOP);
+         CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
+                                           CH_SIDE_TOP);
+       }
       }
-#if 1
-      /* !!! check this case -- currently needed for rnd_rado_negundo_v,
-        !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
-      else if (ELEM_IS_PLAYER(center_element))
-       Store[x][y] = EL_EMPTY;
-      else if (center_element == EL_YAMYAM)
-       Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
-      else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
-       Store[x][y] = element_info[center_element].content.e[xx][yy];
-#if 1
-      /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
-        (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
-        otherwise) -- FIX THIS !!! */
-      else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
-       Store[x][y] = element_info[element].content.e[1][1];
-#else
-      else if (!CAN_EXPLODE(element))
-       Store[x][y] = element_info[element].content.e[1][1];
-#endif
-      else
-       Store[x][y] = EL_EMPTY;
-#else
-      else if (center_element == EL_MOLE)
-       Store[x][y] = EL_EMERALD_RED;
-      else if (center_element == EL_PENGUIN)
-       Store[x][y] = EL_EMERALD_PURPLE;
-      else if (center_element == EL_BUG)
-       Store[x][y] = ((x == ex && y == ey) ? EL_DIAMOND : EL_EMERALD);
-      else if (center_element == EL_BD_BUTTERFLY)
-       Store[x][y] = EL_BD_DIAMOND;
-      else if (center_element == EL_SP_ELECTRON)
-       Store[x][y] = EL_SP_INFOTRON;
-      else if (center_element == EL_AMOEBA_TO_DIAMOND)
-       Store[x][y] = level.amoeba_content;
-      else if (center_element == EL_YAMYAM)
-       Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
-      else if (IS_CUSTOM_ELEMENT(center_element) &&
-              element_info[center_element].content.e[xx][yy] != EL_EMPTY)
-       Store[x][y] = element_info[center_element].content.e[xx][yy];
-      else if (element == EL_WALL_EMERALD)
-       Store[x][y] = EL_EMERALD;
-      else if (element == EL_WALL_DIAMOND)
-       Store[x][y] = EL_DIAMOND;
-      else if (element == EL_WALL_BD_DIAMOND)
-       Store[x][y] = EL_BD_DIAMOND;
-      else if (element == EL_WALL_EMERALD_YELLOW)
-       Store[x][y] = EL_EMERALD_YELLOW;
-      else if (element == EL_WALL_EMERALD_RED)
-       Store[x][y] = EL_EMERALD_RED;
-      else if (element == EL_WALL_EMERALD_PURPLE)
-       Store[x][y] = EL_EMERALD_PURPLE;
-      else if (element == EL_WALL_PEARL)
-       Store[x][y] = EL_PEARL;
-      else if (element == EL_WALL_CRYSTAL)
-       Store[x][y] = EL_CRYSTAL;
-      else if (IS_CUSTOM_ELEMENT(element) && !CAN_EXPLODE(element))
-       Store[x][y] = element_info[element].content.e[1][1];
       else
-       Store[x][y] = EL_EMPTY;
-#endif
+      {
+       CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
+      }
+    }
+  }
 
-      if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
-         center_element == EL_AMOEBA_TO_DIAMOND)
-       Store2[x][y] = element;
+  // play sound of magic wall / mill
+  if (!last_line &&
+      (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
+       Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
+       Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
+  {
+    if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
+      PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
+    else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
+      PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
+    else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
+      PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
 
-      Feld[x][y] = EL_EXPLOSION;
-      GfxElement[x][y] = artwork_element;
+    return;
+  }
 
-      ExplodePhase[x][y] = 1;
-      ExplodeDelay[x][y] = last_phase;
+  // play sound of object that hits the ground
+  if (last_line || object_hit)
+    PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+}
 
-      Stop[x][y] = TRUE;
-    }
+static void TurnRoundExt(int x, int y)
+{
+  static struct
+  {
+    int dx, dy;
+  } move_xy[] =
+  {
+    {  0,  0 },
+    { -1,  0 },
+    { +1,  0 },
+    {  0,  0 },
+    {  0, -1 },
+    {  0,  0 }, { 0, 0 }, { 0, 0 },
+    {  0, +1 }
+  };
+  static struct
+  {
+    int left, right, back;
+  } turn[] =
+  {
+    { 0,       0,              0        },
+    { MV_DOWN, MV_UP,          MV_RIGHT },
+    { MV_UP,   MV_DOWN,        MV_LEFT  },
+    { 0,       0,              0        },
+    { MV_LEFT, MV_RIGHT,       MV_DOWN  },
+    { 0,       0,              0        },
+    { 0,       0,              0        },
+    { 0,       0,              0        },
+    { MV_RIGHT,        MV_LEFT,        MV_UP    }
+  };
 
-    if (center_element == EL_YAMYAM)
-      game.yamyam_content_nr =
-       (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
+  int element = Feld[x][y];
+  int move_pattern = element_info[element].move_pattern;
+
+  int old_move_dir = MovDir[x][y];
+  int left_dir  = turn[old_move_dir].left;
+  int right_dir = turn[old_move_dir].right;
+  int back_dir  = turn[old_move_dir].back;
 
-    return;
-  }
+  int left_dx  = move_xy[left_dir].dx,     left_dy  = move_xy[left_dir].dy;
+  int right_dx = move_xy[right_dir].dx,    right_dy = move_xy[right_dir].dy;
+  int move_dx  = move_xy[old_move_dir].dx, move_dy  = move_xy[old_move_dir].dy;
+  int back_dx  = move_xy[back_dir].dx,     back_dy  = move_xy[back_dir].dy;
 
-  if (Stop[ex][ey])
-    return;
+  int left_x  = x + left_dx,  left_y  = y + left_dy;
+  int right_x = x + right_dx, right_y = y + right_dy;
+  int move_x  = x + move_dx,  move_y  = y + move_dy;
 
-  x = ex;
-  y = ey;
+  int xx, yy;
 
-  if (phase == 1)
-    GfxFrame[x][y] = 0;                /* restart explosion animation */
+  if (element == EL_BUG || element == EL_BD_BUTTERFLY)
+  {
+    TestIfBadThingTouchesOtherBadThing(x, y);
 
-  last_phase = ExplodeDelay[x][y];
+    if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
+      MovDir[x][y] = right_dir;
+    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
+      MovDir[x][y] = left_dir;
 
-  ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
+    if (element == EL_BUG && MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = 9;
+    else if (element == EL_BD_BUTTERFLY)     // && MovDir[x][y] == left_dir)
+      MovDelay[x][y] = 1;
+  }
+  else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
+  {
+    TestIfBadThingTouchesOtherBadThing(x, y);
 
-#ifdef DEBUG
+    if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
+      MovDir[x][y] = left_dir;
+    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
+      MovDir[x][y] = right_dir;
 
-  /* activate this even in non-DEBUG version until cause for crash in
-     getGraphicAnimationFrame() (see below) is found and eliminated */
+    if (element == EL_SPACESHIP        && MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = 9;
+    else if (element == EL_BD_FIREFLY)     // && MovDir[x][y] == right_dir)
+      MovDelay[x][y] = 1;
+  }
+  else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
+  {
+    TestIfBadThingTouchesOtherBadThing(x, y);
 
-#endif
-#if 1
+    if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
+      MovDir[x][y] = left_dir;
+    else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
+      MovDir[x][y] = right_dir;
 
-  if (GfxElement[x][y] == EL_UNDEFINED)
+    if (MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = 9;
+  }
+  else if (element == EL_YAMYAM)
   {
-    printf("\n\n");
-    printf("Explode(): x = %d, y = %d: GfxElement == EL_UNDEFINED\n", x, y);
-    printf("Explode(): This should never happen!\n");
-    printf("\n\n");
+    boolean can_turn_left  = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
 
-    GfxElement[x][y] = EL_EMPTY;
+    if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+    else if (can_turn_left)
+      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+    else if (can_turn_right)
+      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+    else
+      MovDir[x][y] = back_dir;
+
+    MovDelay[x][y] = 16 + 16 * RND(3);
   }
-#endif
+  else if (element == EL_DARK_YAMYAM)
+  {
+    boolean can_turn_left  = DARK_YAMYAM_CAN_ENTER_FIELD(element,
+                                                        left_x, left_y);
+    boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
+                                                        right_x, right_y);
 
-  border_element = Store2[x][y];
-  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
-    border_element = StorePlayer[x][y];
+    if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+    else if (can_turn_left)
+      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+    else if (can_turn_right)
+      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+    else
+      MovDir[x][y] = back_dir;
 
-  if (phase == element_info[border_element].ignition_delay ||
-      phase == last_phase)
+    MovDelay[x][y] = 16 + 16 * RND(3);
+  }
+  else if (element == EL_PACMAN)
   {
-    boolean border_explosion = FALSE;
+    boolean can_turn_left  = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
 
-    if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
-       !PLAYER_EXPLOSION_PROTECTED(x, y))
-    {
-      KillPlayerUnlessExplosionProtected(x, y);
-      border_explosion = TRUE;
-    }
-    else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
-    {
-      Feld[x][y] = Store2[x][y];
-      Store2[x][y] = 0;
-      Bang(x, y);
-      border_explosion = TRUE;
-    }
-    else if (border_element == EL_AMOEBA_TO_DIAMOND)
-    {
-      AmoebeUmwandeln(x, y);
-      Store2[x][y] = 0;
-      border_explosion = TRUE;
-    }
+    if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+    else if (can_turn_left)
+      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+    else if (can_turn_right)
+      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+    else
+      MovDir[x][y] = back_dir;
 
-    /* if an element just explodes due to another explosion (chain-reaction),
-       do not immediately end the new explosion when it was the last frame of
-       the explosion (as it would be done in the following "if"-statement!) */
-    if (border_explosion && phase == last_phase)
-      return;
+    MovDelay[x][y] = 6 + RND(40);
   }
-
-  if (phase == last_phase)
+  else if (element == EL_PIG)
   {
-    int element;
+    boolean can_turn_left  = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
+    boolean can_move_on    = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
+    boolean should_turn_left, should_turn_right, should_move_on;
+    int rnd_value = 24;
+    int rnd = RND(rnd_value);
 
-    element = Feld[x][y] = Store[x][y];
-    Store[x][y] = Store2[x][y] = 0;
-    GfxElement[x][y] = EL_UNDEFINED;
+    should_turn_left = (can_turn_left &&
+                       (!can_move_on ||
+                        IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
+                                                  y + back_dy + left_dy)));
+    should_turn_right = (can_turn_right &&
+                        (!can_move_on ||
+                         IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
+                                                   y + back_dy + right_dy)));
+    should_move_on = (can_move_on &&
+                     (!can_turn_left ||
+                      !can_turn_right ||
+                      IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
+                                                y + move_dy + left_dy) ||
+                      IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
+                                                y + move_dy + right_dy)));
 
-    /* player can escape from explosions and might therefore be still alive */
-    if (element >= EL_PLAYER_IS_EXPLODING_1 &&
-       element <= EL_PLAYER_IS_EXPLODING_4)
+    if (should_turn_left || should_turn_right || should_move_on)
     {
-      int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
-      int explosion_element = EL_PLAYER_1 + player_nr;
-      int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
-      int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
-
-      if (level.use_explosion_element[player_nr])
-       explosion_element = level.explosion_element[player_nr];
-
-      Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
-                   element_info[explosion_element].content.e[xx][yy]);
+      if (should_turn_left && should_turn_right && should_move_on)
+       MovDir[x][y] = (rnd < rnd_value / 3     ? left_dir :
+                       rnd < 2 * rnd_value / 3 ? right_dir :
+                       old_move_dir);
+      else if (should_turn_left && should_turn_right)
+       MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
+      else if (should_turn_left && should_move_on)
+       MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
+      else if (should_turn_right && should_move_on)
+       MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
+      else if (should_turn_left)
+       MovDir[x][y] = left_dir;
+      else if (should_turn_right)
+       MovDir[x][y] = right_dir;
+      else if (should_move_on)
+       MovDir[x][y] = old_move_dir;
     }
+    else if (can_move_on && rnd > rnd_value / 8)
+      MovDir[x][y] = old_move_dir;
+    else if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
+    else if (can_turn_left && rnd > rnd_value / 8)
+      MovDir[x][y] = left_dir;
+    else if (can_turn_right && rnd > rnd_value/8)
+      MovDir[x][y] = right_dir;
+    else
+      MovDir[x][y] = back_dir;
 
-    /* restore probably existing indestructible background element */
-    if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
-      element = Feld[x][y] = Back[x][y];
-    Back[x][y] = 0;
+    xx = x + move_xy[MovDir[x][y]].dx;
+    yy = y + move_xy[MovDir[x][y]].dy;
 
-    MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
-    GfxDir[x][y] = MV_NONE;
-    ChangeDelay[x][y] = 0;
-    ChangePage[x][y] = -1;
+    if (!IN_LEV_FIELD(xx, yy) ||
+        (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
+      MovDir[x][y] = old_move_dir;
 
-#if USE_NEW_CUSTOM_VALUE
-    CustomValue[x][y] = 0;
-#endif
+    MovDelay[x][y] = 0;
+  }
+  else if (element == EL_DRAGON)
+  {
+    boolean can_turn_left  = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
+    boolean can_move_on    = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
+    int rnd_value = 24;
+    int rnd = RND(rnd_value);
 
-    InitField_WithBug2(x, y, FALSE);
+    if (can_move_on && rnd > rnd_value / 8)
+      MovDir[x][y] = old_move_dir;
+    else if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
+    else if (can_turn_left && rnd > rnd_value / 8)
+      MovDir[x][y] = left_dir;
+    else if (can_turn_right && rnd > rnd_value / 8)
+      MovDir[x][y] = right_dir;
+    else
+      MovDir[x][y] = back_dir;
 
-    DrawLevelField(x, y);
+    xx = x + move_xy[MovDir[x][y]].dx;
+    yy = y + move_xy[MovDir[x][y]].dy;
 
-    TestIfElementTouchesCustomElement(x, y);
+    if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
+      MovDir[x][y] = old_move_dir;
 
-    if (GFX_CRUMBLED(element))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
+    MovDelay[x][y] = 0;
+  }
+  else if (element == EL_MOLE)
+  {
+    boolean can_move_on =
+      (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
+                           IS_AMOEBOID(Feld[move_x][move_y]) ||
+                           Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
+    if (!can_move_on)
+    {
+      boolean can_turn_left =
+       (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
+                             IS_AMOEBOID(Feld[left_x][left_y])));
 
-    if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
-      StorePlayer[x][y] = 0;
+      boolean can_turn_right =
+       (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
+                             IS_AMOEBOID(Feld[right_x][right_y])));
+
+      if (can_turn_left && can_turn_right)
+       MovDir[x][y] = (RND(2) ? left_dir : right_dir);
+      else if (can_turn_left)
+       MovDir[x][y] = left_dir;
+      else
+       MovDir[x][y] = right_dir;
+    }
 
-    if (ELEM_IS_PLAYER(element))
-      RelocatePlayer(x, y, element);
+    if (MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = 9;
   }
-  else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+  else if (element == EL_BALLOON)
   {
-    int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
-    int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+    MovDir[x][y] = game.wind_direction;
+    MovDelay[x][y] = 0;
+  }
+  else if (element == EL_SPRING)
+  {
+    if (MovDir[x][y] & MV_HORIZONTAL)
+    {
+      if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
+         !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+      {
+       Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
+       ResetGfxAnimation(move_x, move_y);
+       TEST_DrawLevelField(move_x, move_y);
 
-    if (phase == delay)
-      DrawLevelFieldCrumbledSand(x, y);
+       MovDir[x][y] = back_dir;
+      }
+      else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
+              SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+       MovDir[x][y] = MV_NONE;
+    }
 
-    if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
+    MovDelay[x][y] = 0;
+  }
+  else if (element == EL_ROBOT ||
+          element == EL_SATELLITE ||
+          element == EL_PENGUIN ||
+          element == EL_EMC_ANDROID)
+  {
+    int attr_x = -1, attr_y = -1;
+
+    if (game.all_players_gone)
     {
-      DrawLevelElement(x, y, Back[x][y]);
-      DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
+      attr_x = game.exit_x;
+      attr_y = game.exit_y;
     }
-    else if (IS_WALKABLE_UNDER(Back[x][y]))
+    else
     {
-      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
-      DrawLevelElementThruMask(x, y, Back[x][y]);
-    }
-    else if (!IS_WALKABLE_INSIDE(Back[x][y]))
-      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
-  }
-}
+      int i;
 
-void DynaExplode(int ex, int ey)
-{
-  int i, j;
-  int dynabomb_element = Feld[ex][ey];
-  int dynabomb_size = 1;
-  boolean dynabomb_xl = FALSE;
-  struct PlayerInfo *player;
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int jx = player->jx, jy = player->jy;
 
-  if (IS_ACTIVE_BOMB(dynabomb_element))
-  {
-    player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
-    dynabomb_size = player->dynabomb_size;
-    dynabomb_xl = player->dynabomb_xl;
-    player->dynabombs_left++;
-  }
+       if (!player->active)
+         continue;
 
-  Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
+       if (attr_x == -1 ||
+           ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
+       {
+         attr_x = jx;
+         attr_y = jy;
+       }
+      }
+    }
 
-  for (i = 0; i < NUM_DIRECTIONS; i++)
-  {
-    for (j = 1; j <= dynabomb_size; j++)
+    if (element == EL_ROBOT &&
+       game.robot_wheel_x >= 0 &&
+       game.robot_wheel_y >= 0 &&
+       (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
+        game.engine_version < VERSION_IDENT(3,1,0,0)))
     {
-      int x = ex + j * xy[i][0];
-      int y = ey + j * xy[i][1];
-      int element;
-
-      if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
-       break;
-
-      element = Feld[x][y];
+      attr_x = game.robot_wheel_x;
+      attr_y = game.robot_wheel_y;
+    }
 
-      /* do not restart explosions of fields with active bombs */
-      if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
-       continue;
+    if (element == EL_PENGUIN)
+    {
+      int i;
+      static int xy[4][2] =
+      {
+       { 0, -1 },
+       { -1, 0 },
+       { +1, 0 },
+       { 0, +1 }
+      };
 
-      Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
+      for (i = 0; i < NUM_DIRECTIONS; i++)
+      {
+       int ex = x + xy[i][0];
+       int ey = y + xy[i][1];
 
-      if (element != EL_EMPTY && element != EL_EXPLOSION &&
-         !IS_DIGGABLE(element) && !dynabomb_xl)
-       break;
+       if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
+                                    Feld[ex][ey] == EL_EM_EXIT_OPEN ||
+                                    Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
+                                    Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
+       {
+         attr_x = ex;
+         attr_y = ey;
+         break;
+       }
+      }
     }
-  }
-}
-
-void Bang(int x, int y)
-{
-  int element = MovingOrBlocked2Element(x, y);
-  int explosion_type = EX_TYPE_NORMAL;
-
-  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
-  {
-    struct PlayerInfo *player = PLAYERINFO(x, y);
 
-    element = Feld[x][y] = (player->use_murphy ? EL_SP_MURPHY :
-                           player->element_nr);
+    MovDir[x][y] = MV_NONE;
+    if (attr_x < x)
+      MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
+    else if (attr_x > x)
+      MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
+    if (attr_y < y)
+      MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
+    else if (attr_y > y)
+      MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
 
-    if (level.use_explosion_element[player->index_nr])
+    if (element == EL_ROBOT)
     {
-      int explosion_element = level.explosion_element[player->index_nr];
+      int newx, newy;
 
-      if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
-       explosion_type = EX_TYPE_CROSS;
-      else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
-       explosion_type = EX_TYPE_CENTER;
+      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+       MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
+      Moving2Blocked(x, y, &newx, &newy);
+
+      if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
+       MovDelay[x][y] = 8 + 8 * !RND(3);
+      else
+       MovDelay[x][y] = 16;
     }
-  }
+    else if (element == EL_PENGUIN)
+    {
+      int newx, newy;
 
-  switch(element)
-  {
-    case EL_BUG:
-    case EL_SPACESHIP:
-    case EL_BD_BUTTERFLY:
-    case EL_BD_FIREFLY:
-    case EL_YAMYAM:
-    case EL_DARK_YAMYAM:
-    case EL_ROBOT:
-    case EL_PACMAN:
-    case EL_MOLE:
-      RaiseScoreElement(element);
-      break;
+      MovDelay[x][y] = 1;
 
-    case EL_DYNABOMB_PLAYER_1_ACTIVE:
-    case EL_DYNABOMB_PLAYER_2_ACTIVE:
-    case EL_DYNABOMB_PLAYER_3_ACTIVE:
-    case EL_DYNABOMB_PLAYER_4_ACTIVE:
-    case EL_DYNABOMB_INCREASE_NUMBER:
-    case EL_DYNABOMB_INCREASE_SIZE:
-    case EL_DYNABOMB_INCREASE_POWER:
-      explosion_type = EX_TYPE_DYNA;
-      break;
+      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+      {
+       boolean first_horiz = RND(2);
+       int new_move_dir = MovDir[x][y];
 
-    case EL_PENGUIN:
-    case EL_LAMP:
-    case EL_LAMP_ACTIVE:
-    case EL_AMOEBA_TO_DIAMOND:
-      if (!IS_PLAYER(x, y))    /* penguin and player may be at same field */
-       explosion_type = EX_TYPE_CENTER;
-      break;
+       MovDir[x][y] =
+         new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+       Moving2Blocked(x, y, &newx, &newy);
 
-    default:
-      if (element_info[element].explosion_type == EXPLODES_CROSS)
-       explosion_type = EX_TYPE_CROSS;
-      else if (element_info[element].explosion_type == EXPLODES_1X1)
-       explosion_type = EX_TYPE_CENTER;
-      break;
-  }
+       if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
+         return;
 
-  if (explosion_type == EX_TYPE_DYNA)
-    DynaExplode(x, y);
-  else
-    Explode(x, y, EX_PHASE_START, explosion_type);
+       MovDir[x][y] =
+         new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+       Moving2Blocked(x, y, &newx, &newy);
 
-  CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
-}
+       if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
+         return;
 
-void SplashAcid(int x, int y)
-{
-  if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
-      (!IN_LEV_FIELD(x - 1, y - 2) ||
-       !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
-    Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
+       MovDir[x][y] = old_move_dir;
+       return;
+      }
+    }
+    else if (element == EL_SATELLITE)
+    {
+      int newx, newy;
 
-  if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
-      (!IN_LEV_FIELD(x + 1, y - 2) ||
-       !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
-    Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
+      MovDelay[x][y] = 1;
 
-  PlayLevelSound(x, y, SND_ACID_SPLASHING);
-}
+      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+      {
+       boolean first_horiz = RND(2);
+       int new_move_dir = MovDir[x][y];
 
-static void InitBeltMovement()
-{
-  static int belt_base_element[4] =
-  {
-    EL_CONVEYOR_BELT_1_LEFT,
-    EL_CONVEYOR_BELT_2_LEFT,
-    EL_CONVEYOR_BELT_3_LEFT,
-    EL_CONVEYOR_BELT_4_LEFT
-  };
-  static int belt_base_active_element[4] =
-  {
-    EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
-    EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
-    EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
-    EL_CONVEYOR_BELT_4_LEFT_ACTIVE
-  };
+       MovDir[x][y] =
+         new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+       Moving2Blocked(x, y, &newx, &newy);
 
-  int x, y, i, j;
+       if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
+         return;
 
-  /* set frame order for belt animation graphic according to belt direction */
-  for (i = 0; i < NUM_BELTS; i++)
-  {
-    int belt_nr = i;
+       MovDir[x][y] =
+         new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+       Moving2Blocked(x, y, &newx, &newy);
 
-    for (j = 0; j < NUM_BELT_PARTS; j++)
-    {
-      int element = belt_base_active_element[belt_nr] + j;
-      int graphic = el2img(element);
+       if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
+         return;
 
-      if (game.belt_dir[i] == MV_LEFT)
-       graphic_info[graphic].anim_mode &= ~ANIM_REVERSE;
-      else
-       graphic_info[graphic].anim_mode |=  ANIM_REVERSE;
+       MovDir[x][y] = old_move_dir;
+       return;
+      }
     }
-  }
-
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
-  {
-    int element = Feld[x][y];
+    else if (element == EL_EMC_ANDROID)
+    {
+      static int check_pos[16] =
+      {
+       -1,             //  0 => (invalid)
+       7,              //  1 => MV_LEFT
+       3,              //  2 => MV_RIGHT
+       -1,             //  3 => (invalid)
+       1,              //  4 =>            MV_UP
+       0,              //  5 => MV_LEFT  | MV_UP
+       2,              //  6 => MV_RIGHT | MV_UP
+       -1,             //  7 => (invalid)
+       5,              //  8 =>            MV_DOWN
+       6,              //  9 => MV_LEFT  | MV_DOWN
+       4,              // 10 => MV_RIGHT | MV_DOWN
+       -1,             // 11 => (invalid)
+       -1,             // 12 => (invalid)
+       -1,             // 13 => (invalid)
+       -1,             // 14 => (invalid)
+       -1,             // 15 => (invalid)
+      };
+      static struct
+      {
+       int dx, dy;
+       int dir;
+      } check_xy[8] =
+      {
+        { -1, -1,      MV_LEFT  | MV_UP   },
+               {  0, -1,                  MV_UP   },
+       { +1, -1,       MV_RIGHT | MV_UP   },
+       { +1,  0,       MV_RIGHT           },
+       { +1, +1,       MV_RIGHT | MV_DOWN },
+       {  0, +1,                  MV_DOWN },
+       { -1, +1,       MV_LEFT  | MV_DOWN },
+       { -1,  0,       MV_LEFT            },
+      };
+      int start_pos, check_order;
+      boolean can_clone = FALSE;
+      int i;
 
-    for (i = 0; i < NUM_BELTS; i++)
-    {
-      if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
+      // check if there is any free field around current position
+      for (i = 0; i < 8; i++)
       {
-       int e_belt_nr = getBeltNrFromBeltElement(element);
-       int belt_nr = i;
+       int newx = x + check_xy[i].dx;
+       int newy = y + check_xy[i].dy;
 
-       if (e_belt_nr == belt_nr)
+       if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
        {
-         int belt_part = Feld[x][y] - belt_base_element[belt_nr];
+         can_clone = TRUE;
 
-         Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
+         break;
        }
       }
-    }
-  }
-}
 
-static void ToggleBeltSwitch(int x, int y)
-{
-  static int belt_base_element[4] =
-  {
-    EL_CONVEYOR_BELT_1_LEFT,
-    EL_CONVEYOR_BELT_2_LEFT,
-    EL_CONVEYOR_BELT_3_LEFT,
-    EL_CONVEYOR_BELT_4_LEFT
-  };
-  static int belt_base_active_element[4] =
-  {
-    EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
-    EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
-    EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
-    EL_CONVEYOR_BELT_4_LEFT_ACTIVE
-  };
-  static int belt_base_switch_element[4] =
-  {
-    EL_CONVEYOR_BELT_1_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_2_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_3_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_4_SWITCH_LEFT
-  };
-  static int belt_move_dir[4] =
-  {
-    MV_LEFT,
-    MV_NONE,
-    MV_RIGHT,
-    MV_NONE,
-  };
+      if (can_clone)           // randomly find an element to clone
+      {
+       can_clone = FALSE;
 
-  int element = Feld[x][y];
-  int belt_nr = getBeltNrFromBeltSwitchElement(element);
-  int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
-  int belt_dir = belt_move_dir[belt_dir_nr];
-  int xx, yy, i;
+       start_pos = check_pos[RND(8)];
+       check_order = (RND(2) ? -1 : +1);
 
-  if (!IS_BELT_SWITCH(element))
-    return;
+       for (i = 0; i < 8; i++)
+       {
+         int pos_raw = start_pos + i * check_order;
+         int pos = (pos_raw + 8) % 8;
+         int newx = x + check_xy[pos].dx;
+         int newy = y + check_xy[pos].dy;
 
-  game.belt_dir_nr[belt_nr] = belt_dir_nr;
-  game.belt_dir[belt_nr] = belt_dir;
+         if (ANDROID_CAN_CLONE_FIELD(newx, newy))
+         {
+           element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
+           element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
 
-  if (belt_dir_nr == 3)
-    belt_dir_nr = 1;
+           Store[x][y] = Feld[newx][newy];
 
-  /* set frame order for belt animation graphic according to belt direction */
-  for (i = 0; i < NUM_BELT_PARTS; i++)
-  {
-    int element = belt_base_active_element[belt_nr] + i;
-    int graphic = el2img(element);
+           can_clone = TRUE;
 
-    if (belt_dir == MV_LEFT)
-      graphic_info[graphic].anim_mode &= ~ANIM_REVERSE;
-    else
-      graphic_info[graphic].anim_mode |=  ANIM_REVERSE;
-  }
+           break;
+         }
+       }
+      }
 
-#if 1
-  SCAN_PLAYFIELD(xx, yy)
-#else
-  for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++)
-#endif
-  {
-    int element = Feld[xx][yy];
+      if (can_clone)           // randomly find a direction to move
+      {
+       can_clone = FALSE;
 
-    if (IS_BELT_SWITCH(element))
-    {
-      int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
+       start_pos = check_pos[RND(8)];
+       check_order = (RND(2) ? -1 : +1);
 
-      if (e_belt_nr == belt_nr)
-      {
-       Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
-       DrawLevelField(xx, yy);
-      }
-    }
-    else if (IS_BELT(element) && belt_dir != MV_NONE)
-    {
-      int e_belt_nr = getBeltNrFromBeltElement(element);
+       for (i = 0; i < 8; i++)
+       {
+         int pos_raw = start_pos + i * check_order;
+         int pos = (pos_raw + 8) % 8;
+         int newx = x + check_xy[pos].dx;
+         int newy = y + check_xy[pos].dy;
+         int new_move_dir = check_xy[pos].dir;
 
-      if (e_belt_nr == belt_nr)
-      {
-       int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
+         if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+         {
+           MovDir[x][y] = new_move_dir;
+           MovDelay[x][y] = level.android_clone_time * 8 + 1;
 
-       Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
-       DrawLevelField(xx, yy);
+           can_clone = TRUE;
+
+           break;
+         }
+       }
       }
-    }
-    else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
-    {
-      int e_belt_nr = getBeltNrFromBeltActiveElement(element);
 
-      if (e_belt_nr == belt_nr)
+      if (can_clone)           // cloning and moving successful
+       return;
+
+      // cannot clone -- try to move towards player
+
+      start_pos = check_pos[MovDir[x][y] & 0x0f];
+      check_order = (RND(2) ? -1 : +1);
+
+      for (i = 0; i < 3; i++)
       {
-       int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
+       // first check start_pos, then previous/next or (next/previous) pos
+       int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
+       int pos = (pos_raw + 8) % 8;
+       int newx = x + check_xy[pos].dx;
+       int newy = y + check_xy[pos].dy;
+       int new_move_dir = check_xy[pos].dir;
 
-       Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
-       DrawLevelField(xx, yy);
+       if (IS_PLAYER(newx, newy))
+         break;
+
+       if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+       {
+         MovDir[x][y] = new_move_dir;
+         MovDelay[x][y] = level.android_move_time * 8 + 1;
+
+         break;
+       }
       }
     }
   }
-}
-
-static void ToggleSwitchgateSwitch(int x, int y)
-{
-  int xx, yy;
-
-  game.switchgate_pos = !game.switchgate_pos;
-
-#if 1
-  SCAN_PLAYFIELD(xx, yy)
-#else
-  for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++)
-#endif
+  else if (move_pattern == MV_TURNING_LEFT ||
+          move_pattern == MV_TURNING_RIGHT ||
+          move_pattern == MV_TURNING_LEFT_RIGHT ||
+          move_pattern == MV_TURNING_RIGHT_LEFT ||
+          move_pattern == MV_TURNING_RANDOM ||
+          move_pattern == MV_ALL_DIRECTIONS)
   {
-    int element = Feld[xx][yy];
+    boolean can_turn_left =
+      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right =
+      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
 
-    if (element == EL_SWITCHGATE_SWITCH_UP ||
-       element == EL_SWITCHGATE_SWITCH_DOWN)
-    {
-      Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
-      DrawLevelField(xx, yy);
-    }
-    else if (element == EL_SWITCHGATE_OPEN ||
-            element == EL_SWITCHGATE_OPENING)
-    {
-      Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
+    if (element_info[element].move_stepsize == 0)      // "not moving"
+      return;
 
-      PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
-    }
-    else if (element == EL_SWITCHGATE_CLOSED ||
-            element == EL_SWITCHGATE_CLOSING)
-    {
-      Feld[xx][yy] = EL_SWITCHGATE_OPENING;
+    if (move_pattern == MV_TURNING_LEFT)
+      MovDir[x][y] = left_dir;
+    else if (move_pattern == MV_TURNING_RIGHT)
+      MovDir[x][y] = right_dir;
+    else if (move_pattern == MV_TURNING_LEFT_RIGHT)
+      MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
+    else if (move_pattern == MV_TURNING_RIGHT_LEFT)
+      MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
+    else if (move_pattern == MV_TURNING_RANDOM)
+      MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
+                     can_turn_right && !can_turn_left ? right_dir :
+                     RND(2) ? left_dir : right_dir);
+    else if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+    else if (can_turn_left)
+      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+    else if (can_turn_right)
+      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+    else
+      MovDir[x][y] = back_dir;
 
-      PlayLevelSoundAction(xx, yy, ACTION_OPENING);
-    }
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
   }
-}
-
-static int getInvisibleActiveFromInvisibleElement(int element)
-{
-  return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
-         element == EL_INVISIBLE_WALL      ? EL_INVISIBLE_WALL_ACTIVE :
-         element == EL_INVISIBLE_SAND      ? EL_INVISIBLE_SAND_ACTIVE :
-         element);
-}
+  else if (move_pattern == MV_HORIZONTAL ||
+          move_pattern == MV_VERTICAL)
+  {
+    if (move_pattern & old_move_dir)
+      MovDir[x][y] = back_dir;
+    else if (move_pattern == MV_HORIZONTAL)
+      MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
+    else if (move_pattern == MV_VERTICAL)
+      MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
 
-static int getInvisibleFromInvisibleActiveElement(int element)
-{
-  return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
-         element == EL_INVISIBLE_WALL_ACTIVE      ? EL_INVISIBLE_WALL :
-         element == EL_INVISIBLE_SAND_ACTIVE      ? EL_INVISIBLE_SAND :
-         element);
-}
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (move_pattern & MV_ANY_DIRECTION)
+  {
+    MovDir[x][y] = move_pattern;
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (move_pattern & MV_WIND_DIRECTION)
+  {
+    MovDir[x][y] = game.wind_direction;
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (move_pattern == MV_ALONG_LEFT_SIDE)
+  {
+    if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
+      MovDir[x][y] = left_dir;
+    else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+      MovDir[x][y] = right_dir;
 
-static void RedrawAllLightSwitchesAndInvisibleElements()
-{
-  int x, y;
+    if (MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (move_pattern == MV_ALONG_RIGHT_SIDE)
+  {
+    if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
+      MovDir[x][y] = right_dir;
+    else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+      MovDir[x][y] = left_dir;
 
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
+    if (MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (move_pattern == MV_TOWARDS_PLAYER ||
+          move_pattern == MV_AWAY_FROM_PLAYER)
   {
-    int element = Feld[x][y];
+    int attr_x = -1, attr_y = -1;
+    int newx, newy;
+    boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
 
-    if (element == EL_LIGHT_SWITCH &&
-       game.light_time_left > 0)
-    {
-      Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
-      DrawLevelField(x, y);
-    }
-    else if (element == EL_LIGHT_SWITCH_ACTIVE &&
-            game.light_time_left == 0)
-    {
-      Feld[x][y] = EL_LIGHT_SWITCH;
-      DrawLevelField(x, y);
-    }
-    else if (element == EL_EMC_DRIPPER &&
-            game.light_time_left > 0)
-    {
-      Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
-      DrawLevelField(x, y);
-    }
-    else if (element == EL_EMC_DRIPPER_ACTIVE &&
-            game.light_time_left == 0)
+    if (game.all_players_gone)
     {
-      Feld[x][y] = EL_EMC_DRIPPER;
-      DrawLevelField(x, y);
+      attr_x = game.exit_x;
+      attr_y = game.exit_y;
     }
-    else if (element == EL_INVISIBLE_STEELWALL ||
-            element == EL_INVISIBLE_WALL ||
-            element == EL_INVISIBLE_SAND)
+    else
     {
-      if (game.light_time_left > 0)
-       Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+      int i;
 
-      DrawLevelField(x, y);
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int jx = player->jx, jy = player->jy;
 
-      /* uncrumble neighbour fields, if needed */
-      if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
+       if (!player->active)
+         continue;
+
+       if (attr_x == -1 ||
+           ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
+       {
+         attr_x = jx;
+         attr_y = jy;
+       }
+      }
     }
-    else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
-            element == EL_INVISIBLE_WALL_ACTIVE ||
-            element == EL_INVISIBLE_SAND_ACTIVE)
+
+    MovDir[x][y] = MV_NONE;
+    if (attr_x < x)
+      MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
+    else if (attr_x > x)
+      MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
+    if (attr_y < y)
+      MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
+    else if (attr_y > y)
+      MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
+
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+
+    if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
     {
-      if (game.light_time_left == 0)
-       Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+      boolean first_horiz = RND(2);
+      int new_move_dir = MovDir[x][y];
+
+      if (element_info[element].move_stepsize == 0)    // "not moving"
+      {
+       first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
+       MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
 
-      DrawLevelField(x, y);
+       return;
+      }
 
-      /* re-crumble neighbour fields, if needed */
-      if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
-    }
-  }
-}
+      MovDir[x][y] =
+       new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+      Moving2Blocked(x, y, &newx, &newy);
 
-static void RedrawAllInvisibleElementsForLenses()
-{
-  int x, y;
+      if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
+       return;
 
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
+      MovDir[x][y] =
+       new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+      Moving2Blocked(x, y, &newx, &newy);
+
+      if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
+       return;
+
+      MovDir[x][y] = old_move_dir;
+    }
+  }
+  else if (move_pattern == MV_WHEN_PUSHED ||
+          move_pattern == MV_WHEN_DROPPED)
   {
-    int element = Feld[x][y];
+    if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+      MovDir[x][y] = MV_NONE;
 
-    if (element == EL_EMC_DRIPPER &&
-       game.lenses_time_left > 0)
+    MovDelay[x][y] = 0;
+  }
+  else if (move_pattern & MV_MAZE_RUNNER_STYLE)
+  {
+    static int test_xy[7][2] =
     {
-      Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
-      DrawLevelField(x, y);
-    }
-    else if (element == EL_EMC_DRIPPER_ACTIVE &&
-            game.lenses_time_left == 0)
+      { 0, -1 },
+      { -1, 0 },
+      { +1, 0 },
+      { 0, +1 },
+      { 0, -1 },
+      { -1, 0 },
+      { +1, 0 },
+    };
+    static int test_dir[7] =
     {
-      Feld[x][y] = EL_EMC_DRIPPER;
-      DrawLevelField(x, y);
-    }
-    else if (element == EL_INVISIBLE_STEELWALL ||
-            element == EL_INVISIBLE_WALL ||
-            element == EL_INVISIBLE_SAND)
+      MV_UP,
+      MV_LEFT,
+      MV_RIGHT,
+      MV_DOWN,
+      MV_UP,
+      MV_LEFT,
+      MV_RIGHT,
+    };
+    boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
+    int move_preference = -1000000;    // start with very low preference
+    int new_move_dir = MV_NONE;
+    int start_test = RND(4);
+    int i;
+
+    for (i = 0; i < NUM_DIRECTIONS; i++)
     {
-      if (game.lenses_time_left > 0)
-       Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+      int move_dir = test_dir[start_test + i];
+      int move_dir_preference;
 
-      DrawLevelField(x, y);
+      xx = x + test_xy[start_test + i][0];
+      yy = y + test_xy[start_test + i][1];
 
-      /* uncrumble neighbour fields, if needed */
-      if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
-    }
-    else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
-            element == EL_INVISIBLE_WALL_ACTIVE ||
-            element == EL_INVISIBLE_SAND_ACTIVE)
-    {
-      if (game.lenses_time_left == 0)
-       Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+      if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
+         (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
+      {
+       new_move_dir = move_dir;
+
+       break;
+      }
 
-      DrawLevelField(x, y);
+      if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
+       continue;
 
-      /* re-crumble neighbour fields, if needed */
-      if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
+      move_dir_preference = -1 * RunnerVisit[xx][yy];
+      if (hunter_mode && PlayerVisit[xx][yy] > 0)
+       move_dir_preference = PlayerVisit[xx][yy];
+
+      if (move_dir_preference > move_preference)
+      {
+       // prefer field that has not been visited for the longest time
+       move_preference = move_dir_preference;
+       new_move_dir = move_dir;
+      }
+      else if (move_dir_preference == move_preference &&
+              move_dir == old_move_dir)
+      {
+       // prefer last direction when all directions are preferred equally
+       move_preference = move_dir_preference;
+       new_move_dir = move_dir;
+      }
     }
+
+    MovDir[x][y] = new_move_dir;
+    if (old_move_dir != new_move_dir)
+      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
   }
 }
 
-static void RedrawAllInvisibleElementsForMagnifier()
+static void TurnRound(int x, int y)
 {
-  int x, y;
+  int direction = MovDir[x][y];
 
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
-  {
-    int element = Feld[x][y];
+  TurnRoundExt(x, y);
 
-    if (element == EL_EMC_FAKE_GRASS &&
-       game.magnify_time_left > 0)
-    {
-      Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
-      DrawLevelField(x, y);
-    }
-    else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
-            game.magnify_time_left == 0)
-    {
-      Feld[x][y] = EL_EMC_FAKE_GRASS;
-      DrawLevelField(x, y);
-    }
-    else if (IS_GATE_GRAY(element) &&
-            game.magnify_time_left > 0)
-    {
-      Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
-                   element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
-                   IS_EM_GATE_GRAY(element) ?
-                   element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
-                   IS_EMC_GATE_GRAY(element) ?
-                   element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
-                   element);
-      DrawLevelField(x, y);
-    }
-    else if (IS_GATE_GRAY_ACTIVE(element) &&
-            game.magnify_time_left == 0)
-    {
-      Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
-                   element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
-                   IS_EM_GATE_GRAY_ACTIVE(element) ?
-                   element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
-                   IS_EMC_GATE_GRAY_ACTIVE(element) ?
-                   element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
-                   element);
-      DrawLevelField(x, y);
-    }
-  }
-}
+  GfxDir[x][y] = MovDir[x][y];
 
-static void ToggleLightSwitch(int x, int y)
-{
-  int element = Feld[x][y];
+  if (direction != MovDir[x][y])
+    GfxFrame[x][y] = 0;
 
-  game.light_time_left =
-    (element == EL_LIGHT_SWITCH ?
-     level.time_light * FRAMES_PER_SECOND : 0);
+  if (MovDelay[x][y])
+    GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
 
-  RedrawAllLightSwitchesAndInvisibleElements();
+  ResetGfxFrame(x, y);
 }
 
-static void ActivateTimegateSwitch(int x, int y)
+static boolean JustBeingPushed(int x, int y)
 {
-  int xx, yy;
-
-  game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
+  int i;
 
-#if 1
-  SCAN_PLAYFIELD(xx, yy)
-#else
-  for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++)
-#endif
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    int element = Feld[xx][yy];
-
-    if (element == EL_TIMEGATE_CLOSED ||
-       element == EL_TIMEGATE_CLOSING)
-    {
-      Feld[xx][yy] = EL_TIMEGATE_OPENING;
-      PlayLevelSound(xx, yy, SND_TIMEGATE_OPENING);
-    }
+    struct PlayerInfo *player = &stored_player[i];
 
-    /*
-    else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
+    if (player->active && player->is_pushing && player->MovPos)
     {
-      Feld[xx][yy] = EL_TIMEGATE_SWITCH;
-      DrawLevelField(xx, yy);
-    }
-    */
+      int next_jx = player->jx + (player->jx - player->last_jx);
+      int next_jy = player->jy + (player->jy - player->last_jy);
 
+      if (x == next_jx && y == next_jy)
+       return TRUE;
+    }
   }
 
-  Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE;
+  return FALSE;
 }
 
-void Impact(int x, int y)
+static void StartMoving(int x, int y)
 {
-  boolean last_line = (y == lev_fieldy - 1);
-  boolean object_hit = FALSE;
-  boolean impact = (last_line || object_hit);
+  boolean started_moving = FALSE;      // some elements can fall _and_ move
   int element = Feld[x][y];
-  int smashed = EL_STEELWALL;
 
-  if (!last_line)      /* check if element below was hit */
-  {
-    if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
-      return;
+  if (Stop[x][y])
+    return;
 
-    object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
-                                        MovDir[x][y + 1] != MV_DOWN ||
-                                        MovPos[x][y + 1] <= TILEY / 2));
+  if (MovDelay[x][y] == 0)
+    GfxAction[x][y] = ACTION_DEFAULT;
 
-    /* do not smash moving elements that left the smashed field in time */
-    if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
-       ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
-      object_hit = FALSE;
+  if (CAN_FALL(element) && y < lev_fieldy - 1)
+  {
+    if ((x > 0              && IS_PLAYER(x - 1, y)) ||
+       (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
+      if (JustBeingPushed(x, y))
+       return;
 
-#if USE_QUICKSAND_IMPACT_BUGFIX
-    if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
+    if (element == EL_QUICKSAND_FULL)
     {
-      RemoveMovingField(x, y + 1);
-      Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
-      Feld[x][y + 2] = EL_ROCK;
-      DrawLevelField(x, y + 2);
+      if (IS_FREE(x, y + 1))
+      {
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
 
-      object_hit = TRUE;
-    }
+       Feld[x][y] = EL_QUICKSAND_EMPTYING;
+#if USE_QUICKSAND_BD_ROCK_BUGFIX
+       if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
+         Store[x][y] = EL_ROCK;
+#else
+       Store[x][y] = EL_ROCK;
 #endif
 
-    if (object_hit)
-      smashed = MovingOrBlocked2Element(x, y + 1);
+       PlayLevelSoundAction(x, y, ACTION_EMPTYING);
+      }
+      else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
+      {
+       if (!MovDelay[x][y])
+       {
+         MovDelay[x][y] = TILEY + 1;
 
-    impact = (last_line || object_hit);
-  }
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
 
-  if (!last_line && smashed == EL_ACID)        /* element falls into acid */
-  {
-    SplashAcid(x, y + 1);
-    return;
-  }
+       if (MovDelay[x][y])
+       {
+         DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
 
-  /* !!! not sufficient for all cases -- see EL_PEARL below !!! */
-  /* only reset graphic animation if graphic really changes after impact */
-  if (impact &&
-      el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
-  {
-    ResetGfxAnimation(x, y);
-    DrawLevelField(x, y);
-  }
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
 
-  if (impact && CAN_EXPLODE_IMPACT(element))
-  {
-    Bang(x, y);
-    return;
-  }
-  else if (impact && element == EL_PEARL)
-  {
-    ResetGfxAnimation(x, y);
+       Feld[x][y] = EL_QUICKSAND_EMPTY;
+       Feld[x][y + 1] = EL_QUICKSAND_FULL;
+       Store[x][y + 1] = Store[x][y];
+       Store[x][y] = 0;
 
-    Feld[x][y] = EL_PEARL_BREAKING;
-    PlayLevelSound(x, y, SND_PEARL_BREAKING);
-    return;
-  }
-  else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
-  {
-    PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+       PlayLevelSoundAction(x, y, ACTION_FILLING);
+      }
+      else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
+      {
+       if (!MovDelay[x][y])
+       {
+         MovDelay[x][y] = TILEY + 1;
 
-    return;
-  }
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
 
-  if (impact && element == EL_AMOEBA_DROP)
-  {
-    if (object_hit && IS_PLAYER(x, y + 1))
-      KillPlayerUnlessEnemyProtected(x, y + 1);
-    else if (object_hit && smashed == EL_PENGUIN)
-      Bang(x, y + 1);
-    else
-    {
-      Feld[x][y] = EL_AMOEBA_GROWING;
-      Store[x][y] = EL_AMOEBA_WET;
+       if (MovDelay[x][y])
+       {
+         DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
 
-      ResetRandomAnimationValue(x, y);
-    }
-    return;
-  }
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
 
-  if (object_hit)              /* check which object was hit */
-  {
-    if (CAN_PASS_MAGIC_WALL(element) && 
-       (smashed == EL_MAGIC_WALL ||
-        smashed == EL_BD_MAGIC_WALL))
+       Feld[x][y] = EL_QUICKSAND_EMPTY;
+       Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
+       Store[x][y + 1] = Store[x][y];
+       Store[x][y] = 0;
+
+       PlayLevelSoundAction(x, y, ACTION_FILLING);
+      }
+    }
+    else if (element == EL_QUICKSAND_FAST_FULL)
     {
-      int xx, yy;
-      int activated_magic_wall =
-       (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
-        EL_BD_MAGIC_WALL_ACTIVE);
+      if (IS_FREE(x, y + 1))
+      {
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
 
-      /* activate magic wall / mill */
-#if 1
-      SCAN_PLAYFIELD(xx, yy)
+       Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
+#if USE_QUICKSAND_BD_ROCK_BUGFIX
+       if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
+         Store[x][y] = EL_ROCK;
 #else
-      for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++)
+       Store[x][y] = EL_ROCK;
 #endif
-       if (Feld[xx][yy] == smashed)
-         Feld[xx][yy] = activated_magic_wall;
 
-      game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
-      game.magic_wall_active = TRUE;
+       PlayLevelSoundAction(x, y, ACTION_EMPTYING);
+      }
+      else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
+      {
+       if (!MovDelay[x][y])
+       {
+         MovDelay[x][y] = TILEY + 1;
 
-      PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
-                           SND_MAGIC_WALL_ACTIVATING :
-                           SND_BD_MAGIC_WALL_ACTIVATING));
-    }
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
 
-    if (IS_PLAYER(x, y + 1))
-    {
-      if (CAN_SMASH_PLAYER(element))
+       if (MovDelay[x][y])
+       {
+         DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
+
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
+
+       Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
+       Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
+       Store[x][y + 1] = Store[x][y];
+       Store[x][y] = 0;
+
+       PlayLevelSoundAction(x, y, ACTION_FILLING);
+      }
+      else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
       {
-       KillPlayerUnlessEnemyProtected(x, y + 1);
-       return;
+       if (!MovDelay[x][y])
+       {
+         MovDelay[x][y] = TILEY + 1;
+
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
+
+       if (MovDelay[x][y])
+       {
+         DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
+
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
+
+       Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
+       Feld[x][y + 1] = EL_QUICKSAND_FULL;
+       Store[x][y + 1] = Store[x][y];
+       Store[x][y] = 0;
+
+       PlayLevelSoundAction(x, y, ACTION_FILLING);
       }
     }
-    else if (smashed == EL_PENGUIN)
+    else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
+            Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
     {
-      if (CAN_SMASH_PLAYER(element))
-      {
-       Bang(x, y + 1);
-       return;
-      }
+      InitMovingField(x, y, MV_DOWN);
+      started_moving = TRUE;
+
+      Feld[x][y] = EL_QUICKSAND_FILLING;
+      Store[x][y] = element;
+
+      PlayLevelSoundAction(x, y, ACTION_FILLING);
     }
-    else if (element == EL_BD_DIAMOND)
+    else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
+            Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
     {
-      if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
-      {
-       Bang(x, y + 1);
-       return;
-      }
+      InitMovingField(x, y, MV_DOWN);
+      started_moving = TRUE;
+
+      Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
+      Store[x][y] = element;
+
+      PlayLevelSoundAction(x, y, ACTION_FILLING);
     }
-    else if (((element == EL_SP_INFOTRON ||
-              element == EL_SP_ZONK) &&
-             (smashed == EL_SP_SNIKSNAK ||
-              smashed == EL_SP_ELECTRON ||
-              smashed == EL_SP_DISK_ORANGE)) ||
-            (element == EL_SP_INFOTRON &&
-             smashed == EL_SP_DISK_YELLOW))
+    else if (element == EL_MAGIC_WALL_FULL)
     {
-      Bang(x, y + 1);
-      return;
+      if (IS_FREE(x, y + 1))
+      {
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
+
+       Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
+       Store[x][y] = EL_CHANGED(Store[x][y]);
+      }
+      else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
+      {
+       if (!MovDelay[x][y])
+         MovDelay[x][y] = TILEY / 4 + 1;
+
+       if (MovDelay[x][y])
+       {
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
+
+       Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
+       Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
+       Store[x][y + 1] = EL_CHANGED(Store[x][y]);
+       Store[x][y] = 0;
+      }
     }
-    else if (CAN_SMASH_EVERYTHING(element))
+    else if (element == EL_BD_MAGIC_WALL_FULL)
     {
-      if (IS_CLASSIC_ENEMY(smashed) ||
-         CAN_EXPLODE_SMASHED(smashed))
+      if (IS_FREE(x, y + 1))
       {
-       Bang(x, y + 1);
-       return;
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
+
+       Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
+       Store[x][y] = EL_CHANGED_BD(Store[x][y]);
       }
-      else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
+      else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
       {
-       if (smashed == EL_LAMP ||
-           smashed == EL_LAMP_ACTIVE)
-       {
-         Bang(x, y + 1);
-         return;
-       }
-       else if (smashed == EL_NUT)
-       {
-         Feld[x][y + 1] = EL_NUT_BREAKING;
-         PlayLevelSound(x, y, SND_NUT_BREAKING);
-         RaiseScoreElement(EL_NUT);
-         return;
-       }
-       else if (smashed == EL_PEARL)
-       {
-         ResetGfxAnimation(x, y);
+       if (!MovDelay[x][y])
+         MovDelay[x][y] = TILEY / 4 + 1;
 
-         Feld[x][y + 1] = EL_PEARL_BREAKING;
-         PlayLevelSound(x, y, SND_PEARL_BREAKING);
-         return;
-       }
-       else if (smashed == EL_DIAMOND)
-       {
-         Feld[x][y + 1] = EL_DIAMOND_BREAKING;
-         PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
-         return;
-       }
-       else if (IS_BELT_SWITCH(smashed))
-       {
-         ToggleBeltSwitch(x, y + 1);
-       }
-       else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
-                smashed == EL_SWITCHGATE_SWITCH_DOWN)
-       {
-         ToggleSwitchgateSwitch(x, y + 1);
-       }
-       else if (smashed == EL_LIGHT_SWITCH ||
-                smashed == EL_LIGHT_SWITCH_ACTIVE)
+       if (MovDelay[x][y])
        {
-         ToggleLightSwitch(x, y + 1);
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
        }
-       else
-       {
-#if 0
-         TestIfElementSmashesCustomElement(x, y, MV_DOWN);
-#endif
 
-         CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
+       Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
+       Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
+       Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
+       Store[x][y] = 0;
+      }
+    }
+    else if (element == EL_DC_MAGIC_WALL_FULL)
+    {
+      if (IS_FREE(x, y + 1))
+      {
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
 
-         CheckElementChangeBySide(x, y + 1, smashed, element,
-                                  CE_SWITCHED, CH_SIDE_TOP);
-         CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
-                                           CH_SIDE_TOP);
-       }
+       Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
+       Store[x][y] = EL_CHANGED_DC(Store[x][y]);
       }
-      else
+      else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
       {
-       CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
+       if (!MovDelay[x][y])
+         MovDelay[x][y] = TILEY / 4 + 1;
+
+       if (MovDelay[x][y])
+       {
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
+
+       Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
+       Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
+       Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
+       Store[x][y] = 0;
       }
     }
-  }
+    else if ((CAN_PASS_MAGIC_WALL(element) &&
+             (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
+              Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
+            (CAN_PASS_DC_MAGIC_WALL(element) &&
+             (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
 
-  /* play sound of magic wall / mill */
-  if (!last_line &&
-      (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
-       Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
-  {
-    if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
-      PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
-    else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
-      PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
+    {
+      InitMovingField(x, y, MV_DOWN);
+      started_moving = TRUE;
 
-    return;
-  }
+      Feld[x][y] =
+       (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
+        Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
+        EL_DC_MAGIC_WALL_FILLING);
+      Store[x][y] = element;
+    }
+    else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
+    {
+      SplashAcid(x, y + 1);
 
-  /* play sound of object that hits the ground */
-  if (last_line || object_hit)
-    PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
-}
+      InitMovingField(x, y, MV_DOWN);
+      started_moving = TRUE;
 
-inline static void TurnRoundExt(int x, int y)
-{
-  static struct
-  {
-    int dx, dy;
-  } move_xy[] =
-  {
-    {  0,  0 },
-    { -1,  0 },
-    { +1,  0 },
-    {  0,  0 },
-    {  0, -1 },
-    {  0,  0 }, { 0, 0 }, { 0, 0 },
-    {  0, +1 }
-  };
-  static struct
-  {
-    int left, right, back;
-  } turn[] =
-  {
-    { 0,       0,              0        },
-    { MV_DOWN, MV_UP,          MV_RIGHT },
-    { MV_UP,   MV_DOWN,        MV_LEFT  },
-    { 0,       0,              0        },
-    { MV_LEFT, MV_RIGHT,       MV_DOWN  },
-    { 0,       0,              0        },
-    { 0,       0,              0        },
-    { 0,       0,              0        },
-    { MV_RIGHT,        MV_LEFT,        MV_UP    }
-  };
+      Store[x][y] = EL_ACID;
+    }
+    else if (
+            (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+             CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
+            (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
+             CAN_FALL(element) && WasJustFalling[x][y] &&
+             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
 
-  int element = Feld[x][y];
-  int move_pattern = element_info[element].move_pattern;
+            (game.engine_version < VERSION_IDENT(2,2,0,7) &&
+             CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
+             (Feld[x][y + 1] == EL_BLOCKED)))
+    {
+      /* this is needed for a special case not covered by calling "Impact()"
+        from "ContinueMoving()": if an element moves to a tile directly below
+        another element which was just falling on that tile (which was empty
+        in the previous frame), the falling element above would just stop
+        instead of smashing the element below (in previous version, the above
+        element was just checked for "moving" instead of "falling", resulting
+        in incorrect smashes caused by horizontal movement of the above
+        element; also, the case of the player being the element to smash was
+        simply not covered here... :-/ ) */
 
-  int old_move_dir = MovDir[x][y];
-  int left_dir  = turn[old_move_dir].left;
-  int right_dir = turn[old_move_dir].right;
-  int back_dir  = turn[old_move_dir].back;
+      CheckCollision[x][y] = 0;
+      CheckImpact[x][y] = 0;
 
-  int left_dx  = move_xy[left_dir].dx,     left_dy  = move_xy[left_dir].dy;
-  int right_dx = move_xy[right_dir].dx,    right_dy = move_xy[right_dir].dy;
-  int move_dx  = move_xy[old_move_dir].dx, move_dy  = move_xy[old_move_dir].dy;
-  int back_dx  = move_xy[back_dir].dx,     back_dy  = move_xy[back_dir].dy;
+      Impact(x, y);
+    }
+    else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
+    {
+      if (MovDir[x][y] == MV_NONE)
+      {
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
+      }
+    }
+    else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
+    {
+      if (WasJustFalling[x][y])        // prevent animation from being restarted
+       MovDir[x][y] = MV_DOWN;
 
-  int left_x  = x + left_dx,  left_y  = y + left_dy;
-  int right_x = x + right_dx, right_y = y + right_dy;
-  int move_x  = x + move_dx,  move_y  = y + move_dy;
+      InitMovingField(x, y, MV_DOWN);
+      started_moving = TRUE;
+    }
+    else if (element == EL_AMOEBA_DROP)
+    {
+      Feld[x][y] = EL_AMOEBA_GROWING;
+      Store[x][y] = EL_AMOEBA_WET;
+    }
+    else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
+             (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
+            !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
+            element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
+    {
+      boolean can_fall_left  = (x > 0 && IS_FREE(x - 1, y) &&
+                               (IS_FREE(x - 1, y + 1) ||
+                                Feld[x - 1][y + 1] == EL_ACID));
+      boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
+                               (IS_FREE(x + 1, y + 1) ||
+                                Feld[x + 1][y + 1] == EL_ACID));
+      boolean can_fall_any  = (can_fall_left || can_fall_right);
+      boolean can_fall_both = (can_fall_left && can_fall_right);
+      int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
 
-  int xx, yy;
+      if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
+      {
+       if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
+         can_fall_right = FALSE;
+       else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
+         can_fall_left = FALSE;
+       else if (slippery_type == SLIPPERY_ONLY_LEFT)
+         can_fall_right = FALSE;
+       else if (slippery_type == SLIPPERY_ONLY_RIGHT)
+         can_fall_left = FALSE;
 
-  if (element == EL_BUG || element == EL_BD_BUTTERFLY)
-  {
-    TestIfBadThingTouchesOtherBadThing(x, y);
+       can_fall_any  = (can_fall_left || can_fall_right);
+       can_fall_both = FALSE;
+      }
 
-    if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
-      MovDir[x][y] = right_dir;
-    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = left_dir;
+      if (can_fall_both)
+      {
+       if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
+         can_fall_right = FALSE;       // slip down on left side
+       else
+         can_fall_left = !(can_fall_right = RND(2));
 
-    if (element == EL_BUG && MovDir[x][y] != old_move_dir)
-      MovDelay[x][y] = 9;
-    else if (element == EL_BD_BUTTERFLY)     /* && MovDir[x][y] == left_dir) */
-      MovDelay[x][y] = 1;
-  }
-  else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
-  {
-    TestIfBadThingTouchesOtherBadThing(x, y);
+       can_fall_both = FALSE;
+      }
+
+      if (can_fall_any)
+      {
+       // if not determined otherwise, prefer left side for slipping down
+       InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
+       started_moving = TRUE;
+      }
+    }
+    else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
+    {
+      boolean left_is_free  = (x > 0 && IS_FREE(x - 1, y));
+      boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
+      int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
+      int belt_dir = game.belt_dir[belt_nr];
 
-    if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
-      MovDir[x][y] = left_dir;
-    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = right_dir;
+      if ((belt_dir == MV_LEFT  && left_is_free) ||
+         (belt_dir == MV_RIGHT && right_is_free))
+      {
+       int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
 
-    if (element == EL_SPACESHIP        && MovDir[x][y] != old_move_dir)
-      MovDelay[x][y] = 9;
-    else if (element == EL_BD_FIREFLY)     /* && MovDir[x][y] == right_dir) */
-      MovDelay[x][y] = 1;
-  }
-  else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
-  {
-    TestIfBadThingTouchesOtherBadThing(x, y);
+       InitMovingField(x, y, belt_dir);
+       started_moving = TRUE;
 
-    if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
-      MovDir[x][y] = left_dir;
-    else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
-      MovDir[x][y] = right_dir;
+       Pushed[x][y] = TRUE;
+       Pushed[nextx][y] = TRUE;
 
-    if (MovDir[x][y] != old_move_dir)
-      MovDelay[x][y] = 9;
+       GfxAction[x][y] = ACTION_DEFAULT;
+      }
+      else
+      {
+       MovDir[x][y] = 0;       // if element was moving, stop it
+      }
+    }
   }
-  else if (element == EL_YAMYAM)
-  {
-    boolean can_turn_left  = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
-    boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
-
-    if (can_turn_left && can_turn_right)
-      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
-    else if (can_turn_left)
-      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
-    else if (can_turn_right)
-      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
-    else
-      MovDir[x][y] = back_dir;
 
-    MovDelay[x][y] = 16 + 16 * RND(3);
-  }
-  else if (element == EL_DARK_YAMYAM)
+  // not "else if" because of elements that can fall and move (EL_SPRING)
+  if (CAN_MOVE(element) && !started_moving)
   {
-    boolean can_turn_left  = DARK_YAMYAM_CAN_ENTER_FIELD(element,
-                                                        left_x, left_y);
-    boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
-                                                        right_x, right_y);
-
-    if (can_turn_left && can_turn_right)
-      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
-    else if (can_turn_left)
-      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
-    else if (can_turn_right)
-      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
-    else
-      MovDir[x][y] = back_dir;
+    int move_pattern = element_info[element].move_pattern;
+    int newx, newy;
 
-    MovDelay[x][y] = 16 + 16 * RND(3);
-  }
-  else if (element == EL_PACMAN)
-  {
-    boolean can_turn_left  = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
-    boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
+    Moving2Blocked(x, y, &newx, &newy);
 
-    if (can_turn_left && can_turn_right)
-      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
-    else if (can_turn_left)
-      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
-    else if (can_turn_right)
-      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
-    else
-      MovDir[x][y] = back_dir;
+    if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
+      return;
 
-    MovDelay[x][y] = 6 + RND(40);
-  }
-  else if (element == EL_PIG)
-  {
-    boolean can_turn_left  = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
-    boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
-    boolean can_move_on    = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
-    boolean should_turn_left, should_turn_right, should_move_on;
-    int rnd_value = 24;
-    int rnd = RND(rnd_value);
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+       CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+    {
+      WasJustMoving[x][y] = 0;
+      CheckCollision[x][y] = 0;
 
-    should_turn_left = (can_turn_left &&
-                       (!can_move_on ||
-                        IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
-                                                  y + back_dy + left_dy)));
-    should_turn_right = (can_turn_right &&
-                        (!can_move_on ||
-                         IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
-                                                   y + back_dy + right_dy)));
-    should_move_on = (can_move_on &&
-                     (!can_turn_left ||
-                      !can_turn_right ||
-                      IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
-                                                y + move_dy + left_dy) ||
-                      IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
-                                                y + move_dy + right_dy)));
+      TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
 
-    if (should_turn_left || should_turn_right || should_move_on)
-    {
-      if (should_turn_left && should_turn_right && should_move_on)
-       MovDir[x][y] = (rnd < rnd_value / 3     ? left_dir :
-                       rnd < 2 * rnd_value / 3 ? right_dir :
-                       old_move_dir);
-      else if (should_turn_left && should_turn_right)
-       MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
-      else if (should_turn_left && should_move_on)
-       MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
-      else if (should_turn_right && should_move_on)
-       MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
-      else if (should_turn_left)
-       MovDir[x][y] = left_dir;
-      else if (should_turn_right)
-       MovDir[x][y] = right_dir;
-      else if (should_move_on)
-       MovDir[x][y] = old_move_dir;
+      if (Feld[x][y] != element)       // element has changed
+       return;
     }
-    else if (can_move_on && rnd > rnd_value / 8)
-      MovDir[x][y] = old_move_dir;
-    else if (can_turn_left && can_turn_right)
-      MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
-    else if (can_turn_left && rnd > rnd_value / 8)
-      MovDir[x][y] = left_dir;
-    else if (can_turn_right && rnd > rnd_value/8)
-      MovDir[x][y] = right_dir;
-    else
-      MovDir[x][y] = back_dir;
-
-    xx = x + move_xy[MovDir[x][y]].dx;
-    yy = y + move_xy[MovDir[x][y]].dy;
 
-    if (!IN_LEV_FIELD(xx, yy) ||
-        (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
-      MovDir[x][y] = old_move_dir;
+    if (!MovDelay[x][y])       // start new movement phase
+    {
+      // all objects that can change their move direction after each step
+      // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
 
-    MovDelay[x][y] = 0;
-  }
-  else if (element == EL_DRAGON)
-  {
-    boolean can_turn_left  = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
-    boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
-    boolean can_move_on    = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
-    int rnd_value = 24;
-    int rnd = RND(rnd_value);
+      if (element != EL_YAMYAM &&
+         element != EL_DARK_YAMYAM &&
+         element != EL_PACMAN &&
+         !(move_pattern & MV_ANY_DIRECTION) &&
+         move_pattern != MV_TURNING_LEFT &&
+         move_pattern != MV_TURNING_RIGHT &&
+         move_pattern != MV_TURNING_LEFT_RIGHT &&
+         move_pattern != MV_TURNING_RIGHT_LEFT &&
+         move_pattern != MV_TURNING_RANDOM)
+      {
+       TurnRound(x, y);
 
-    if (can_move_on && rnd > rnd_value / 8)
-      MovDir[x][y] = old_move_dir;
-    else if (can_turn_left && can_turn_right)
-      MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
-    else if (can_turn_left && rnd > rnd_value / 8)
-      MovDir[x][y] = left_dir;
-    else if (can_turn_right && rnd > rnd_value / 8)
-      MovDir[x][y] = right_dir;
-    else
-      MovDir[x][y] = back_dir;
+       if (MovDelay[x][y] && (element == EL_BUG ||
+                              element == EL_SPACESHIP ||
+                              element == EL_SP_SNIKSNAK ||
+                              element == EL_SP_ELECTRON ||
+                              element == EL_MOLE))
+         TEST_DrawLevelField(x, y);
+      }
+    }
 
-    xx = x + move_xy[MovDir[x][y]].dx;
-    yy = y + move_xy[MovDir[x][y]].dy;
+    if (MovDelay[x][y])                // wait some time before next movement
+    {
+      MovDelay[x][y]--;
 
-    if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
-      MovDir[x][y] = old_move_dir;
+      if (element == EL_ROBOT ||
+         element == EL_YAMYAM ||
+         element == EL_DARK_YAMYAM)
+      {
+       DrawLevelElementAnimationIfNeeded(x, y, element);
+       PlayLevelSoundAction(x, y, ACTION_WAITING);
+      }
+      else if (element == EL_SP_ELECTRON)
+       DrawLevelElementAnimationIfNeeded(x, y, element);
+      else if (element == EL_DRAGON)
+      {
+       int i;
+       int dir = MovDir[x][y];
+       int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
+       int dy = (dir == MV_UP   ? -1 : dir == MV_DOWN  ? +1 : 0);
+       int graphic = (dir == MV_LEFT   ? IMG_FLAMES_1_LEFT :
+                      dir == MV_RIGHT  ? IMG_FLAMES_1_RIGHT :
+                      dir == MV_UP     ? IMG_FLAMES_1_UP :
+                      dir == MV_DOWN   ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
+       int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
-    MovDelay[x][y] = 0;
-  }
-  else if (element == EL_MOLE)
-  {
-    boolean can_move_on =
-      (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
-                           IS_AMOEBOID(Feld[move_x][move_y]) ||
-                           Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
-    if (!can_move_on)
-    {
-      boolean can_turn_left =
-       (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
-                             IS_AMOEBOID(Feld[left_x][left_y])));
+       GfxAction[x][y] = ACTION_ATTACKING;
 
-      boolean can_turn_right =
-       (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
-                             IS_AMOEBOID(Feld[right_x][right_y])));
+       if (IS_PLAYER(x, y))
+         DrawPlayerField(x, y);
+       else
+         TEST_DrawLevelField(x, y);
 
-      if (can_turn_left && can_turn_right)
-       MovDir[x][y] = (RND(2) ? left_dir : right_dir);
-      else if (can_turn_left)
-       MovDir[x][y] = left_dir;
-      else
-       MovDir[x][y] = right_dir;
-    }
+       PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
 
-    if (MovDir[x][y] != old_move_dir)
-      MovDelay[x][y] = 9;
-  }
-  else if (element == EL_BALLOON)
-  {
-    MovDir[x][y] = game.wind_direction;
-    MovDelay[x][y] = 0;
-  }
-  else if (element == EL_SPRING)
-  {
-    if (MovDir[x][y] & MV_HORIZONTAL &&
-       (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
-        SPRING_CAN_ENTER_FIELD(element, x, y + 1)))
-      MovDir[x][y] = MV_NONE;
+       for (i = 1; i <= 3; i++)
+       {
+         int xx = x + i * dx;
+         int yy = y + i * dy;
+         int sx = SCREENX(xx);
+         int sy = SCREENY(yy);
+         int flame_graphic = graphic + (i - 1);
 
-    MovDelay[x][y] = 0;
-  }
-  else if (element == EL_ROBOT ||
-          element == EL_SATELLITE ||
-          element == EL_PENGUIN)
-  {
-    int attr_x = -1, attr_y = -1;
+         if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
+           break;
 
-    if (AllPlayersGone)
-    {
-      attr_x = ExitX;
-      attr_y = ExitY;
-    }
-    else
-    {
-      int i;
+         if (MovDelay[x][y])
+         {
+           int flamed = MovingOrBlocked2Element(xx, yy);
 
-      for (i = 0; i < MAX_PLAYERS; i++)
-      {
-       struct PlayerInfo *player = &stored_player[i];
-       int jx = player->jx, jy = player->jy;
+           if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
+             Bang(xx, yy);
+           else
+             RemoveMovingField(xx, yy);
 
-       if (!player->active)
-         continue;
+           ChangeDelay[xx][yy] = 0;
 
-       if (attr_x == -1 ||
-           ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
-       {
-         attr_x = jx;
-         attr_y = jy;
+           Feld[xx][yy] = EL_FLAMES;
+
+           if (IN_SCR_FIELD(sx, sy))
+           {
+             TEST_DrawLevelFieldCrumbled(xx, yy);
+             DrawGraphic(sx, sy, flame_graphic, frame);
+           }
+         }
+         else
+         {
+           if (Feld[xx][yy] == EL_FLAMES)
+             Feld[xx][yy] = EL_EMPTY;
+           TEST_DrawLevelField(xx, yy);
+         }
        }
       }
-    }
-
-    if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
-       (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
-        game.engine_version < VERSION_IDENT(3,1,0,0)))
-    {
-      attr_x = ZX;
-      attr_y = ZY;
-    }
-
-    if (element == EL_PENGUIN)
-    {
-      int i;
-      static int xy[4][2] =
-      {
-       { 0, -1 },
-       { -1, 0 },
-       { +1, 0 },
-       { 0, +1 }
-      };
 
-      for (i = 0; i < NUM_DIRECTIONS; i++)
+      if (MovDelay[x][y])      // element still has to wait some time
       {
-       int ex = x + xy[i][0];
-       int ey = y + xy[i][1];
+       PlayLevelSoundAction(x, y, ACTION_WAITING);
 
-       if (IN_LEV_FIELD(ex, ey) && Feld[ex][ey] == EL_EXIT_OPEN)
-       {
-         attr_x = ex;
-         attr_y = ey;
-         break;
-       }
+       return;
       }
     }
 
-    MovDir[x][y] = MV_NONE;
-    if (attr_x < x)
-      MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
-    else if (attr_x > x)
-      MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
-    if (attr_y < y)
-      MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
-    else if (attr_y > y)
-      MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
+    // now make next step
 
-    if (element == EL_ROBOT)
+    Moving2Blocked(x, y, &newx, &newy);        // get next screen position
+
+    if (DONT_COLLIDE_WITH(element) &&
+       IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
+       !PLAYER_ENEMY_PROTECTED(newx, newy))
     {
-      int newx, newy;
+      TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
 
-      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
-       MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
-      Moving2Blocked(x, y, &newx, &newy);
+      return;
+    }
 
-      if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
-       MovDelay[x][y] = 8 + 8 * !RND(3);
-      else
-       MovDelay[x][y] = 16;
+    else if (CAN_MOVE_INTO_ACID(element) &&
+            IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
+            !IS_MV_DIAGONAL(MovDir[x][y]) &&
+            (MovDir[x][y] == MV_DOWN ||
+             game.engine_version >= VERSION_IDENT(3,1,0,0)))
+    {
+      SplashAcid(newx, newy);
+      Store[x][y] = EL_ACID;
     }
-    else if (element == EL_PENGUIN)
+    else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
     {
-      int newx, newy;
-
-      MovDelay[x][y] = 1;
-
-      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+      if (Feld[newx][newy] == EL_EXIT_OPEN ||
+         Feld[newx][newy] == EL_EM_EXIT_OPEN ||
+         Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
+         Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
       {
-       boolean first_horiz = RND(2);
-       int new_move_dir = MovDir[x][y];
+       RemoveField(x, y);
+       TEST_DrawLevelField(x, y);
 
-       MovDir[x][y] =
-         new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
-       Moving2Blocked(x, y, &newx, &newy);
+       PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
+       if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
+         DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
 
-       if (PENGUIN_CAN_ENTER_FIELD(EL_PENGUIN, newx, newy))
-         return;
+       game.friends_still_needed--;
+       if (!game.friends_still_needed &&
+           !game.GameOver &&
+           game.all_players_gone)
+         LevelSolved();
 
-       MovDir[x][y] =
-         new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
-       Moving2Blocked(x, y, &newx, &newy);
+       return;
+      }
+      else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
+      {
+       if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
+         TEST_DrawLevelField(newx, newy);
+       else
+         GfxDir[x][y] = MovDir[x][y] = MV_NONE;
+      }
+      else if (!IS_FREE(newx, newy))
+      {
+       GfxAction[x][y] = ACTION_WAITING;
 
-       if (PENGUIN_CAN_ENTER_FIELD(EL_PENGUIN, newx, newy))
-         return;
+       if (IS_PLAYER(x, y))
+         DrawPlayerField(x, y);
+       else
+         TEST_DrawLevelField(x, y);
 
-       MovDir[x][y] = old_move_dir;
        return;
       }
     }
-    else       /* (element == EL_SATELLITE) */
+    else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
     {
-      int newx, newy;
+      if (IS_FOOD_PIG(Feld[newx][newy]))
+      {
+       if (IS_MOVING(newx, newy))
+         RemoveMovingField(newx, newy);
+       else
+       {
+         Feld[newx][newy] = EL_EMPTY;
+         TEST_DrawLevelField(newx, newy);
+       }
 
-      MovDelay[x][y] = 1;
+       PlayLevelSound(x, y, SND_PIG_DIGGING);
+      }
+      else if (!IS_FREE(newx, newy))
+      {
+       if (IS_PLAYER(x, y))
+         DrawPlayerField(x, y);
+       else
+         TEST_DrawLevelField(x, y);
 
-      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+       return;
+      }
+    }
+    else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
+    {
+      if (Store[x][y] != EL_EMPTY)
       {
-       boolean first_horiz = RND(2);
-       int new_move_dir = MovDir[x][y];
+       boolean can_clone = FALSE;
+       int xx, yy;
 
-       MovDir[x][y] =
-         new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
-       Moving2Blocked(x, y, &newx, &newy);
+       // check if element to clone is still there
+       for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
+       {
+         if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
+         {
+           can_clone = TRUE;
 
-       if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
-         return;
+           break;
+         }
+       }
 
-       MovDir[x][y] =
-         new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
-       Moving2Blocked(x, y, &newx, &newy);
+       // cannot clone or target field not free anymore -- do not clone
+       if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+         Store[x][y] = EL_EMPTY;
+      }
 
-       if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
-         return;
+      if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+      {
+       if (IS_MV_DIAGONAL(MovDir[x][y]))
+       {
+         int diagonal_move_dir = MovDir[x][y];
+         int stored = Store[x][y];
+         int change_delay = 8;
+         int graphic;
 
-       MovDir[x][y] = old_move_dir;
-       return;
-      }
-    }
-  }
-  else if (move_pattern == MV_TURNING_LEFT ||
-          move_pattern == MV_TURNING_RIGHT ||
-          move_pattern == MV_TURNING_LEFT_RIGHT ||
-          move_pattern == MV_TURNING_RIGHT_LEFT ||
-          move_pattern == MV_TURNING_RANDOM ||
-          move_pattern == MV_ALL_DIRECTIONS)
-  {
-    boolean can_turn_left =
-      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
-    boolean can_turn_right =
-      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
+         // android is moving diagonally
 
-    if (element_info[element].move_stepsize == 0)      /* "not moving" */
-      return;
+         CreateField(x, y, EL_DIAGONAL_SHRINKING);
 
-    if (move_pattern == MV_TURNING_LEFT)
-      MovDir[x][y] = left_dir;
-    else if (move_pattern == MV_TURNING_RIGHT)
-      MovDir[x][y] = right_dir;
-    else if (move_pattern == MV_TURNING_LEFT_RIGHT)
-      MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
-    else if (move_pattern == MV_TURNING_RIGHT_LEFT)
-      MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
-    else if (move_pattern == MV_TURNING_RANDOM)
-      MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
-                     can_turn_right && !can_turn_left ? right_dir :
-                     RND(2) ? left_dir : right_dir);
-    else if (can_turn_left && can_turn_right)
-      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
-    else if (can_turn_left)
-      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
-    else if (can_turn_right)
-      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
-    else
-      MovDir[x][y] = back_dir;
+         Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
+         GfxElement[x][y] = EL_EMC_ANDROID;
+         GfxAction[x][y] = ACTION_SHRINKING;
+         GfxDir[x][y] = diagonal_move_dir;
+         ChangeDelay[x][y] = change_delay;
 
-    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-  }
-  else if (move_pattern == MV_HORIZONTAL ||
-          move_pattern == MV_VERTICAL)
-  {
-    if (move_pattern & old_move_dir)
-      MovDir[x][y] = back_dir;
-    else if (move_pattern == MV_HORIZONTAL)
-      MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
-    else if (move_pattern == MV_VERTICAL)
-      MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
+         graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
+                                  GfxDir[x][y]);
 
-    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-  }
-  else if (move_pattern & MV_ANY_DIRECTION)
-  {
-    MovDir[x][y] = move_pattern;
-    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-  }
-  else if (move_pattern & MV_WIND_DIRECTION)
-  {
-    MovDir[x][y] = game.wind_direction;
-    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-  }
-  else if (move_pattern == MV_ALONG_LEFT_SIDE)
-  {
-    if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
-      MovDir[x][y] = left_dir;
-    else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = right_dir;
+         DrawLevelGraphicAnimation(x, y, graphic);
+         PlayLevelSoundAction(x, y, ACTION_SHRINKING);
 
-    if (MovDir[x][y] != old_move_dir)
-      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-  }
-  else if (move_pattern == MV_ALONG_RIGHT_SIDE)
-  {
-    if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
-      MovDir[x][y] = right_dir;
-    else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = left_dir;
+         if (Feld[newx][newy] == EL_ACID)
+         {
+           SplashAcid(newx, newy);
+
+           return;
+         }
 
-    if (MovDir[x][y] != old_move_dir)
-      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-  }
-  else if (move_pattern == MV_TOWARDS_PLAYER ||
-          move_pattern == MV_AWAY_FROM_PLAYER)
-  {
-    int attr_x = -1, attr_y = -1;
-    int newx, newy;
-    boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
+         CreateField(newx, newy, EL_DIAGONAL_GROWING);
 
-    if (AllPlayersGone)
-    {
-      attr_x = ExitX;
-      attr_y = ExitY;
-    }
-    else
-    {
-      int i;
+         Store[newx][newy] = EL_EMC_ANDROID;
+         GfxElement[newx][newy] = EL_EMC_ANDROID;
+         GfxAction[newx][newy] = ACTION_GROWING;
+         GfxDir[newx][newy] = diagonal_move_dir;
+         ChangeDelay[newx][newy] = change_delay;
 
-      for (i = 0; i < MAX_PLAYERS; i++)
-      {
-       struct PlayerInfo *player = &stored_player[i];
-       int jx = player->jx, jy = player->jy;
+         graphic = el_act_dir2img(GfxElement[newx][newy],
+                                  GfxAction[newx][newy], GfxDir[newx][newy]);
 
-       if (!player->active)
-         continue;
+         DrawLevelGraphicAnimation(newx, newy, graphic);
+         PlayLevelSoundAction(newx, newy, ACTION_GROWING);
 
-       if (attr_x == -1 ||
-           ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
+         return;
+       }
+       else
        {
-         attr_x = jx;
-         attr_y = jy;
+         Feld[newx][newy] = EL_EMPTY;
+         TEST_DrawLevelField(newx, newy);
+
+         PlayLevelSoundAction(x, y, ACTION_DIGGING);
        }
       }
+      else if (!IS_FREE(newx, newy))
+      {
+       return;
+      }
     }
-
-    MovDir[x][y] = MV_NONE;
-    if (attr_x < x)
-      MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
-    else if (attr_x > x)
-      MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
-    if (attr_y < y)
-      MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
-    else if (attr_y > y)
-      MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
-
-    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-
-    if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+    else if (IS_CUSTOM_ELEMENT(element) &&
+            CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
     {
-      boolean first_horiz = RND(2);
-      int new_move_dir = MovDir[x][y];
+      if (!DigFieldByCE(newx, newy, element))
+       return;
 
-      if (element_info[element].move_stepsize == 0)    /* "not moving" */
+      if (move_pattern & MV_MAZE_RUNNER_STYLE)
       {
-       first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
-       MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+       RunnerVisit[x][y] = FrameCounter;
+       PlayerVisit[x][y] /= 8;         // expire player visit path
+      }
+    }
+    else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
+    {
+      if (!IS_FREE(newx, newy))
+      {
+       if (IS_PLAYER(x, y))
+         DrawPlayerField(x, y);
+       else
+         TEST_DrawLevelField(x, y);
 
        return;
       }
+      else
+      {
+       boolean wanna_flame = !RND(10);
+       int dx = newx - x, dy = newy - y;
+       int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
+       int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
+       int element1 = (IN_LEV_FIELD(newx1, newy1) ?
+                       MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
+       int element2 = (IN_LEV_FIELD(newx2, newy2) ?
+                       MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
 
-      MovDir[x][y] =
-       new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
-      Moving2Blocked(x, y, &newx, &newy);
+       if ((wanna_flame ||
+            IS_CLASSIC_ENEMY(element1) ||
+            IS_CLASSIC_ENEMY(element2)) &&
+           element1 != EL_DRAGON && element2 != EL_DRAGON &&
+           element1 != EL_FLAMES && element2 != EL_FLAMES)
+       {
+         ResetGfxAnimation(x, y);
+         GfxAction[x][y] = ACTION_ATTACKING;
 
-      if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
-       return;
+         if (IS_PLAYER(x, y))
+           DrawPlayerField(x, y);
+         else
+           TEST_DrawLevelField(x, y);
 
-      MovDir[x][y] =
-       new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
-      Moving2Blocked(x, y, &newx, &newy);
+         PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
 
-      if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
-       return;
+         MovDelay[x][y] = 50;
 
-      MovDir[x][y] = old_move_dir;
-    }
-  }
-  else if (move_pattern == MV_WHEN_PUSHED ||
-          move_pattern == MV_WHEN_DROPPED)
-  {
-    if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = MV_NONE;
+         Feld[newx][newy] = EL_FLAMES;
+         if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
+           Feld[newx1][newy1] = EL_FLAMES;
+         if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
+           Feld[newx2][newy2] = EL_FLAMES;
 
-    MovDelay[x][y] = 0;
-  }
-  else if (move_pattern & MV_MAZE_RUNNER_STYLE)
-  {
-    static int test_xy[7][2] =
-    {
-      { 0, -1 },
-      { -1, 0 },
-      { +1, 0 },
-      { 0, +1 },
-      { 0, -1 },
-      { -1, 0 },
-      { +1, 0 },
-    };
-    static int test_dir[7] =
+         return;
+       }
+      }
+    }
+    else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
+            Feld[newx][newy] == EL_DIAMOND)
     {
-      MV_UP,
-      MV_LEFT,
-      MV_RIGHT,
-      MV_DOWN,
-      MV_UP,
-      MV_LEFT,
-      MV_RIGHT,
-    };
-    boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
-    int move_preference = -1000000;    /* start with very low preference */
-    int new_move_dir = MV_NONE;
-    int start_test = RND(4);
-    int i;
+      if (IS_MOVING(newx, newy))
+       RemoveMovingField(newx, newy);
+      else
+      {
+       Feld[newx][newy] = EL_EMPTY;
+       TEST_DrawLevelField(newx, newy);
+      }
 
-    for (i = 0; i < NUM_DIRECTIONS; i++)
+      PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
+    }
+    else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
+            IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
     {
-      int move_dir = test_dir[start_test + i];
-      int move_dir_preference;
-
-      xx = x + test_xy[start_test + i][0];
-      yy = y + test_xy[start_test + i][1];
+      if (AmoebaNr[newx][newy])
+      {
+       AmoebaCnt2[AmoebaNr[newx][newy]]--;
+       if (Feld[newx][newy] == EL_AMOEBA_FULL ||
+           Feld[newx][newy] == EL_BD_AMOEBA)
+         AmoebaCnt[AmoebaNr[newx][newy]]--;
+      }
 
-      if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
-         (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
+      if (IS_MOVING(newx, newy))
       {
-       new_move_dir = move_dir;
+       RemoveMovingField(newx, newy);
+      }
+      else
+      {
+       Feld[newx][newy] = EL_EMPTY;
+       TEST_DrawLevelField(newx, newy);
+      }
 
-       break;
+      PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
+    }
+    else if ((element == EL_PACMAN || element == EL_MOLE)
+            && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
+    {
+      if (AmoebaNr[newx][newy])
+      {
+       AmoebaCnt2[AmoebaNr[newx][newy]]--;
+       if (Feld[newx][newy] == EL_AMOEBA_FULL ||
+           Feld[newx][newy] == EL_BD_AMOEBA)
+         AmoebaCnt[AmoebaNr[newx][newy]]--;
       }
 
-      if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
-       continue;
+      if (element == EL_MOLE)
+      {
+       Feld[newx][newy] = EL_AMOEBA_SHRINKING;
+       PlayLevelSound(x, y, SND_MOLE_DIGGING);
 
-      move_dir_preference = -1 * RunnerVisit[xx][yy];
-      if (hunter_mode && PlayerVisit[xx][yy] > 0)
-       move_dir_preference = PlayerVisit[xx][yy];
+       ResetGfxAnimation(x, y);
+       GfxAction[x][y] = ACTION_DIGGING;
+       TEST_DrawLevelField(x, y);
 
-      if (move_dir_preference > move_preference)
-      {
-       /* prefer field that has not been visited for the longest time */
-       move_preference = move_dir_preference;
-       new_move_dir = move_dir;
+       MovDelay[newx][newy] = 0;       // start amoeba shrinking delay
+
+       return;                         // wait for shrinking amoeba
       }
-      else if (move_dir_preference == move_preference &&
-              move_dir == old_move_dir)
+      else     // element == EL_PACMAN
       {
-       /* prefer last direction when all directions are preferred equally */
-       move_preference = move_dir_preference;
-       new_move_dir = move_dir;
+       Feld[newx][newy] = EL_EMPTY;
+       TEST_DrawLevelField(newx, newy);
+       PlayLevelSound(x, y, SND_PACMAN_DIGGING);
       }
     }
+    else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
+            (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
+             (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
+    {
+      // wait for shrinking amoeba to completely disappear
+      return;
+    }
+    else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
+    {
+      // object was running against a wall
 
-    MovDir[x][y] = new_move_dir;
-    if (old_move_dir != new_move_dir)
-      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+      TurnRound(x, y);
+
+      if (GFX_ELEMENT(element) != EL_SAND)     // !!! FIX THIS (crumble) !!!
+       DrawLevelElementAnimation(x, y, element);
+
+      if (DONT_TOUCH(element))
+       TestIfBadThingTouchesPlayer(x, y);
+
+      return;
+    }
+
+    InitMovingField(x, y, MovDir[x][y]);
+
+    PlayLevelSoundAction(x, y, ACTION_MOVING);
   }
+
+  if (MovDir[x][y])
+    ContinueMoving(x, y);
 }
 
-static void TurnRound(int x, int y)
+void ContinueMoving(int x, int y)
 {
+  int element = Feld[x][y];
+  struct ElementInfo *ei = &element_info[element];
   int direction = MovDir[x][y];
+  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
+  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
+  int newx = x + dx, newy = y + dy;
+  int stored = Store[x][y];
+  int stored_new = Store[newx][newy];
+  boolean pushed_by_player   = (Pushed[x][y] && IS_PLAYER(x, y));
+  boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
+  boolean last_line = (newy == lev_fieldy - 1);
+
+  MovPos[x][y] += getElementMoveStepsize(x, y);
+
+  if (pushed_by_player)        // special case: moving object pushed by player
+    MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
+
+  if (ABS(MovPos[x][y]) < TILEX)
+  {
+    TEST_DrawLevelField(x, y);
 
-  TurnRoundExt(x, y);
+    return;    // element is still moving
+  }
 
-  GfxDir[x][y] = MovDir[x][y];
+  // element reached destination field
 
-  if (direction != MovDir[x][y])
-    GfxFrame[x][y] = 0;
+  Feld[x][y] = EL_EMPTY;
+  Feld[newx][newy] = element;
+  MovPos[x][y] = 0;    // force "not moving" for "crumbled sand"
 
-  if (MovDelay[x][y])
-    GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_BIT(direction);
-}
+  if (Store[x][y] == EL_ACID)  // element is moving into acid pool
+  {
+    element = Feld[newx][newy] = EL_ACID;
+  }
+  else if (element == EL_MOLE)
+  {
+    Feld[x][y] = EL_SAND;
 
-static boolean JustBeingPushed(int x, int y)
-{
-  int i;
+    TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+  }
+  else if (element == EL_QUICKSAND_FILLING)
+  {
+    element = Feld[newx][newy] = get_next_element(element);
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_QUICKSAND_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    element = Feld[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_QUICKSAND_FAST_FILLING)
+  {
+    element = Feld[newx][newy] = get_next_element(element);
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_QUICKSAND_FAST_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    element = Feld[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_MAGIC_WALL_FILLING)
+  {
+    element = Feld[newx][newy] = get_next_element(element);
+    if (!game.magic_wall_active)
+      element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_MAGIC_WALL_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    if (!game.magic_wall_active)
+      Feld[x][y] = EL_MAGIC_WALL_DEAD;
+    element = Feld[newx][newy] = Store[x][y];
 
-  for (i = 0; i < MAX_PLAYERS; i++)
+    InitField(newx, newy, FALSE);
+  }
+  else if (element == EL_BD_MAGIC_WALL_FILLING)
   {
-    struct PlayerInfo *player = &stored_player[i];
+    element = Feld[newx][newy] = get_next_element(element);
+    if (!game.magic_wall_active)
+      element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_BD_MAGIC_WALL_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    if (!game.magic_wall_active)
+      Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
+    element = Feld[newx][newy] = Store[x][y];
 
-    if (player->active && player->is_pushing && player->MovPos)
-    {
-      int next_jx = player->jx + (player->jx - player->last_jx);
-      int next_jy = player->jy + (player->jy - player->last_jy);
+    InitField(newx, newy, FALSE);
+  }
+  else if (element == EL_DC_MAGIC_WALL_FILLING)
+  {
+    element = Feld[newx][newy] = get_next_element(element);
+    if (!game.magic_wall_active)
+      element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_DC_MAGIC_WALL_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    if (!game.magic_wall_active)
+      Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
+    element = Feld[newx][newy] = Store[x][y];
 
-      if (x == next_jx && y == next_jy)
-       return TRUE;
-    }
+    InitField(newx, newy, FALSE);
+  }
+  else if (element == EL_AMOEBA_DROPPING)
+  {
+    Feld[x][y] = get_next_element(element);
+    element = Feld[newx][newy] = Store[x][y];
   }
+  else if (element == EL_SOKOBAN_OBJECT)
+  {
+    if (Back[x][y])
+      Feld[x][y] = Back[x][y];
 
-  return FALSE;
-}
+    if (Back[newx][newy])
+      Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
 
-void StartMoving(int x, int y)
-{
-  boolean started_moving = FALSE;      /* some elements can fall _and_ move */
-  int element = Feld[x][y];
+    Back[x][y] = Back[newx][newy] = 0;
+  }
 
-  if (Stop[x][y])
-    return;
+  Store[x][y] = EL_EMPTY;
+  MovPos[x][y] = 0;
+  MovDir[x][y] = 0;
+  MovDelay[x][y] = 0;
 
-  if (MovDelay[x][y] == 0)
-    GfxAction[x][y] = ACTION_DEFAULT;
+  MovDelay[newx][newy] = 0;
 
-  if (CAN_FALL(element) && y < lev_fieldy - 1)
+  if (CAN_CHANGE_OR_HAS_ACTION(element))
   {
-    if ((x > 0              && IS_PLAYER(x - 1, y)) ||
-       (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
-      if (JustBeingPushed(x, y))
-       return;
+    // copy element change control values to new field
+    ChangeDelay[newx][newy] = ChangeDelay[x][y];
+    ChangePage[newx][newy]  = ChangePage[x][y];
+    ChangeCount[newx][newy] = ChangeCount[x][y];
+    ChangeEvent[newx][newy] = ChangeEvent[x][y];
+  }
 
-    if (element == EL_QUICKSAND_FULL)
-    {
-      if (IS_FREE(x, y + 1))
-      {
-       InitMovingField(x, y, MV_DOWN);
-       started_moving = TRUE;
+  CustomValue[newx][newy] = CustomValue[x][y];
 
-       Feld[x][y] = EL_QUICKSAND_EMPTYING;
-       Store[x][y] = EL_ROCK;
+  ChangeDelay[x][y] = 0;
+  ChangePage[x][y] = -1;
+  ChangeCount[x][y] = 0;
+  ChangeEvent[x][y] = -1;
 
-       PlayLevelSoundAction(x, y, ACTION_EMPTYING);
-      }
-      else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
-      {
-       if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY + 1;
+  CustomValue[x][y] = 0;
 
-       if (MovDelay[x][y])
-       {
-         MovDelay[x][y]--;
-         if (MovDelay[x][y])
-           return;
-       }
+  // copy animation control values to new field
+  GfxFrame[newx][newy]  = GfxFrame[x][y];
+  GfxRandom[newx][newy] = GfxRandom[x][y];     // keep same random value
+  GfxAction[newx][newy] = GfxAction[x][y];     // keep action one frame
+  GfxDir[newx][newy]    = GfxDir[x][y];                // keep element direction
 
-       Feld[x][y] = EL_QUICKSAND_EMPTY;
-       Feld[x][y + 1] = EL_QUICKSAND_FULL;
-       Store[x][y + 1] = Store[x][y];
-       Store[x][y] = 0;
+  Pushed[x][y] = Pushed[newx][newy] = FALSE;
 
-       PlayLevelSoundAction(x, y, ACTION_FILLING);
-      }
-    }
-    else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
-            Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
-    {
-      InitMovingField(x, y, MV_DOWN);
-      started_moving = TRUE;
+  // some elements can leave other elements behind after moving
+  if (ei->move_leave_element != EL_EMPTY &&
+      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
+      (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
+  {
+    int move_leave_element = ei->move_leave_element;
 
-      Feld[x][y] = EL_QUICKSAND_FILLING;
-      Store[x][y] = element;
+    // this makes it possible to leave the removed element again
+    if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
+      move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
 
-      PlayLevelSoundAction(x, y, ACTION_FILLING);
-    }
-    else if (element == EL_MAGIC_WALL_FULL)
-    {
-      if (IS_FREE(x, y + 1))
-      {
-       InitMovingField(x, y, MV_DOWN);
-       started_moving = TRUE;
+    Feld[x][y] = move_leave_element;
 
-       Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
-       Store[x][y] = EL_CHANGED(Store[x][y]);
-      }
-      else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
-      {
-       if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = direction;
 
-       if (MovDelay[x][y])
-       {
-         MovDelay[x][y]--;
-         if (MovDelay[x][y])
-           return;
-       }
+    InitField(x, y, FALSE);
 
-       Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
-       Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
-       Store[x][y + 1] = EL_CHANGED(Store[x][y]);
-       Store[x][y] = 0;
-      }
-    }
-    else if (element == EL_BD_MAGIC_WALL_FULL)
-    {
-      if (IS_FREE(x, y + 1))
-      {
-       InitMovingField(x, y, MV_DOWN);
-       started_moving = TRUE;
+    if (GFX_CRUMBLED(Feld[x][y]))
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
-       Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
-       Store[x][y] = EL_CHANGED2(Store[x][y]);
-      }
-      else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
-      {
-       if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+    if (ELEM_IS_PLAYER(move_leave_element))
+      RelocatePlayer(x, y, move_leave_element);
+  }
 
-       if (MovDelay[x][y])
-       {
-         MovDelay[x][y]--;
-         if (MovDelay[x][y])
-           return;
-       }
+  // do this after checking for left-behind element
+  ResetGfxAnimation(x, y);     // reset animation values for old field
 
-       Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
-       Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
-       Store[x][y + 1] = EL_CHANGED2(Store[x][y]);
-       Store[x][y] = 0;
-      }
-    }
-    else if (CAN_PASS_MAGIC_WALL(element) &&
-            (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
-             Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
-    {
-      InitMovingField(x, y, MV_DOWN);
-      started_moving = TRUE;
+  if (!CAN_MOVE(element) ||
+      (CAN_FALL(element) && direction == MV_DOWN &&
+       (element == EL_SPRING ||
+       element_info[element].move_pattern == MV_WHEN_PUSHED ||
+       element_info[element].move_pattern == MV_WHEN_DROPPED)))
+    GfxDir[x][y] = MovDir[newx][newy] = 0;
 
-      Feld[x][y] =
-       (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
-        EL_BD_MAGIC_WALL_FILLING);
-      Store[x][y] = element;
-    }
-    else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
-    {
-      SplashAcid(x, y + 1);
+  TEST_DrawLevelField(x, y);
+  TEST_DrawLevelField(newx, newy);
 
-      InitMovingField(x, y, MV_DOWN);
-      started_moving = TRUE;
+  Stop[newx][newy] = TRUE;     // ignore this element until the next frame
+
+  // prevent pushed element from moving on in pushed direction
+  if (pushed_by_player && CAN_MOVE(element) &&
+      element_info[element].move_pattern & MV_ANY_DIRECTION &&
+      !(element_info[element].move_pattern & direction))
+    TurnRound(newx, newy);
+
+  // prevent elements on conveyor belt from moving on in last direction
+  if (pushed_by_conveyor && CAN_FALL(element) &&
+      direction & MV_HORIZONTAL)
+    MovDir[newx][newy] = 0;
+
+  if (!pushed_by_player)
+  {
+    int nextx = newx + dx, nexty = newy + dy;
+    boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
 
-      Store[x][y] = EL_ACID;
-    }
-    else if ((game.engine_version >= VERSION_IDENT(3,1,0,0) &&
-             CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
+    WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
 
-            (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
-             CAN_FALL(element) && WasJustFalling[x][y] &&
-             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
+    if (CAN_FALL(element) && direction == MV_DOWN)
+      WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
 
-            (game.engine_version < VERSION_IDENT(2,2,0,7) &&
-             CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
-             (Feld[x][y + 1] == EL_BLOCKED)))
-    {
-      /* this is needed for a special case not covered by calling "Impact()"
-        from "ContinueMoving()": if an element moves to a tile directly below
-        another element which was just falling on that tile (which was empty
-        in the previous frame), the falling element above would just stop
-        instead of smashing the element below (in previous version, the above
-        element was just checked for "moving" instead of "falling", resulting
-        in incorrect smashes caused by horizontal movement of the above
-        element; also, the case of the player being the element to smash was
-        simply not covered here... :-/ ) */
+    if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
+      CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
 
-      CheckCollision[x][y] = 0;
+    if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
+      CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
+  }
 
-      Impact(x, y);
-    }
-    else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
-    {
-      if (MovDir[x][y] == MV_NONE)
-      {
-       InitMovingField(x, y, MV_DOWN);
-       started_moving = TRUE;
-      }
-    }
-    else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
-    {
-      if (WasJustFalling[x][y])        /* prevent animation from being restarted */
-       MovDir[x][y] = MV_DOWN;
+  if (DONT_TOUCH(element))     // object may be nasty to player or others
+  {
+    TestIfBadThingTouchesPlayer(newx, newy);
+    TestIfBadThingTouchesFriend(newx, newy);
 
-      InitMovingField(x, y, MV_DOWN);
-      started_moving = TRUE;
-    }
-    else if (element == EL_AMOEBA_DROP)
-    {
-      Feld[x][y] = EL_AMOEBA_GROWING;
-      Store[x][y] = EL_AMOEBA_WET;
-    }
-    else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
-             (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
-            !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
-            element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
-    {
-      boolean can_fall_left  = (x > 0 && IS_FREE(x - 1, y) &&
-                               (IS_FREE(x - 1, y + 1) ||
-                                Feld[x - 1][y + 1] == EL_ACID));
-      boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
-                               (IS_FREE(x + 1, y + 1) ||
-                                Feld[x + 1][y + 1] == EL_ACID));
-      boolean can_fall_any  = (can_fall_left || can_fall_right);
-      boolean can_fall_both = (can_fall_left && can_fall_right);
-      int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
+    if (!IS_CUSTOM_ELEMENT(element))
+      TestIfBadThingTouchesOtherBadThing(newx, newy);
+  }
+  else if (element == EL_PENGUIN)
+    TestIfFriendTouchesBadThing(newx, newy);
 
-#if USE_NEW_ALL_SLIPPERY
-      if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
-      {
-       if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
-         can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
-         can_fall_left = FALSE;
-       else if (slippery_type == SLIPPERY_ONLY_LEFT)
-         can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ONLY_RIGHT)
-         can_fall_left = FALSE;
+  if (DONT_GET_HIT_BY(element))
+  {
+    TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
+  }
 
-       can_fall_any  = (can_fall_left || can_fall_right);
-       can_fall_both = FALSE;
-      }
-#else
-      if (can_fall_any && IS_CUSTOM_ELEMENT(Feld[x][y + 1]))
-      {
-       if (slippery_type == SLIPPERY_ONLY_LEFT)
-         can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ONLY_RIGHT)
-         can_fall_left = FALSE;
-       else if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
-         can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
-         can_fall_left = FALSE;
+  // give the player one last chance (one more frame) to move away
+  if (CAN_FALL(element) && direction == MV_DOWN &&
+      (last_line || (!IS_FREE(x, newy + 1) &&
+                    (!IS_PLAYER(x, newy + 1) ||
+                     game.engine_version < VERSION_IDENT(3,1,1,0)))))
+    Impact(x, newy);
 
-       can_fall_any  = (can_fall_left || can_fall_right);
-       can_fall_both = (can_fall_left && can_fall_right);
-      }
-#endif
+  if (pushed_by_player && !game.use_change_when_pushing_bug)
+  {
+    int push_side = MV_DIR_OPPOSITE(direction);
+    struct PlayerInfo *player = PLAYERINFO(x, y);
 
-#if USE_NEW_ALL_SLIPPERY
-#else
-#if USE_NEW_SP_SLIPPERY
-      /* !!! better use the same properties as for custom elements here !!! */
-      else if (game.engine_version >= VERSION_IDENT(3,1,1,0) &&
-              can_fall_both && IS_SP_ELEMENT(Feld[x][y + 1]))
-      {
-       can_fall_right = FALSE;         /* slip down on left side */
-       can_fall_both = FALSE;
-      }
-#endif
-#endif
+    CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
+                              player->index_bit, push_side);
+    CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
+                                       player->index_bit, push_side);
+  }
 
-#if USE_NEW_ALL_SLIPPERY
-      if (can_fall_both)
-      {
-       if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
-         can_fall_right = FALSE;       /* slip down on left side */
-       else
-         can_fall_left = !(can_fall_right = RND(2));
+  if (element == EL_EMC_ANDROID && pushed_by_player)   // make another move
+    MovDelay[newx][newy] = 1;
 
-       can_fall_both = FALSE;
-      }
-#else
-      if (can_fall_both)
-      {
-       if (game.emulation == EMU_BOULDERDASH ||
-           element == EL_BD_ROCK || element == EL_BD_DIAMOND)
-         can_fall_right = FALSE;       /* slip down on left side */
-       else
-         can_fall_left = !(can_fall_right = RND(2));
+  CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
 
-       can_fall_both = FALSE;
-      }
-#endif
+  TestIfElementTouchesCustomElement(x, y);     // empty or new element
+  TestIfElementHitsCustomElement(newx, newy, direction);
+  TestIfPlayerTouchesCustomElement(newx, newy);
+  TestIfElementTouchesCustomElement(newx, newy);
 
-      if (can_fall_any)
-      {
-       /* if not determined otherwise, prefer left side for slipping down */
-       InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
-       started_moving = TRUE;
-      }
-    }
-#if 0
-    else if (IS_BELT_ACTIVE(Feld[x][y + 1]) && !CAN_MOVE(element))
-#else
-    else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
-#endif
-    {
-      boolean left_is_free  = (x > 0 && IS_FREE(x - 1, y));
-      boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
-      int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
-      int belt_dir = game.belt_dir[belt_nr];
+  if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
+      IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
+    CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
+                            MV_DIR_OPPOSITE(direction));
+}
 
-      if ((belt_dir == MV_LEFT  && left_is_free) ||
-         (belt_dir == MV_RIGHT && right_is_free))
-      {
-       int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
+int AmoebeNachbarNr(int ax, int ay)
+{
+  int i;
+  int element = Feld[ax][ay];
+  int group_nr = 0;
+  static int xy[4][2] =
+  {
+    { 0, -1 },
+    { -1, 0 },
+    { +1, 0 },
+    { 0, +1 }
+  };
 
-       InitMovingField(x, y, belt_dir);
-       started_moving = TRUE;
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    int x = ax + xy[i][0];
+    int y = ay + xy[i][1];
 
-       Pushed[x][y] = TRUE;
-       Pushed[nextx][y] = TRUE;
+    if (!IN_LEV_FIELD(x, y))
+      continue;
 
-       GfxAction[x][y] = ACTION_DEFAULT;
-      }
-      else
-      {
-       MovDir[x][y] = 0;       /* if element was moving, stop it */
-      }
-    }
+    if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
+      group_nr = AmoebaNr[x][y];
   }
 
-  /* not "else if" because of elements that can fall and move (EL_SPRING) */
-#if 0
-  if (CAN_MOVE(element) && !started_moving && MovDir[x][y] != MV_NONE)
-#else
-  if (CAN_MOVE(element) && !started_moving)
-#endif
+  return group_nr;
+}
+
+static void AmoebenVereinigen(int ax, int ay)
+{
+  int i, x, y, xx, yy;
+  int new_group_nr = AmoebaNr[ax][ay];
+  static int xy[4][2] =
   {
-    int move_pattern = element_info[element].move_pattern;
-    int newx, newy;
+    { 0, -1 },
+    { -1, 0 },
+    { +1, 0 },
+    { 0, +1 }
+  };
 
-#if 0
-#if DEBUG
-    if (MovDir[x][y] == MV_NONE)
-    {
-      printf("StartMoving(): %d,%d: element %d ['%s'] not moving\n",
-            x, y, element, element_info[element].token_name);
-      printf("StartMoving(): This should never happen!\n");
-    }
-#endif
-#endif
+  if (new_group_nr == 0)
+    return;
 
-    Moving2Blocked(x, y, &newx, &newy);
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    x = ax + xy[i][0];
+    y = ay + xy[i][1];
 
-    if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
-      return;
+    if (!IN_LEV_FIELD(x, y))
+      continue;
 
-    if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
-       CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+    if ((Feld[x][y] == EL_AMOEBA_FULL ||
+        Feld[x][y] == EL_BD_AMOEBA ||
+        Feld[x][y] == EL_AMOEBA_DEAD) &&
+       AmoebaNr[x][y] != new_group_nr)
     {
-      WasJustMoving[x][y] = 0;
-      CheckCollision[x][y] = 0;
-
-      TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
+      int old_group_nr = AmoebaNr[x][y];
 
-      if (Feld[x][y] != element)       /* element has changed */
+      if (old_group_nr == 0)
        return;
-    }
-
-    if (!MovDelay[x][y])       /* start new movement phase */
-    {
-      /* all objects that can change their move direction after each step
-        (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall */
 
-      if (element != EL_YAMYAM &&
-         element != EL_DARK_YAMYAM &&
-         element != EL_PACMAN &&
-         !(move_pattern & MV_ANY_DIRECTION) &&
-         move_pattern != MV_TURNING_LEFT &&
-         move_pattern != MV_TURNING_RIGHT &&
-         move_pattern != MV_TURNING_LEFT_RIGHT &&
-         move_pattern != MV_TURNING_RIGHT_LEFT &&
-         move_pattern != MV_TURNING_RANDOM)
-      {
-       TurnRound(x, y);
+      AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
+      AmoebaCnt[old_group_nr] = 0;
+      AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
+      AmoebaCnt2[old_group_nr] = 0;
 
-       if (MovDelay[x][y] && (element == EL_BUG ||
-                              element == EL_SPACESHIP ||
-                              element == EL_SP_SNIKSNAK ||
-                              element == EL_SP_ELECTRON ||
-                              element == EL_MOLE))
-         DrawLevelField(x, y);
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       if (AmoebaNr[xx][yy] == old_group_nr)
+         AmoebaNr[xx][yy] = new_group_nr;
       }
     }
+  }
+}
+
+void AmoebeUmwandeln(int ax, int ay)
+{
+  int i, x, y;
+
+  if (Feld[ax][ay] == EL_AMOEBA_DEAD)
+  {
+    int group_nr = AmoebaNr[ax][ay];
 
-    if (MovDelay[x][y])                /* wait some time before next movement */
+#ifdef DEBUG
+    if (group_nr == 0)
     {
-      MovDelay[x][y]--;
+      printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
+      printf("AmoebeUmwandeln(): This should never happen!\n");
+      return;
+    }
+#endif
 
-      if (element == EL_ROBOT ||
-         element == EL_YAMYAM ||
-         element == EL_DARK_YAMYAM)
+    SCAN_PLAYFIELD(x, y)
+    {
+      if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
       {
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-       PlayLevelSoundAction(x, y, ACTION_WAITING);
+       AmoebaNr[x][y] = 0;
+       Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
       }
-      else if (element == EL_SP_ELECTRON)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-      else if (element == EL_DRAGON)
-      {
-       int i;
-       int dir = MovDir[x][y];
-       int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
-       int dy = (dir == MV_UP   ? -1 : dir == MV_DOWN  ? +1 : 0);
-       int graphic = (dir == MV_LEFT   ? IMG_FLAMES_1_LEFT :
-                      dir == MV_RIGHT  ? IMG_FLAMES_1_RIGHT :
-                      dir == MV_UP     ? IMG_FLAMES_1_UP :
-                      dir == MV_DOWN   ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
-       int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
-
-       GfxAction[x][y] = ACTION_ATTACKING;
+    }
 
-       if (IS_PLAYER(x, y))
-         DrawPlayerField(x, y);
-       else
-         DrawLevelField(x, y);
+    PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
+                           SND_AMOEBA_TURNING_TO_GEM :
+                           SND_AMOEBA_TURNING_TO_ROCK));
+    Bang(ax, ay);
+  }
+  else
+  {
+    static int xy[4][2] =
+    {
+      { 0, -1 },
+      { -1, 0 },
+      { +1, 0 },
+      { 0, +1 }
+    };
 
-       PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
+    for (i = 0; i < NUM_DIRECTIONS; i++)
+    {
+      x = ax + xy[i][0];
+      y = ay + xy[i][1];
 
-       for (i = 1; i <= 3; i++)
-       {
-         int xx = x + i * dx;
-         int yy = y + i * dy;
-         int sx = SCREENX(xx);
-         int sy = SCREENY(yy);
-         int flame_graphic = graphic + (i - 1);
+      if (!IN_LEV_FIELD(x, y))
+       continue;
 
-         if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
-           break;
+      if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
+      {
+       PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
+                             SND_AMOEBA_TURNING_TO_GEM :
+                             SND_AMOEBA_TURNING_TO_ROCK));
+       Bang(x, y);
+      }
+    }
+  }
+}
 
-         if (MovDelay[x][y])
-         {
-           int flamed = MovingOrBlocked2Element(xx, yy);
+static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
+{
+  int x, y;
+  int group_nr = AmoebaNr[ax][ay];
+  boolean done = FALSE;
 
-           /* !!! */
-#if 0
-           if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
-             Bang(xx, yy);
-           else if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
-             RemoveMovingField(xx, yy);
-           else
-             RemoveField(xx, yy);
-#else
-           if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
-             Bang(xx, yy);
-           else
-             RemoveMovingField(xx, yy);
+#ifdef DEBUG
+  if (group_nr == 0)
+  {
+    printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
+    printf("AmoebeUmwandelnBD(): This should never happen!\n");
+    return;
+  }
 #endif
 
-           ChangeDelay[xx][yy] = 0;
+  SCAN_PLAYFIELD(x, y)
+  {
+    if (AmoebaNr[x][y] == group_nr &&
+       (Feld[x][y] == EL_AMOEBA_DEAD ||
+        Feld[x][y] == EL_BD_AMOEBA ||
+        Feld[x][y] == EL_AMOEBA_GROWING))
+    {
+      AmoebaNr[x][y] = 0;
+      Feld[x][y] = new_element;
+      InitField(x, y, FALSE);
+      TEST_DrawLevelField(x, y);
+      done = TRUE;
+    }
+  }
 
-           Feld[xx][yy] = EL_FLAMES;
+  if (done)
+    PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
+                           SND_BD_AMOEBA_TURNING_TO_ROCK :
+                           SND_BD_AMOEBA_TURNING_TO_GEM));
+}
 
-           if (IN_SCR_FIELD(sx, sy))
-           {
-             DrawLevelFieldCrumbledSand(xx, yy);
-             DrawGraphic(sx, sy, flame_graphic, frame);
-           }
-         }
-         else
-         {
-           if (Feld[xx][yy] == EL_FLAMES)
-             Feld[xx][yy] = EL_EMPTY;
-           DrawLevelField(xx, yy);
-         }
-       }
-      }
+static void AmoebeWaechst(int x, int y)
+{
+  static unsigned int sound_delay = 0;
+  static unsigned int sound_delay_value = 0;
 
-      if (MovDelay[x][y])      /* element still has to wait some time */
-      {
-       PlayLevelSoundAction(x, y, ACTION_WAITING);
+  if (!MovDelay[x][y])         // start new growing cycle
+  {
+    MovDelay[x][y] = 7;
 
-       return;
-      }
+    if (DelayReached(&sound_delay, sound_delay_value))
+    {
+      PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
+      sound_delay_value = 30;
     }
+  }
 
-    /* now make next step */
-
-    Moving2Blocked(x, y, &newx, &newy);        /* get next screen position */
-
-    if (DONT_COLLIDE_WITH(element) &&
-       IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
-       !PLAYER_ENEMY_PROTECTED(newx, newy))
+  if (MovDelay[x][y])          // wait some time before growing bigger
+  {
+    MovDelay[x][y]--;
+    if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
     {
-      TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
+      int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
+                                          6 - MovDelay[x][y]);
 
-      return;
+      DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
     }
 
-    else if (CAN_MOVE_INTO_ACID(element) &&
-            IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
-            (MovDir[x][y] == MV_DOWN ||
-             game.engine_version >= VERSION_IDENT(3,1,0,0)))
+    if (!MovDelay[x][y])
     {
-      SplashAcid(newx, newy);
-      Store[x][y] = EL_ACID;
+      Feld[x][y] = Store[x][y];
+      Store[x][y] = 0;
+      TEST_DrawLevelField(x, y);
     }
-    else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
-    {
-      if (Feld[newx][newy] == EL_EXIT_OPEN)
-      {
-       RemoveField(x, y);
-       DrawLevelField(x, y);
+  }
+}
 
-       PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
-       if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
-         DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
+static void AmoebaDisappearing(int x, int y)
+{
+  static unsigned int sound_delay = 0;
+  static unsigned int sound_delay_value = 0;
 
-       local_player->friends_still_needed--;
-       if (!local_player->friends_still_needed &&
-           !local_player->GameOver && AllPlayersGone)
-         local_player->LevelSolved = local_player->GameOver = TRUE;
+  if (!MovDelay[x][y])         // start new shrinking cycle
+  {
+    MovDelay[x][y] = 7;
 
-       return;
-      }
-      else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
-      {
-       if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MF_MOVING)
-         DrawLevelField(newx, newy);
-       else
-         GfxDir[x][y] = MovDir[x][y] = MV_NONE;
-      }
-      else if (!IS_FREE(newx, newy))
-      {
-       GfxAction[x][y] = ACTION_WAITING;
+    if (DelayReached(&sound_delay, sound_delay_value))
+      sound_delay_value = 30;
+  }
 
-       if (IS_PLAYER(x, y))
-         DrawPlayerField(x, y);
-       else
-         DrawLevelField(x, y);
+  if (MovDelay[x][y])          // wait some time before shrinking
+  {
+    MovDelay[x][y]--;
+    if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+    {
+      int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
+                                          6 - MovDelay[x][y]);
 
-       return;
-      }
+      DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
     }
-    else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
+
+    if (!MovDelay[x][y])
     {
-      if (IS_FOOD_PIG(Feld[newx][newy]))
-      {
-       if (IS_MOVING(newx, newy))
-         RemoveMovingField(newx, newy);
-       else
-       {
-         Feld[newx][newy] = EL_EMPTY;
-         DrawLevelField(newx, newy);
-       }
+      Feld[x][y] = EL_EMPTY;
+      TEST_DrawLevelField(x, y);
 
-       PlayLevelSound(x, y, SND_PIG_DIGGING);
-      }
-      else if (!IS_FREE(newx, newy))
-      {
-       if (IS_PLAYER(x, y))
-         DrawPlayerField(x, y);
-       else
-         DrawLevelField(x, y);
+      // don't let mole enter this field in this cycle;
+      // (give priority to objects falling to this field from above)
+      Stop[x][y] = TRUE;
+    }
+  }
+}
+
+static void AmoebeAbleger(int ax, int ay)
+{
+  int i;
+  int element = Feld[ax][ay];
+  int graphic = el2img(element);
+  int newax = ax, neway = ay;
+  boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
+  static int xy[4][2] =
+  {
+    { 0, -1 },
+    { -1, 0 },
+    { +1, 0 },
+    { 0, +1 }
+  };
+
+  if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
+  {
+    Feld[ax][ay] = EL_AMOEBA_DEAD;
+    TEST_DrawLevelField(ax, ay);
+    return;
+  }
 
-       return;
-      }
-    }
-    else if (IS_CUSTOM_ELEMENT(element) &&
-            CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
-    {
-      int new_element = Feld[newx][newy];
+  if (IS_ANIMATED(graphic))
+    DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
 
-      if (!IS_FREE(newx, newy))
-      {
-       int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING :
-                     IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING :
-                     ACTION_BREAKING);
+  if (!MovDelay[ax][ay])       // start making new amoeba field
+    MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
 
-       /* no element can dig solid indestructible elements */
-       if (IS_INDESTRUCTIBLE(new_element) &&
-           !IS_DIGGABLE(new_element) &&
-           !IS_COLLECTIBLE(new_element))
-         return;
+  if (MovDelay[ax][ay])                // wait some time before making new amoeba
+  {
+    MovDelay[ax][ay]--;
+    if (MovDelay[ax][ay])
+      return;
+  }
 
-       if (AmoebaNr[newx][newy] &&
-           (new_element == EL_AMOEBA_FULL ||
-            new_element == EL_BD_AMOEBA ||
-            new_element == EL_AMOEBA_GROWING))
-       {
-         AmoebaCnt[AmoebaNr[newx][newy]]--;
-         AmoebaCnt2[AmoebaNr[newx][newy]]--;
-       }
+  if (can_drop)                        // EL_AMOEBA_WET or EL_EMC_DRIPPER
+  {
+    int start = RND(4);
+    int x = ax + xy[start][0];
+    int y = ay + xy[start][1];
 
-       if (IS_MOVING(newx, newy))
-         RemoveMovingField(newx, newy);
-       else
-       {
-         RemoveField(newx, newy);
-         DrawLevelField(newx, newy);
-       }
+    if (!IN_LEV_FIELD(x, y))
+      return;
 
-       /* if digged element was about to explode, prevent the explosion */
-       ExplodeField[newx][newy] = EX_TYPE_NONE;
+    if (IS_FREE(x, y) ||
+       CAN_GROW_INTO(Feld[x][y]) ||
+       Feld[x][y] == EL_QUICKSAND_EMPTY ||
+       Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
+    {
+      newax = x;
+      neway = y;
+    }
 
-       PlayLevelSoundAction(x, y, action);
-      }
+    if (newax == ax && neway == ay)
+      return;
+  }
+  else                         // normal or "filled" (BD style) amoeba
+  {
+    int start = RND(4);
+    boolean waiting_for_player = FALSE;
 
-      Store[newx][newy] = EL_EMPTY;
-#if 1
-      /* this makes it possible to leave the removed element again */
-      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
-       Store[newx][newy] = new_element;
-#else
-      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
-      {
-       int move_leave_element = element_info[element].move_leave_element;
+    for (i = 0; i < NUM_DIRECTIONS; i++)
+    {
+      int j = (start + i) % 4;
+      int x = ax + xy[j][0];
+      int y = ay + xy[j][1];
 
-       /* this makes it possible to leave the removed element again */
-       Store[newx][newy] = (move_leave_element == EL_TRIGGER_ELEMENT ?
-                            new_element : move_leave_element);
-      }
-#endif
+      if (!IN_LEV_FIELD(x, y))
+       continue;
 
-      if (move_pattern & MV_MAZE_RUNNER_STYLE)
+      if (IS_FREE(x, y) ||
+         CAN_GROW_INTO(Feld[x][y]) ||
+         Feld[x][y] == EL_QUICKSAND_EMPTY ||
+         Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
       {
-       RunnerVisit[x][y] = FrameCounter;
-       PlayerVisit[x][y] /= 8;         /* expire player visit path */
+       newax = x;
+       neway = y;
+       break;
       }
+      else if (IS_PLAYER(x, y))
+       waiting_for_player = TRUE;
     }
-    else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
-    {
-      if (!IS_FREE(newx, newy))
-      {
-       if (IS_PLAYER(x, y))
-         DrawPlayerField(x, y);
-       else
-         DrawLevelField(x, y);
 
-       return;
-      }
-      else
+    if (newax == ax && neway == ay)            // amoeba cannot grow
+    {
+      if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
       {
-       boolean wanna_flame = !RND(10);
-       int dx = newx - x, dy = newy - y;
-       int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
-       int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
-       int element1 = (IN_LEV_FIELD(newx1, newy1) ?
-                       MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
-       int element2 = (IN_LEV_FIELD(newx2, newy2) ?
-                       MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
+       Feld[ax][ay] = EL_AMOEBA_DEAD;
+       TEST_DrawLevelField(ax, ay);
+       AmoebaCnt[AmoebaNr[ax][ay]]--;
 
-       if ((wanna_flame ||
-            IS_CLASSIC_ENEMY(element1) ||
-            IS_CLASSIC_ENEMY(element2)) &&
-           element1 != EL_DRAGON && element2 != EL_DRAGON &&
-           element1 != EL_FLAMES && element2 != EL_FLAMES)
+       if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0)   // amoeba is completely dead
        {
-         ResetGfxAnimation(x, y);
-         GfxAction[x][y] = ACTION_ATTACKING;
-
-         if (IS_PLAYER(x, y))
-           DrawPlayerField(x, y);
-         else
-           DrawLevelField(x, y);
-
-         PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
-
-         MovDelay[x][y] = 50;
-
-         /* !!! */
-#if 0
-         RemoveField(newx, newy);
-#endif
-         Feld[newx][newy] = EL_FLAMES;
-         if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
-         {
-#if 0
-           RemoveField(newx1, newy1);
-#endif
-           Feld[newx1][newy1] = EL_FLAMES;
-         }
-         if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
-         {
-#if 0
-           RemoveField(newx2, newy2);
-#endif
-           Feld[newx2][newy2] = EL_FLAMES;
-         }
-
-         return;
+         if (element == EL_AMOEBA_FULL)
+           AmoebeUmwandeln(ax, ay);
+         else if (element == EL_BD_AMOEBA)
+           AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
        }
       }
+      return;
     }
-    else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
-            Feld[newx][newy] == EL_DIAMOND)
+    else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
     {
-      if (IS_MOVING(newx, newy))
-       RemoveMovingField(newx, newy);
-      else
-      {
-       Feld[newx][newy] = EL_EMPTY;
-       DrawLevelField(newx, newy);
-      }
+      // amoeba gets larger by growing in some direction
 
-      PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
-    }
-    else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
-            IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
-    {
-      if (AmoebaNr[newx][newy])
-      {
-       AmoebaCnt2[AmoebaNr[newx][newy]]--;
-       if (Feld[newx][newy] == EL_AMOEBA_FULL ||
-           Feld[newx][newy] == EL_BD_AMOEBA)
-         AmoebaCnt[AmoebaNr[newx][newy]]--;
-      }
+      int new_group_nr = AmoebaNr[ax][ay];
 
-#if 0
-      /* !!! test !!! */
-      if (IS_MOVING(newx, newy) || IS_BLOCKED(newx, newy))
-      {
-       RemoveMovingField(newx, newy);
-      }
-#else
-      if (IS_MOVING(newx, newy))
-      {
-       RemoveMovingField(newx, newy);
-      }
+#ifdef DEBUG
+  if (new_group_nr == 0)
+  {
+    printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
+    printf("AmoebeAbleger(): This should never happen!\n");
+    return;
+  }
 #endif
-      else
-      {
-       Feld[newx][newy] = EL_EMPTY;
-       DrawLevelField(newx, newy);
-      }
 
-      PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
-    }
-    else if ((element == EL_PACMAN || element == EL_MOLE)
-            && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
-    {
-      if (AmoebaNr[newx][newy])
+      AmoebaNr[newax][neway] = new_group_nr;
+      AmoebaCnt[new_group_nr]++;
+      AmoebaCnt2[new_group_nr]++;
+
+      // if amoeba touches other amoeba(s) after growing, unify them
+      AmoebenVereinigen(newax, neway);
+
+      if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
       {
-       AmoebaCnt2[AmoebaNr[newx][newy]]--;
-       if (Feld[newx][newy] == EL_AMOEBA_FULL ||
-           Feld[newx][newy] == EL_BD_AMOEBA)
-         AmoebaCnt[AmoebaNr[newx][newy]]--;
+       AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
+       return;
       }
+    }
+  }
 
-      if (element == EL_MOLE)
-      {
-       Feld[newx][newy] = EL_AMOEBA_SHRINKING;
-       PlayLevelSound(x, y, SND_MOLE_DIGGING);
+  if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
+      (neway == lev_fieldy - 1 && newax != ax))
+  {
+    Feld[newax][neway] = EL_AMOEBA_GROWING;    // creation of new amoeba
+    Store[newax][neway] = element;
+  }
+  else if (neway == ay || element == EL_EMC_DRIPPER)
+  {
+    Feld[newax][neway] = EL_AMOEBA_DROP;       // drop left/right of amoeba
 
-       ResetGfxAnimation(x, y);
-       GfxAction[x][y] = ACTION_DIGGING;
-       DrawLevelField(x, y);
+    PlayLevelSoundAction(newax, neway, ACTION_GROWING);
+  }
+  else
+  {
+    InitMovingField(ax, ay, MV_DOWN);          // drop dripping from amoeba
+    Feld[ax][ay] = EL_AMOEBA_DROPPING;
+    Store[ax][ay] = EL_AMOEBA_DROP;
+    ContinueMoving(ax, ay);
+    return;
+  }
 
-       MovDelay[newx][newy] = 0;       /* start amoeba shrinking delay */
+  TEST_DrawLevelField(newax, neway);
+}
 
-       return;                         /* wait for shrinking amoeba */
-      }
-      else     /* element == EL_PACMAN */
-      {
-       Feld[newx][newy] = EL_EMPTY;
-       DrawLevelField(newx, newy);
-       PlayLevelSound(x, y, SND_PACMAN_DIGGING);
-      }
-    }
-    else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
-            (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
-             (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
-    {
-      /* wait for shrinking amoeba to completely disappear */
+static void Life(int ax, int ay)
+{
+  int x1, y1, x2, y2;
+  int life_time = 40;
+  int element = Feld[ax][ay];
+  int graphic = el2img(element);
+  int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
+                        level.biomaze);
+  boolean changed = FALSE;
+
+  if (IS_ANIMATED(graphic))
+    DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
+
+  if (Stop[ax][ay])
+    return;
+
+  if (!MovDelay[ax][ay])       // start new "game of life" cycle
+    MovDelay[ax][ay] = life_time;
+
+  if (MovDelay[ax][ay])                // wait some time before next cycle
+  {
+    MovDelay[ax][ay]--;
+    if (MovDelay[ax][ay])
       return;
-    }
-    else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
-    {
-      /* object was running against a wall */
+  }
 
-      TurnRound(x, y);
+  for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
+  {
+    int xx = ax+x1, yy = ay+y1;
+    int old_element = Feld[xx][yy];
+    int num_neighbours = 0;
 
-#if 0
-      /* !!! NEW "CE_BLOCKED" STUFF !!! -- DOES NOT WORK YET... !!! */
-      if (move_pattern & MV_ANY_DIRECTION &&
-         move_pattern == MovDir[x][y])
-      {
-       int blocking_element =
-         (IN_LEV_FIELD(newx, newy) ? Feld[newx][newy] : BorderElement);
+    if (!IN_LEV_FIELD(xx, yy))
+      continue;
 
-       CheckElementChangeBySide(x, y, element, blocking_element, CE_BLOCKED,
-                                MovDir[x][y]);
+    for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
+    {
+      int x = xx+x2, y = yy+y2;
 
-       element = Feld[x][y];   /* element might have changed */
-      }
-#endif
+      if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
+       continue;
 
-      if (GFX_ELEMENT(element) != EL_SAND)     /* !!! FIX THIS (crumble) !!! */
-       DrawLevelElementAnimation(x, y, element);
+      boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
+      boolean is_neighbour = FALSE;
 
-      if (DONT_TOUCH(element))
-       TestIfBadThingTouchesPlayer(x, y);
+      if (level.use_life_bugs)
+       is_neighbour =
+         (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
+          (IS_FREE(x, y)                             &&  Stop[x][y]));
+      else
+       is_neighbour =
+         (Last[x][y] == element || is_player_cell);
 
-      return;
+      if (is_neighbour)
+       num_neighbours++;
     }
 
-    InitMovingField(x, y, MovDir[x][y]);
+    boolean is_free = FALSE;
 
-    PlayLevelSoundAction(x, y, ACTION_MOVING);
+    if (level.use_life_bugs)
+      is_free = (IS_FREE(xx, yy));
+    else
+      is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
+
+    if (xx == ax && yy == ay)          // field in the middle
+    {
+      if (num_neighbours < life_parameter[0] ||
+         num_neighbours > life_parameter[1])
+      {
+       Feld[xx][yy] = EL_EMPTY;
+       if (Feld[xx][yy] != old_element)
+         TEST_DrawLevelField(xx, yy);
+       Stop[xx][yy] = TRUE;
+       changed = TRUE;
+      }
+    }
+    else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
+    {                                  // free border field
+      if (num_neighbours >= life_parameter[2] &&
+         num_neighbours <= life_parameter[3])
+      {
+       Feld[xx][yy] = element;
+       MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
+       if (Feld[xx][yy] != old_element)
+         TEST_DrawLevelField(xx, yy);
+       Stop[xx][yy] = TRUE;
+       changed = TRUE;
+      }
+    }
   }
 
-  if (MovDir[x][y])
-    ContinueMoving(x, y);
+  if (changed)
+    PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
+                  SND_GAME_OF_LIFE_GROWING);
 }
 
-void ContinueMoving(int x, int y)
+static void InitRobotWheel(int x, int y)
 {
-  int element = Feld[x][y];
-  struct ElementInfo *ei = &element_info[element];
-  int direction = MovDir[x][y];
-  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-  int newx = x + dx, newy = y + dy;
-  int stored = Store[x][y];
-  int stored_new = Store[newx][newy];
-  boolean pushed_by_player   = (Pushed[x][y] && IS_PLAYER(x, y));
-  boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
-  boolean last_line = (newy == lev_fieldy - 1);
-
-  MovPos[x][y] += getElementMoveStepsize(x, y);
+  ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+}
 
-  if (pushed_by_player)        /* special case: moving object pushed by player */
-    MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
+static void RunRobotWheel(int x, int y)
+{
+  PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
+}
 
-  if (ABS(MovPos[x][y]) < TILEX)
+static void StopRobotWheel(int x, int y)
+{
+  if (game.robot_wheel_x == x &&
+      game.robot_wheel_y == y)
   {
-    DrawLevelField(x, y);
-
-    return;    /* element is still moving */
+    game.robot_wheel_x = -1;
+    game.robot_wheel_y = -1;
+    game.robot_wheel_active = FALSE;
   }
+}
+
+static void InitTimegateWheel(int x, int y)
+{
+  ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
+}
 
-  /* element reached destination field */
+static void RunTimegateWheel(int x, int y)
+{
+  PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
+}
 
-  Feld[x][y] = EL_EMPTY;
-  Feld[newx][newy] = element;
-  MovPos[x][y] = 0;    /* force "not moving" for "crumbled sand" */
+static void InitMagicBallDelay(int x, int y)
+{
+  ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
+}
 
-  if (Store[x][y] == EL_ACID)  /* element is moving into acid pool */
-  {
-    element = Feld[newx][newy] = EL_ACID;
-  }
-  else if (element == EL_MOLE)
-  {
-    Feld[x][y] = EL_SAND;
+static void ActivateMagicBall(int bx, int by)
+{
+  int x, y;
 
-    DrawLevelFieldCrumbledSandNeighbours(x, y);
-  }
-  else if (element == EL_QUICKSAND_FILLING)
-  {
-    element = Feld[newx][newy] = get_next_element(element);
-    Store[newx][newy] = Store[x][y];
-  }
-  else if (element == EL_QUICKSAND_EMPTYING)
-  {
-    Feld[x][y] = get_next_element(element);
-    element = Feld[newx][newy] = Store[x][y];
-  }
-  else if (element == EL_MAGIC_WALL_FILLING)
-  {
-    element = Feld[newx][newy] = get_next_element(element);
-    if (!game.magic_wall_active)
-      element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
-    Store[newx][newy] = Store[x][y];
-  }
-  else if (element == EL_MAGIC_WALL_EMPTYING)
+  if (level.ball_random)
   {
-    Feld[x][y] = get_next_element(element);
-    if (!game.magic_wall_active)
-      Feld[x][y] = EL_MAGIC_WALL_DEAD;
-    element = Feld[newx][newy] = Store[x][y];
+    int pos_border = RND(8);   // select one of the eight border elements
+    int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
+    int xx = pos_content % 3;
+    int yy = pos_content / 3;
 
-#if USE_NEW_CUSTOM_VALUE
-    InitField(newx, newy, FALSE);
-#endif
-  }
-  else if (element == EL_BD_MAGIC_WALL_FILLING)
-  {
-    element = Feld[newx][newy] = get_next_element(element);
-    if (!game.magic_wall_active)
-      element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
-    Store[newx][newy] = Store[x][y];
-  }
-  else if (element == EL_BD_MAGIC_WALL_EMPTYING)
-  {
-    Feld[x][y] = get_next_element(element);
-    if (!game.magic_wall_active)
-      Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
-    element = Feld[newx][newy] = Store[x][y];
+    x = bx - 1 + xx;
+    y = by - 1 + yy;
 
-#if USE_NEW_CUSTOM_VALUE
-    InitField(newx, newy, FALSE);
-#endif
-  }
-  else if (element == EL_AMOEBA_DROPPING)
-  {
-    Feld[x][y] = get_next_element(element);
-    element = Feld[newx][newy] = Store[x][y];
+    if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
+      CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
   }
-  else if (element == EL_SOKOBAN_OBJECT)
+  else
   {
-    if (Back[x][y])
-      Feld[x][y] = Back[x][y];
-
-    if (Back[newx][newy])
-      Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
+    for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
+    {
+      int xx = x - bx + 1;
+      int yy = y - by + 1;
 
-    Back[x][y] = Back[newx][newy] = 0;
+      if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
+       CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
+    }
   }
 
-  Store[x][y] = EL_EMPTY;
-  MovPos[x][y] = 0;
-  MovDir[x][y] = 0;
-  MovDelay[x][y] = 0;
-
-  MovDelay[newx][newy] = 0;
+  game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
+}
 
-#if 1
-  if (CAN_CHANGE_OR_HAS_ACTION(element))
-#else
-  if (CAN_CHANGE(element))
-#endif
+static void CheckExit(int x, int y)
+{
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
   {
-    /* copy element change control values to new field */
-    ChangeDelay[newx][newy] = ChangeDelay[x][y];
-    ChangePage[newx][newy]  = ChangePage[x][y];
-    ChangeCount[newx][newy] = ChangeCount[x][y];
-    ChangeEvent[newx][newy] = ChangeEvent[x][y];
-
-#if 0
-#if USE_NEW_CUSTOM_VALUE
-    CustomValue[newx][newy] = CustomValue[x][y];
-#endif
-#endif
-  }
+    int element = Feld[x][y];
+    int graphic = el2img(element);
 
-#if 1
-#if USE_NEW_CUSTOM_VALUE
-    CustomValue[newx][newy] = CustomValue[x][y];
-#endif
-#endif
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-  ChangeDelay[x][y] = 0;
-  ChangePage[x][y] = -1;
-  ChangeCount[x][y] = 0;
-  ChangeEvent[x][y] = -1;
+    return;
+  }
 
-#if USE_NEW_CUSTOM_VALUE
-  CustomValue[x][y] = 0;
-#endif
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
+    return;
 
-  /* copy animation control values to new field */
-  GfxFrame[newx][newy]  = GfxFrame[x][y];
-  GfxRandom[newx][newy] = GfxRandom[x][y];     /* keep same random value */
-  GfxAction[newx][newy] = GfxAction[x][y];     /* keep action one frame  */
-  GfxDir[newx][newy]    = GfxDir[x][y];                /* keep element direction */
+  Feld[x][y] = EL_EXIT_OPENING;
 
-  Pushed[x][y] = Pushed[newx][newy] = FALSE;
+  PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
+}
 
-  /* some elements can leave other elements behind after moving */
-  if (IS_CUSTOM_ELEMENT(element) && ei->move_leave_element != EL_EMPTY &&
-      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
-      (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
+static void CheckExitEM(int x, int y)
+{
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
   {
-    int move_leave_element = ei->move_leave_element;
-
-#if 1
-    /* this makes it possible to leave the removed element again */
-    if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
-      move_leave_element = stored;
-#else
-    /* this makes it possible to leave the removed element again */
-    if (ei->move_leave_type == LEAVE_TYPE_LIMITED &&
-        ei->move_leave_element == EL_TRIGGER_ELEMENT)
-      move_leave_element = stored;
-#endif
-
-    Feld[x][y] = move_leave_element;
-
-    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
-      MovDir[x][y] = direction;
-
-    InitField(x, y, FALSE);
+    int element = Feld[x][y];
+    int graphic = el2img(element);
 
-    if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-    if (ELEM_IS_PLAYER(move_leave_element))
-      RelocatePlayer(x, y, move_leave_element);
+    return;
   }
 
-  /* do this after checking for left-behind element */
-  ResetGfxAnimation(x, y);     /* reset animation values for old field */
-
-  if (!CAN_MOVE(element) ||
-      (CAN_FALL(element) && direction == MV_DOWN &&
-       (element == EL_SPRING ||
-       element_info[element].move_pattern == MV_WHEN_PUSHED ||
-       element_info[element].move_pattern == MV_WHEN_DROPPED)))
-    GfxDir[x][y] = MovDir[newx][newy] = 0;
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
+    return;
 
-  DrawLevelField(x, y);
-  DrawLevelField(newx, newy);
+  Feld[x][y] = EL_EM_EXIT_OPENING;
 
-  Stop[newx][newy] = TRUE;     /* ignore this element until the next frame */
+  PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
+}
 
-  /* prevent pushed element from moving on in pushed direction */
-  if (pushed_by_player && CAN_MOVE(element) &&
-      element_info[element].move_pattern & MV_ANY_DIRECTION &&
-      !(element_info[element].move_pattern & direction))
-    TurnRound(newx, newy);
+static void CheckExitSteel(int x, int y)
+{
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
+  {
+    int element = Feld[x][y];
+    int graphic = el2img(element);
 
-  /* prevent elements on conveyor belt from moving on in last direction */
-  if (pushed_by_conveyor && CAN_FALL(element) &&
-      direction & MV_HORIZONTAL)
-    MovDir[newx][newy] = 0;
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-  if (!pushed_by_player)
-  {
-    int nextx = newx + dx, nexty = newy + dy;
-    boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
+    return;
+  }
 
-    WasJustMoving[newx][newy] = 3;
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
+    return;
 
-    if (CAN_FALL(element) && direction == MV_DOWN)
-      WasJustFalling[newx][newy] = 3;
+  Feld[x][y] = EL_STEEL_EXIT_OPENING;
 
-    if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
-      CheckCollision[newx][newy] = 2;
-  }
+  PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
+}
 
-  if (DONT_TOUCH(element))     /* object may be nasty to player or others */
+static void CheckExitSteelEM(int x, int y)
+{
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
   {
-    TestIfBadThingTouchesPlayer(newx, newy);
-    TestIfBadThingTouchesFriend(newx, newy);
+    int element = Feld[x][y];
+    int graphic = el2img(element);
 
-    if (!IS_CUSTOM_ELEMENT(element))
-      TestIfBadThingTouchesOtherBadThing(newx, newy);
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+    return;
   }
-  else if (element == EL_PENGUIN)
-    TestIfFriendTouchesBadThing(newx, newy);
 
-  /* give the player one last chance (one more frame) to move away */
-  if (CAN_FALL(element) && direction == MV_DOWN &&
-      (last_line || (!IS_FREE(x, newy + 1) &&
-                    (!IS_PLAYER(x, newy + 1) ||
-                     game.engine_version < VERSION_IDENT(3,1,1,0)))))
-    Impact(x, newy);
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
+    return;
 
-  if (pushed_by_player && !game.use_change_when_pushing_bug)
+  Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
+
+  PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
+}
+
+static void CheckExitSP(int x, int y)
+{
+  if (game.gems_still_needed > 0)
   {
-    int push_side = MV_DIR_OPPOSITE(direction);
-    struct PlayerInfo *player = PLAYERINFO(x, y);
+    int element = Feld[x][y];
+    int graphic = el2img(element);
 
-    CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
-                              player->index_bit, push_side);
-    CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
-                                       player->index_bit, push_side);
-  }
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-  CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
+    return;
+  }
 
-  TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
+    return;
 
-  TestIfElementHitsCustomElement(newx, newy, direction);
-  TestIfPlayerTouchesCustomElement(newx, newy);
-  TestIfElementTouchesCustomElement(newx, newy);
+  Feld[x][y] = EL_SP_EXIT_OPENING;
 
-#if 1
-  if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
-      IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
-    CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
-                                     MV_DIR_OPPOSITE(direction));
-#endif
+  PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
 }
 
-int AmoebeNachbarNr(int ax, int ay)
+static void CloseAllOpenTimegates(void)
 {
-  int i;
-  int element = Feld[ax][ay];
-  int group_nr = 0;
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+  int x, y;
 
-  for (i = 0; i < NUM_DIRECTIONS; i++)
+  SCAN_PLAYFIELD(x, y)
   {
-    int x = ax + xy[i][0];
-    int y = ay + xy[i][1];
+    int element = Feld[x][y];
 
-    if (!IN_LEV_FIELD(x, y))
-      continue;
+    if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
+    {
+      Feld[x][y] = EL_TIMEGATE_CLOSING;
 
-    if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
-      group_nr = AmoebaNr[x][y];
+      PlayLevelSoundAction(x, y, ACTION_CLOSING);
+    }
   }
-
-  return group_nr;
 }
 
-void AmoebenVereinigen(int ax, int ay)
+static void DrawTwinkleOnField(int x, int y)
 {
-  int i, x, y, xx, yy;
-  int new_group_nr = AmoebaNr[ax][ay];
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+  if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
+    return;
 
-  if (new_group_nr == 0)
+  if (Feld[x][y] == EL_BD_DIAMOND)
     return;
 
-  for (i = 0; i < NUM_DIRECTIONS; i++)
+  if (MovDelay[x][y] == 0)     // next animation frame
+    MovDelay[x][y] = 11 * !GetSimpleRandom(500);
+
+  if (MovDelay[x][y] != 0)     // wait some time before next frame
   {
-    x = ax + xy[i][0];
-    y = ay + xy[i][1];
+    MovDelay[x][y]--;
 
-    if (!IN_LEV_FIELD(x, y))
-      continue;
+    DrawLevelElementAnimation(x, y, Feld[x][y]);
 
-    if ((Feld[x][y] == EL_AMOEBA_FULL ||
-        Feld[x][y] == EL_BD_AMOEBA ||
-        Feld[x][y] == EL_AMOEBA_DEAD) &&
-       AmoebaNr[x][y] != new_group_nr)
+    if (MovDelay[x][y] != 0)
     {
-      int old_group_nr = AmoebaNr[x][y];
-
-      if (old_group_nr == 0)
-       return;
-
-      AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
-      AmoebaCnt[old_group_nr] = 0;
-      AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
-      AmoebaCnt2[old_group_nr] = 0;
+      int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
+                                          10 - MovDelay[x][y]);
 
-#if 1
-      SCAN_PLAYFIELD(xx, yy)
-#else
-      for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++)
-#endif
-      {
-       if (AmoebaNr[xx][yy] == old_group_nr)
-         AmoebaNr[xx][yy] = new_group_nr;
-      }
+      DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
     }
   }
 }
 
-void AmoebeUmwandeln(int ax, int ay)
+static void MauerWaechst(int x, int y)
 {
-  int i, x, y;
+  int delay = 6;
 
-  if (Feld[ax][ay] == EL_AMOEBA_DEAD)
+  if (!MovDelay[x][y])         // next animation frame
+    MovDelay[x][y] = 3 * delay;
+
+  if (MovDelay[x][y])          // wait some time before next frame
   {
-    int group_nr = AmoebaNr[ax][ay];
+    MovDelay[x][y]--;
 
-#ifdef DEBUG
-    if (group_nr == 0)
+    if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
     {
-      printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
-      printf("AmoebeUmwandeln(): This should never happen!\n");
-      return;
+      int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
+      int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
+
+      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
     }
-#endif
 
-#if 1
-    SCAN_PLAYFIELD(x, y)
-#else
-    for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
+    if (!MovDelay[x][y])
     {
-      if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
+      if (MovDir[x][y] == MV_LEFT)
       {
-       AmoebaNr[x][y] = 0;
-       Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
+       if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
+         TEST_DrawLevelField(x - 1, y);
       }
-    }
-
-    PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
-                           SND_AMOEBA_TURNING_TO_GEM :
-                           SND_AMOEBA_TURNING_TO_ROCK));
-    Bang(ax, ay);
-  }
-  else
-  {
-    static int xy[4][2] =
-    {
-      { 0, -1 },
-      { -1, 0 },
-      { +1, 0 },
-      { 0, +1 }
-    };
-
-    for (i = 0; i < NUM_DIRECTIONS; i++)
-    {
-      x = ax + xy[i][0];
-      y = ay + xy[i][1];
-
-      if (!IN_LEV_FIELD(x, y))
-       continue;
-
-      if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
+      else if (MovDir[x][y] == MV_RIGHT)
       {
-       PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
-                             SND_AMOEBA_TURNING_TO_GEM :
-                             SND_AMOEBA_TURNING_TO_ROCK));
-       Bang(x, y);
+       if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
+         TEST_DrawLevelField(x + 1, y);
+      }
+      else if (MovDir[x][y] == MV_UP)
+      {
+       if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
+         TEST_DrawLevelField(x, y - 1);
+      }
+      else
+      {
+       if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
+         TEST_DrawLevelField(x, y + 1);
       }
+
+      Feld[x][y] = Store[x][y];
+      Store[x][y] = 0;
+      GfxDir[x][y] = MovDir[x][y] = MV_NONE;
+      TEST_DrawLevelField(x, y);
     }
   }
 }
 
-void AmoebeUmwandelnBD(int ax, int ay, int new_element)
+static void MauerAbleger(int ax, int ay)
 {
-  int x, y;
-  int group_nr = AmoebaNr[ax][ay];
-  boolean done = FALSE;
+  int element = Feld[ax][ay];
+  int graphic = el2img(element);
+  boolean oben_frei = FALSE, unten_frei = FALSE;
+  boolean links_frei = FALSE, rechts_frei = FALSE;
+  boolean oben_massiv = FALSE, unten_massiv = FALSE;
+  boolean links_massiv = FALSE, rechts_massiv = FALSE;
+  boolean new_wall = FALSE;
 
-#ifdef DEBUG
-  if (group_nr == 0)
-  {
-    printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
-    printf("AmoebeUmwandelnBD(): This should never happen!\n");
-    return;
-  }
-#endif
+  if (IS_ANIMATED(graphic))
+    DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
 
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
+  if (!MovDelay[ax][ay])       // start building new wall
+    MovDelay[ax][ay] = 6;
+
+  if (MovDelay[ax][ay])                // wait some time before building new wall
   {
-    if (AmoebaNr[x][y] == group_nr &&
-       (Feld[x][y] == EL_AMOEBA_DEAD ||
-        Feld[x][y] == EL_BD_AMOEBA ||
-        Feld[x][y] == EL_AMOEBA_GROWING))
-    {
-      AmoebaNr[x][y] = 0;
-      Feld[x][y] = new_element;
-      InitField(x, y, FALSE);
-      DrawLevelField(x, y);
-      done = TRUE;
-    }
+    MovDelay[ax][ay]--;
+    if (MovDelay[ax][ay])
+      return;
   }
 
-  if (done)
-    PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
-                           SND_BD_AMOEBA_TURNING_TO_ROCK :
-                           SND_BD_AMOEBA_TURNING_TO_GEM));
-}
-
-void AmoebeWaechst(int x, int y)
-{
-  static unsigned long sound_delay = 0;
-  static unsigned long sound_delay_value = 0;
+  if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
+    oben_frei = TRUE;
+  if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
+    unten_frei = TRUE;
+  if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
+    links_frei = TRUE;
+  if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
+    rechts_frei = TRUE;
 
-  if (!MovDelay[x][y])         /* start new growing cycle */
+  if (element == EL_EXPANDABLE_WALL_VERTICAL ||
+      element == EL_EXPANDABLE_WALL_ANY)
   {
-    MovDelay[x][y] = 7;
-
-    if (DelayReached(&sound_delay, sound_delay_value))
+    if (oben_frei)
     {
-      PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
-      sound_delay_value = 30;
+      Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
+      Store[ax][ay-1] = element;
+      GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
+      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
+       DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
+                   IMG_EXPANDABLE_WALL_GROWING_UP, 0);
+      new_wall = TRUE;
+    }
+    if (unten_frei)
+    {
+      Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
+      Store[ax][ay+1] = element;
+      GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
+      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
+       DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
+                   IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
+      new_wall = TRUE;
     }
   }
 
-  if (MovDelay[x][y])          /* wait some time before growing bigger */
+  if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
+      element == EL_EXPANDABLE_WALL_ANY ||
+      element == EL_EXPANDABLE_WALL ||
+      element == EL_BD_EXPANDABLE_WALL)
   {
-    MovDelay[x][y]--;
-    if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+    if (links_frei)
     {
-      int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
-                                          6 - MovDelay[x][y]);
-
-      DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
+      Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
+      Store[ax-1][ay] = element;
+      GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
+      if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
+       DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
+                   IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
+      new_wall = TRUE;
     }
 
-    if (!MovDelay[x][y])
+    if (rechts_frei)
     {
-      Feld[x][y] = Store[x][y];
-      Store[x][y] = 0;
-      DrawLevelField(x, y);
+      Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
+      Store[ax+1][ay] = element;
+      GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
+      if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
+       DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
+                   IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
+      new_wall = TRUE;
     }
   }
-}
-
-void AmoebaDisappearing(int x, int y)
-{
-  static unsigned long sound_delay = 0;
-  static unsigned long sound_delay_value = 0;
-
-  if (!MovDelay[x][y])         /* start new shrinking cycle */
-  {
-    MovDelay[x][y] = 7;
-
-    if (DelayReached(&sound_delay, sound_delay_value))
-      sound_delay_value = 30;
-  }
 
-  if (MovDelay[x][y])          /* wait some time before shrinking */
-  {
-    MovDelay[x][y]--;
-    if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
-    {
-      int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
-                                          6 - MovDelay[x][y]);
+  if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
+    TEST_DrawLevelField(ax, ay);
 
-      DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
-    }
+  if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
+    oben_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
+    unten_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
+    links_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
+    rechts_massiv = TRUE;
 
-    if (!MovDelay[x][y])
-    {
-      Feld[x][y] = EL_EMPTY;
-      DrawLevelField(x, y);
+  if (((oben_massiv && unten_massiv) ||
+       element == EL_EXPANDABLE_WALL_HORIZONTAL ||
+       element == EL_EXPANDABLE_WALL) &&
+      ((links_massiv && rechts_massiv) ||
+       element == EL_EXPANDABLE_WALL_VERTICAL))
+    Feld[ax][ay] = EL_WALL;
 
-      /* don't let mole enter this field in this cycle;
-        (give priority to objects falling to this field from above) */
-      Stop[x][y] = TRUE;
-    }
-  }
+  if (new_wall)
+    PlayLevelSoundAction(ax, ay, ACTION_GROWING);
 }
 
-void AmoebeAbleger(int ax, int ay)
+static void MauerAblegerStahl(int ax, int ay)
 {
-  int i;
   int element = Feld[ax][ay];
   int graphic = el2img(element);
-  int newax = ax, neway = ay;
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
-
-  if (!level.amoeba_speed)
-  {
-    Feld[ax][ay] = EL_AMOEBA_DEAD;
-    DrawLevelField(ax, ay);
-    return;
-  }
+  boolean oben_frei = FALSE, unten_frei = FALSE;
+  boolean links_frei = FALSE, rechts_frei = FALSE;
+  boolean oben_massiv = FALSE, unten_massiv = FALSE;
+  boolean links_massiv = FALSE, rechts_massiv = FALSE;
+  boolean new_wall = FALSE;
 
   if (IS_ANIMATED(graphic))
     DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
 
-  if (!MovDelay[ax][ay])       /* start making new amoeba field */
-    MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
+  if (!MovDelay[ax][ay])       // start building new wall
+    MovDelay[ax][ay] = 6;
 
-  if (MovDelay[ax][ay])                /* wait some time before making new amoeba */
+  if (MovDelay[ax][ay])                // wait some time before building new wall
   {
     MovDelay[ax][ay]--;
     if (MovDelay[ax][ay])
       return;
   }
 
-  if (element == EL_AMOEBA_WET)        /* object is an acid / amoeba drop */
-  {
-    int start = RND(4);
-    int x = ax + xy[start][0];
-    int y = ay + xy[start][1];
-
-    if (!IN_LEV_FIELD(x, y))
-      return;
+  if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
+    oben_frei = TRUE;
+  if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
+    unten_frei = TRUE;
+  if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
+    links_frei = TRUE;
+  if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
+    rechts_frei = TRUE;
 
-    if (IS_FREE(x, y) ||
-       CAN_GROW_INTO(Feld[x][y]) ||
-       Feld[x][y] == EL_QUICKSAND_EMPTY)
+  if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
+      element == EL_EXPANDABLE_STEELWALL_ANY)
+  {
+    if (oben_frei)
     {
-      newax = x;
-      neway = y;
+      Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax][ay-1] = element;
+      GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
+      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
+       DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
+      new_wall = TRUE;
+    }
+    if (unten_frei)
+    {
+      Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax][ay+1] = element;
+      GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
+      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
+       DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
+      new_wall = TRUE;
     }
-
-    if (newax == ax && neway == ay)
-      return;
   }
-  else                         /* normal or "filled" (BD style) amoeba */
-  {
-    int start = RND(4);
-    boolean waiting_for_player = FALSE;
 
-    for (i = 0; i < NUM_DIRECTIONS; i++)
+  if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
+      element == EL_EXPANDABLE_STEELWALL_ANY)
+  {
+    if (links_frei)
     {
-      int j = (start + i) % 4;
-      int x = ax + xy[j][0];
-      int y = ay + xy[j][1];
-
-      if (!IN_LEV_FIELD(x, y))
-       continue;
-
-      if (IS_FREE(x, y) ||
-         CAN_GROW_INTO(Feld[x][y]) ||
-         Feld[x][y] == EL_QUICKSAND_EMPTY)
-      {
-       newax = x;
-       neway = y;
-       break;
-      }
-      else if (IS_PLAYER(x, y))
-       waiting_for_player = TRUE;
+      Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax-1][ay] = element;
+      GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
+      if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
+       DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
+      new_wall = TRUE;
     }
 
-    if (newax == ax && neway == ay)            /* amoeba cannot grow */
+    if (rechts_frei)
     {
-      if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
-      {
-       Feld[ax][ay] = EL_AMOEBA_DEAD;
-       DrawLevelField(ax, ay);
-       AmoebaCnt[AmoebaNr[ax][ay]]--;
-
-       if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0)   /* amoeba is completely dead */
-       {
-         if (element == EL_AMOEBA_FULL)
-           AmoebeUmwandeln(ax, ay);
-         else if (element == EL_BD_AMOEBA)
-           AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
-       }
-      }
-      return;
+      Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax+1][ay] = element;
+      GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
+      if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
+       DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
+      new_wall = TRUE;
     }
-    else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
-    {
-      /* amoeba gets larger by growing in some direction */
+  }
 
-      int new_group_nr = AmoebaNr[ax][ay];
+  if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
+    oben_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
+    unten_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
+    links_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
+    rechts_massiv = TRUE;
 
-#ifdef DEBUG
-  if (new_group_nr == 0)
-  {
-    printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
-    printf("AmoebeAbleger(): This should never happen!\n");
-    return;
-  }
-#endif
+  if (((oben_massiv && unten_massiv) ||
+       element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
+      ((links_massiv && rechts_massiv) ||
+       element == EL_EXPANDABLE_STEELWALL_VERTICAL))
+    Feld[ax][ay] = EL_STEELWALL;
 
-      AmoebaNr[newax][neway] = new_group_nr;
-      AmoebaCnt[new_group_nr]++;
-      AmoebaCnt2[new_group_nr]++;
+  if (new_wall)
+    PlayLevelSoundAction(ax, ay, ACTION_GROWING);
+}
 
-      /* if amoeba touches other amoeba(s) after growing, unify them */
-      AmoebenVereinigen(newax, neway);
+static void CheckForDragon(int x, int y)
+{
+  int i, j;
+  boolean dragon_found = FALSE;
+  static int xy[4][2] =
+  {
+    { 0, -1 },
+    { -1, 0 },
+    { +1, 0 },
+    { 0, +1 }
+  };
 
-      if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    for (j = 0; j < 4; j++)
+    {
+      int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
+
+      if (IN_LEV_FIELD(xx, yy) &&
+         (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
       {
-       AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
-       return;
+       if (Feld[xx][yy] == EL_DRAGON)
+         dragon_found = TRUE;
       }
+      else
+       break;
     }
   }
 
-  if (element != EL_AMOEBA_WET || neway < ay || !IS_FREE(newax, neway) ||
-      (neway == lev_fieldy - 1 && newax != ax))
-  {
-    Feld[newax][neway] = EL_AMOEBA_GROWING;    /* creation of new amoeba */
-    Store[newax][neway] = element;
-  }
-  else if (neway == ay)
-  {
-    Feld[newax][neway] = EL_AMOEBA_DROP;       /* drop left/right of amoeba */
-
-    PlayLevelSoundAction(newax, neway, ACTION_GROWING);
-  }
-  else
+  if (!dragon_found)
   {
-    InitMovingField(ax, ay, MV_DOWN);          /* drop dripping from amoeba */
-    Feld[ax][ay] = EL_AMOEBA_DROPPING;
-    Store[ax][ay] = EL_AMOEBA_DROP;
-    ContinueMoving(ax, ay);
-    return;
+    for (i = 0; i < NUM_DIRECTIONS; i++)
+    {
+      for (j = 0; j < 3; j++)
+      {
+       int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
+  
+       if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
+       {
+         Feld[xx][yy] = EL_EMPTY;
+         TEST_DrawLevelField(xx, yy);
+       }
+       else
+         break;
+      }
+    }
   }
-
-  DrawLevelField(newax, neway);
 }
 
-void Life(int ax, int ay)
+static void InitBuggyBase(int x, int y)
 {
-  int x1, y1, x2, y2;
-#if 0
-  static int life[4] = { 2, 3, 3, 3 }; /* parameters for "game of life" */
-#endif
-  int life_time = 40;
-  int element = Feld[ax][ay];
-  int graphic = el2img(element);
-  int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
-                        level.biomaze);
-  boolean changed = FALSE;
-
-  if (IS_ANIMATED(graphic))
-    DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
-
-  if (Stop[ax][ay])
-    return;
+  int element = Feld[x][y];
+  int activating_delay = FRAMES_PER_SECOND / 4;
 
-  if (!MovDelay[ax][ay])       /* start new "game of life" cycle */
-    MovDelay[ax][ay] = life_time;
+  ChangeDelay[x][y] =
+    (element == EL_SP_BUGGY_BASE ?
+     2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
+     element == EL_SP_BUGGY_BASE_ACTIVATING ?
+     activating_delay :
+     element == EL_SP_BUGGY_BASE_ACTIVE ?
+     1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
+}
 
-  if (MovDelay[ax][ay])                /* wait some time before next cycle */
+static void WarnBuggyBase(int x, int y)
+{
+  int i;
+  static int xy[4][2] =
   {
-    MovDelay[ax][ay]--;
-    if (MovDelay[ax][ay])
-      return;
-  }
+    { 0, -1 },
+    { -1, 0 },
+    { +1, 0 },
+    { 0, +1 }
+  };
 
-  for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int xx = ax+x1, yy = ay+y1;
-    int nachbarn = 0;
-
-    if (!IN_LEV_FIELD(xx, yy))
-      continue;
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
 
-    for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
+    if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
     {
-      int x = xx+x2, y = yy+y2;
-
-      if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
-       continue;
-
-      if (((Feld[x][y] == element ||
-           (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y))) &&
-          !Stop[x][y]) ||
-         (IS_FREE(x, y) && Stop[x][y]))
-       nachbarn++;
-    }
+      PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
 
-    if (xx == ax && yy == ay)          /* field in the middle */
-    {
-      if (nachbarn < life_parameter[0] ||
-         nachbarn > life_parameter[1])
-      {
-       Feld[xx][yy] = EL_EMPTY;
-       if (!Stop[xx][yy])
-         DrawLevelField(xx, yy);
-       Stop[xx][yy] = TRUE;
-       changed = TRUE;
-      }
-    }
-    else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
-    {                                  /* free border field */
-      if (nachbarn >= life_parameter[2] &&
-         nachbarn <= life_parameter[3])
-      {
-       Feld[xx][yy] = element;
-       MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
-       if (!Stop[xx][yy])
-         DrawLevelField(xx, yy);
-       Stop[xx][yy] = TRUE;
-       changed = TRUE;
-      }
+      break;
     }
   }
-
-  if (changed)
-    PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
-                  SND_GAME_OF_LIFE_GROWING);
 }
 
-static void InitRobotWheel(int x, int y)
+static void InitTrap(int x, int y)
 {
-  ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+  ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
 }
 
-static void RunRobotWheel(int x, int y)
+static void ActivateTrap(int x, int y)
 {
-  PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
+  PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
 }
 
-static void StopRobotWheel(int x, int y)
+static void ChangeActiveTrap(int x, int y)
 {
-  if (ZX == x && ZY == y)
-    ZX = ZY = -1;
+  int graphic = IMG_TRAP_ACTIVE;
+
+  // if new animation frame was drawn, correct crumbled sand border
+  if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
+    TEST_DrawLevelFieldCrumbled(x, y);
 }
 
-static void InitTimegateWheel(int x, int y)
+static int getSpecialActionElement(int element, int number, int base_element)
 {
-  ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
+  return (element != EL_EMPTY ? element :
+         number != -1 ? base_element + number - 1 :
+         EL_EMPTY);
 }
 
-static void RunTimegateWheel(int x, int y)
+static int getModifiedActionNumber(int value_old, int operator, int operand,
+                                  int value_min, int value_max)
 {
-  PlayLevelSound(x, y, SND_TIMEGATE_SWITCH_ACTIVE);
+  int value_new = (operator == CA_MODE_SET      ? operand :
+                  operator == CA_MODE_ADD      ? value_old + operand :
+                  operator == CA_MODE_SUBTRACT ? value_old - operand :
+                  operator == CA_MODE_MULTIPLY ? value_old * operand :
+                  operator == CA_MODE_DIVIDE   ? value_old / MAX(1, operand) :
+                  operator == CA_MODE_MODULO   ? value_old % MAX(1, operand) :
+                  value_old);
+
+  return (value_new < value_min ? value_min :
+         value_new > value_max ? value_max :
+         value_new);
 }
 
-void CheckExit(int x, int y)
+static void ExecuteCustomElementAction(int x, int y, int element, int page)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
-      local_player->lights_still_needed > 0)
-  {
-    int element = Feld[x][y];
-    int graphic = el2img(element);
-
-    if (IS_ANIMATED(graphic))
-      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  int target_element = change->target_element;
+  int action_type = change->action_type;
+  int action_mode = change->action_mode;
+  int action_arg = change->action_arg;
+  int action_element = change->action_element;
+  int i;
 
+  if (!change->has_action)
     return;
-  }
 
-  if (AllPlayersGone)  /* do not re-open exit door closed after last player */
-    return;
+  // ---------- determine action paramater values -----------------------------
 
-  Feld[x][y] = EL_EXIT_OPENING;
+  int level_time_value =
+    (level.time > 0 ? TimeLeft :
+     TimePlayed);
 
-  PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
-}
+  int action_arg_element_raw =
+    (action_arg == CA_ARG_PLAYER_TRIGGER  ? change->actual_trigger_player :
+     action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
+     action_arg == CA_ARG_ELEMENT_TARGET  ? change->target_element :
+     action_arg == CA_ARG_ELEMENT_ACTION  ? change->action_element :
+     action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
+     action_arg == CA_ARG_INVENTORY_RM_TARGET  ? change->target_element :
+     action_arg == CA_ARG_INVENTORY_RM_ACTION  ? change->action_element :
+     EL_EMPTY);
+  int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
 
-void CheckExitSP(int x, int y)
-{
-  if (local_player->gems_still_needed > 0)
-  {
-    int element = Feld[x][y];
-    int graphic = el2img(element);
+  int action_arg_direction =
+    (action_arg >= CA_ARG_DIRECTION_LEFT &&
+     action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
+     action_arg == CA_ARG_DIRECTION_TRIGGER ?
+     change->actual_trigger_side :
+     action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
+     MV_DIR_OPPOSITE(change->actual_trigger_side) :
+     MV_NONE);
 
-    if (IS_ANIMATED(graphic))
-      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+  int action_arg_number_min =
+    (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
+     CA_ARG_MIN);
 
-    return;
-  }
+  int action_arg_number_max =
+    (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
+     action_type == CA_SET_LEVEL_GEMS ? 999 :
+     action_type == CA_SET_LEVEL_TIME ? 9999 :
+     action_type == CA_SET_LEVEL_SCORE ? 99999 :
+     action_type == CA_SET_CE_VALUE ? 9999 :
+     action_type == CA_SET_CE_SCORE ? 9999 :
+     CA_ARG_MAX);
 
-  if (AllPlayersGone)  /* do not re-open exit door closed after last player */
-    return;
+  int action_arg_number_reset =
+    (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
+     action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
+     action_type == CA_SET_LEVEL_TIME ? level.time :
+     action_type == CA_SET_LEVEL_SCORE ? 0 :
+     action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
+     action_type == CA_SET_CE_SCORE ? 0 :
+     0);
 
-  Feld[x][y] = EL_SP_EXIT_OPENING;
+  int action_arg_number =
+    (action_arg <= CA_ARG_MAX ? action_arg :
+     action_arg >= CA_ARG_SPEED_NOT_MOVING &&
+     action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
+     action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
+     action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
+     action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
+     action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
+     action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
+     action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
+     action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
+     action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
+     action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
+     action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
+     action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
+     action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
+     action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
+     action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
+     action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
+     action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
+     action_arg == CA_ARG_ELEMENT_NR_TARGET  ? change->target_element :
+     action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
+     action_arg == CA_ARG_ELEMENT_NR_ACTION  ? change->action_element :
+     -1);
 
-  PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
-}
+  int action_arg_number_old =
+    (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
+     action_type == CA_SET_LEVEL_TIME ? TimeLeft :
+     action_type == CA_SET_LEVEL_SCORE ? game.score :
+     action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
+     action_type == CA_SET_CE_SCORE ? ei->collect_score :
+     0);
 
-static void CloseAllOpenTimegates()
-{
-  int x, y;
+  int action_arg_number_new =
+    getModifiedActionNumber(action_arg_number_old,
+                           action_mode, action_arg_number,
+                           action_arg_number_min, action_arg_number_max);
 
-#if 1
-  SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
+  int trigger_player_bits =
+    (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
+     change->actual_trigger_player_bits : change->trigger_player);
+
+  int action_arg_player_bits =
+    (action_arg >= CA_ARG_PLAYER_1 &&
+     action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
+     action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
+     action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
+     PLAYER_BITS_ANY);
+
+  // ---------- execute action  -----------------------------------------------
+
+  switch (action_type)
   {
-    int element = Feld[x][y];
+    case CA_NO_ACTION:
+    {
+      return;
+    }
 
-    if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
+    // ---------- level actions  ----------------------------------------------
+
+    case CA_RESTART_LEVEL:
     {
-      Feld[x][y] = EL_TIMEGATE_CLOSING;
+      game.restart_level = TRUE;
 
-      PlayLevelSoundAction(x, y, ACTION_CLOSING);
+      break;
     }
-  }
-}
 
-void EdelsteinFunkeln(int x, int y)
-{
-  if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
-    return;
+    case CA_SHOW_ENVELOPE:
+    {
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_ENVELOPE_1);
+
+      if (IS_ENVELOPE(element))
+       local_player->show_envelope = element;
+
+      break;
+    }
+
+    case CA_SET_LEVEL_TIME:
+    {
+      if (level.time > 0)      // only modify limited time value
+      {
+       TimeLeft = action_arg_number_new;
+
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+       DisplayGameControlValues();
+
+       if (!TimeLeft && setup.time_limit)
+         for (i = 0; i < MAX_PLAYERS; i++)
+           KillPlayer(&stored_player[i]);
+      }
+
+      break;
+    }
+
+    case CA_SET_LEVEL_SCORE:
+    {
+      game.score = action_arg_number_new;
+
+      game_panel_controls[GAME_PANEL_SCORE].value = game.score;
+
+      DisplayGameControlValues();
+
+      break;
+    }
+
+    case CA_SET_LEVEL_GEMS:
+    {
+      game.gems_still_needed = action_arg_number_new;
+
+      game.snapshot.collected_item = TRUE;
 
-  if (Feld[x][y] == EL_BD_DIAMOND)
-    return;
+      game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
 
-  if (MovDelay[x][y] == 0)     /* next animation frame */
-    MovDelay[x][y] = 11 * !SimpleRND(500);
+      DisplayGameControlValues();
 
-  if (MovDelay[x][y] != 0)     /* wait some time before next frame */
-  {
-    MovDelay[x][y]--;
+      break;
+    }
 
-    if (setup.direct_draw && MovDelay[x][y])
-      SetDrawtoField(DRAW_BUFFERED);
+    case CA_SET_LEVEL_WIND:
+    {
+      game.wind_direction = action_arg_direction;
 
-    DrawLevelElementAnimation(x, y, Feld[x][y]);
+      break;
+    }
 
-    if (MovDelay[x][y] != 0)
+    case CA_SET_LEVEL_RANDOM_SEED:
     {
-      int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
-                                          10 - MovDelay[x][y]);
+      // ensure that setting a new random seed while playing is predictable
+      InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
 
-      DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
+      break;
+    }
 
-      if (setup.direct_draw)
-      {
-       int dest_x, dest_y;
+    // ---------- player actions  ---------------------------------------------
 
-       dest_x = FX + SCREENX(x) * TILEX;
-       dest_y = FY + SCREENY(y) * TILEY;
+    case CA_MOVE_PLAYER:
+    {
+      // automatically move to the next field in specified direction
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (trigger_player_bits & (1 << i))
+         stored_player[i].programmed_action = action_arg_direction;
 
-       BlitBitmap(drawto_field, window,
-                  dest_x, dest_y, TILEX, TILEY, dest_x, dest_y);
-       SetDrawtoField(DRAW_DIRECT);
-      }
+      break;
     }
-  }
-}
 
-void MauerWaechst(int x, int y)
-{
-  int delay = 6;
+    case CA_EXIT_PLAYER:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (action_arg_player_bits & (1 << i))
+         ExitPlayer(&stored_player[i]);
 
-  if (!MovDelay[x][y])         /* next animation frame */
-    MovDelay[x][y] = 3 * delay;
+      if (game.players_still_needed == 0)
+       LevelSolved();
 
-  if (MovDelay[x][y])          /* wait some time before next frame */
-  {
-    MovDelay[x][y]--;
+      break;
+    }
 
-    if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+    case CA_KILL_PLAYER:
     {
-      int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
-      int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (action_arg_player_bits & (1 << i))
+         KillPlayer(&stored_player[i]);
 
-      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+      break;
     }
 
-    if (!MovDelay[x][y])
+    case CA_SET_PLAYER_KEYS:
     {
-      if (MovDir[x][y] == MV_LEFT)
+      int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_KEY_1);
+
+      if (IS_KEY(element))
       {
-       if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
-         DrawLevelField(x - 1, y);
+       for (i = 0; i < MAX_PLAYERS; i++)
+       {
+         if (trigger_player_bits & (1 << i))
+         {
+           stored_player[i].key[KEY_NR(element)] = key_state;
+
+           DrawGameDoorValues();
+         }
+       }
       }
-      else if (MovDir[x][y] == MV_RIGHT)
+
+      break;
+    }
+
+    case CA_SET_PLAYER_SPEED:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
       {
-       if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
-         DrawLevelField(x + 1, y);
+       if (trigger_player_bits & (1 << i))
+       {
+         int move_stepsize = TILEX / stored_player[i].move_delay_value;
+
+         if (action_arg == CA_ARG_SPEED_FASTER &&
+             stored_player[i].cannot_move)
+         {
+           action_arg_number = STEPSIZE_VERY_SLOW;
+         }
+         else if (action_arg == CA_ARG_SPEED_SLOWER ||
+                  action_arg == CA_ARG_SPEED_FASTER)
+         {
+           action_arg_number = 2;
+           action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
+                          CA_MODE_MULTIPLY);
+         }
+         else if (action_arg == CA_ARG_NUMBER_RESET)
+         {
+           action_arg_number = level.initial_player_stepsize[i];
+         }
+
+         move_stepsize =
+           getModifiedActionNumber(move_stepsize,
+                                   action_mode,
+                                   action_arg_number,
+                                   action_arg_number_min,
+                                   action_arg_number_max);
+
+         SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
+       }
       }
-      else if (MovDir[x][y] == MV_UP)
+
+      break;
+    }
+
+    case CA_SET_PLAYER_SHIELD:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
       {
-       if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
-         DrawLevelField(x, y - 1);
+       if (trigger_player_bits & (1 << i))
+       {
+         if (action_arg == CA_ARG_SHIELD_OFF)
+         {
+           stored_player[i].shield_normal_time_left = 0;
+           stored_player[i].shield_deadly_time_left = 0;
+         }
+         else if (action_arg == CA_ARG_SHIELD_NORMAL)
+         {
+           stored_player[i].shield_normal_time_left = 999999;
+         }
+         else if (action_arg == CA_ARG_SHIELD_DEADLY)
+         {
+           stored_player[i].shield_normal_time_left = 999999;
+           stored_player[i].shield_deadly_time_left = 999999;
+         }
+       }
       }
-      else
+
+      break;
+    }
+
+    case CA_SET_PLAYER_GRAVITY:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
       {
-       if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
-         DrawLevelField(x, y + 1);
+       if (trigger_player_bits & (1 << i))
+       {
+         stored_player[i].gravity =
+           (action_arg == CA_ARG_GRAVITY_OFF    ? FALSE                     :
+            action_arg == CA_ARG_GRAVITY_ON     ? TRUE                      :
+            action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
+            stored_player[i].gravity);
+       }
       }
 
-      Feld[x][y] = Store[x][y];
-      Store[x][y] = 0;
-      GfxDir[x][y] = MovDir[x][y] = MV_NONE;
-      DrawLevelField(x, y);
+      break;
     }
-  }
-}
 
-void MauerAbleger(int ax, int ay)
-{
-  int element = Feld[ax][ay];
-  int graphic = el2img(element);
-  boolean oben_frei = FALSE, unten_frei = FALSE;
-  boolean links_frei = FALSE, rechts_frei = FALSE;
-  boolean oben_massiv = FALSE, unten_massiv = FALSE;
-  boolean links_massiv = FALSE, rechts_massiv = FALSE;
-  boolean new_wall = FALSE;
+    case CA_SET_PLAYER_ARTWORK:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (trigger_player_bits & (1 << i))
+       {
+         int artwork_element = action_arg_element;
 
-  if (IS_ANIMATED(graphic))
-    DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
+         if (action_arg == CA_ARG_ELEMENT_RESET)
+           artwork_element =
+             (level.use_artwork_element[i] ? level.artwork_element[i] :
+              stored_player[i].element_nr);
 
-  if (!MovDelay[ax][ay])       /* start building new wall */
-    MovDelay[ax][ay] = 6;
+         if (stored_player[i].artwork_element != artwork_element)
+           stored_player[i].Frame = 0;
 
-  if (MovDelay[ax][ay])                /* wait some time before building new wall */
-  {
-    MovDelay[ax][ay]--;
-    if (MovDelay[ax][ay])
-      return;
-  }
+         stored_player[i].artwork_element = artwork_element;
 
-  if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
-    oben_frei = TRUE;
-  if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
-    unten_frei = TRUE;
-  if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
-    links_frei = TRUE;
-  if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
-    rechts_frei = TRUE;
+         SetPlayerWaiting(&stored_player[i], FALSE);
 
-  if (element == EL_EXPANDABLE_WALL_VERTICAL ||
-      element == EL_EXPANDABLE_WALL_ANY)
-  {
-    if (oben_frei)
-    {
-      Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
-      Store[ax][ay-1] = element;
-      GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
-      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
-       DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
-                   IMG_EXPANDABLE_WALL_GROWING_UP, 0);
-      new_wall = TRUE;
-    }
-    if (unten_frei)
-    {
-      Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
-      Store[ax][ay+1] = element;
-      GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
-      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
-       DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
-                   IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
-      new_wall = TRUE;
-    }
-  }
+         // set number of special actions for bored and sleeping animation
+         stored_player[i].num_special_action_bored =
+           get_num_special_action(artwork_element,
+                                  ACTION_BORING_1, ACTION_BORING_LAST);
+         stored_player[i].num_special_action_sleeping =
+           get_num_special_action(artwork_element,
+                                  ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+       }
+      }
 
-  if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
-      element == EL_EXPANDABLE_WALL_ANY ||
-      element == EL_EXPANDABLE_WALL)
-  {
-    if (links_frei)
-    {
-      Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
-      Store[ax-1][ay] = element;
-      GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
-      if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
-       DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
-                   IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
-      new_wall = TRUE;
+      break;
     }
 
-    if (rechts_frei)
+    case CA_SET_PLAYER_INVENTORY:
     {
-      Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
-      Store[ax+1][ay] = element;
-      GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
-      if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
-       DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
-                   IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
-      new_wall = TRUE;
-    }
-  }
-
-  if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
-    DrawLevelField(ax, ay);
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int j, k;
 
-  if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
-    oben_massiv = TRUE;
-  if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
-    unten_massiv = TRUE;
-  if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
-    links_massiv = TRUE;
-  if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
-    rechts_massiv = TRUE;
+       if (trigger_player_bits & (1 << i))
+       {
+         int inventory_element = action_arg_element;
 
-  if (((oben_massiv && unten_massiv) ||
-       element == EL_EXPANDABLE_WALL_HORIZONTAL ||
-       element == EL_EXPANDABLE_WALL) &&
-      ((links_massiv && rechts_massiv) ||
-       element == EL_EXPANDABLE_WALL_VERTICAL))
-    Feld[ax][ay] = EL_WALL;
+         if (action_arg == CA_ARG_ELEMENT_TARGET ||
+             action_arg == CA_ARG_ELEMENT_TRIGGER ||
+             action_arg == CA_ARG_ELEMENT_ACTION)
+         {
+           int element = inventory_element;
+           int collect_count = element_info[element].collect_count_initial;
 
-  if (new_wall)
-    PlayLevelSoundAction(ax, ay, ACTION_GROWING);
-}
+           if (!IS_CUSTOM_ELEMENT(element))
+             collect_count = 1;
 
-void CheckForDragon(int x, int y)
-{
-  int i, j;
-  boolean dragon_found = FALSE;
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+           if (collect_count == 0)
+             player->inventory_infinite_element = element;
+           else
+             for (k = 0; k < collect_count; k++)
+               if (player->inventory_size < MAX_INVENTORY_SIZE)
+                 player->inventory_element[player->inventory_size++] =
+                   element;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
+                  action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
+                  action_arg == CA_ARG_INVENTORY_RM_ACTION)
+         {
+           if (player->inventory_infinite_element != EL_UNDEFINED &&
+               IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
+                                    action_arg_element_raw))
+             player->inventory_infinite_element = EL_UNDEFINED;
 
-  for (i = 0; i < NUM_DIRECTIONS; i++)
-  {
-    for (j = 0; j < 4; j++)
-    {
-      int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
+           for (k = 0, j = 0; j < player->inventory_size; j++)
+           {
+             if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
+                                       action_arg_element_raw))
+               player->inventory_element[k++] = player->inventory_element[j];
+           }
 
-      if (IN_LEV_FIELD(xx, yy) &&
-         (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
-      {
-       if (Feld[xx][yy] == EL_DRAGON)
-         dragon_found = TRUE;
+           player->inventory_size = k;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
+         {
+           if (player->inventory_size > 0)
+           {
+             for (j = 0; j < player->inventory_size - 1; j++)
+               player->inventory_element[j] = player->inventory_element[j + 1];
+
+             player->inventory_size--;
+           }
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
+         {
+           if (player->inventory_size > 0)
+             player->inventory_size--;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
+         {
+           player->inventory_infinite_element = EL_UNDEFINED;
+           player->inventory_size = 0;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RESET)
+         {
+           player->inventory_infinite_element = EL_UNDEFINED;
+           player->inventory_size = 0;
+
+           if (level.use_initial_inventory[i])
+           {
+             for (j = 0; j < level.initial_inventory_size[i]; j++)
+             {
+               int element = level.initial_inventory_content[i][j];
+               int collect_count = element_info[element].collect_count_initial;
+
+               if (!IS_CUSTOM_ELEMENT(element))
+                 collect_count = 1;
+
+               if (collect_count == 0)
+                 player->inventory_infinite_element = element;
+               else
+                 for (k = 0; k < collect_count; k++)
+                   if (player->inventory_size < MAX_INVENTORY_SIZE)
+                     player->inventory_element[player->inventory_size++] =
+                       element;
+             }
+           }
+         }
+       }
       }
-      else
-       break;
+
+      break;
     }
-  }
 
-  if (!dragon_found)
-  {
-    for (i = 0; i < NUM_DIRECTIONS; i++)
+    // ---------- CE actions  -------------------------------------------------
+
+    case CA_SET_CE_VALUE:
     {
-      for (j = 0; j < 3; j++)
+      int last_ce_value = CustomValue[x][y];
+
+      CustomValue[x][y] = action_arg_number_new;
+
+      if (CustomValue[x][y] != last_ce_value)
       {
-       int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
-  
-       if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
-       {
-         Feld[xx][yy] = EL_EMPTY;
-         DrawLevelField(xx, yy);
-       }
-       else
-         break;
+       CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
+       CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
+
+       if (CustomValue[x][y] == 0)
+       {
+         CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
+         CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
+       }
       }
-    }
-  }
-}
 
-static void InitBuggyBase(int x, int y)
-{
-  int element = Feld[x][y];
-  int activating_delay = FRAMES_PER_SECOND / 4;
+      break;
+    }
 
-  ChangeDelay[x][y] =
-    (element == EL_SP_BUGGY_BASE ?
-     2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
-     element == EL_SP_BUGGY_BASE_ACTIVATING ?
-     activating_delay :
-     element == EL_SP_BUGGY_BASE_ACTIVE ?
-     1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
-}
+    case CA_SET_CE_SCORE:
+    {
+      int last_ce_score = ei->collect_score;
 
-static void WarnBuggyBase(int x, int y)
-{
-  int i;
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+      ei->collect_score = action_arg_number_new;
 
-  for (i = 0; i < NUM_DIRECTIONS; i++)
-  {
-    int xx = x + xy[i][0], yy = y + xy[i][1];
+      if (ei->collect_score != last_ce_score)
+      {
+       CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
+       CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
 
-    if (IS_PLAYER(xx, yy))
-    {
-      PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
+       if (ei->collect_score == 0)
+       {
+         int xx, yy;
+
+         CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
+         CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
+
+         /*
+           This is a very special case that seems to be a mixture between
+           CheckElementChange() and CheckTriggeredElementChange(): while
+           the first one only affects single elements that are triggered
+           directly, the second one affects multiple elements in the playfield
+           that are triggered indirectly by another element. This is a third
+           case: Changing the CE score always affects multiple identical CEs,
+           so every affected CE must be checked, not only the single CE for
+           which the CE score was changed in the first place (as every instance
+           of that CE shares the same CE score, and therefore also can change)!
+         */
+         SCAN_PLAYFIELD(xx, yy)
+         {
+           if (Feld[xx][yy] == element)
+             CheckElementChange(xx, yy, element, EL_UNDEFINED,
+                                CE_SCORE_GETS_ZERO);
+         }
+       }
+      }
 
       break;
     }
-  }
-}
-
-static void InitTrap(int x, int y)
-{
-  ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
-}
 
-static void ActivateTrap(int x, int y)
-{
-  PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
-}
+    case CA_SET_CE_ARTWORK:
+    {
+      int artwork_element = action_arg_element;
+      boolean reset_frame = FALSE;
+      int xx, yy;
 
-static void ChangeActiveTrap(int x, int y)
-{
-  int graphic = IMG_TRAP_ACTIVE;
+      if (action_arg == CA_ARG_ELEMENT_RESET)
+       artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
+                          element);
 
-  /* if new animation frame was drawn, correct crumbled sand border */
-  if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
-    DrawLevelFieldCrumbledSand(x, y);
-}
+      if (ei->gfx_element != artwork_element)
+       reset_frame = TRUE;
 
-static int getSpecialActionElement(int element, int number, int base_element)
-{
-  return (element != EL_EMPTY ? element :
-         number != -1 ? base_element + number - 1 :
-         EL_EMPTY);
-}
+      ei->gfx_element = artwork_element;
 
-static int getModifiedActionNumber(int value_old, int operator, int operand,
-                                  int value_min, int value_max)
-{
-  int value_new = (operator == CA_MODE_SET      ? operand :
-                  operator == CA_MODE_ADD      ? value_old + operand :
-                  operator == CA_MODE_SUBTRACT ? value_old - operand :
-                  operator == CA_MODE_MULTIPLY ? value_old * operand :
-                  operator == CA_MODE_DIVIDE   ? value_old / MAX(1, operand) :
-                  operator == CA_MODE_MODULO   ? value_old % MAX(1, operand) :
-                  value_old);
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       if (Feld[xx][yy] == element)
+       {
+         if (reset_frame)
+         {
+           ResetGfxAnimation(xx, yy);
+           ResetRandomAnimationValue(xx, yy);
+         }
 
-  return (value_new < value_min ? value_min :
-         value_new > value_max ? value_max :
-         value_new);
-}
+         TEST_DrawLevelField(xx, yy);
+       }
+      }
 
-static void ExecuteCustomElementAction(int x, int y, int element, int page)
-{
-  struct ElementInfo *ei = &element_info[element];
-  struct ElementChangeInfo *change = &ei->change_page[page];
-  int action_type = change->action_type;
-  int action_mode = change->action_mode;
-  int action_arg = change->action_arg;
-  int i;
+      break;
+    }
 
-  if (!change->has_action)
-    return;
+    // ---------- engine actions  ---------------------------------------------
 
-  /* ---------- determine action paramater values -------------------------- */
+    case CA_SET_ENGINE_SCAN_MODE:
+    {
+      InitPlayfieldScanMode(action_arg);
 
-  int level_time_value =
-    (level.time > 0 ? TimeLeft :
-     TimePlayed);
+      break;
+    }
 
-  int action_arg_element =
-    (action_arg == CA_ARG_PLAYER_TRIGGER  ? change->actual_trigger_player :
-     action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
-     action_arg == CA_ARG_ELEMENT_TARGET  ? change->target_element :
-     EL_EMPTY);
+    default:
+      break;
+  }
+}
 
-  int action_arg_direction =
-    (action_arg >= CA_ARG_DIRECTION_LEFT &&
-     action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
-     action_arg == CA_ARG_DIRECTION_TRIGGER ?
-     change->actual_trigger_side :
-     action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
-     MV_DIR_OPPOSITE(change->actual_trigger_side) :
-     MV_NONE);
+static void CreateFieldExt(int x, int y, int element, boolean is_change)
+{
+  int old_element = Feld[x][y];
+  int new_element = GetElementFromGroupElement(element);
+  int previous_move_direction = MovDir[x][y];
+  int last_ce_value = CustomValue[x][y];
+  boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
+  boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
+  boolean add_player_onto_element = (new_element_is_player &&
+                                    new_element != EL_SOKOBAN_FIELD_PLAYER &&
+                                    IS_WALKABLE(old_element));
 
-  int action_arg_number_min =
-    (action_type == CA_SET_PLAYER_SPEED ? MOVE_STEPSIZE_MIN :
-     CA_ARG_MIN);
+  if (!add_player_onto_element)
+  {
+    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+      RemoveMovingField(x, y);
+    else
+      RemoveField(x, y);
 
-  int action_arg_number_max =
-    (action_type == CA_SET_PLAYER_SPEED ? MOVE_STEPSIZE_MAX :
-     action_type == CA_SET_LEVEL_GEMS ? 999 :
-     action_type == CA_SET_LEVEL_TIME ? 9999 :
-     action_type == CA_SET_LEVEL_SCORE ? 99999 :
-     action_type == CA_SET_CE_SCORE ? 9999 :
-     action_type == CA_SET_CE_VALUE ? 9999 :
-     CA_ARG_MAX);
+    Feld[x][y] = new_element;
 
-  int action_arg_number_reset =
-    (action_type == CA_SET_PLAYER_SPEED ? TILEX/game.initial_move_delay_value :
-     action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
-     action_type == CA_SET_LEVEL_TIME ? level.time :
-     action_type == CA_SET_LEVEL_SCORE ? 0 :
-     action_type == CA_SET_CE_SCORE ? 0 :
-#if 1
-     action_type == CA_SET_CE_VALUE ? GET_NEW_CUSTOM_VALUE(element) :
-#else
-     action_type == CA_SET_CE_VALUE ? ei->custom_value_initial :
-#endif
-     0);
+    if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = previous_move_direction;
 
-  int action_arg_number =
-    (action_arg <= CA_ARG_MAX ? action_arg :
-     action_arg >= CA_ARG_SPEED_NOT_MOVING &&
-     action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
-     action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
-     action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
-     action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
-     action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
-     action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
-#if USE_NEW_CUSTOM_VALUE
-     action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
-#else
-     action_arg == CA_ARG_NUMBER_CE_VALUE ? ei->custom_value_initial :
-#endif
-     action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
-     action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
-     action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
-     action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
-     action_arg == CA_ARG_ELEMENT_TARGET ? GET_NEW_CUSTOM_VALUE(change->target_element) :
-     action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_ce_value :
-     -1);
+    if (element_info[new_element].use_last_ce_value)
+      CustomValue[x][y] = last_ce_value;
 
-  int action_arg_number_old =
-    (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
-     action_type == CA_SET_LEVEL_TIME ? TimeLeft :
-     action_type == CA_SET_LEVEL_SCORE ? local_player->score :
-     action_type == CA_SET_CE_SCORE ? ei->collect_score :
-     action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
-     0);
+    InitField_WithBug1(x, y, FALSE);
 
-  int action_arg_number_new =
-    getModifiedActionNumber(action_arg_number_old,
-                           action_mode, action_arg_number,
-                           action_arg_number_min, action_arg_number_max);
+    new_element = Feld[x][y];  // element may have changed
 
-  int trigger_player_bits =
-    (change->actual_trigger_player >= EL_PLAYER_1 &&
-     change->actual_trigger_player <= EL_PLAYER_4 ?
-     (1 << (change->actual_trigger_player - EL_PLAYER_1)) :
-     PLAYER_BITS_ANY);
+    ResetGfxAnimation(x, y);
+    ResetRandomAnimationValue(x, y);
 
-  int action_arg_player_bits =
-    (action_arg >= CA_ARG_PLAYER_1 &&
-     action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
-     action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
-     PLAYER_BITS_ANY);
+    TEST_DrawLevelField(x, y);
 
-  /* ---------- execute action  -------------------------------------------- */
+    if (GFX_CRUMBLED(new_element))
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+  }
 
-  switch(action_type)
+  // check if element under the player changes from accessible to unaccessible
+  // (needed for special case of dropping element which then changes)
+  // (must be checked after creating new element for walkable group elements)
+  if (IS_PLAYER(x, y) && !player_explosion_protected &&
+      IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
   {
-    case CA_NO_ACTION:
-    {
-      return;
-    }
+    Bang(x, y);
 
-    /* ---------- level actions  ------------------------------------------- */
+    return;
+  }
 
-    case CA_RESTART_LEVEL:
-    {
-      game.restart_level = TRUE;
+  // "ChangeCount" not set yet to allow "entered by player" change one time
+  if (new_element_is_player)
+    RelocatePlayer(x, y, new_element);
 
-      break;
-    }
+  if (is_change)
+    ChangeCount[x][y]++;       // count number of changes in the same frame
 
-    case CA_SHOW_ENVELOPE:
-    {
-      int element = getSpecialActionElement(action_arg_element,
-                                           action_arg_number, EL_ENVELOPE_1);
+  TestIfBadThingTouchesPlayer(x, y);
+  TestIfPlayerTouchesCustomElement(x, y);
+  TestIfElementTouchesCustomElement(x, y);
+}
 
-      if (IS_ENVELOPE(element))
-       local_player->show_envelope = element;
+static void CreateField(int x, int y, int element)
+{
+  CreateFieldExt(x, y, element, FALSE);
+}
 
-      break;
-    }
+static void CreateElementFromChange(int x, int y, int element)
+{
+  element = GET_VALID_RUNTIME_ELEMENT(element);
 
-    case CA_SET_LEVEL_TIME:
-    {
-      if (level.time > 0)      /* only modify limited time value */
-      {
-       TimeLeft = action_arg_number_new;
+  if (game.engine_version >= VERSION_IDENT(3,2,0,7))
+  {
+    int old_element = Feld[x][y];
 
-       DrawGameValue_Time(TimeLeft);
+    // prevent changed element from moving in same engine frame
+    // unless both old and new element can either fall or move
+    if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
+       (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
+      Stop[x][y] = TRUE;
+  }
 
-       if (!TimeLeft && setup.time_limit)
-         for (i = 0; i < MAX_PLAYERS; i++)
-           KillPlayer(&stored_player[i]);
-      }
+  CreateFieldExt(x, y, element, TRUE);
+}
 
-      break;
-    }
+static boolean ChangeElement(int x, int y, int element, int page)
+{
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  int ce_value = CustomValue[x][y];
+  int ce_score = ei->collect_score;
+  int target_element;
+  int old_element = Feld[x][y];
 
-    case CA_SET_LEVEL_SCORE:
-    {
-      local_player->score = action_arg_number_new;
+  // always use default change event to prevent running into a loop
+  if (ChangeEvent[x][y] == -1)
+    ChangeEvent[x][y] = CE_DELAY;
+
+  if (ChangeEvent[x][y] == CE_DELAY)
+  {
+    // reset actual trigger element, trigger player and action element
+    change->actual_trigger_element = EL_EMPTY;
+    change->actual_trigger_player = EL_EMPTY;
+    change->actual_trigger_player_bits = CH_PLAYER_NONE;
+    change->actual_trigger_side = CH_SIDE_NONE;
+    change->actual_trigger_ce_value = 0;
+    change->actual_trigger_ce_score = 0;
+  }
 
-      DrawGameValue_Score(local_player->score);
+  // do not change elements more than a specified maximum number of changes
+  if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
+    return FALSE;
 
-      break;
-    }
+  ChangeCount[x][y]++;         // count number of changes in the same frame
 
-    case CA_SET_LEVEL_GEMS:
-    {
-      local_player->gems_still_needed = action_arg_number_new;
+  if (change->explode)
+  {
+    Bang(x, y);
 
-      DrawGameValue_Emeralds(local_player->gems_still_needed);
+    return TRUE;
+  }
 
-      break;
-    }
+  if (change->use_target_content)
+  {
+    boolean complete_replace = TRUE;
+    boolean can_replace[3][3];
+    int xx, yy;
 
-    case CA_SET_LEVEL_GRAVITY:
+    for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
     {
-      game.gravity = (action_arg == CA_ARG_GRAVITY_OFF    ? FALSE         :
-                     action_arg == CA_ARG_GRAVITY_ON     ? TRUE          :
-                     action_arg == CA_ARG_GRAVITY_TOGGLE ? !game.gravity :
-                     game.gravity);
-      break;
-    }
+      boolean is_empty;
+      boolean is_walkable;
+      boolean is_diggable;
+      boolean is_collectible;
+      boolean is_removable;
+      boolean is_destructible;
+      int ex = x + xx - 1;
+      int ey = y + yy - 1;
+      int content_element = change->target_content.e[xx][yy];
+      int e;
 
-    case CA_SET_LEVEL_WIND:
-    {
-      game.wind_direction = action_arg_direction;
+      can_replace[xx][yy] = TRUE;
 
-      break;
-    }
+      if (ex == x && ey == y)  // do not check changing element itself
+       continue;
 
-    /* ---------- player actions  ------------------------------------------ */
+      if (content_element == EL_EMPTY_SPACE)
+      {
+       can_replace[xx][yy] = FALSE;    // do not replace border with space
 
-    case CA_MOVE_PLAYER:
-    {
-      /* automatically move to the next field in specified direction */
-      for (i = 0; i < MAX_PLAYERS; i++)
-       if (trigger_player_bits & (1 << i))
-         stored_player[i].programmed_action = action_arg_direction;
+       continue;
+      }
 
-      break;
-    }
+      if (!IN_LEV_FIELD(ex, ey))
+      {
+       can_replace[xx][yy] = FALSE;
+       complete_replace = FALSE;
 
-    case CA_EXIT_PLAYER:
-    {
-      for (i = 0; i < MAX_PLAYERS; i++)
-       if (action_arg_player_bits & (1 << i))
-         stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE;
+       continue;
+      }
 
-      break;
-    }
+      e = Feld[ex][ey];
 
-    case CA_KILL_PLAYER:
-    {
-      for (i = 0; i < MAX_PLAYERS; i++)
-       if (action_arg_player_bits & (1 << i))
-         KillPlayer(&stored_player[i]);
+      if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+       e = MovingOrBlocked2Element(ex, ey);
 
-      break;
+      is_empty = (IS_FREE(ex, ey) ||
+                 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
+
+      is_walkable     = (is_empty || IS_WALKABLE(e));
+      is_diggable     = (is_empty || IS_DIGGABLE(e));
+      is_collectible  = (is_empty || IS_COLLECTIBLE(e));
+      is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
+      is_removable    = (is_diggable || is_collectible);
+
+      can_replace[xx][yy] =
+       (((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
+         (change->replace_when == CP_WHEN_WALKABLE     && is_walkable) ||
+         (change->replace_when == CP_WHEN_DIGGABLE     && is_diggable) ||
+         (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
+         (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
+         (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
+        !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
+
+      if (!can_replace[xx][yy])
+       complete_replace = FALSE;
     }
 
-    case CA_SET_PLAYER_KEYS:
+    if (!change->only_if_complete || complete_replace)
     {
-      int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
-      int element = getSpecialActionElement(action_arg_element,
-                                           action_arg_number, EL_KEY_1);
+      boolean something_has_changed = FALSE;
 
-      if (IS_KEY(element))
+      if (change->only_if_complete && change->use_random_replace &&
+         RND(100) < change->random_percentage)
+       return FALSE;
+
+      for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
       {
-       for (i = 0; i < MAX_PLAYERS; i++)
+       int ex = x + xx - 1;
+       int ey = y + yy - 1;
+       int content_element;
+
+       if (can_replace[xx][yy] && (!change->use_random_replace ||
+                                   RND(100) < change->random_percentage))
        {
-         if (trigger_player_bits & (1 << i))
-         {
-           stored_player[i].key[KEY_NR(element)] = key_state;
+         if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+           RemoveMovingField(ex, ey);
 
-           DrawGameValue_Keys(stored_player[i].key);
+         ChangeEvent[ex][ey] = ChangeEvent[x][y];
 
-           redraw_mask |= REDRAW_DOOR_1;
-         }
+         content_element = change->target_content.e[xx][yy];
+         target_element = GET_TARGET_ELEMENT(element, content_element, change,
+                                             ce_value, ce_score);
+
+         CreateElementFromChange(ex, ey, target_element);
+
+         something_has_changed = TRUE;
+
+         // for symmetry reasons, freeze newly created border elements
+         if (ex != x || ey != y)
+           Stop[ex][ey] = TRUE;        // no more moving in this frame
        }
       }
 
-      break;
+      if (something_has_changed)
+      {
+       PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
+      }
     }
+  }
+  else
+  {
+    target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
+                                       ce_value, ce_score);
 
-    case CA_SET_PLAYER_SPEED:
+    if (element == EL_DIAGONAL_GROWING ||
+       element == EL_DIAGONAL_SHRINKING)
     {
-      for (i = 0; i < MAX_PLAYERS; i++)
-      {
-       if (trigger_player_bits & (1 << i))
-       {
-         int move_stepsize = TILEX / stored_player[i].move_delay_value;
+      target_element = Store[x][y];
 
-         if (action_arg == CA_ARG_SPEED_SLOWER ||
-             action_arg == CA_ARG_SPEED_FASTER)
-         {
-           action_arg_number = 2;
-           action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
-                          CA_MODE_MULTIPLY);
-         }
+      Store[x][y] = EL_EMPTY;
+    }
 
-         move_stepsize =
-           getModifiedActionNumber(move_stepsize,
-                                   action_mode,
-                                   action_arg_number,
-                                   action_arg_number_min,
-                                   action_arg_number_max);
+    CreateElementFromChange(x, y, target_element);
 
-         /* make sure that value is power of 2 */
-         move_stepsize = (1 << log_2(move_stepsize));
+    PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+    PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
+  }
 
-         /* do no immediately change -- the player might just be moving */
-         stored_player[i].move_delay_value_next = TILEX / move_stepsize;
+  // this uses direct change before indirect change
+  CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
 
-         stored_player[i].cannot_move =
-           (action_arg == CA_ARG_SPEED_NOT_MOVING ? TRUE : FALSE);
-       }
-      }
+  return TRUE;
+}
 
-      break;
-    }
+static void HandleElementChange(int x, int y, int page)
+{
+  int element = MovingOrBlocked2Element(x, y);
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  boolean handle_action_before_change = FALSE;
 
-    case CA_SET_PLAYER_SHIELD:
-    {
-      for (i = 0; i < MAX_PLAYERS; i++)
-      {
-       if (trigger_player_bits & (1 << i))
-       {
-         if (action_arg == CA_ARG_SHIELD_OFF)
-         {
-           stored_player[i].shield_normal_time_left = 0;
-           stored_player[i].shield_deadly_time_left = 0;
-         }
-         else if (action_arg == CA_ARG_SHIELD_NORMAL)
-         {
-           stored_player[i].shield_normal_time_left = 999999;
-         }
-         else if (action_arg == CA_ARG_SHIELD_DEADLY)
-         {
-           stored_player[i].shield_normal_time_left = 999999;
-           stored_player[i].shield_deadly_time_left = 999999;
-         }
-       }
-      }
+#ifdef DEBUG
+  if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
+      !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
+  {
+    printf("\n\n");
+    printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
+          x, y, element, element_info[element].token_name);
+    printf("HandleElementChange(): This should never happen!\n");
+    printf("\n\n");
+  }
+#endif
+
+  // this can happen with classic bombs on walkable, changing elements
+  if (!CAN_CHANGE_OR_HAS_ACTION(element))
+  {
+    return;
+  }
 
-      break;
-    }
+  if (ChangeDelay[x][y] == 0)          // initialize element change
+  {
+    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
 
-    case CA_SET_PLAYER_ARTWORK:
+    if (change->can_change)
     {
-      for (i = 0; i < MAX_PLAYERS; i++)
+      // !!! not clear why graphic animation should be reset at all here !!!
+      // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
+      // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
+
+      /*
+       GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
+
+       When using an animation frame delay of 1 (this only happens with
+       "sp_zonk.moving.left/right" in the classic graphics), the default
+       (non-moving) animation shows wrong animation frames (while the
+       moving animation, like "sp_zonk.moving.left/right", is correct,
+       so this graphical bug never shows up with the classic graphics).
+       For an animation with 4 frames, this causes wrong frames 0,0,1,2
+       be drawn instead of the correct frames 0,1,2,3. This is caused by
+       "GfxFrame[][]" being reset *twice* (in two successive frames) after
+       an element change: First when the change delay ("ChangeDelay[][]")
+       counter has reached zero after decrementing, then a second time in
+       the next frame (after "GfxFrame[][]" was already incremented) when
+       "ChangeDelay[][]" is reset to the initial delay value again.
+
+       This causes frame 0 to be drawn twice, while the last frame won't
+       be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
+
+       As some animations may already be cleverly designed around this bug
+       (at least the "Snake Bite" snake tail animation does this), it cannot
+       simply be fixed here without breaking such existing animations.
+       Unfortunately, it cannot easily be detected if a graphics set was
+       designed "before" or "after" the bug was fixed. As a workaround,
+       a new graphics set option "game.graphics_engine_version" was added
+       to be able to specify the game's major release version for which the
+       graphics set was designed, which can then be used to decide if the
+       bugfix should be used (version 4 and above) or not (version 3 or
+       below, or if no version was specified at all, as with old sets).
+
+       (The wrong/fixed animation frames can be tested with the test level set
+       "test_gfxframe" and level "000", which contains a specially prepared
+       custom element at level position (x/y) == (11/9) which uses the zonk
+       animation mentioned above. Using "game.graphics_engine_version: 4"
+       fixes the wrong animation frames, showing the correct frames 0,1,2,3.
+       This can also be seen from the debug output for this test element.)
+      */
+
+      // when a custom element is about to change (for example by change delay),
+      // do not reset graphic animation when the custom element is moving
+      if (game.graphics_engine_version < 4 &&
+         !IS_MOVING(x, y))
       {
-       if (trigger_player_bits & (1 << i))
-       {
-         int artwork_element = action_arg_element;
-
-         if (action_arg == CA_ARG_ELEMENT_RESET)
-           artwork_element =
-             (level.use_artwork_element[i] ? level.artwork_element[i] :
-              stored_player[i].element_nr);
-
-         stored_player[i].artwork_element = artwork_element;
-
-         SetPlayerWaiting(&stored_player[i], FALSE);
-
-         /* set number of special actions for bored and sleeping animation */
-         stored_player[i].num_special_action_bored =
-           get_num_special_action(artwork_element,
-                                  ACTION_BORING_1, ACTION_BORING_LAST);
-         stored_player[i].num_special_action_sleeping =
-           get_num_special_action(artwork_element,
-                                  ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
-       }
+       ResetGfxAnimation(x, y);
+       ResetRandomAnimationValue(x, y);
       }
 
-      break;
+      if (change->pre_change_function)
+       change->pre_change_function(x, y);
     }
+  }
 
-    /* ---------- CE actions  ---------------------------------------------- */
+  ChangeDelay[x][y]--;
 
-    case CA_SET_CE_SCORE:
+  if (ChangeDelay[x][y] != 0)          // continue element change
+  {
+    if (change->can_change)
     {
-      ei->collect_score = action_arg_number_new;
+      int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
 
-      break;
-    }
+      if (IS_ANIMATED(graphic))
+       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-    case CA_SET_CE_VALUE:
+      if (change->change_function)
+       change->change_function(x, y);
+    }
+  }
+  else                                 // finish element change
+  {
+    if (ChangePage[x][y] != -1)                // remember page from delayed change
     {
-#if USE_NEW_CUSTOM_VALUE
-      int last_custom_value = CustomValue[x][y];
-
-      CustomValue[x][y] = action_arg_number_new;
-
-#if 0
-      printf("::: Count == %d\n", CustomValue[x][y]);
-#endif
+      page = ChangePage[x][y];
+      ChangePage[x][y] = -1;
 
-      if (CustomValue[x][y] == 0 && last_custom_value > 0)
-      {
-#if 0
-       printf("::: CE_VALUE_GETS_ZERO\n");
-#endif
+      change = &ei->change_page[page];
+    }
 
-       CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
-       CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
-      }
-#endif
+    if (IS_MOVING(x, y))               // never change a running system ;-)
+    {
+      ChangeDelay[x][y] = 1;           // try change after next move step
+      ChangePage[x][y] = page;         // remember page to use for change
 
-      break;
+      return;
     }
 
-    /* ---------- engine actions  ------------------------------------------ */
+    // special case: set new level random seed before changing element
+    if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
+      handle_action_before_change = TRUE;
 
-    case CA_SET_ENGINE_SCAN_MODE:
-    {
-      InitPlayfieldScanMode(action_arg);
+    if (change->has_action && handle_action_before_change)
+      ExecuteCustomElementAction(x, y, element, page);
 
-      break;
+    if (change->can_change)
+    {
+      if (ChangeElement(x, y, element, page))
+      {
+       if (change->post_change_function)
+         change->post_change_function(x, y);
+      }
     }
 
-    default:
-      break;
+    if (change->has_action && !handle_action_before_change)
+      ExecuteCustomElementAction(x, y, element, page);
   }
 }
 
-static void ChangeElementNowExt(struct ElementChangeInfo *change,
-                               int x, int y, int target_element)
+static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
+                                             int trigger_element,
+                                             int trigger_event,
+                                             int trigger_player,
+                                             int trigger_side,
+                                             int trigger_page)
 {
-  int previous_move_direction = MovDir[x][y];
-#if USE_NEW_CUSTOM_VALUE
-  int last_ce_value = CustomValue[x][y];
-#endif
-  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
-                       IS_WALKABLE(Feld[x][y]));
+  boolean change_done_any = FALSE;
+  int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
+  int i;
 
-  /* check if element under player changes from accessible to unaccessible
-     (needed for special case of dropping element which then changes) */
-  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
-      IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element))
-  {
-    Bang(x, y);
-    return;
-  }
+  if (!(trigger_events[trigger_element][trigger_event]))
+    return FALSE;
 
-  if (!add_player)
+  RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
-    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
-      RemoveMovingField(x, y);
-    else
-      RemoveField(x, y);
+    int element = EL_CUSTOM_START + i;
+    boolean change_done = FALSE;
+    int p;
 
-    Feld[x][y] = target_element;
+    if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+       !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+      continue;
 
-    ResetGfxAnimation(x, y);
-    ResetRandomAnimationValue(x, y);
+    for (p = 0; p < element_info[element].num_change_pages; p++)
+    {
+      struct ElementChangeInfo *change = &element_info[element].change_page[p];
 
-    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
-      MovDir[x][y] = previous_move_direction;
+      if (change->can_change_or_has_action &&
+         change->has_event[trigger_event] &&
+         change->trigger_side & trigger_side &&
+         change->trigger_player & trigger_player &&
+         change->trigger_page & trigger_page_bits &&
+         IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
+      {
+       change->actual_trigger_element = trigger_element;
+       change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
+       change->actual_trigger_player_bits = trigger_player;
+       change->actual_trigger_side = trigger_side;
+       change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
+       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
 
-#if USE_NEW_CUSTOM_VALUE
-    if (element_info[Feld[x][y]].use_last_ce_value)
-      CustomValue[x][y] = last_ce_value;
-#endif
+       if ((change->can_change && !change_done) || change->has_action)
+       {
+         int x, y;
 
-    InitField_WithBug1(x, y, FALSE);
+         SCAN_PLAYFIELD(x, y)
+         {
+           if (Feld[x][y] == element)
+           {
+             if (change->can_change && !change_done)
+             {
+               // if element already changed in this frame, not only prevent
+               // another element change (checked in ChangeElement()), but
+               // also prevent additional element actions for this element
 
-    DrawLevelField(x, y);
+               if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+                   !level.use_action_after_change_bug)
+                 continue;
 
-    if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
-  }
+               ChangeDelay[x][y] = 1;
+               ChangeEvent[x][y] = trigger_event;
+
+               HandleElementChange(x, y, p);
+             }
+             else if (change->has_action)
+             {
+               // if element already changed in this frame, not only prevent
+               // another element change (checked in ChangeElement()), but
+               // also prevent additional element actions for this element
 
-  /* "ChangeCount" not set yet to allow "entered by player" change one time */
-  if (ELEM_IS_PLAYER(target_element))
-    RelocatePlayer(x, y, target_element);
+               if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+                   !level.use_action_after_change_bug)
+                 continue;
 
-  ChangeCount[x][y]++;         /* count number of changes in the same frame */
+               ExecuteCustomElementAction(x, y, element, p);
+               PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+             }
+           }
+         }
 
-  TestIfBadThingTouchesPlayer(x, y);
-  TestIfPlayerTouchesCustomElement(x, y);
-  TestIfElementTouchesCustomElement(x, y);
+         if (change->can_change)
+         {
+           change_done = TRUE;
+           change_done_any = TRUE;
+         }
+       }
+      }
+    }
+  }
+
+  RECURSION_LOOP_DETECTION_END();
+
+  return change_done_any;
 }
 
-static boolean ChangeElementNow(int x, int y, int element, int page)
+static boolean CheckElementChangeExt(int x, int y,
+                                    int element,
+                                    int trigger_element,
+                                    int trigger_event,
+                                    int trigger_player,
+                                    int trigger_side)
 {
-  struct ElementChangeInfo *change = &element_info[element].change_page[page];
-  int target_element;
-  int old_element = Feld[x][y];
+  boolean change_done = FALSE;
+  int p;
 
-  /* always use default change event to prevent running into a loop */
-  if (ChangeEvent[x][y] == -1)
-    ChangeEvent[x][y] = CE_DELAY;
+  if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+      !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+    return FALSE;
 
-  if (ChangeEvent[x][y] == CE_DELAY)
+  if (Feld[x][y] == EL_BLOCKED)
   {
-    /* reset actual trigger element, trigger player and action element */
-    change->actual_trigger_element = EL_EMPTY;
-    change->actual_trigger_player = EL_PLAYER_1;
-    change->actual_trigger_side = CH_SIDE_NONE;
-    change->actual_trigger_ce_value = 0;
+    Blocked2Moving(x, y, &x, &y);
+    element = Feld[x][y];
   }
 
-  /* do not change elements more than a specified maximum number of changes */
-  if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
+  // check if element has already changed or is about to change after moving
+  if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
+       Feld[x][y] != element) ||
+
+      (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
+       (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
+       ChangePage[x][y] != -1)))
     return FALSE;
 
-  ChangeCount[x][y]++;         /* count number of changes in the same frame */
+  RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
+  for (p = 0; p < element_info[element].num_change_pages; p++)
+  {
+    struct ElementChangeInfo *change = &element_info[element].change_page[p];
+
+    /* check trigger element for all events where the element that is checked
+       for changing interacts with a directly adjacent element -- this is
+       different to element changes that affect other elements to change on the
+       whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
+    boolean check_trigger_element =
+      (trigger_event == CE_TOUCHING_X ||
+       trigger_event == CE_HITTING_X ||
+       trigger_event == CE_HIT_BY_X ||
+       trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
+
+    if (change->can_change_or_has_action &&
+       change->has_event[trigger_event] &&
+       change->trigger_side & trigger_side &&
+       change->trigger_player & trigger_player &&
+       (!check_trigger_element ||
+        IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
+    {
+      change->actual_trigger_element = trigger_element;
+      change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
+      change->actual_trigger_player_bits = trigger_player;
+      change->actual_trigger_side = trigger_side;
+      change->actual_trigger_ce_value = CustomValue[x][y];
+      change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+
+      // special case: trigger element not at (x,y) position for some events
+      if (check_trigger_element)
+      {
+       static struct
+       {
+         int dx, dy;
+       } move_xy[] =
+         {
+           {  0,  0 },
+           { -1,  0 },
+           { +1,  0 },
+           {  0,  0 },
+           {  0, -1 },
+           {  0,  0 }, { 0, 0 }, { 0, 0 },
+           {  0, +1 }
+         };
+
+       int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
+       int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
+
+       change->actual_trigger_ce_value = CustomValue[xx][yy];
+       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+      }
+
+      if (change->can_change && !change_done)
+      {
+       ChangeDelay[x][y] = 1;
+       ChangeEvent[x][y] = trigger_event;
+
+       HandleElementChange(x, y, p);
+
+       change_done = TRUE;
+      }
+      else if (change->has_action)
+      {
+       ExecuteCustomElementAction(x, y, element, p);
+       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+      }
+    }
+  }
+
+  RECURSION_LOOP_DETECTION_END();
+
+  return change_done;
+}
+
+static void PlayPlayerSound(struct PlayerInfo *player)
+{
+  int jx = player->jx, jy = player->jy;
+  int sound_element = player->artwork_element;
+  int last_action = player->last_action_waiting;
+  int action = player->action_waiting;
 
-  if (change->explode)
+  if (player->is_waiting)
   {
-    Bang(x, y);
-
-    return TRUE;
+    if (action != last_action)
+      PlayLevelSoundElementAction(jx, jy, sound_element, action);
+    else
+      PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
   }
-
-  if (change->use_target_content)
+  else
   {
-    boolean complete_replace = TRUE;
-    boolean can_replace[3][3];
-    int xx, yy;
+    if (action != last_action)
+      StopSound(element_info[sound_element].sound[last_action]);
 
-    for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
-    {
-      boolean is_empty;
-      boolean is_walkable;
-      boolean is_diggable;
-      boolean is_collectible;
-      boolean is_removable;
-      boolean is_destructible;
-      int ex = x + xx - 1;
-      int ey = y + yy - 1;
-      int content_element = change->target_content.e[xx][yy];
-      int e;
+    if (last_action == ACTION_SLEEPING)
+      PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
+  }
+}
 
-      can_replace[xx][yy] = TRUE;
+static void PlayAllPlayersSound(void)
+{
+  int i;
 
-      if (ex == x && ey == y)  /* do not check changing element itself */
-       continue;
+  for (i = 0; i < MAX_PLAYERS; i++)
+    if (stored_player[i].active)
+      PlayPlayerSound(&stored_player[i]);
+}
 
-      if (content_element == EL_EMPTY_SPACE)
-      {
-       can_replace[xx][yy] = FALSE;    /* do not replace border with space */
+static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
+{
+  boolean last_waiting = player->is_waiting;
+  int move_dir = player->MovDir;
 
-       continue;
-      }
+  player->dir_waiting = move_dir;
+  player->last_action_waiting = player->action_waiting;
 
-      if (!IN_LEV_FIELD(ex, ey))
-      {
-       can_replace[xx][yy] = FALSE;
-       complete_replace = FALSE;
+  if (is_waiting)
+  {
+    if (!last_waiting)         // not waiting -> waiting
+    {
+      player->is_waiting = TRUE;
 
-       continue;
-      }
+      player->frame_counter_bored =
+       FrameCounter +
+       game.player_boring_delay_fixed +
+       GetSimpleRandom(game.player_boring_delay_random);
+      player->frame_counter_sleeping =
+       FrameCounter +
+       game.player_sleeping_delay_fixed +
+       GetSimpleRandom(game.player_sleeping_delay_random);
 
-      e = Feld[ex][ey];
+      InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
+    }
 
-      if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
-       e = MovingOrBlocked2Element(ex, ey);
+    if (game.player_sleeping_delay_fixed +
+       game.player_sleeping_delay_random > 0 &&
+       player->anim_delay_counter == 0 &&
+       player->post_delay_counter == 0 &&
+       FrameCounter >= player->frame_counter_sleeping)
+      player->is_sleeping = TRUE;
+    else if (game.player_boring_delay_fixed +
+            game.player_boring_delay_random > 0 &&
+            FrameCounter >= player->frame_counter_bored)
+      player->is_bored = TRUE;
 
-      is_empty = (IS_FREE(ex, ey) ||
-                 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
+    player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
+                             player->is_bored ? ACTION_BORING :
+                             ACTION_WAITING);
 
-      is_walkable     = (is_empty || IS_WALKABLE(e));
-      is_diggable     = (is_empty || IS_DIGGABLE(e));
-      is_collectible  = (is_empty || IS_COLLECTIBLE(e));
-      is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
-      is_removable    = (is_diggable || is_collectible);
+    if (player->is_sleeping && player->use_murphy)
+    {
+      // special case for sleeping Murphy when leaning against non-free tile
 
-      can_replace[xx][yy] =
-       (((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
-         (change->replace_when == CP_WHEN_WALKABLE     && is_walkable) ||
-         (change->replace_when == CP_WHEN_DIGGABLE     && is_diggable) ||
-         (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
-         (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
-         (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
-        !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
+      if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
+         (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
+          !IS_MOVING(player->jx - 1, player->jy)))
+       move_dir = MV_LEFT;
+      else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
+              (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
+               !IS_MOVING(player->jx + 1, player->jy)))
+       move_dir = MV_RIGHT;
+      else
+       player->is_sleeping = FALSE;
 
-      if (!can_replace[xx][yy])
-       complete_replace = FALSE;
+      player->dir_waiting = move_dir;
     }
 
-    if (!change->only_if_complete || complete_replace)
+    if (player->is_sleeping)
     {
-      boolean something_has_changed = FALSE;
-
-      if (change->only_if_complete && change->use_random_replace &&
-         RND(100) < change->random_percentage)
-       return FALSE;
-
-      for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+      if (player->num_special_action_sleeping > 0)
       {
-       int ex = x + xx - 1;
-       int ey = y + yy - 1;
-       int content_element;
-
-       if (can_replace[xx][yy] && (!change->use_random_replace ||
-                                   RND(100) < change->random_percentage))
+       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
        {
-         if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
-           RemoveMovingField(ex, ey);
+         int last_special_action = player->special_action_sleeping;
+         int num_special_action = player->num_special_action_sleeping;
+         int special_action =
+           (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
+            last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
+            last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
+            last_special_action + 1 : ACTION_SLEEPING);
+         int special_graphic =
+           el_act_dir2img(player->artwork_element, special_action, move_dir);
 
-         ChangeEvent[ex][ey] = ChangeEvent[x][y];
+         player->anim_delay_counter =
+           graphic_info[special_graphic].anim_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
+         player->post_delay_counter =
+           graphic_info[special_graphic].post_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
 
-         content_element = change->target_content.e[xx][yy];
-         target_element = GET_TARGET_ELEMENT(content_element, change);
+         player->special_action_sleeping = special_action;
+       }
 
-         ChangeElementNowExt(change, ex, ey, target_element);
+       if (player->anim_delay_counter > 0)
+       {
+         player->action_waiting = player->special_action_sleeping;
+         player->anim_delay_counter--;
+       }
+       else if (player->post_delay_counter > 0)
+       {
+         player->post_delay_counter--;
+       }
+      }
+    }
+    else if (player->is_bored)
+    {
+      if (player->num_special_action_bored > 0)
+      {
+       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
+       {
+         int special_action =
+           ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
+         int special_graphic =
+           el_act_dir2img(player->artwork_element, special_action, move_dir);
 
-         something_has_changed = TRUE;
+         player->anim_delay_counter =
+           graphic_info[special_graphic].anim_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
+         player->post_delay_counter =
+           graphic_info[special_graphic].post_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
 
-         /* for symmetry reasons, freeze newly created border elements */
-         if (ex != x || ey != y)
-           Stop[ex][ey] = TRUE;        /* no more moving in this frame */
+         player->special_action_bored = special_action;
        }
-      }
 
-      if (something_has_changed)
-      {
-       PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
-       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
+       if (player->anim_delay_counter > 0)
+       {
+         player->action_waiting = player->special_action_bored;
+         player->anim_delay_counter--;
+       }
+       else if (player->post_delay_counter > 0)
+       {
+         player->post_delay_counter--;
+       }
       }
     }
   }
-  else
+  else if (last_waiting)       // waiting -> not waiting
   {
-    target_element = GET_TARGET_ELEMENT(change->target_element, change);
+    player->is_waiting = FALSE;
+    player->is_bored = FALSE;
+    player->is_sleeping = FALSE;
 
-    ChangeElementNowExt(change, x, y, target_element);
+    player->frame_counter_bored = -1;
+    player->frame_counter_sleeping = -1;
 
-    PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
-    PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
-  }
+    player->anim_delay_counter = 0;
+    player->post_delay_counter = 0;
 
-  /* this uses direct change before indirect change */
-  CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
+    player->dir_waiting = player->MovDir;
+    player->action_waiting = ACTION_DEFAULT;
 
-  return TRUE;
+    player->special_action_bored = ACTION_DEFAULT;
+    player->special_action_sleeping = ACTION_DEFAULT;
+  }
 }
 
-#if USE_NEW_DELAYED_ACTION
-
-static void ChangeElement(int x, int y, int page)
+static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
 {
-  int element = MovingOrBlocked2Element(x, y);
-  struct ElementInfo *ei = &element_info[element];
-  struct ElementChangeInfo *change = &ei->change_page[page];
-
-#ifdef DEBUG
-  if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
-      !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
-  {
-    printf("\n\n");
-    printf("ChangeElement(): %d,%d: element = %d ('%s')\n",
-          x, y, element, element_info[element].token_name);
-    printf("ChangeElement(): This should never happen!\n");
-    printf("\n\n");
-  }
-#endif
-
-  /* this can happen with classic bombs on walkable, changing elements */
-  if (!CAN_CHANGE_OR_HAS_ACTION(element))
+  if ((!player->is_moving  && player->was_moving) ||
+      (player->MovPos == 0 && player->was_moving) ||
+      (player->is_snapping && !player->was_snapping) ||
+      (player->is_dropping && !player->was_dropping))
   {
-#if 0
-    if (!CAN_CHANGE(Back[x][y]))       /* prevent permanent repetition */
-      ChangeDelay[x][y] = 0;
-#endif
+    if (!CheckSaveEngineSnapshotToList())
+      return;
 
-    return;
+    player->was_moving = FALSE;
+    player->was_snapping = TRUE;
+    player->was_dropping = TRUE;
   }
-
-  if (ChangeDelay[x][y] == 0)          /* initialize element change */
+  else
   {
-    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
+    if (player->is_moving)
+      player->was_moving = TRUE;
 
-    if (change->can_change)
-    {
-      ResetGfxAnimation(x, y);
-      ResetRandomAnimationValue(x, y);
+    if (!player->is_snapping)
+      player->was_snapping = FALSE;
 
-      if (change->pre_change_function)
-       change->pre_change_function(x, y);
-    }
+    if (!player->is_dropping)
+      player->was_dropping = FALSE;
   }
+}
 
-  ChangeDelay[x][y]--;
-
-  if (ChangeDelay[x][y] != 0)          /* continue element change */
+static void CheckSingleStepMode(struct PlayerInfo *player)
+{
+  if (tape.single_step && tape.recording && !tape.pausing)
   {
-    if (change->can_change)
-    {
-      int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+    /* as it is called "single step mode", just return to pause mode when the
+       player stopped moving after one tile (or never starts moving at all) */
+    if (!player->is_moving &&
+       !player->is_pushing &&
+       !player->is_dropping_pressed)
+      TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+  }
 
-      if (IS_ANIMATED(graphic))
-       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+  CheckSaveEngineSnapshot(player);
+}
 
-      if (change->change_function)
-       change->change_function(x, y);
-    }
-  }
-  else                                 /* finish element change */
-  {
-    if (ChangePage[x][y] != -1)                /* remember page from delayed change */
-    {
-      page = ChangePage[x][y];
-      ChangePage[x][y] = -1;
+static byte PlayerActions(struct PlayerInfo *player, byte player_action)
+{
+  int left     = player_action & JOY_LEFT;
+  int right    = player_action & JOY_RIGHT;
+  int up       = player_action & JOY_UP;
+  int down     = player_action & JOY_DOWN;
+  int button1  = player_action & JOY_BUTTON_1;
+  int button2  = player_action & JOY_BUTTON_2;
+  int dx       = (left ? -1 : right ? 1 : 0);
+  int dy       = (up   ? -1 : down  ? 1 : 0);
 
-      change = &ei->change_page[page];
-    }
+  if (!player->active || tape.pausing)
+    return 0;
 
-    if (IS_MOVING(x, y))               /* never change a running system ;-) */
+  if (player_action)
+  {
+    if (button1)
+      SnapField(player, dx, dy);
+    else
     {
-      ChangeDelay[x][y] = 1;           /* try change after next move step */
-      ChangePage[x][y] = page;         /* remember page to use for change */
+      if (button2)
+       DropElement(player);
 
-      return;
+      MovePlayer(player, dx, dy);
     }
 
-    if (change->can_change)
-    {
-      if (ChangeElementNow(x, y, element, page))
-      {
-       if (change->post_change_function)
-         change->post_change_function(x, y);
-      }
-    }
+    CheckSingleStepMode(player);
 
-    if (change->has_action)
-      ExecuteCustomElementAction(x, y, element, page);
+    SetPlayerWaiting(player, FALSE);
+
+    return player_action;
   }
-}
+  else
+  {
+    // no actions for this player (no input at player's configured device)
 
-#else
+    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
+    SnapField(player, 0, 0);
+    CheckGravityMovementWhenNotMoving(player);
 
-static void ChangeElement(int x, int y, int page)
-{
-  int element = MovingOrBlocked2Element(x, y);
-  struct ElementInfo *ei = &element_info[element];
-  struct ElementChangeInfo *change = &ei->change_page[page];
+    if (player->MovPos == 0)
+      SetPlayerWaiting(player, TRUE);
 
-#ifdef DEBUG
-  if (!CAN_CHANGE(element) && !CAN_CHANGE(Back[x][y]))
-  {
-    printf("\n\n");
-    printf("ChangeElement(): %d,%d: element = %d ('%s')\n",
-          x, y, element, element_info[element].token_name);
-    printf("ChangeElement(): This should never happen!\n");
-    printf("\n\n");
-  }
-#endif
+    if (player->MovPos == 0)   // needed for tape.playing
+      player->is_moving = FALSE;
 
-  /* this can happen with classic bombs on walkable, changing elements */
-  if (!CAN_CHANGE(element))
-  {
-#if 0
-    if (!CAN_CHANGE(Back[x][y]))       /* prevent permanent repetition */
-      ChangeDelay[x][y] = 0;
-#endif
+    player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
+    player->drop_pressed_delay = 0;
 
-    return;
+    CheckSingleStepMode(player);
+
+    return 0;
   }
+}
 
-  if (ChangeDelay[x][y] == 0)          /* initialize element change */
-  {
-    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
+static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
+                                        byte *tape_action)
+{
+  if (!tape.use_mouse)
+    return;
 
-    ResetGfxAnimation(x, y);
-    ResetRandomAnimationValue(x, y);
+  mouse_action->lx     = tape_action[TAPE_ACTION_LX];
+  mouse_action->ly     = tape_action[TAPE_ACTION_LY];
+  mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
+}
 
-    if (change->pre_change_function)
-      change->pre_change_function(x, y);
-  }
+static void SetTapeActionFromMouseAction(byte *tape_action,
+                                        struct MouseActionInfo *mouse_action)
+{
+  if (!tape.use_mouse)
+    return;
 
-  ChangeDelay[x][y]--;
+  tape_action[TAPE_ACTION_LX]     = mouse_action->lx;
+  tape_action[TAPE_ACTION_LY]     = mouse_action->ly;
+  tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
+}
 
-  if (ChangeDelay[x][y] != 0)          /* continue element change */
+static void CheckLevelSolved(void)
+{
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
-    int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+    if (game_em.level_solved &&
+       !game_em.game_over)                             // game won
+    {
+      LevelSolved();
 
-    if (IS_ANIMATED(graphic))
-      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+      game_em.game_over = TRUE;
 
-    if (change->change_function)
-      change->change_function(x, y);
+      game.all_players_gone = TRUE;
+    }
+
+    if (game_em.game_over)                             // game lost
+      game.all_players_gone = TRUE;
   }
-  else                                 /* finish element change */
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
   {
-    if (ChangePage[x][y] != -1)                /* remember page from delayed change */
+    if (game_sp.level_solved &&
+       !game_sp.game_over)                             // game won
     {
-      page = ChangePage[x][y];
-      ChangePage[x][y] = -1;
+      LevelSolved();
 
-      change = &ei->change_page[page];
+      game_sp.game_over = TRUE;
+
+      game.all_players_gone = TRUE;
     }
 
-    if (IS_MOVING(x, y))               /* never change a running system ;-) */
+    if (game_sp.game_over)                             // game lost
+      game.all_players_gone = TRUE;
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+  {
+    if (game_mm.level_solved &&
+       !game_mm.game_over)                             // game won
     {
-      ChangeDelay[x][y] = 1;           /* try change after next move step */
-      ChangePage[x][y] = page;         /* remember page to use for change */
+      LevelSolved();
 
-      return;
-    }
+      game_mm.game_over = TRUE;
 
-    if (ChangeElementNow(x, y, element, page))
-    {
-      if (change->post_change_function)
-       change->post_change_function(x, y);
+      game.all_players_gone = TRUE;
     }
+
+    if (game_mm.game_over)                             // game lost
+      game.all_players_gone = TRUE;
   }
 }
 
-#endif
-
-static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
-                                             int trigger_element,
-                                             int trigger_event,
-                                             int trigger_player,
-                                             int trigger_side,
-                                             int trigger_page)
+static void CheckLevelTime(void)
 {
-  boolean change_done_any = FALSE;
-  int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
   int i;
 
-  if (!(trigger_events[trigger_element][trigger_event]))
-    return FALSE;
-
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  if (TimeFrames >= FRAMES_PER_SECOND)
   {
-    int element = EL_CUSTOM_START + i;
-    boolean change_done = FALSE;
-    int p;
+    TimeFrames = 0;
+    TapeTime++;
 
-    if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
-       !HAS_ANY_CHANGE_EVENT(element, trigger_event))
-      continue;
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
 
-    for (p = 0; p < element_info[element].num_change_pages; p++)
+      if (SHIELD_ON(player))
+      {
+       player->shield_normal_time_left--;
+
+       if (player->shield_deadly_time_left > 0)
+         player->shield_deadly_time_left--;
+      }
+    }
+
+    if (!game.LevelSolved && !level.use_step_counter)
     {
-      struct ElementChangeInfo *change = &element_info[element].change_page[p];
+      TimePlayed++;
 
-      if (change->can_change_or_has_action &&
-         change->has_event[trigger_event] &&
-         change->trigger_side & trigger_side &&
-         change->trigger_player & trigger_player &&
-         change->trigger_page & trigger_page_bits &&
-         IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
+      if (TimeLeft > 0)
       {
-       change->actual_trigger_element = trigger_element;
-       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
-       change->actual_trigger_side = trigger_side;
-       change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
+       TimeLeft--;
 
-       if ((change->can_change && !change_done) || change->has_action)
-       {
-         int x, y;
+       if (TimeLeft <= 10 && setup.time_limit)
+         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
 
-#if 1
-         SCAN_PLAYFIELD(x, y)
-#else
-         for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
-         {
-           if (Feld[x][y] == element)
-           {
-             if (change->can_change && !change_done)
-             {
-               ChangeDelay[x][y] = 1;
-               ChangeEvent[x][y] = trigger_event;
-               ChangeElement(x, y, p);
-             }
-#if USE_NEW_DELAYED_ACTION
-             else if (change->has_action)
-             {
-               ExecuteCustomElementAction(x, y, element, p);
-               PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
-             }
-#else
-             if (change->has_action)
-             {
-               ExecuteCustomElementAction(x, y, element, p);
-               PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
-             }
-#endif
-           }
-         }
+       /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
+          is reset from other values in UpdateGameDoorValues() -- FIX THIS */
 
-         if (change->can_change)
-         {
-           change_done = TRUE;
-           change_done_any = TRUE;
-         }
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+       if (!TimeLeft && setup.time_limit)
+       {
+         if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+           level.native_em_level->lev->killed_out_of_time = TRUE;
+         else
+           for (i = 0; i < MAX_PLAYERS; i++)
+             KillPlayer(&stored_player[i]);
        }
       }
+      else if (game.no_time_limit && !game.all_players_gone)
+      {
+       game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
+      }
+
+      level.native_em_level->lev->time =
+       (game.no_time_limit ? TimePlayed : TimeLeft);
     }
+
+    if (tape.recording || tape.playing)
+      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
   }
 
-  return change_done_any;
+  if (tape.recording || tape.playing)
+    DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
+
+  UpdateAndDisplayGameControlValues();
 }
 
-static boolean CheckElementChangeExt(int x, int y,
-                                    int element,
-                                    int trigger_element,
-                                    int trigger_event,
-                                    int trigger_player,
-                                    int trigger_side)
+void AdvanceFrameAndPlayerCounters(int player_nr)
 {
-  boolean change_done = FALSE;
-  int p;
+  int i;
 
-  if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
-      !HAS_ANY_CHANGE_EVENT(element, trigger_event))
-    return FALSE;
+  // advance frame counters (global frame counter and time frame counter)
+  FrameCounter++;
+  TimeFrames++;
 
-  if (Feld[x][y] == EL_BLOCKED)
+  // advance player counters (counters for move delay, move animation etc.)
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    Blocked2Moving(x, y, &x, &y);
-    element = Feld[x][y];
-  }
+    boolean advance_player_counters = (player_nr == -1 || player_nr == i);
+    int move_delay_value = stored_player[i].move_delay_value;
+    int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
 
-  if (Feld[x][y] != element)   /* check if element has already changed */
-    return FALSE;
+    if (!advance_player_counters)      // not all players may be affected
+      continue;
 
-  for (p = 0; p < element_info[element].num_change_pages; p++)
-  {
-    struct ElementChangeInfo *change = &element_info[element].change_page[p];
+    if (move_frames == 0)      // less than one move per game frame
+    {
+      int stepsize = TILEX / move_delay_value;
+      int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
+      int count = (stored_player[i].is_moving ?
+                  ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
 
-    boolean check_trigger_element =
-      (trigger_event == CE_TOUCHING_X ||
-       trigger_event == CE_HITTING_X ||
-       trigger_event == CE_HIT_BY_X);
+      if (count % delay == 0)
+       move_frames = 1;
+    }
 
-    if (change->can_change_or_has_action &&
-       change->has_event[trigger_event] &&
-       change->trigger_side & trigger_side &&
-       change->trigger_player & trigger_player &&
-       (!check_trigger_element ||
-        IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
-    {
-      change->actual_trigger_element = trigger_element;
-      change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
-      change->actual_trigger_side = trigger_side;
-      change->actual_trigger_ce_value = CustomValue[x][y];
+    stored_player[i].Frame += move_frames;
 
-      /* special case: trigger element not at (x,y) position for some events */
-      if (check_trigger_element)
-      {
-       static struct
-       {
-         int dx, dy;
-       } move_xy[] =
-         {
-           {  0,  0 },
-           { -1,  0 },
-           { +1,  0 },
-           {  0,  0 },
-           {  0, -1 },
-           {  0,  0 }, { 0, 0 }, { 0, 0 },
-           {  0, +1 }
-         };
+    if (stored_player[i].MovPos != 0)
+      stored_player[i].StepFrame += move_frames;
 
-       int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
-       int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
+    if (stored_player[i].move_delay > 0)
+      stored_player[i].move_delay--;
+
+    // due to bugs in previous versions, counter must count up, not down
+    if (stored_player[i].push_delay != -1)
+      stored_player[i].push_delay++;
+
+    if (stored_player[i].drop_delay > 0)
+      stored_player[i].drop_delay--;
+
+    if (stored_player[i].is_dropping_pressed)
+      stored_player[i].drop_pressed_delay++;
+  }
+}
+
+void StartGameActions(boolean init_network_game, boolean record_tape,
+                     int random_seed)
+{
+  unsigned int new_random_seed = InitRND(random_seed);
 
-       change->actual_trigger_ce_value = CustomValue[xx][yy];
-      }
+  if (record_tape)
+    TapeStartRecording(new_random_seed);
 
-      if (change->can_change && !change_done)
-      {
-       ChangeDelay[x][y] = 1;
-       ChangeEvent[x][y] = trigger_event;
-       ChangeElement(x, y, p);
+  if (init_network_game)
+  {
+    SendToServer_LevelFile();
+    SendToServer_StartPlaying();
 
-       change_done = TRUE;
-      }
-#if USE_NEW_DELAYED_ACTION
-      else if (change->has_action)
-      {
-       ExecuteCustomElementAction(x, y, element, p);
-       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
-      }
-#else
-      if (change->has_action)
-      {
-       ExecuteCustomElementAction(x, y, element, p);
-       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
-      }
-#endif
-    }
+    return;
   }
 
-  return change_done;
+  InitGame();
 }
 
-static void PlayPlayerSound(struct PlayerInfo *player)
+static void GameActionsExt(void)
 {
-  int jx = player->jx, jy = player->jy;
-  int sound_element = player->artwork_element;
-  int last_action = player->last_action_waiting;
-  int action = player->action_waiting;
+#if 0
+  static unsigned int game_frame_delay = 0;
+#endif
+  unsigned int game_frame_delay_value;
+  byte *recorded_player_action;
+  byte summarized_player_action = 0;
+  byte tape_action[MAX_PLAYERS];
+  int i;
 
-  if (player->is_waiting)
+  // detect endless loops, caused by custom element programming
+  if (recursion_loop_detected && recursion_loop_depth == 0)
   {
-    if (action != last_action)
-      PlayLevelSoundElementAction(jx, jy, sound_element, action);
-    else
-      PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
+    char *message = getStringCat3("Internal Error! Element ",
+                                 EL_NAME(recursion_loop_element),
+                                 " caused endless loop! Quit the game?");
+
+    Error(ERR_WARN, "element '%s' caused endless loop in game engine",
+         EL_NAME(recursion_loop_element));
+
+    RequestQuitGameExt(FALSE, level_editor_test_game, message);
+
+    recursion_loop_detected = FALSE;   // if game should be continued
+
+    free(message);
+
+    return;
   }
-  else
-  {
-    if (action != last_action)
-      StopSound(element_info[sound_element].sound[last_action]);
 
-    if (last_action == ACTION_SLEEPING)
-      PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
+  if (game.restart_level)
+    StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+
+  CheckLevelSolved();
+
+  if (game.LevelSolved && !game.LevelSolved_GameEnd)
+    GameWon();
+
+  if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
+    TapeStop();
+
+  if (game_status != GAME_MODE_PLAYING)                // status might have changed
+    return;
+
+  game_frame_delay_value =
+    (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
+
+  if (tape.playing && tape.warp_forward && !tape.pausing)
+    game_frame_delay_value = 0;
+
+  SetVideoFrameDelay(game_frame_delay_value);
+
+  // (de)activate virtual buttons depending on current game status
+  if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
+  {
+    if (game.all_players_gone) // if no players there to be controlled anymore
+      SetOverlayActive(FALSE);
+    else if (!tape.playing)    // if game continues after tape stopped playing
+      SetOverlayActive(TRUE);
   }
-}
 
-static void PlayAllPlayersSound()
-{
-  int i;
+#if 0
+#if 0
+  // ---------- main game synchronization point ----------
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    if (stored_player[i].active)
-      PlayPlayerSound(&stored_player[i]);
-}
+  int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
 
-static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
-{
-  boolean last_waiting = player->is_waiting;
-  int move_dir = player->MovDir;
+  printf("::: skip == %d\n", skip);
 
-  player->last_action_waiting = player->action_waiting;
+#else
+  // ---------- main game synchronization point ----------
 
-  if (is_waiting)
+  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+#endif
+#endif
+
+  if (network_playing && !network_player_action_received)
   {
-    if (!last_waiting)         /* not waiting -> waiting */
-    {
-      player->is_waiting = TRUE;
+    // try to get network player actions in time
 
-      player->frame_counter_bored =
-       FrameCounter +
-       game.player_boring_delay_fixed +
-       SimpleRND(game.player_boring_delay_random);
-      player->frame_counter_sleeping =
-       FrameCounter +
-       game.player_sleeping_delay_fixed +
-       SimpleRND(game.player_sleeping_delay_random);
+    // last chance to get network player actions without main loop delay
+    HandleNetworking();
 
-      InitPlayerGfxAnimation(player, ACTION_WAITING, player->MovDir);
-    }
+    // game was quit by network peer
+    if (game_status != GAME_MODE_PLAYING)
+      return;
 
-    if (game.player_sleeping_delay_fixed +
-       game.player_sleeping_delay_random > 0 &&
-       player->anim_delay_counter == 0 &&
-       player->post_delay_counter == 0 &&
-       FrameCounter >= player->frame_counter_sleeping)
-      player->is_sleeping = TRUE;
-    else if (game.player_boring_delay_fixed +
-            game.player_boring_delay_random > 0 &&
-            FrameCounter >= player->frame_counter_bored)
-      player->is_bored = TRUE;
+    // check if network player actions still missing and game still running
+    if (!network_player_action_received && !checkGameEnded())
+      return;          // failed to get network player actions in time
 
-    player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
-                             player->is_bored ? ACTION_BORING :
-                             ACTION_WAITING);
+    // do not yet reset "network_player_action_received" (for tape.pausing)
+  }
 
-    if (player->is_sleeping)
-    {
-      if (player->num_special_action_sleeping > 0)
-      {
-       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
-       {
-         int last_special_action = player->special_action_sleeping;
-         int num_special_action = player->num_special_action_sleeping;
-         int special_action =
-           (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
-            last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
-            last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
-            last_special_action + 1 : ACTION_SLEEPING);
-         int special_graphic =
-           el_act_dir2img(player->artwork_element, special_action, move_dir);
+  if (tape.pausing)
+    return;
 
-         player->anim_delay_counter =
-           graphic_info[special_graphic].anim_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].anim_delay_random);
-         player->post_delay_counter =
-           graphic_info[special_graphic].post_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].post_delay_random);
+  // at this point we know that we really continue executing the game
 
-         player->special_action_sleeping = special_action;
-       }
+  network_player_action_received = FALSE;
 
-       if (player->anim_delay_counter > 0)
-       {
-         player->action_waiting = player->special_action_sleeping;
-         player->anim_delay_counter--;
-       }
-       else if (player->post_delay_counter > 0)
-       {
-         player->post_delay_counter--;
-       }
-      }
-    }
-    else if (player->is_bored)
-    {
-      if (player->num_special_action_bored > 0)
-      {
-       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
-       {
-         int special_action =
-           ACTION_BORING_1 + SimpleRND(player->num_special_action_bored);
-         int special_graphic =
-           el_act_dir2img(player->artwork_element, special_action, move_dir);
+  // when playing tape, read previously recorded player input from tape data
+  recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
 
-         player->anim_delay_counter =
-           graphic_info[special_graphic].anim_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].anim_delay_random);
-         player->post_delay_counter =
-           graphic_info[special_graphic].post_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].post_delay_random);
+  local_player->effective_mouse_action = local_player->mouse_action;
 
-         player->special_action_bored = special_action;
-       }
+  if (recorded_player_action != NULL)
+    SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
+                                recorded_player_action);
 
-       if (player->anim_delay_counter > 0)
-       {
-         player->action_waiting = player->special_action_bored;
-         player->anim_delay_counter--;
-       }
-       else if (player->post_delay_counter > 0)
-       {
-         player->post_delay_counter--;
-       }
-      }
-    }
+  // TapePlayAction() may return NULL when toggling to "pause before death"
+  if (tape.pausing)
+    return;
+
+  if (tape.set_centered_player)
+  {
+    game.centered_player_nr_next = tape.centered_player_nr_next;
+    game.set_centered_player = TRUE;
   }
-  else if (last_waiting)       /* waiting -> not waiting */
+
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    player->is_waiting = FALSE;
-    player->is_bored = FALSE;
-    player->is_sleeping = FALSE;
+    summarized_player_action |= stored_player[i].action;
 
-    player->frame_counter_bored = -1;
-    player->frame_counter_sleeping = -1;
+    if (!network_playing && (game.team_mode || tape.playing))
+      stored_player[i].effective_action = stored_player[i].action;
+  }
 
-    player->anim_delay_counter = 0;
-    player->post_delay_counter = 0;
+  if (network_playing && !checkGameEnded())
+    SendToServer_MovePlayer(summarized_player_action);
 
-    player->action_waiting = ACTION_DEFAULT;
+  // summarize all actions at local players mapped input device position
+  // (this allows using different input devices in single player mode)
+  if (!network.enabled && !game.team_mode)
+    stored_player[map_player_action[local_player->index_nr]].effective_action =
+      summarized_player_action;
 
-    player->special_action_bored = ACTION_DEFAULT;
-    player->special_action_sleeping = ACTION_DEFAULT;
+  if (tape.recording &&
+      setup.team_mode &&
+      setup.input_on_focus &&
+      game.centered_player_nr != -1)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action =
+       (i == game.centered_player_nr ? summarized_player_action : 0);
   }
-}
 
-static byte PlayerActions(struct PlayerInfo *player, byte player_action)
-{
-  boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
-  int left     = player_action & JOY_LEFT;
-  int right    = player_action & JOY_RIGHT;
-  int up       = player_action & JOY_UP;
-  int down     = player_action & JOY_DOWN;
-  int button1  = player_action & JOY_BUTTON_1;
-  int button2  = player_action & JOY_BUTTON_2;
-  int dx       = (left ? -1    : right ? 1     : 0);
-  int dy       = (up   ? -1    : down  ? 1     : 0);
+  if (recorded_player_action != NULL)
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action = recorded_player_action[i];
 
-  if (!player->active || tape.pausing)
-    return 0;
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    tape_action[i] = stored_player[i].effective_action;
 
-  if (player_action)
+    /* (this may happen in the RND game engine if a player was not present on
+       the playfield on level start, but appeared later from a custom element */
+    if (setup.team_mode &&
+       tape.recording &&
+       tape_action[i] &&
+       !tape.player_participates[i])
+      tape.player_participates[i] = TRUE;
+  }
+
+  SetTapeActionFromMouseAction(tape_action,
+                              &local_player->effective_mouse_action);
+
+  // only record actions from input devices, but not programmed actions
+  if (tape.recording)
+    TapeRecordAction(tape_action);
+
+  // remember if game was played (especially after tape stopped playing)
+  if (!tape.playing && summarized_player_action)
+    game.GamePlayed = TRUE;
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+  // !!! also map player actions in single player mode !!!
+  // if (game.team_mode)
+  if (1)
   {
-    if (button1)
-      snapped = SnapField(player, dx, dy);
-    else
-    {
-      if (button2)
-       dropped = DropElement(player);
+    byte mapped_action[MAX_PLAYERS];
 
-      moved = MovePlayer(player, dx, dy);
-    }
+#if DEBUG_PLAYER_ACTIONS
+    printf(":::");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+#endif
 
-    if (tape.single_step && tape.recording && !tape.pausing)
-    {
-      if (button1 || (dropped && !moved))
-      {
-       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
-       SnapField(player, 0, 0);                /* stop snapping */
-      }
-    }
+    for (i = 0; i < MAX_PLAYERS; i++)
+      mapped_action[i] = stored_player[map_player_action[i]].effective_action;
 
-    SetPlayerWaiting(player, FALSE);
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action = mapped_action[i];
 
-    return player_action;
+#if DEBUG_PLAYER_ACTIONS
+    printf(" =>");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+    printf("\n");
+#endif
   }
+#if DEBUG_PLAYER_ACTIONS
   else
   {
-    /* no actions for this player (no input at player's configured device) */
-
-    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
-    SnapField(player, 0, 0);
-    CheckGravityMovementWhenNotMoving(player);
+    printf(":::");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+    printf("\n");
+  }
+#endif
+#endif
 
-    if (player->MovPos == 0)
-      SetPlayerWaiting(player, TRUE);
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    // allow engine snapshot in case of changed movement attempt
+    if ((game.snapshot.last_action[i] & KEY_MOTION) !=
+       (stored_player[i].effective_action & KEY_MOTION))
+      game.snapshot.changed_action = TRUE;
 
-    if (player->MovPos == 0)   /* needed for tape.playing */
-      player->is_moving = FALSE;
+    // allow engine snapshot in case of snapping/dropping attempt
+    if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
+       (stored_player[i].effective_action & KEY_BUTTON) != 0)
+      game.snapshot.changed_action = TRUE;
 
-    player->is_dropping = FALSE;
+    game.snapshot.last_action[i] = stored_player[i].effective_action;
+  }
 
-    return 0;
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    GameActions_EM_Main();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    GameActions_SP_Main();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+  {
+    GameActions_MM_Main();
+  }
+  else
+  {
+    GameActions_RND_Main();
   }
-}
 
-void AdvanceFrameAndPlayerCounters(int player_nr)
-{
-  int i;
+  BlitScreenToBitmap(backbuffer);
 
-  /* advance frame counters (global frame counter and time frame counter) */
-  FrameCounter++;
-  TimeFrames++;
+  CheckLevelSolved();
+  CheckLevelTime();
 
-  /* advance player counters (counters for move delay, move animation etc.) */
-  for (i = 0; i < MAX_PLAYERS; i++)
+  AdvanceFrameAndPlayerCounters(-1);   // advance counters for all players
+
+  if (global.show_frames_per_second)
   {
-    boolean advance_player_counters = (player_nr == -1 || player_nr == i);
-    int move_delay_value = stored_player[i].move_delay_value;
-    int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
+    static unsigned int fps_counter = 0;
+    static int fps_frames = 0;
+    unsigned int fps_delay_ms = Counter() - fps_counter;
 
-    if (!advance_player_counters)      /* not all players may be affected */
-      continue;
+    fps_frames++;
 
-#if USE_NEW_PLAYER_ANIM
-    if (move_frames == 0)      /* less than one move per game frame */
+    if (fps_delay_ms >= 500)   // calculate FPS every 0.5 seconds
     {
-      int stepsize = TILEX / move_delay_value;
-      int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
-      int count = (stored_player[i].is_moving ?
-                  ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
+      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
 
-      if (count % delay == 0)
-       move_frames = 1;
-    }
-#endif
+      fps_frames = 0;
+      fps_counter = Counter();
 
-    stored_player[i].Frame += move_frames;
+      // always draw FPS to screen after FPS value was updated
+      redraw_mask |= REDRAW_FPS;
+    }
 
-    if (stored_player[i].MovPos != 0)
-      stored_player[i].StepFrame += move_frames;
+    // only draw FPS if no screen areas are deactivated (invisible warp mode)
+    if (GetDrawDeactivationMask() == REDRAW_NONE)
+      redraw_mask |= REDRAW_FPS;
+  }
+}
 
-    if (stored_player[i].move_delay > 0)
-      stored_player[i].move_delay--;
+static void GameActions_CheckSaveEngineSnapshot(void)
+{
+  if (!game.snapshot.save_snapshot)
+    return;
 
-    /* due to bugs in previous versions, counter must count up, not down */
-    if (stored_player[i].push_delay != -1)
-      stored_player[i].push_delay++;
+  // clear flag for saving snapshot _before_ saving snapshot
+  game.snapshot.save_snapshot = FALSE;
 
-    if (stored_player[i].drop_delay > 0)
-      stored_player[i].drop_delay--;
-  }
+  SaveEngineSnapshotToList();
 }
 
-void GameActions()
+void GameActions(void)
 {
-  static unsigned long game_frame_delay = 0;
-  unsigned long game_frame_delay_value;
-  int magic_wall_x = 0, magic_wall_y = 0;
-  int i, x, y, element, graphic;
-  byte *recorded_player_action;
-  byte summarized_player_action = 0;
-  byte tape_action[MAX_PLAYERS];
+  GameActionsExt();
 
-  if (game_status != GAME_MODE_PLAYING)
-    return;
+  GameActions_CheckSaveEngineSnapshot();
+}
 
-  game_frame_delay_value =
-    (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
+void GameActions_EM_Main(void)
+{
+  byte effective_action[MAX_PLAYERS];
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+  int i;
 
-  if (tape.playing && tape.warp_forward && !tape.pausing)
-    game_frame_delay_value = 0;
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
+
+  GameActions_EM(effective_action, warp_mode);
+}
 
-  /* ---------- main game synchronization point ---------- */
+void GameActions_SP_Main(void)
+{
+  byte effective_action[MAX_PLAYERS];
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+  int i;
 
-  InitPlayfieldScanModeVars();
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
 
-  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+  GameActions_SP(effective_action, warp_mode);
 
-  if (network_playing && !network_player_action_received)
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    /* try to get network player actions in time */
+    if (stored_player[i].force_dropping)
+      stored_player[i].action |= KEY_BUTTON_DROP;
 
-#if defined(NETWORK_AVALIABLE)
-    /* last chance to get network player actions without main loop delay */
-    HandleNetworking();
-#endif
+    stored_player[i].force_dropping = FALSE;
+  }
+}
 
-    /* game was quit by network peer */
-    if (game_status != GAME_MODE_PLAYING)
-      return;
+void GameActions_MM_Main(void)
+{
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
 
-    if (!network_player_action_received)
-      return;          /* failed to get network player actions in time */
-  }
+  GameActions_MM(local_player->effective_mouse_action, warp_mode);
+}
 
-  if (tape.pausing)
-    return;
+void GameActions_RND_Main(void)
+{
+  GameActions_RND();
+}
 
-  recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+void GameActions_RND(void)
+{
+  int magic_wall_x = 0, magic_wall_y = 0;
+  int i, x, y, element, graphic, last_gfx_frame;
 
-#if 1
-  /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
-  if (recorded_player_action == NULL && tape.pausing)
-    return;
-#endif
+  InitPlayfieldScanModeVars();
 
-  for (i = 0; i < MAX_PLAYERS; i++)
+  if (game.engine_version >= VERSION_IDENT(3,2,0,7))
   {
-    summarized_player_action |= stored_player[i].action;
-
-    if (!network_playing)
-      stored_player[i].effective_action = stored_player[i].action;
+    SCAN_PLAYFIELD(x, y)
+    {
+      ChangeCount[x][y] = 0;
+      ChangeEvent[x][y] = -1;
+    }
   }
 
-#if defined(NETWORK_AVALIABLE)
-  if (network_playing)
-    SendToServer_MovePlayer(summarized_player_action);
-#endif
+  if (game.set_centered_player)
+  {
+    boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
 
-  if (!options.network && !setup.team_mode)
-    local_player->effective_action = summarized_player_action;
+    // switching to "all players" only possible if all players fit to screen
+    if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
+    {
+      game.centered_player_nr_next = game.centered_player_nr;
+      game.set_centered_player = FALSE;
+    }
 
-  if (recorded_player_action != NULL)
-    for (i = 0; i < MAX_PLAYERS; i++)
-      stored_player[i].effective_action = recorded_player_action[i];
+    // do not switch focus to non-existing (or non-active) player
+    if (game.centered_player_nr_next >= 0 &&
+       !stored_player[game.centered_player_nr_next].active)
+    {
+      game.centered_player_nr_next = game.centered_player_nr;
+      game.set_centered_player = FALSE;
+    }
+  }
 
-  for (i = 0; i < MAX_PLAYERS; i++)
+  if (game.set_centered_player &&
+      ScreenMovPos == 0)       // screen currently aligned at tile position
   {
-    tape_action[i] = stored_player[i].effective_action;
+    int sx, sy;
 
-    if (tape.recording && tape_action[i] && !tape.player_participates[i])
-      tape.player_participates[i] = TRUE;    /* player just appeared from CE */
-  }
+    if (game.centered_player_nr_next == -1)
+    {
+      setScreenCenteredToAllPlayers(&sx, &sy);
+    }
+    else
+    {
+      sx = stored_player[game.centered_player_nr_next].jx;
+      sy = stored_player[game.centered_player_nr_next].jy;
+    }
 
-  /* only save actions from input devices, but not programmed actions */
-  if (tape.recording)
-    TapeRecordAction(tape_action);
+    game.centered_player_nr = game.centered_player_nr_next;
+    game.set_centered_player = FALSE;
+
+    DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
+    DrawGameDoorValues();
+  }
 
   for (i = 0; i < MAX_PLAYERS; i++)
   {
@@ -8291,24 +11739,15 @@ void GameActions()
       CheckGravityMovement(&stored_player[i]);
 #endif
 
-    /* overwrite programmed action with tape action */
+    // overwrite programmed action with tape action
     if (stored_player[i].programmed_action)
       actual_player_action = stored_player[i].programmed_action;
 
-#if 1
     PlayerActions(&stored_player[i], actual_player_action);
-#else
-    tape_action[i] = PlayerActions(&stored_player[i], actual_player_action);
-
-    if (tape.recording && tape_action[i] && !tape.player_participates[i])
-      tape.player_participates[i] = TRUE;    /* player just appeared from CE */
-#endif
 
     ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
   }
 
-  network_player_action_received = FALSE;
-
   ScrollScreen(NULL, SCROLL_GO_ON);
 
   /* for backwards compatibility, the following code emulates a fixed bug that
@@ -8334,25 +11773,21 @@ void GameActions()
       {
        ContinueMoving(x, y);
 
-       /* continue moving after pushing (this is actually a bug) */
+       // continue moving after pushing (this is actually a bug)
        if (!IS_MOVING(x, y))
-       {
          Stop[x][y] = FALSE;
-       }
       }
     }
   }
 
-#if 1
   SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
   {
+    Last[x][y] = Feld[x][y];
+
     ChangeCount[x][y] = 0;
     ChangeEvent[x][y] = -1;
 
-    /* this must be handled before main playfield loop */
+    // this must be handled before main playfield loop
     if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
     {
       MovDelay[x][y]--;
@@ -8360,19 +11795,17 @@ void GameActions()
        RemoveField(x, y);
     }
 
-#if USE_NEW_SNAP_DELAY
     if (Feld[x][y] == EL_ELEMENT_SNAPPING)
     {
       MovDelay[x][y]--;
       if (MovDelay[x][y] <= 0)
       {
        RemoveField(x, y);
-       DrawLevelField(x, y);
+       TEST_DrawLevelField(x, y);
 
-       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
+       TestIfElementTouchesCustomElement(x, y);        // for empty space
       }
     }
-#endif
 
 #if DEBUG
     if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
@@ -8391,15 +11824,17 @@ void GameActions()
       WasJustFalling[x][y]--;
     if (CheckCollision[x][y] > 0)
       CheckCollision[x][y]--;
+    if (CheckImpact[x][y] > 0)
+      CheckImpact[x][y]--;
 
     GfxFrame[x][y]++;
 
     /* reset finished pushing action (not done in ContinueMoving() to allow
-       continous pushing animation for elements with zero push delay) */
+       continuous pushing animation for elements with zero push delay) */
     if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
     {
       ResetGfxAnimation(x, y);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
 
 #if DEBUG
@@ -8419,46 +11854,16 @@ void GameActions()
 #endif
   }
 
-#if 1
   SCAN_PLAYFIELD(x, y)
-#else
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
   {
     element = Feld[x][y];
     graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+    last_gfx_frame = GfxFrame[x][y];
 
-#if 0
-    printf("::: %d,%d\n", x, y);
-
-    if (element == EL_ROCK)
-      printf("::: Yo man! Rocks can fall!\n");
-#endif
-
-    if (graphic_info[graphic].anim_global_sync)
-      GfxFrame[x][y] = FrameCounter;
-    else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
-    {
-      int old_gfx_frame = GfxFrame[x][y];
-
-      GfxFrame[x][y] = CustomValue[x][y];
-
-#if 1
-      if (GfxFrame[x][y] != old_gfx_frame)
-#endif
-       DrawLevelGraphicAnimation(x, y, graphic);
-    }
-    else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
-    {
-      int old_gfx_frame = GfxFrame[x][y];
-
-      GfxFrame[x][y] = element_info[element].collect_score;
+    ResetGfxFrame(x, y);
 
-#if 1
-      if (GfxFrame[x][y] != old_gfx_frame)
-#endif
-       DrawLevelGraphicAnimation(x, y, graphic);
-    }
+    if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
+      DrawLevelGraphicAnimation(x, y, graphic);
 
     if (ANIM_MODE(graphic) == ANIM_RANDOM &&
        IS_NEXT_FRAME(GfxFrame[x][y], graphic))
@@ -8476,35 +11881,13 @@ void GameActions()
       continue;
     }
 
-    /* this may take place after moving, so 'element' may have changed */
+    // this may take place after moving, so 'element' may have changed
     if (IS_CHANGING(x, y) &&
        (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
     {
       int page = element_info[element].event_page_nr[CE_DELAY];
-#if 0
-      ChangeElement(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] : page);
-#else
-
-#if 0
-      printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]);
-#endif
-
-#if 0
-      if (element == EL_CUSTOM_255)
-       printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]);
-#endif
-
-#if 1
-      ChangeElement(x, y, page);
-#else
-      if (CAN_CHANGE(element))
-       ChangeElement(x, y, page);
 
-      if (HAS_ACTION(element))
-       ExecuteCustomElementAction(x, y, element, page);
-#endif
-
-#endif
+      HandleElementChange(x, y, page);
 
       element = Feld[x][y];
       graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
@@ -8523,11 +11906,18 @@ void GameActions()
        DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
       if (IS_GEM(element) || element == EL_SP_INFOTRON)
-       EdelsteinFunkeln(x, y);
+       TEST_DrawTwinkleOnField(x, y);
+    }
+    else if (element == EL_ACID)
+    {
+      if (!Stop[x][y])
+       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
     }
-    else if ((element == EL_ACID ||
-             element == EL_EXIT_OPEN ||
+    else if ((element == EL_EXIT_OPEN ||
+             element == EL_EM_EXIT_OPEN ||
              element == EL_SP_EXIT_OPEN ||
+             element == EL_STEEL_EXIT_OPEN ||
+             element == EL_EM_STEEL_EXIT_OPEN ||
              element == EL_SP_TERMINAL ||
              element == EL_SP_TERMINAL_ACTIVE ||
              element == EL_EXTRA_TIME ||
@@ -8553,36 +11943,42 @@ void GameActions()
       Life(x, y);
     else if (element == EL_EXIT_CLOSED)
       CheckExit(x, y);
+    else if (element == EL_EM_EXIT_CLOSED)
+      CheckExitEM(x, y);
+    else if (element == EL_STEEL_EXIT_CLOSED)
+      CheckExitSteel(x, y);
+    else if (element == EL_EM_STEEL_EXIT_CLOSED)
+      CheckExitSteelEM(x, y);
     else if (element == EL_SP_EXIT_CLOSED)
       CheckExitSP(x, y);
-    else if (element == EL_EXPANDABLE_WALL_GROWING)
+    else if (element == EL_EXPANDABLE_WALL_GROWING ||
+            element == EL_EXPANDABLE_STEELWALL_GROWING)
       MauerWaechst(x, y);
     else if (element == EL_EXPANDABLE_WALL ||
             element == EL_EXPANDABLE_WALL_HORIZONTAL ||
             element == EL_EXPANDABLE_WALL_VERTICAL ||
-            element == EL_EXPANDABLE_WALL_ANY)
+            element == EL_EXPANDABLE_WALL_ANY ||
+            element == EL_BD_EXPANDABLE_WALL)
       MauerAbleger(x, y);
+    else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
+            element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
+            element == EL_EXPANDABLE_STEELWALL_ANY)
+      MauerAblegerStahl(x, y);
     else if (element == EL_FLAMES)
       CheckForDragon(x, y);
     else if (element == EL_EXPLOSION)
-      ;        /* drawing of correct explosion animation is handled separately */
-    else if (element == EL_ELEMENT_SNAPPING)
+      ;        // drawing of correct explosion animation is handled separately
+    else if (element == EL_ELEMENT_SNAPPING ||
+            element == EL_DIAGONAL_SHRINKING ||
+            element == EL_DIAGONAL_GROWING)
     {
-#if 1
       graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
 
       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
-#endif
     }
     else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-#if 0
-    if (element == EL_CUSTOM_255 ||
-       element == EL_CUSTOM_256)
-      DrawLevelGraphicAnimation(x, y, graphic);
-#endif
-
     if (IS_BELT_ACTIVE(element))
       PlayLevelSoundAction(x, y, ACTION_ACTIVE);
 
@@ -8590,14 +11986,18 @@ void GameActions()
     {
       int jx = local_player->jx, jy = local_player->jy;
 
-      /* play the element sound at the position nearest to the player */
+      // play the element sound at the position nearest to the player
       if ((element == EL_MAGIC_WALL_FULL ||
           element == EL_MAGIC_WALL_ACTIVE ||
           element == EL_MAGIC_WALL_EMPTYING ||
           element == EL_BD_MAGIC_WALL_FULL ||
           element == EL_BD_MAGIC_WALL_ACTIVE ||
-          element == EL_BD_MAGIC_WALL_EMPTYING) &&
-         ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
+          element == EL_BD_MAGIC_WALL_EMPTYING ||
+          element == EL_DC_MAGIC_WALL_FULL ||
+          element == EL_DC_MAGIC_WALL_ACTIVE ||
+          element == EL_DC_MAGIC_WALL_EMPTYING) &&
+         ABS(x - jx) + ABS(y - jy) <
+         ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
       {
        magic_wall_x = x;
        magic_wall_y = y;
@@ -8606,10 +12006,10 @@ void GameActions()
   }
 
 #if USE_NEW_AMOEBA_CODE
-  /* new experimental amoeba growth stuff */
+  // new experimental amoeba growth stuff
   if (!(FrameCounter % 8))
   {
-    static unsigned long random = 1684108901;
+    static unsigned int random = 1684108901;
 
     for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
     {
@@ -8621,6 +12021,7 @@ void GameActions()
          (element == EL_EMPTY ||
           CAN_GROW_INTO(element) ||
           element == EL_QUICKSAND_EMPTY ||
+          element == EL_QUICKSAND_FAST_EMPTY ||
           element == EL_ACID_SPLASH_LEFT ||
           element == EL_ACID_SPLASH_RIGHT))
       {
@@ -8636,31 +12037,22 @@ void GameActions()
   }
 #endif
 
-#if 0
-  if (game.explosions_delayed)
-#endif
-  {
-    game.explosions_delayed = FALSE;
-
-#if 1
-    SCAN_PLAYFIELD(x, y)
-#else
-    for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
-    {
-      element = Feld[x][y];
+  game.explosions_delayed = FALSE;
 
-      if (ExplodeField[x][y])
-       Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
-      else if (element == EL_EXPLOSION)
-       Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
+  SCAN_PLAYFIELD(x, y)
+  {
+    element = Feld[x][y];
 
-      ExplodeField[x][y] = EX_TYPE_NONE;
-    }
+    if (ExplodeField[x][y])
+      Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
+    else if (element == EL_EXPLOSION)
+      Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
 
-    game.explosions_delayed = TRUE;
+    ExplodeField[x][y] = EX_TYPE_NONE;
   }
 
+  game.explosions_delayed = TRUE;
+
   if (game.magic_wall_active)
   {
     if (!(game.magic_wall_time_left % 4))
@@ -8671,6 +12063,10 @@ void GameActions()
          element == EL_BD_MAGIC_WALL_ACTIVE ||
          element == EL_BD_MAGIC_WALL_EMPTYING)
        PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
+      else if (element == EL_DC_MAGIC_WALL_FULL ||
+              element == EL_DC_MAGIC_WALL_ACTIVE ||
+              element == EL_DC_MAGIC_WALL_EMPTYING)
+       PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
       else
        PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
     }
@@ -8678,13 +12074,10 @@ void GameActions()
     if (game.magic_wall_time_left > 0)
     {
       game.magic_wall_time_left--;
+
       if (!game.magic_wall_time_left)
       {
-#if 1
        SCAN_PLAYFIELD(x, y)
-#else
-       for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-#endif
        {
          element = Feld[x][y];
 
@@ -8692,13 +12085,19 @@ void GameActions()
              element == EL_MAGIC_WALL_FULL)
          {
            Feld[x][y] = EL_MAGIC_WALL_DEAD;
-           DrawLevelField(x, y);
+           TEST_DrawLevelField(x, y);
          }
          else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
                   element == EL_BD_MAGIC_WALL_FULL)
          {
            Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
-           DrawLevelField(x, y);
+           TEST_DrawLevelField(x, y);
+         }
+         else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
+                  element == EL_DC_MAGIC_WALL_FULL)
+         {
+           Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
+           TEST_DrawLevelField(x, y);
          }
        }
 
@@ -8739,94 +12138,56 @@ void GameActions()
       RedrawAllInvisibleElementsForMagnifier();
   }
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    struct PlayerInfo *player = &stored_player[i];
-
-    if (SHIELD_ON(player))
-    {
-      if (player->shield_deadly_time_left)
-       PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
-      else if (player->shield_normal_time_left)
-       PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
-    }
-  }
-
-  if (TimeFrames >= FRAMES_PER_SECOND)
-  {
-    TimeFrames = 0;
-    TapeTime++;
-
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      struct PlayerInfo *player = &stored_player[i];
-
-      if (SHIELD_ON(player))
-      {
-       player->shield_normal_time_left--;
-
-       if (player->shield_deadly_time_left > 0)
-         player->shield_deadly_time_left--;
-      }
-    }
-
-    if (!level.use_step_counter)
-    {
-      TimePlayed++;
-
-      if (TimeLeft > 0)
-      {
-       TimeLeft--;
-
-       if (TimeLeft <= 10 && setup.time_limit)
-         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
-
-       DrawGameValue_Time(TimeLeft);
-
-       if (!TimeLeft && setup.time_limit)
-         for (i = 0; i < MAX_PLAYERS; i++)
-           KillPlayer(&stored_player[i]);
-      }
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
-       DrawGameValue_Time(TimePlayed);
-    }
-
-    if (tape.recording || tape.playing)
-      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
-  }
-
-  DrawAllPlayers();
-  PlayAllPlayersSound();
-
-  if (options.debug)                   /* calculate frames per second */
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    static unsigned long fps_counter = 0;
-    static int fps_frames = 0;
-    unsigned long fps_delay_ms = Counter() - fps_counter;
+    struct PlayerInfo *player = &stored_player[i];
 
-    fps_frames++;
+    if (SHIELD_ON(player))
+    {
+      if (player->shield_deadly_time_left)
+       PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
+      else if (player->shield_normal_time_left)
+       PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
+    }
+  }
 
-    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
+#if USE_DELAYED_GFX_REDRAW
+  SCAN_PLAYFIELD(x, y)
+  {
+    if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
     {
-      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
+      /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
+        !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
 
-      fps_frames = 0;
-      fps_counter = Counter();
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
+       DrawLevelField(x, y);
+
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
+       DrawLevelFieldCrumbled(x, y);
+
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
+       DrawLevelFieldCrumbledNeighbours(x, y);
+
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
+       DrawTwinkleOnField(x, y);
     }
 
-    redraw_mask |= REDRAW_FPS;
+    GfxRedraw[x][y] = GFX_REDRAW_NONE;
   }
+#endif
 
-  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+  DrawAllPlayers();
+  PlayAllPlayersSound();
 
-  if (local_player->show_envelope != 0 && local_player->MovPos == 0)
+  if (local_player->show_envelope != 0 && (!local_player->active ||
+                                          local_player->MovPos == 0))
   {
     ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
 
     local_player->show_envelope = 0;
   }
 
-  /* use random number generator in every frame to make it less predictable */
+  // use random number generator in every frame to make it less predictable
   if (game.engine_version >= VERSION_IDENT(3,1,1,0))
     RND(1);
 }
@@ -8852,7 +12213,7 @@ static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
   return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
 }
 
-static boolean AllPlayersInVisibleScreen()
+static boolean AllPlayersInVisibleScreen(void)
 {
   int i;
 
@@ -8872,25 +12233,25 @@ static boolean AllPlayersInVisibleScreen()
 
 void ScrollLevel(int dx, int dy)
 {
-  int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
+  int scroll_offset = 2 * TILEX_VAR;
   int x, y;
 
   BlitBitmap(drawto_field, drawto_field,
-            FX + TILEX * (dx == -1) - softscroll_offset,
-            FY + TILEY * (dy == -1) - softscroll_offset,
-            SXSIZE - TILEX * (dx!=0) + 2 * softscroll_offset,
-            SYSIZE - TILEY * (dy!=0) + 2 * softscroll_offset,
-            FX + TILEX * (dx == 1) - softscroll_offset,
-            FY + TILEY * (dy == 1) - softscroll_offset);
+            FX + TILEX_VAR * (dx == -1) - scroll_offset,
+            FY + TILEY_VAR * (dy == -1) - scroll_offset,
+            SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
+            SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
+            FX + TILEX_VAR * (dx == 1) - scroll_offset,
+            FY + TILEY_VAR * (dy == 1) - scroll_offset);
 
-  if (dx)
+  if (dx != 0)
   {
     x = (dx == 1 ? BX1 : BX2);
     for (y = BY1; y <= BY2; y++)
       DrawScreenField(x, y);
   }
 
-  if (dy)
+  if (dy != 0)
   {
     y = (dy == 1 ? BY1 : BY2);
     for (x = BX1; x <= BX2; x++)
@@ -8944,11 +12305,11 @@ static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
 
 static void CheckGravityMovement(struct PlayerInfo *player)
 {
-  if (game.gravity && !player->programmed_action)
+  if (player->gravity && !player->programmed_action)
   {
     int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
     int move_dir_vertical   = player->effective_action & MV_VERTICAL;
-    boolean player_is_snapping = player->effective_action & JOY_BUTTON_1;
+    boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
     int jx = player->jx, jy = player->jy;
     boolean player_is_moving_to_valid_field =
       (!player_is_snapping &&
@@ -8966,7 +12327,7 @@ static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
 {
   return CheckGravityMovement(player);
 
-  if (game.gravity && !player->programmed_action)
+  if (player->gravity && !player->programmed_action)
   {
     int jx = player->jx, jy = player->jy;
     boolean field_under_player_is_free =
@@ -8993,11 +12354,11 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 {
   int jx = player->jx, jy = player->jy;
   int new_jx = jx + dx, new_jy = jy + dy;
-  int element;
   int can_move;
+  boolean player_can_move = !player->cannot_move;
 
   if (!player->active || (!dx && !dy))
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
 
   player->MovDir = (dx < 0 ? MV_LEFT :
                    dx > 0 ? MV_RIGHT :
@@ -9005,11 +12366,10 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
                    dy > 0 ? MV_DOWN :  MV_NONE);
 
   if (!IN_LEV_FIELD(new_jx, new_jy))
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
 
-  if (player->cannot_move)
+  if (!player_can_move)
   {
-#if 1
     if (player->MovPos == 0)
     {
       player->is_moving = FALSE;
@@ -9018,43 +12378,19 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
       player->is_snapping = FALSE;
       player->is_pushing = FALSE;
     }
-#else
-    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
-    SnapField(player, 0, 0);
-#endif
-
-    return MF_NO_ACTION;
   }
 
-  if (!options.network && !AllPlayersInSight(player, new_jx, new_jy))
-    return MF_NO_ACTION;
-
-  element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
-
-  if (DONT_RUN_INTO(element))
-  {
-    if (element == EL_ACID && dx == 0 && dy == 1)
-    {
-      SplashAcid(new_jx, new_jy);
-      Feld[jx][jy] = EL_PLAYER_1;
-      InitMovingField(jx, jy, MV_DOWN);
-      Store[jx][jy] = EL_ACID;
-      ContinueMoving(jx, jy);
-      BuryPlayer(player);
-    }
-    else
-      TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
-
-    return MF_MOVING;
-  }
+  if (!network.enabled && game.centered_player_nr == -1 &&
+      !AllPlayersInSight(player, new_jx, new_jy))
+    return MP_NO_ACTION;
 
   can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
-  if (can_move != MF_MOVING)
+  if (can_move != MP_MOVING)
     return can_move;
 
-  /* check if DigField() has caused relocation of the player */
+  // check if DigField() has caused relocation of the player
   if (player->jx != jx || player->jy != jy)
-    return MF_NO_ACTION;       /* <-- !!! CHECK THIS [-> MF_ACTION ?] !!! */
+    return MP_NO_ACTION;       // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
 
   StorePlayer[jx][jy] = 0;
   player->last_jx = jx;
@@ -9076,16 +12412,21 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 
   PlayerVisit[jx][jy] = FrameCounter;
 
+  player->is_moving = TRUE;
+
+#if 1
+  // should better be called in MovePlayer(), but this breaks some tapes
   ScrollPlayer(player, SCROLL_INIT);
+#endif
 
-  return MF_MOVING;
+  return MP_MOVING;
 }
 
 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 {
   int jx = player->jx, jy = player->jy;
   int old_jx = jx, old_jy = jy;
-  int moved = MF_NO_ACTION;
+  int moved = MP_NO_ACTION;
 
   if (!player->active)
     return FALSE;
@@ -9107,27 +12448,27 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
   if (player->move_delay > 0)
     return FALSE;
 
-  player->move_delay = -1;             /* set to "uninitialized" value */
+  player->move_delay = -1;             // set to "uninitialized" value
 
-  /* store if player is automatically moved to next field */
+  // store if player is automatically moved to next field
   player->is_auto_moving = (player->programmed_action != MV_NONE);
 
-  /* remove the last programmed player action */
+  // remove the last programmed player action
   player->programmed_action = 0;
 
   if (player->MovPos)
   {
-    /* should only happen if pre-1.2 tape recordings are played */
-    /* this is only for backward compatibility */
+    // should only happen if pre-1.2 tape recordings are played
+    // this is only for backward compatibility
 
     int original_move_delay_value = player->move_delay_value;
 
 #if DEBUG
-    printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%ld]\n",
+    printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
           tape.counter);
 #endif
 
-    /* scroll remaining steps with finest movement resolution */
+    // scroll remaining steps with finest movement resolution
     player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
 
     while (player->MovPos)
@@ -9138,12 +12479,14 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       AdvanceFrameAndPlayerCounters(player->index_nr);
 
       DrawAllPlayers();
-      BackToFront();
+      BackToFront_WithFrameDelay(0);
     }
 
     player->move_delay_value = original_move_delay_value;
   }
 
+  player->is_active = FALSE;
+
   if (player->last_move_dir & MV_HORIZONTAL)
   {
     if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
@@ -9155,57 +12498,72 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
   }
 
+  if (!moved && !player->is_active)
+  {
+    player->is_moving = FALSE;
+    player->is_digging = FALSE;
+    player->is_collecting = FALSE;
+    player->is_snapping = FALSE;
+    player->is_pushing = FALSE;
+  }
+
   jx = player->jx;
   jy = player->jy;
 
-  if (moved & MF_MOVING && !ScreenMovPos &&
-      (player == local_player || !options.network))
+  if (moved & MP_MOVING && !ScreenMovPos &&
+      (player->index_nr == game.centered_player_nr ||
+       game.centered_player_nr == -1))
   {
     int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
-    int offset = (setup.scroll_delay ? 3 : 0);
 
     if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
     {
-      /* actual player has left the screen -- scroll in that direction */
-      if (jx != old_jx)                /* player has moved horizontally */
+      // actual player has left the screen -- scroll in that direction
+      if (jx != old_jx)                // player has moved horizontally
        scroll_x += (jx - old_jx);
-      else                     /* player has moved vertically */
+      else                     // player has moved vertically
        scroll_y += (jy - old_jy);
     }
     else
     {
-      if (jx != old_jx)                /* player has moved horizontally */
+      int offset = game.scroll_delay_value;
+
+      if (jx != old_jx)                // player has moved horizontally
       {
-       if ((player->MovDir == MV_LEFT  && scroll_x > jx - MIDPOSX + offset) ||
-           (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
-         scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
+       int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
+       int new_scroll_x = jx - MIDPOSX + offset_x;
+
+       if ((player->MovDir == MV_LEFT  && scroll_x > new_scroll_x) ||
+           (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
+         scroll_x = new_scroll_x;
 
-       /* don't scroll over playfield boundaries */
-       if (scroll_x < SBX_Left || scroll_x > SBX_Right)
-         scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
+       // don't scroll over playfield boundaries
+       scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
 
-       /* don't scroll more than one field at a time */
+       // don't scroll more than one field at a time
        scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
 
-       /* don't scroll against the player's moving direction */
+       // don't scroll against the player's moving direction
        if ((player->MovDir == MV_LEFT  && scroll_x > old_scroll_x) ||
            (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
          scroll_x = old_scroll_x;
       }
-      else                     /* player has moved vertically */
+      else                     // player has moved vertically
       {
-       if ((player->MovDir == MV_UP   && scroll_y > jy - MIDPOSY + offset) ||
-           (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
-         scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
+       int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
+       int new_scroll_y = jy - MIDPOSY + offset_y;
 
-       /* don't scroll over playfield boundaries */
-       if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
-         scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+       if ((player->MovDir == MV_UP   && scroll_y > new_scroll_y) ||
+           (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
+         scroll_y = new_scroll_y;
 
-       /* don't scroll more than one field at a time */
+       // don't scroll over playfield boundaries
+       scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
+
+       // don't scroll more than one field at a time
        scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
 
-       /* don't scroll against the player's moving direction */
+       // don't scroll against the player's moving direction
        if ((player->MovDir == MV_UP   && scroll_y > old_scroll_y) ||
            (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
          scroll_y = old_scroll_y;
@@ -9214,7 +12572,8 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
     if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
     {
-      if (!options.network && !AllPlayersInVisibleScreen())
+      if (!network.enabled && game.centered_player_nr == -1 &&
+         !AllPlayersInVisibleScreen())
       {
        scroll_x = old_scroll_x;
        scroll_y = old_scroll_y;
@@ -9229,20 +12588,27 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
   player->StepFrame = 0;
 
-  if (moved & MF_MOVING)
+  if (moved & MP_MOVING)
   {
     if (old_jx != jx && old_jy == jy)
       player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
     else if (old_jx == jx && old_jy != jy)
       player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
 
-    DrawLevelField(jx, jy);    /* for "crumbled sand" */
+    TEST_DrawLevelField(jx, jy);       // for "crumbled sand"
 
     player->last_move_dir = player->MovDir;
     player->is_moving = TRUE;
     player->is_snapping = FALSE;
     player->is_switching = FALSE;
     player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
+    player->drop_pressed_delay = 0;
+
+#if 0
+    // should better be called here than above, but this breaks some tapes
+    ScrollPlayer(player, SCROLL_INIT);
+#endif
   }
   else
   {
@@ -9256,10 +12622,10 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
        the player was forced to wait again for eight frames before next try) */
 
     if (game.engine_version >= VERSION_IDENT(3,1,1,0))
-      player->move_delay = 0;  /* allow direct movement in the next frame */
+      player->move_delay = 0;  // allow direct movement in the next frame
   }
 
-  if (player->move_delay == -1)                /* not yet initialized by DigField() */
+  if (player->move_delay == -1)                // not yet initialized by DigField()
     player->move_delay = player->move_delay_value;
 
   if (game.engine_version < VERSION_IDENT(3,0,7,0))
@@ -9280,16 +12646,11 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   int last_jx = player->last_jx, last_jy = player->last_jy;
   int move_stepsize = TILEX / player->move_delay_value;
 
-#if USE_NEW_PLAYER_SPEED
   if (!player->active)
     return;
 
-  if (player->MovPos == 0 && mode == SCROLL_GO_ON)     /* player not moving */
+  if (player->MovPos == 0 && mode == SCROLL_GO_ON)     // player not moving
     return;
-#else
-  if (!player->active || player->MovPos == 0)
-    return;
-#endif
 
   if (mode == SCROLL_INIT)
   {
@@ -9299,77 +12660,54 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     if ((player->block_last_field || player->block_delay_adjustment > 0) &&
        Feld[last_jx][last_jy] == EL_EMPTY)
     {
-      int last_field_block_delay = 0;  /* start with no blocking at all */
+      int last_field_block_delay = 0;  // start with no blocking at all
       int block_delay_adjustment = player->block_delay_adjustment;
 
-      /* if player blocks last field, add delay for exactly one move */
+      // if player blocks last field, add delay for exactly one move
       if (player->block_last_field)
       {
        last_field_block_delay += player->move_delay_value;
 
-       /* when blocking enabled, prevent moving up despite gravity */
-       if (game.gravity && player->MovDir == MV_UP)
+       // when blocking enabled, prevent moving up despite gravity
+       if (player->gravity && player->MovDir == MV_UP)
          block_delay_adjustment = -1;
       }
 
-      /* add block delay adjustment (also possible when not blocking) */
+      // add block delay adjustment (also possible when not blocking)
       last_field_block_delay += block_delay_adjustment;
 
       Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
       MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
     }
 
-#if USE_NEW_PLAYER_SPEED
-    if (player->MovPos != 0)   /* player has not yet reached destination */
+    if (player->MovPos != 0)   // player has not yet reached destination
       return;
-#else
-    return;
-#endif
   }
   else if (!FrameReached(&player->actual_frame_counter, 1))
     return;
 
-#if 0
-  printf("::: player->MovPos: %d -> %d\n",
-        player->MovPos,
-        player->MovPos + (player->MovPos > 0 ? -1 : 1) * move_stepsize);
-#endif
-
-#if USE_NEW_PLAYER_SPEED
   if (player->MovPos != 0)
   {
     player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
     player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
 
-    /* before DrawPlayer() to draw correct player graphic for this case */
+    // before DrawPlayer() to draw correct player graphic for this case
     if (player->MovPos == 0)
       CheckGravityMovement(player);
   }
-#else
-  player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
-  player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
-
-  /* before DrawPlayer() to draw correct player graphic for this case */
-  if (player->MovPos == 0)
-    CheckGravityMovement(player);
-#endif
 
-  if (player->MovPos == 0)     /* player reached destination field */
+  if (player->MovPos == 0)     // player reached destination field
   {
-#if 0
-    printf("::: player reached destination field\n");
-#endif
-
     if (player->move_delay_reset_counter > 0)
     {
       player->move_delay_reset_counter--;
 
       if (player->move_delay_reset_counter == 0)
       {
-       /* continue with normal speed after quickly moving through gate */
+       // continue with normal speed after quickly moving through gate
        HALVE_PLAYER_SPEED(player);
 
-       /* be able to make the next move without delay */
+       // be able to make the next move without delay
        player->move_delay = 0;
       }
     }
@@ -9378,18 +12716,23 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     player->last_jy = jy;
 
     if (Feld[jx][jy] == EL_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_EXIT_OPENING ||
+       Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
        Feld[jx][jy] == EL_SP_EXIT_OPEN ||
-       Feld[jx][jy] == EL_SP_EXIT_OPENING)     /* <-- special case */
+       Feld[jx][jy] == EL_SP_EXIT_OPENING)     // <-- special case
     {
-      DrawPlayer(player);      /* needed here only to cleanup last field */
-      RemovePlayer(player);
+      ExitPlayer(player);
 
-      if (local_player->friends_still_needed == 0 ||
-         IS_SP_ELEMENT(Feld[jx][jy]))
-       player->LevelSolved = player->GameOver = TRUE;
+      if (game.players_still_needed == 0 &&
+         (game.friends_still_needed == 0 ||
+          IS_SP_ELEMENT(Feld[jx][jy])))
+       LevelSolved();
     }
 
-    /* this breaks one level: "machine", level 000 */
+    // this breaks one level: "machine", level 000
     {
       int move_direction = player->MovDir;
       int enter_side = MV_DIR_OPPOSITE(move_direction);
@@ -9416,7 +12759,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
                                          CE_PLAYER_ENTERS_X,
                                          player->index_bit, enter_side);
 
-      CheckTriggeredElementChangeBySide(jx, jy, player->element_nr,
+      CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
                                        CE_MOVE_OF_X, move_direction);
     }
 
@@ -9428,13 +12771,13 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
       /* needed because pushed element has not yet reached its destination,
         so it would trigger a change event at its previous field location */
       if (!player->is_pushing)
-       TestIfElementTouchesCustomElement(jx, jy);      /* for empty space */
+       TestIfElementTouchesCustomElement(jx, jy);      // for empty space
 
       if (!player->active)
        RemovePlayer(player);
     }
 
-    if (level.use_step_counter)
+    if (!game.LevelSolved && level.use_step_counter)
     {
       int i;
 
@@ -9445,31 +12788,40 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        TimeLeft--;
 
        if (TimeLeft <= 10 && setup.time_limit)
-         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
 
-       DrawGameValue_Time(TimeLeft);
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+       DisplayGameControlValues();
 
        if (!TimeLeft && setup.time_limit)
          for (i = 0; i < MAX_PLAYERS; i++)
            KillPlayer(&stored_player[i]);
       }
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
-       DrawGameValue_Time(TimePlayed);
+      else if (game.no_time_limit && !game.all_players_gone)
+      {
+       game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
+
+       DisplayGameControlValues();
+      }
     }
 
     if (tape.single_step && tape.recording && !tape.pausing &&
        !player->programmed_action)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
+    if (!player->programmed_action)
+      CheckSaveEngineSnapshot(player);
   }
 }
 
 void ScrollScreen(struct PlayerInfo *player, int mode)
 {
-  static unsigned long screen_frame_counter = 0;
+  static unsigned int screen_frame_counter = 0;
 
   if (mode == SCROLL_INIT)
   {
-    /* set scrolling step size according to actual player's moving speed */
+    // set scrolling step size according to actual player's moving speed
     ScrollStepSize = TILEX / player->move_delay_value;
 
     screen_frame_counter = FrameCounter;
@@ -9502,11 +12854,11 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
   };
   static int trigger_sides[4][2] =
   {
-    /* center side       border side */
-    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      /* check top    */
-    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* check left   */
-    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* check right  */
-    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     }       /* check bottom */
+    // center side       border side
+    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      // check top
+    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      // check left
+    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      // check right
+    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     }       // check bottom
   };
   static int touch_dir[4] =
   {
@@ -9515,7 +12867,7 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
     MV_UP   | MV_DOWN,
     MV_LEFT | MV_RIGHT
   };
-  int center_element = Feld[x][y];     /* should always be non-moving! */
+  int center_element = Feld[x][y];     // should always be non-moving!
   int i;
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
@@ -9529,33 +12881,44 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
     if (!IN_LEV_FIELD(xx, yy))
       continue;
 
-    if (IS_PLAYER(x, y))
+    if (IS_PLAYER(x, y))               // player found at center element
     {
       struct PlayerInfo *player = PLAYERINFO(x, y);
 
       if (game.engine_version < VERSION_IDENT(3,0,7,0))
-       border_element = Feld[xx][yy];          /* may be moving! */
+       border_element = Feld[xx][yy];          // may be moving!
       else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
        border_element = Feld[xx][yy];
-      else if (MovDir[xx][yy] & touch_dir[i])  /* elements are touching */
+      else if (MovDir[xx][yy] & touch_dir[i])  // elements are touching
        border_element = MovingOrBlocked2Element(xx, yy);
       else
-       continue;               /* center and border element do not touch */
+       continue;               // center and border element do not touch
 
       CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
                                 player->index_bit, border_side);
       CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
                                          CE_PLAYER_TOUCHES_X,
                                          player->index_bit, border_side);
+
+      {
+       /* use player element that is initially defined in the level playfield,
+          not the player element that corresponds to the runtime player number
+          (example: a level that contains EL_PLAYER_3 as the only player would
+          incorrectly give EL_PLAYER_1 for "player->element_nr") */
+       int player_element = PLAYERINFO(x, y)->initial_element;
+
+       CheckElementChangeBySide(xx, yy, border_element, player_element,
+                                CE_TOUCHING_X, border_side);
+      }
     }
-    else if (IS_PLAYER(xx, yy))
+    else if (IS_PLAYER(xx, yy))                // player found at border element
     {
       struct PlayerInfo *player = PLAYERINFO(xx, yy);
 
       if (game.engine_version >= VERSION_IDENT(3,0,7,0))
       {
        if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
-         continue;             /* center and border element do not touch */
+         continue;             // center and border element do not touch
       }
 
       CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
@@ -9563,6 +12926,18 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
       CheckTriggeredElementChangeByPlayer(x, y, center_element,
                                          CE_PLAYER_TOUCHES_X,
                                          player->index_bit, center_side);
+
+      {
+       /* use player element that is initially defined in the level playfield,
+          not the player element that corresponds to the runtime player number
+          (example: a level that contains EL_PLAYER_3 as the only player would
+          incorrectly give EL_PLAYER_1 for "player->element_nr") */
+       int player_element = PLAYERINFO(xx, yy)->initial_element;
+
+       CheckElementChangeBySide(x, y, center_element, player_element,
+                                CE_TOUCHING_X, center_side);
+      }
+
       break;
     }
   }
@@ -9579,11 +12954,11 @@ void TestIfElementTouchesCustomElement(int x, int y)
   };
   static int trigger_sides[4][2] =
   {
-    /* center side     border side */
-    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      /* check top    */
-    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* check left   */
-    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* check right  */
-    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     }       /* check bottom */
+    // center side     border side
+    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      // check top
+    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      // check left
+    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      // check right
+    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     }       // check bottom
   };
   static int touch_dir[4] =
   {
@@ -9593,150 +12968,139 @@ void TestIfElementTouchesCustomElement(int x, int y)
     MV_LEFT | MV_RIGHT
   };
   boolean change_center_element = FALSE;
-  int center_element = Feld[x][y];     /* should always be non-moving! */
+  int center_element = Feld[x][y];     // should always be non-moving!
+  int border_element_old[NUM_DIRECTIONS];
   int i;
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int xx = x + xy[i][0];
     int yy = y + xy[i][1];
-    int center_side = trigger_sides[i][0];
-    int border_side = trigger_sides[i][1];
     int border_element;
 
+    border_element_old[i] = -1;
+
     if (!IN_LEV_FIELD(xx, yy))
       continue;
 
     if (game.engine_version < VERSION_IDENT(3,0,7,0))
-      border_element = Feld[xx][yy];   /* may be moving! */
+      border_element = Feld[xx][yy];   // may be moving!
     else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
       border_element = Feld[xx][yy];
-    else if (MovDir[xx][yy] & touch_dir[i])    /* elements are touching */
+    else if (MovDir[xx][yy] & touch_dir[i])    // elements are touching
       border_element = MovingOrBlocked2Element(xx, yy);
     else
-      continue;                        /* center and border element do not touch */
-
-    /* check for change of center element (but change it only once) */
-    if (!change_center_element)
-      change_center_element =
-       CheckElementChangeBySide(x, y, center_element, border_element,
-                                CE_TOUCHING_X, border_side);
+      continue;                        // center and border element do not touch
 
-    /* check for change of border element */
-    CheckElementChangeBySide(xx, yy, border_element, center_element,
-                            CE_TOUCHING_X, center_side);
+    border_element_old[i] = border_element;
   }
-}
 
-void TestIfElementHitsCustomElement(int x, int y, int direction)
-{
-  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-  int hitx = x + dx, hity = y + dy;
-  int hitting_element = Feld[x][y];
-  int touched_element;
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
+    int center_side = trigger_sides[i][0];
+    int border_element = border_element_old[i];
 
-  if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
-    return;
+    if (border_element == -1)
+      continue;
 
-  touched_element = (IN_LEV_FIELD(hitx, hity) ?
-                    MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
+    // check for change of border element
+    CheckElementChangeBySide(xx, yy, border_element, center_element,
+                            CE_TOUCHING_X, center_side);
 
-  if (IN_LEV_FIELD(hitx, hity))
+    // (center element cannot be player, so we dont have to check this here)
+  }
+
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int opposite_direction = MV_DIR_OPPOSITE(direction);
-    int hitting_side = direction;
-    int touched_side = opposite_direction;
-    boolean object_hit = (!IS_MOVING(hitx, hity) ||
-                         MovDir[hitx][hity] != direction ||
-                         ABS(MovPos[hitx][hity]) <= TILEY / 2);
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
+    int border_side = trigger_sides[i][1];
+    int border_element = border_element_old[i];
 
-    object_hit = TRUE;
+    if (border_element == -1)
+      continue;
 
-    if (object_hit)
-    {
-      CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                              CE_HITTING_X, touched_side);
+    // check for change of center element (but change it only once)
+    if (!change_center_element)
+      change_center_element =
+       CheckElementChangeBySide(x, y, center_element, border_element,
+                                CE_TOUCHING_X, border_side);
 
-      CheckElementChangeBySide(hitx, hity, touched_element,
-                              hitting_element, CE_HIT_BY_X, hitting_side);
+    if (IS_PLAYER(xx, yy))
+    {
+      /* use player element that is initially defined in the level playfield,
+        not the player element that corresponds to the runtime player number
+        (example: a level that contains EL_PLAYER_3 as the only player would
+        incorrectly give EL_PLAYER_1 for "player->element_nr") */
+      int player_element = PLAYERINFO(xx, yy)->initial_element;
 
-      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_HIT_BY_SOMETHING, opposite_direction);
+      CheckElementChangeBySide(x, y, center_element, player_element,
+                              CE_TOUCHING_X, border_side);
     }
   }
-
-  /* "hitting something" is also true when hitting the playfield border */
-  CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                          CE_HITTING_SOMETHING, direction);
 }
 
-#if 0
-void TestIfElementSmashesCustomElement(int x, int y, int direction)
+void TestIfElementHitsCustomElement(int x, int y, int direction)
 {
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
   int hitx = x + dx, hity = y + dy;
   int hitting_element = Feld[x][y];
   int touched_element;
-#if 0
-  boolean object_hit = (IN_LEV_FIELD(hitx, hity) &&
-                       !IS_FREE(hitx, hity) &&
-                       (!IS_MOVING(hitx, hity) ||
-                        MovDir[hitx][hity] != direction ||
-                        ABS(MovPos[hitx][hity]) <= TILEY / 2));
-#endif
 
   if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
     return;
 
-#if 0
-  if (IN_LEV_FIELD(hitx, hity) && !object_hit)
-    return;
-#endif
-
   touched_element = (IN_LEV_FIELD(hitx, hity) ?
                     MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
 
-  CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                          EP_CAN_SMASH_EVERYTHING, direction);
-
   if (IN_LEV_FIELD(hitx, hity))
   {
     int opposite_direction = MV_DIR_OPPOSITE(direction);
     int hitting_side = direction;
     int touched_side = opposite_direction;
-#if 0
-    int touched_element = MovingOrBlocked2Element(hitx, hity);
-#endif
-#if 1
     boolean object_hit = (!IS_MOVING(hitx, hity) ||
                          MovDir[hitx][hity] != direction ||
                          ABS(MovPos[hitx][hity]) <= TILEY / 2);
 
     object_hit = TRUE;
-#endif
 
     if (object_hit)
     {
-      int i;
+      CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                              CE_HITTING_X, touched_side);
 
       CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_SMASHED_BY_SOMETHING, opposite_direction);
-
-      CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                              CE_OTHER_IS_SMASHING, touched_side);
+                              CE_HIT_BY_X, hitting_side);
 
       CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_OTHER_GETS_SMASHED, hitting_side);
+                              CE_HIT_BY_SOMETHING, opposite_direction);
+
+      if (IS_PLAYER(hitx, hity))
+      {
+       /* use player element that is initially defined in the level playfield,
+          not the player element that corresponds to the runtime player number
+          (example: a level that contains EL_PLAYER_3 as the only player would
+          incorrectly give EL_PLAYER_1 for "player->element_nr") */
+       int player_element = PLAYERINFO(hitx, hity)->initial_element;
+
+       CheckElementChangeBySide(x, y, hitting_element, player_element,
+                                CE_HITTING_X, touched_side);
+      }
     }
   }
+
+  // "hitting something" is also true when hitting the playfield border
+  CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                          CE_HITTING_SOMETHING, direction);
 }
-#endif
 
 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
 {
   int i, kill_x = -1, kill_y = -1;
+
   int bad_element = -1;
   static int test_xy[4][2] =
   {
@@ -9825,7 +13189,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
     MV_DOWN
   };
 
-  if (bad_element == EL_EXPLOSION)     /* skip just exploding bad things */
+  if (bad_element == EL_EXPLOSION)     // skip just exploding bad things
     return;
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
@@ -9834,6 +13198,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
     test_x = bad_x + test_xy[i][0];
     test_y = bad_y + test_xy[i][1];
+
     if (!IN_LEV_FIELD(test_x, test_y))
       continue;
 
@@ -9848,28 +13213,30 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
     if ((DONT_RUN_INTO(bad_element) &&  bad_move_dir == test_dir[i]) ||
        (DONT_TOUCH(bad_element)    && test_move_dir != test_dir[i]))
     {
-      /* good thing is player or penguin that does not move away */
+      // good thing is player or penguin that does not move away
       if (IS_PLAYER(test_x, test_y))
       {
        struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
 
        if (bad_element == EL_ROBOT && player->is_moving)
-         continue;     /* robot does not kill player if he is moving */
+         continue;     // robot does not kill player if he is moving
 
        if (game.engine_version >= VERSION_IDENT(3,0,7,0))
        {
          if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
-           continue;           /* center and border element do not touch */
+           continue;           // center and border element do not touch
        }
 
        kill_x = test_x;
        kill_y = test_y;
+
        break;
       }
       else if (test_element == EL_PENGUIN)
       {
        kill_x = test_x;
        kill_y = test_y;
+
        break;
       }
     }
@@ -9892,6 +13259,63 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
   }
 }
 
+void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
+{
+  int bad_element = Feld[bad_x][bad_y];
+  int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
+  int dy = (bad_move_dir == MV_UP   ? -1 : bad_move_dir == MV_DOWN  ? +1 : 0);
+  int test_x = bad_x + dx, test_y = bad_y + dy;
+  int test_move_dir, test_element;
+  int kill_x = -1, kill_y = -1;
+
+  if (!IN_LEV_FIELD(test_x, test_y))
+    return;
+
+  test_move_dir =
+    (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
+
+  test_element = Feld[test_x][test_y];
+
+  if (test_move_dir != bad_move_dir)
+  {
+    // good thing can be player or penguin that does not move away
+    if (IS_PLAYER(test_x, test_y))
+    {
+      struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
+
+      /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
+        player as being hit when he is moving towards the bad thing, because
+        the "get hit by" condition would be lost after the player stops) */
+      if (player->MovPos != 0 && player->MovDir == bad_move_dir)
+       return;         // player moves away from bad thing
+
+      kill_x = test_x;
+      kill_y = test_y;
+    }
+    else if (test_element == EL_PENGUIN)
+    {
+      kill_x = test_x;
+      kill_y = test_y;
+    }
+  }
+
+  if (kill_x != -1 || kill_y != -1)
+  {
+    if (IS_PLAYER(kill_x, kill_y))
+    {
+      struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
+
+      if (player->shield_deadly_time_left > 0 &&
+         !IS_INDESTRUCTIBLE(bad_element))
+       Bang(bad_x, bad_y);
+      else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
+       KillPlayer(player);
+    }
+    else
+      Bang(kill_x, kill_y);
+  }
+}
+
 void TestIfPlayerTouchesBadThing(int x, int y)
 {
   TestIfGoodThingHitsBadThing(x, y, MV_NONE);
@@ -9963,15 +13387,51 @@ void KillPlayer(struct PlayerInfo *player)
   if (!player->active)
     return;
 
-  /* remove accessible field at the player's position */
+#if 0
+  printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
+  /* the following code was introduced to prevent an infinite loop when calling
+     -> Bang()
+     -> CheckTriggeredElementChangeExt()
+     -> ExecuteCustomElementAction()
+     -> KillPlayer()
+     -> (infinitely repeating the above sequence of function calls)
+     which occurs when killing the player while having a CE with the setting
+     "kill player X when explosion of <player X>"; the solution using a new
+     field "player->killed" was chosen for backwards compatibility, although
+     clever use of the fields "player->active" etc. would probably also work */
+#if 1
+  if (player->killed)
+    return;
+#endif
+
+  player->killed = TRUE;
+
+  // remove accessible field at the player's position
   Feld[jx][jy] = EL_EMPTY;
 
-  /* deactivate shield (else Bang()/Explode() would not work right) */
+  // deactivate shield (else Bang()/Explode() would not work right)
   player->shield_normal_time_left = 0;
   player->shield_deadly_time_left = 0;
 
+#if 0
+  printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
   Bang(jx, jy);
-  BuryPlayer(player);
+
+#if 0
+  printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
+  if (player->reanimated)      // killed player may have been reanimated
+    player->killed = player->reanimated = FALSE;
+  else
+    BuryPlayer(player);
 }
 
 static void KillPlayerUnlessEnemyProtected(int x, int y)
@@ -9996,8 +13456,12 @@ void BuryPlayer(struct PlayerInfo *player)
   PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
   PlayLevelSound(jx, jy, SND_GAME_LOSING);
 
-  player->GameOver = TRUE;
   RemovePlayer(player);
+
+  player->buried = TRUE;
+
+  if (game.all_players_gone)
+    game.GameOver = TRUE;
 }
 
 void RemovePlayer(struct PlayerInfo *player)
@@ -10008,28 +13472,42 @@ void RemovePlayer(struct PlayerInfo *player)
   player->present = FALSE;
   player->active = FALSE;
 
+  // required for some CE actions (even if the player is not active anymore)
+  player->MovPos = 0;
+
   if (!ExplodeField[jx][jy])
     StorePlayer[jx][jy] = 0;
 
   if (player->is_moving)
-    DrawLevelField(player->last_jx, player->last_jy);
+    TEST_DrawLevelField(player->last_jx, player->last_jy);
 
   for (i = 0; i < MAX_PLAYERS; i++)
     if (stored_player[i].active)
       found = TRUE;
 
   if (!found)
-    AllPlayersGone = TRUE;
+  {
+    game.all_players_gone = TRUE;
+    game.GameOver = TRUE;
+  }
+
+  game.exit_x = game.robot_wheel_x = jx;
+  game.exit_y = game.robot_wheel_y = jy;
+}
+
+void ExitPlayer(struct PlayerInfo *player)
+{
+  DrawPlayer(player);  // needed here only to cleanup last field
+  RemovePlayer(player);
 
-  ExitX = ZX = jx;
-  ExitY = ZY = jy;
+  if (game.players_still_needed > 0)
+    game.players_still_needed--;
 }
 
-#if USE_NEW_SNAP_DELAY
 static void setFieldForSnapping(int x, int y, int element, int direction)
 {
   struct ElementInfo *ei = &element_info[element];
-  int direction_bit = MV_DIR_BIT(direction);
+  int direction_bit = MV_DIR_TO_BIT(direction);
   int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
   int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
                IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
@@ -10044,7 +13522,6 @@ static void setFieldForSnapping(int x, int y, int element, int direction)
   GfxDir[x][y] = direction;
   GfxFrame[x][y] = -1;
 }
-#endif
 
 /*
   =============================================================================
@@ -10060,10 +13537,10 @@ static boolean checkDiagonalPushing(struct PlayerInfo *player,
 {
   int jx, jy, dx, dy, xx, yy;
 
-  if (real_dx == 0 || real_dy == 0)    /* no diagonal direction => push */
+  if (real_dx == 0 || real_dy == 0)    // no diagonal direction => push
     return TRUE;
 
-  /* diagonal direction: check alternative direction */
+  // diagonal direction: check alternative direction
   jx = player->jx;
   jy = player->jy;
   dx = x - jx;
@@ -10083,12 +13560,14 @@ static boolean checkDiagonalPushing(struct PlayerInfo *player,
   =============================================================================
 */
 
-int DigField(struct PlayerInfo *player,
-            int oldx, int oldy, int x, int y,
-            int real_dx, int real_dy, int mode)
+static int DigField(struct PlayerInfo *player,
+                   int oldx, int oldy, int x, int y,
+                   int real_dx, int real_dy, int mode)
 {
   boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
   boolean player_was_pushing = player->is_pushing;
+  boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
+  boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
   int jx = oldx, jy = oldy;
   int dx = x - jx, dy = y - jy;
   int nextx = x + dx, nexty = y + dy;
@@ -10099,10 +13578,10 @@ int DigField(struct PlayerInfo *player,
   int opposite_direction = MV_DIR_OPPOSITE(move_direction);
   int dig_side = MV_DIR_OPPOSITE(move_direction);
   int old_element = Feld[jx][jy];
-  int element;
+  int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
   int collect_count;
 
-  if (is_player)               /* function can also be called by EL_PENGUIN */
+  if (is_player)               // function can also be called by EL_PENGUIN
   {
     if (player->MovPos == 0)
     {
@@ -10110,59 +13589,62 @@ int DigField(struct PlayerInfo *player,
       player->is_collecting = FALSE;
     }
 
-    if (player->MovPos == 0)   /* last pushing move finished */
+    if (player->MovPos == 0)   // last pushing move finished
       player->is_pushing = FALSE;
 
-    if (mode == DF_NO_PUSH)    /* player just stopped pushing */
+    if (mode == DF_NO_PUSH)    // player just stopped pushing
     {
       player->is_switching = FALSE;
       player->push_delay = -1;
 
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
     }
   }
 
-  if (IS_MOVING(x, y) || IS_PLAYER(x, y))
-    return MF_NO_ACTION;
-
   if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
 
-  /* in case of element dropped at player position, check background */
+  // in case of element dropped at player position, check background
   else if (Back[jx][jy] != EL_EMPTY &&
           game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
 
   if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
-    return MF_NO_ACTION;       /* field has no opening in this direction */
+    return MP_NO_ACTION;       // field has no opening in this direction
 
   if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
-    return MF_NO_ACTION;       /* field has no opening in this direction */
+    return MP_NO_ACTION;       // field has no opening in this direction
 
-  element = Feld[x][y];
-#if USE_NEW_CUSTOM_VALUE
+  if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
+  {
+    SplashAcid(x, y);
+
+    Feld[jx][jy] = player->artwork_element;
+    InitMovingField(jx, jy, MV_DOWN);
+    Store[jx][jy] = EL_ACID;
+    ContinueMoving(jx, jy);
+    BuryPlayer(player);
+
+    return MP_DONT_RUN_INTO;
+  }
+
+  if (player_can_move && DONT_RUN_INTO(element))
+  {
+    TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
+
+    return MP_DONT_RUN_INTO;
+  }
 
-#if 1
-  collect_count = element_info[element].collect_count_initial;
-#else
-  collect_count = CustomValue[x][y];
-#endif
+  if (IS_MOVING(x, y) || IS_PLAYER(x, y))
+    return MP_NO_ACTION;
 
-#else
   collect_count = element_info[element].collect_count_initial;
-#endif
 
-#if 0
-  if (element != EL_BLOCKED &&
-      CustomValue[x][y] != element_info[element].collect_count_initial)
-    printf("::: %d: %d != %d\n",
-          element,
-          CustomValue[x][y],
-          element_info[element].collect_count_initial);
-#endif
+  if (!is_player && !IS_COLLECTIBLE(element))  // penguin cannot collect it
+    return MP_NO_ACTION;
 
-  if (!is_player && !IS_COLLECTIBLE(element))  /* penguin cannot collect it */
-    return MF_NO_ACTION;
+  if (game.engine_version < VERSION_IDENT(2,2,0,0))
+    player_can_move = player_can_move_or_snap;
 
   if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
       game.engine_version >= VERSION_IDENT(2,2,0,0))
@@ -10172,18 +13654,22 @@ int DigField(struct PlayerInfo *player,
     CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
                                        player->index_bit, dig_side);
 
-    if (Feld[x][y] != element)         /* field changed by snapping */
-      return MF_ACTION;
+    if (element == EL_DC_LANDMINE)
+      Bang(x, y);
+
+    if (Feld[x][y] != element)         // field changed by snapping
+      return MP_ACTION;
 
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
   }
 
-  if (game.gravity && is_player && !player->is_auto_moving &&
+  if (player->gravity && is_player && !player->is_auto_moving &&
       canFallDown(player) && move_direction != MV_DOWN &&
       !canMoveToValidFieldWithGravity(jx, jy, move_direction))
-    return MF_NO_ACTION;       /* player cannot walk here due to gravity */
+    return MP_NO_ACTION;       // player cannot walk here due to gravity
 
-  if (IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
+  if (player_can_move &&
+      IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
   {
     int sound_element = SND_ELEMENT(element);
     int sound_action = ACTION_WALKING;
@@ -10191,57 +13677,87 @@ int DigField(struct PlayerInfo *player,
     if (IS_RND_GATE(element))
     {
       if (!player->key[RND_GATE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_RND_GATE_GRAY(element))
     {
       if (!player->key[RND_GATE_GRAY_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_RND_GATE_GRAY_ACTIVE(element))
     {
       if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (element == EL_EXIT_OPEN ||
+            element == EL_EM_EXIT_OPEN ||
+            element == EL_EM_EXIT_OPENING ||
+            element == EL_STEEL_EXIT_OPEN ||
+            element == EL_EM_STEEL_EXIT_OPEN ||
+            element == EL_EM_STEEL_EXIT_OPENING ||
             element == EL_SP_EXIT_OPEN ||
             element == EL_SP_EXIT_OPENING)
     {
-      sound_action = ACTION_PASSING;   /* player is passing exit */
+      sound_action = ACTION_PASSING;   // player is passing exit
     }
     else if (element == EL_EMPTY)
     {
-      sound_action = ACTION_MOVING;            /* nothing to walk on */
+      sound_action = ACTION_MOVING;            // nothing to walk on
     }
 
-    /* play sound from background or player, whatever is available */
+    // play sound from background or player, whatever is available
     if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
       PlayLevelSoundElementAction(x, y, sound_element, sound_action);
     else
       PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
   }
-  else if (IS_PASSABLE(element) && canPassField(x, y, move_direction))
+  else if (player_can_move &&
+          IS_PASSABLE(element) && canPassField(x, y, move_direction))
   {
     if (!ACCESS_FROM(element, opposite_direction))
-      return MF_NO_ACTION;     /* field not accessible from this direction */
+      return MP_NO_ACTION;     // field not accessible from this direction
 
-    if (CAN_MOVE(element))     /* only fixed elements can be passed! */
-      return MF_NO_ACTION;
+    if (CAN_MOVE(element))     // only fixed elements can be passed!
+      return MP_NO_ACTION;
 
     if (IS_EM_GATE(element))
     {
       if (!player->key[EM_GATE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_EM_GATE_GRAY(element))
     {
       if (!player->key[EM_GATE_GRAY_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_EM_GATE_GRAY_ACTIVE(element))
     {
       if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE(element))
+    {
+      if (!player->key[EMC_GATE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE_GRAY(element))
+    {
+      if (!player->key[EMC_GATE_GRAY_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE_GRAY_ACTIVE(element))
+    {
+      if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (element == EL_DC_GATE_WHITE ||
+            element == EL_DC_GATE_WHITE_GRAY ||
+            element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
+    {
+      if (player->num_white_keys == 0)
+       return MP_NO_ACTION;
+
+      player->num_white_keys--;
     }
     else if (IS_SP_PORT(element))
     {
@@ -10249,32 +13765,32 @@ int DigField(struct PlayerInfo *player,
          element == EL_SP_GRAVITY_PORT_RIGHT ||
          element == EL_SP_GRAVITY_PORT_UP ||
          element == EL_SP_GRAVITY_PORT_DOWN)
-       game.gravity = !game.gravity;
+       player->gravity = !player->gravity;
       else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
               element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
               element == EL_SP_GRAVITY_ON_PORT_UP ||
               element == EL_SP_GRAVITY_ON_PORT_DOWN)
-       game.gravity = TRUE;
+       player->gravity = TRUE;
       else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
               element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
               element == EL_SP_GRAVITY_OFF_PORT_UP ||
               element == EL_SP_GRAVITY_OFF_PORT_DOWN)
-       game.gravity = FALSE;
+       player->gravity = FALSE;
     }
 
-    /* automatically move to the next field with double speed */
+    // automatically move to the next field with double speed
     player->programmed_action = move_direction;
 
     if (player->move_delay_reset_counter == 0)
     {
-      player->move_delay_reset_counter = 2;    /* two double speed steps */
+      player->move_delay_reset_counter = 2;    // two double speed steps
 
       DOUBLE_PLAYER_SPEED(player);
     }
 
     PlayLevelSoundAction(x, y, ACTION_PASSING);
   }
-  else if (IS_DIGGABLE(element))
+  else if (player_can_move_or_snap && IS_DIGGABLE(element))
   {
     RemoveField(x, y);
 
@@ -10291,20 +13807,16 @@ int DigField(struct PlayerInfo *player,
 
     if (mode == DF_SNAP)
     {
-#if USE_NEW_SNAP_DELAY
       if (level.block_snap_field)
        setFieldForSnapping(x, y, element, move_direction);
       else
-       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
-#else
-      TestIfElementTouchesCustomElement(x, y);         /* for empty space */
-#endif
+       TestIfElementTouchesCustomElement(x, y);        // for empty space
 
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
                                          player->index_bit, dig_side);
     }
   }
-  else if (IS_COLLECTIBLE(element))
+  else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
   {
     RemoveField(x, y);
 
@@ -10321,7 +13833,10 @@ int DigField(struct PlayerInfo *player,
     else if (element == EL_EXTRA_TIME && level.time > 0)
     {
       TimeLeft += level.extra_time;
-      DrawGameValue_Time(TimeLeft);
+
+      game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+      DisplayGameControlValues();
     }
     else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
     {
@@ -10329,12 +13844,14 @@ int DigField(struct PlayerInfo *player,
       if (element == EL_SHIELD_DEADLY)
        player->shield_deadly_time_left += level.shield_deadly_time;
     }
-    else if (element == EL_DYNAMITE || element == EL_SP_DISK_RED)
+    else if (element == EL_DYNAMITE ||
+            element == EL_EM_DYNAMITE ||
+            element == EL_SP_DISK_RED)
     {
       if (player->inventory_size < MAX_INVENTORY_SIZE)
        player->inventory_element[player->inventory_size++] = element;
 
-      DrawGameValue_Dynamite(local_player->inventory_size);
+      DrawGameDoorValues();
     }
     else if (element == EL_DYNABOMB_INCREASE_NUMBER)
     {
@@ -10353,9 +13870,14 @@ int DigField(struct PlayerInfo *player,
     {
       player->key[KEY_NR(element)] = TRUE;
 
-      DrawGameValue_Keys(player->key);
+      DrawGameDoorValues();
+    }
+    else if (element == EL_DC_KEY_WHITE)
+    {
+      player->num_white_keys++;
 
-      redraw_mask |= REDRAW_DOOR_1;
+      // display white keys?
+      // DrawGameDoorValues();
     }
     else if (IS_ENVELOPE(element))
     {
@@ -10374,7 +13896,7 @@ int DigField(struct PlayerInfo *player,
       RedrawAllInvisibleElementsForMagnifier();
     }
     else if (IS_DROPPABLE(element) ||
-            IS_THROWABLE(element))     /* can be collected and dropped */
+            IS_THROWABLE(element))     // can be collected and dropped
     {
       int i;
 
@@ -10385,15 +13907,19 @@ int DigField(struct PlayerInfo *player,
          if (player->inventory_size < MAX_INVENTORY_SIZE)
            player->inventory_element[player->inventory_size++] = element;
 
-      DrawGameValue_Dynamite(local_player->inventory_size);
+      DrawGameDoorValues();
     }
     else if (collect_count > 0)
     {
-      local_player->gems_still_needed -= collect_count;
-      if (local_player->gems_still_needed < 0)
-       local_player->gems_still_needed = 0;
+      game.gems_still_needed -= collect_count;
+      if (game.gems_still_needed < 0)
+       game.gems_still_needed = 0;
+
+      game.snapshot.collected_item = TRUE;
+
+      game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
 
-      DrawGameValue_Emeralds(local_player->gems_still_needed);
+      DisplayGameControlValues();
     }
 
     RaiseScoreElement(element);
@@ -10405,30 +13931,26 @@ int DigField(struct PlayerInfo *player,
 
     if (mode == DF_SNAP)
     {
-#if USE_NEW_SNAP_DELAY
       if (level.block_snap_field)
        setFieldForSnapping(x, y, element, move_direction);
       else
-       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
-#else
-      TestIfElementTouchesCustomElement(x, y);         /* for empty space */
-#endif
+       TestIfElementTouchesCustomElement(x, y);        // for empty space
 
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
                                          player->index_bit, dig_side);
     }
   }
-  else if (IS_PUSHABLE(element))
+  else if (player_can_move_or_snap && IS_PUSHABLE(element))
   {
     if (mode == DF_SNAP && element != EL_BD_ROCK)
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (CAN_FALL(element) && dy)
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
        !(element == EL_SPRING && level.use_spring_bug))
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
        ((move_direction & MV_VERTICAL &&
@@ -10441,12 +13963,12 @@ int DigField(struct PlayerInfo *player,
            IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
           (element_info[element].move_pattern & MV_DOWN &&
            IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
-    /* do not push elements already moving away faster than player */
+    // do not push elements already moving away faster than player
     if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
        ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (game.engine_version >= VERSION_IDENT(3,1,0,0))
     {
@@ -10465,42 +13987,62 @@ int DigField(struct PlayerInfo *player,
     }
 
     player->is_pushing = TRUE;
+    player->is_active = TRUE;
 
     if (!(IN_LEV_FIELD(nextx, nexty) &&
          (IS_FREE(nextx, nexty) ||
-          (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY &&
-           IS_SB_ELEMENT(element)))))
-      return MF_NO_ACTION;
+          (IS_SB_ELEMENT(element) &&
+           Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
+          (IS_CUSTOM_ELEMENT(element) &&
+           CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
+      return MP_NO_ACTION;
 
     if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
-    if (player->push_delay == -1)      /* new pushing; restart delay */
+    if (player->push_delay == -1)      // new pushing; restart delay
       player->push_delay = 0;
 
     if (player->push_delay < player->push_delay_value &&
        !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
        element != EL_SPRING && element != EL_BALLOON)
     {
-      /* make sure that there is no move delay before next try to push */
+      // make sure that there is no move delay before next try to push
       if (game.engine_version >= VERSION_IDENT(3,0,7,1))
        player->move_delay = 0;
 
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
+    }
+
+    if (IS_CUSTOM_ELEMENT(element) &&
+       CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
+    {
+      if (!DigFieldByCE(nextx, nexty, element))
+       return MP_NO_ACTION;
     }
 
     if (IS_SB_ELEMENT(element))
     {
+      boolean sokoban_task_solved = FALSE;
+
       if (element == EL_SOKOBAN_FIELD_FULL)
       {
        Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokobanfields_still_needed++;
+
+       IncrementSokobanFieldsNeeded();
+       IncrementSokobanObjectsNeeded();
       }
 
       if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
       {
        Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokobanfields_still_needed--;
+
+       DecrementSokobanFieldsNeeded();
+       DecrementSokobanObjectsNeeded();
+
+       // sokoban object was pushed from empty field to sokoban field
+       if (Back[x][y] == EL_EMPTY)
+         sokoban_task_solved = TRUE;
       }
 
       Feld[x][y] = EL_SOKOBAN_OBJECT;
@@ -10514,10 +14056,15 @@ int DigField(struct PlayerInfo *player,
        PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
                                    ACTION_FILLING);
 
-      if (local_player->sokobanfields_still_needed == 0 &&
-         game.emulation == EMU_SOKOBAN)
+      if (sokoban_task_solved &&
+         game.sokoban_fields_still_needed == 0 &&
+         game.sokoban_objects_still_needed == 0 &&
+         (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
       {
-       player->LevelSolved = player->GameOver = TRUE;
+       game.players_still_needed = 0;
+
+       LevelSolved();
+
        PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
       }
     }
@@ -10538,9 +14085,9 @@ int DigField(struct PlayerInfo *player,
     if (game.engine_version < VERSION_IDENT(2,2,0,7))
       player->push_delay_value = GET_NEW_PUSH_DELAY(element);
     else
-      player->push_delay_value = -1;   /* get new value later */
+      player->push_delay_value = -1;   // get new value later
 
-    /* check for element change _after_ element has been pushed */
+    // check for element change _after_ element has been pushed
     if (game.use_change_when_pushing_bug)
     {
       CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
@@ -10556,7 +14103,7 @@ int DigField(struct PlayerInfo *player,
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                          player->index_bit, dig_side);
 
-      return MF_ACTION;
+      return MP_ACTION;
     }
 
     player->is_switching = TRUE;
@@ -10568,25 +14115,30 @@ int DigField(struct PlayerInfo *player,
     if (element == EL_ROBOT_WHEEL)
     {
       Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
-      ZX = x;
-      ZY = y;
 
-      DrawLevelField(x, y);
+      game.robot_wheel_x = x;
+      game.robot_wheel_y = y;
+      game.robot_wheel_active = TRUE;
+
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_SP_TERMINAL)
     {
       int xx, yy;
 
-#if 1
       SCAN_PLAYFIELD(xx, yy)
-#else
-      for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++)
-#endif
       {
        if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
+       {
          Bang(xx, yy);
+       }
        else if (Feld[xx][yy] == EL_SP_TERMINAL)
+       {
          Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
+
+         ResetGfxAnimation(xx, yy);
+         TEST_DrawLevelField(xx, yy);
+       }
       }
     }
     else if (IS_BELT_SWITCH(element))
@@ -10594,7 +14146,9 @@ int DigField(struct PlayerInfo *player,
       ToggleBeltSwitch(x, y);
     }
     else if (element == EL_SWITCHGATE_SWITCH_UP ||
-            element == EL_SWITCHGATE_SWITCH_DOWN)
+            element == EL_SWITCHGATE_SWITCH_DOWN ||
+            element == EL_DC_SWITCHGATE_SWITCH_UP ||
+            element == EL_DC_SWITCHGATE_SWITCH_DOWN)
     {
       ToggleSwitchgateSwitch(x, y);
     }
@@ -10603,7 +14157,8 @@ int DigField(struct PlayerInfo *player,
     {
       ToggleLightSwitch(x, y);
     }
-    else if (element == EL_TIMEGATE_SWITCH)
+    else if (element == EL_TIMEGATE_SWITCH ||
+            element == EL_DC_TIMEGATE_SWITCH)
     {
       ActivateTimegateSwitch(x, y);
     }
@@ -10624,10 +14179,10 @@ int DigField(struct PlayerInfo *player,
     else if (element == EL_LAMP)
     {
       Feld[x][y] = EL_LAMP_ACTIVE;
-      local_player->lights_still_needed--;
+      game.lights_still_needed--;
 
       ResetGfxAnimation(x, y);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_TIME_ORB_FULL)
     {
@@ -10636,11 +14191,42 @@ int DigField(struct PlayerInfo *player,
       if (level.time > 0 || level.use_time_orb_bug)
       {
        TimeLeft += level.time_orb_time;
-       DrawGameValue_Time(TimeLeft);
+       game.no_time_limit = FALSE;
+
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+       DisplayGameControlValues();
       }
 
       ResetGfxAnimation(x, y);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
+            element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
+    {
+      int xx, yy;
+
+      game.ball_state = !game.ball_state;
+
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       int e = Feld[xx][yy];
+
+       if (game.ball_state)
+       {
+         if (e == EL_EMC_MAGIC_BALL)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
+         else if (e == EL_EMC_MAGIC_BALL_SWITCH)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
+       }
+       else
+       {
+         if (e == EL_EMC_MAGIC_BALL_ACTIVE)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL);
+         else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
+       }
+      }
     }
 
     CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
@@ -10652,7 +14238,7 @@ int DigField(struct PlayerInfo *player,
     CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                        player->index_bit, dig_side);
 
-    return MF_ACTION;
+    return MP_ACTION;
   }
   else
   {
@@ -10678,21 +14264,72 @@ int DigField(struct PlayerInfo *player,
     CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                        player->index_bit, dig_side);
 
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
   }
 
   player->push_delay = -1;
 
-  if (is_player)               /* function can also be called by EL_PENGUIN */
+  if (is_player)               // function can also be called by EL_PENGUIN
   {
-    if (Feld[x][y] != element)         /* really digged/collected something */
+    if (Feld[x][y] != element)         // really digged/collected something
+    {
       player->is_collecting = !player->is_digging;
+      player->is_active = TRUE;
+    }
+  }
+
+  return MP_MOVING;
+}
+
+static boolean DigFieldByCE(int x, int y, int digging_element)
+{
+  int element = Feld[x][y];
+
+  if (!IS_FREE(x, y))
+  {
+    int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
+                 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
+                 ACTION_BREAKING);
+
+    // no element can dig solid indestructible elements
+    if (IS_INDESTRUCTIBLE(element) &&
+       !IS_DIGGABLE(element) &&
+       !IS_COLLECTIBLE(element))
+      return FALSE;
+
+    if (AmoebaNr[x][y] &&
+       (element == EL_AMOEBA_FULL ||
+        element == EL_BD_AMOEBA ||
+        element == EL_AMOEBA_GROWING))
+    {
+      AmoebaCnt[AmoebaNr[x][y]]--;
+      AmoebaCnt2[AmoebaNr[x][y]]--;
+    }
+
+    if (IS_MOVING(x, y))
+      RemoveMovingField(x, y);
+    else
+    {
+      RemoveField(x, y);
+      TEST_DrawLevelField(x, y);
+    }
+
+    // if digged element was about to explode, prevent the explosion
+    ExplodeField[x][y] = EX_TYPE_NONE;
+
+    PlayLevelSoundAction(x, y, action);
   }
 
-  return MF_MOVING;
+  Store[x][y] = EL_EMPTY;
+
+  // this makes it possible to leave the removed element again
+  if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
+    Store[x][y] = element;
+
+  return TRUE;
 }
 
-boolean SnapField(struct PlayerInfo *player, int dx, int dy)
+static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 {
   int jx = player->jx, jy = player->jy;
   int x = jx + dx, y = jy + dy;
@@ -10700,6 +14337,8 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
                        dx == +1 ? MV_RIGHT :
                        dy == -1 ? MV_UP    :
                        dy == +1 ? MV_DOWN  : MV_NONE);
+  boolean can_continue_snapping = (level.continuous_snapping &&
+                                  WasJustFalling[x][y] < CHECK_DELAY_FALLING);
 
   if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
     return FALSE;
@@ -10727,7 +14366,8 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
     return FALSE;
   }
 
-  if (player->is_snapping)
+  // prevent snapping with already pressed snap key when not allowed
+  if (player->is_snapping && !can_continue_snapping)
     return FALSE;
 
   player->MovDir = snap_direction;
@@ -10740,11 +14380,14 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
   }
 
   player->is_dropping = FALSE;
+  player->is_dropping_pressed = FALSE;
+  player->drop_pressed_delay = 0;
 
-  if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MF_NO_ACTION)
+  if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
     return FALSE;
 
   player->is_snapping = TRUE;
+  player->is_active = TRUE;
 
   if (player->MovPos == 0)
   {
@@ -10753,34 +14396,28 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
     player->is_collecting = FALSE;
   }
 
-  if (player->MovPos != 0)     /* prevent graphic bugs in versions < 2.2.0 */
-    DrawLevelField(player->last_jx, player->last_jy);
+  if (player->MovPos != 0)     // prevent graphic bugs in versions < 2.2.0
+    TEST_DrawLevelField(player->last_jx, player->last_jy);
 
-  DrawLevelField(x, y);
+  TEST_DrawLevelField(x, y);
 
   return TRUE;
 }
 
-boolean DropElement(struct PlayerInfo *player)
+static boolean DropElement(struct PlayerInfo *player)
 {
   int old_element, new_element;
   int dropx = player->jx, dropy = player->jy;
   int drop_direction = player->MovDir;
   int drop_side = drop_direction;
-  int drop_element = (player->inventory_size > 0 ?
-                     player->inventory_element[player->inventory_size - 1] :
-                     player->inventory_infinite_element != EL_UNDEFINED ?
-                     player->inventory_infinite_element :
-                     player->dynabombs_left > 0 ?
-                     EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
-                     EL_UNDEFINED);
+  int drop_element = get_next_dropped_element(player);
 
   /* do not drop an element on top of another element; when holding drop key
      pressed without moving, dropped element must move away before the next
      element can be dropped (this is especially important if the next element
      is dynamite, which can be placed on background for historical reasons) */
   if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
-    return MF_ACTION;
+    return MP_ACTION;
 
   if (IS_THROWABLE(drop_element))
   {
@@ -10791,27 +14428,34 @@ boolean DropElement(struct PlayerInfo *player)
       return FALSE;
   }
 
-  old_element = Feld[dropx][dropy];    /* old element at dropping position */
-  new_element = drop_element;          /* default: no change when dropping */
+  old_element = Feld[dropx][dropy];    // old element at dropping position
+  new_element = drop_element;          // default: no change when dropping
 
-  /* check if player is active, not moving and ready to drop */
+  // check if player is active, not moving and ready to drop
   if (!player->active || player->MovPos || player->drop_delay > 0)
     return FALSE;
 
-  /* check if player has anything that can be dropped */
+  // check if player has anything that can be dropped
   if (new_element == EL_UNDEFINED)
     return FALSE;
 
-  /* check if anything can be dropped at the current position */
+  // only set if player has anything that can be dropped
+  player->is_dropping_pressed = TRUE;
+
+  // check if drop key was pressed long enough for EM style dynamite
+  if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
+    return FALSE;
+
+  // check if anything can be dropped at the current position
   if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
     return FALSE;
 
-  /* collected custom elements can only be dropped on empty fields */
+  // collected custom elements can only be dropped on empty fields
   if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
     return FALSE;
 
   if (old_element != EL_EMPTY)
-    Back[dropx][dropy] = old_element;  /* store old element on this field */
+    Back[dropx][dropy] = old_element;  // store old element on this field
 
   ResetGfxAnimation(dropx, dropy);
   ResetRandomAnimationValue(dropx, dropy);
@@ -10823,10 +14467,12 @@ boolean DropElement(struct PlayerInfo *player)
     {
       player->inventory_size--;
 
-      DrawGameValue_Dynamite(local_player->inventory_size);
+      DrawGameDoorValues();
 
       if (new_element == EL_DYNAMITE)
        new_element = EL_DYNAMITE_ACTIVE;
+      else if (new_element == EL_EM_DYNAMITE)
+       new_element = EL_EM_DYNAMITE_ACTIVE;
       else if (new_element == EL_SP_DISK_RED)
        new_element = EL_SP_DISK_RED_ACTIVE;
     }
@@ -10839,8 +14485,8 @@ boolean DropElement(struct PlayerInfo *player)
 
     PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
 
-    /* needed if previous element just changed to "empty" in the last frame */
-    ChangeCount[dropx][dropy] = 0;     /* allow at least one more change */
+    // needed if previous element just changed to "empty" in the last frame
+    ChangeCount[dropx][dropy] = 0;     // allow at least one more change
 
     CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
                               player->index_bit, drop_side);
@@ -10850,7 +14496,7 @@ boolean DropElement(struct PlayerInfo *player)
 
     TestIfElementTouchesCustomElement(dropx, dropy);
   }
-  else         /* player is dropping a dyna bomb */
+  else         // player is dropping a dyna bomb
   {
     player->dynabombs_left--;
 
@@ -10863,44 +14509,43 @@ boolean DropElement(struct PlayerInfo *player)
     PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
   }
 
-  if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
+  if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
     InitField_WithBug1(dropx, dropy, FALSE);
 
-  new_element = Feld[dropx][dropy];    /* element might have changed */
+  new_element = Feld[dropx][dropy];    // element might have changed
 
   if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
       element_info[new_element].move_pattern == MV_WHEN_DROPPED)
   {
-    int move_direction, nextx, nexty;
-
     if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
       MovDir[dropx][dropy] = drop_direction;
 
-    move_direction = MovDir[dropx][dropy];
-    nextx = dropx + GET_DX_FROM_DIR(move_direction);
-    nexty = dropy + GET_DY_FROM_DIR(move_direction);
+    ChangeCount[dropx][dropy] = 0;     // allow at least one more change
 
-    ChangeCount[dropx][dropy] = 0;     /* allow at least one more change */
-    CheckCollision[dropx][dropy] = 2;
+    // do not cause impact style collision by dropping elements that can fall
+    CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
   }
 
   player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
   player->is_dropping = TRUE;
 
+  player->drop_pressed_delay = 0;
+  player->is_dropping_pressed = FALSE;
+
   player->drop_x = dropx;
   player->drop_y = dropy;
 
   return TRUE;
 }
 
-/* ------------------------------------------------------------------------- */
-/* game sound playing functions                                              */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// game sound playing functions
+// ----------------------------------------------------------------------------
 
 static int *loop_sound_frame = NULL;
 static int *loop_sound_volume = NULL;
 
-void InitPlayLevelSound()
+void InitPlayLevelSound(void)
 {
   int num_sounds = getSoundListSize();
 
@@ -11003,17 +14648,51 @@ static void StopLevelSoundActionIfLoop(int x, int y, int action)
     StopSound(sound_effect);
 }
 
-static void PlayLevelMusic()
+static int getLevelMusicNr(void)
 {
   if (levelset.music[level_nr] != MUS_UNDEFINED)
-    PlayMusic(levelset.music[level_nr]);       /* from config file */
+    return levelset.music[level_nr];           // from config file
   else
-    PlayMusic(MAP_NOCONF_MUSIC(level_nr));     /* from music dir */
+    return MAP_NOCONF_MUSIC(level_nr);         // from music dir
+}
+
+static void FadeLevelSounds(void)
+{
+  FadeSounds();
+}
+
+static void FadeLevelMusic(void)
+{
+  int music_nr = getLevelMusicNr();
+  char *curr_music = getCurrentlyPlayingMusicFilename();
+  char *next_music = getMusicInfoEntryFilename(music_nr);
+
+  if (!strEqual(curr_music, next_music))
+    FadeMusic();
 }
 
-void PlayLevelSound_EM(int x, int y, int element_em, int sample)
+void FadeLevelSoundsAndMusic(void)
+{
+  FadeLevelSounds();
+  FadeLevelMusic();
+}
+
+static void PlayLevelMusic(void)
+{
+  int music_nr = getLevelMusicNr();
+  char *curr_music = getCurrentlyPlayingMusicFilename();
+  char *next_music = getMusicInfoEntryFilename(music_nr);
+
+  if (!strEqual(curr_music, next_music))
+    PlayMusicLoop(music_nr);
+}
+
+void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
 {
   int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
+  int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
+  int x = xx - 1 - offset;
+  int y = yy - 1 - offset;
 
   switch (sample)
   {
@@ -11058,7 +14737,7 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
       break;
 
     case SAMPLE_slurp:
-      PlayLevelSoundElementAction(x, y, element, ACTION_SLURPED_BY_SPRING);
+      PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
       break;
 
     case SAMPLE_eater:
@@ -11082,7 +14761,7 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
       break;
 
     case SAMPLE_squash:
-      /* !!! CHECK THIS !!! */
+      // !!! CHECK THIS !!!
 #if 1
       PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
 #else
@@ -11159,7 +14838,7 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
       break;
 
     case SAMPLE_time:
-      PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+      PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
       break;
 
     default:
@@ -11168,16 +14847,73 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
   }
 }
 
+void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
+{
+  int element = map_element_SP_to_RND(element_sp);
+  int action = map_action_SP_to_RND(action_sp);
+  int offset = (setup.sp_show_border_elements ? 0 : 1);
+  int x = xx - offset;
+  int y = yy - offset;
+
+  PlayLevelSoundElementAction(x, y, element, action);
+}
+
+void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
+{
+  int element = map_element_MM_to_RND(element_mm);
+  int action = map_action_MM_to_RND(action_mm);
+  int offset = 0;
+  int x = xx - offset;
+  int y = yy - offset;
+
+  if (!IS_MM_ELEMENT(element))
+    element = EL_MM_DEFAULT;
+
+  PlayLevelSoundElementAction(x, y, element, action);
+}
+
+void PlaySound_MM(int sound_mm)
+{
+  int sound = map_sound_MM_to_RND(sound_mm);
+
+  if (sound == SND_UNDEFINED)
+    return;
+
+  PlaySound(sound);
+}
+
+void PlaySoundLoop_MM(int sound_mm)
+{
+  int sound = map_sound_MM_to_RND(sound_mm);
+
+  if (sound == SND_UNDEFINED)
+    return;
+
+  PlaySoundLoop(sound);
+}
+
+void StopSound_MM(int sound_mm)
+{
+  int sound = map_sound_MM_to_RND(sound_mm);
+
+  if (sound == SND_UNDEFINED)
+    return;
+
+  StopSound(sound);
+}
+
 void RaiseScore(int value)
 {
-  local_player->score += value;
+  game.score += value;
 
-  DrawGameValue_Score(local_player->score);
+  game_panel_controls[GAME_PANEL_SCORE].value = game.score;
+
+  DisplayGameControlValues();
 }
 
 void RaiseScoreElement(int element)
 {
-  switch(element)
+  switch (element)
   {
     case EL_EMERALD:
     case EL_BD_DIAMOND:
@@ -11220,6 +14956,7 @@ void RaiseScoreElement(int element)
       RaiseScore(level.score[SC_NUT]);
       break;
     case EL_DYNAMITE:
+    case EL_EM_DYNAMITE:
     case EL_SP_DISK_RED:
     case EL_DYNABOMB_INCREASE_NUMBER:
     case EL_DYNABOMB_INCREASE_SIZE:
@@ -11245,6 +14982,7 @@ void RaiseScoreElement(int element)
     case EL_EMC_KEY_6:
     case EL_EMC_KEY_7:
     case EL_EMC_KEY_8:
+    case EL_DC_KEY_WHITE:
       RaiseScore(level.score[SC_KEY]);
       break;
     default:
@@ -11253,148 +14991,646 @@ void RaiseScoreElement(int element)
   }
 }
 
-void RequestQuitGame(boolean ask_if_really_quit)
+void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
+{
+  if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
+  {
+    // closing door required in case of envelope style request dialogs
+    if (!skip_request)
+    {
+      // prevent short reactivation of overlay buttons while closing door
+      SetOverlayActive(FALSE);
+
+      CloseDoor(DOOR_CLOSE_1);
+    }
+
+    if (network.enabled)
+      SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
+    else
+    {
+      if (quick_quit)
+       FadeSkipNextFadeIn();
+
+      SetGameStatus(GAME_MODE_MAIN);
+
+      DrawMainMenu();
+    }
+  }
+  else         // continue playing the game
+  {
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOff(TRUE);
+
+    OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
+
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOn();
+  }
+}
+
+void RequestQuitGame(boolean ask_if_really_quit)
+{
+  boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
+  boolean skip_request = game.all_players_gone || quick_quit;
+
+  RequestQuitGameExt(skip_request, quick_quit,
+                    "Do you really want to quit the game?");
+}
+
+void RequestRestartGame(char *message)
+{
+  game.restart_game_message = NULL;
+
+  boolean has_started_game = hasStartedNetworkGame();
+  int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
+
+  if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
+  {
+    StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+  }
+  else
+  {
+    SetGameStatus(GAME_MODE_MAIN);
+
+    DrawMainMenu();
+  }
+}
+
+void CheckGameOver(void)
+{
+  static boolean last_game_over = FALSE;
+  static int game_over_delay = 0;
+  int game_over_delay_value = 50;
+  boolean game_over = checkGameFailed();
+
+  // do not handle game over if request dialog is already active
+  if (game.request_active)
+    return;
+
+  // do not ask to play again if game was never actually played
+  if (!game.GamePlayed)
+    return;
+
+  if (!game_over)
+  {
+    last_game_over = FALSE;
+    game_over_delay = game_over_delay_value;
+
+    return;
+  }
+
+  if (game_over_delay > 0)
+  {
+    game_over_delay--;
+
+    return;
+  }
+
+  if (last_game_over != game_over)
+    game.restart_game_message = (hasStartedNetworkGame() ?
+                                "Game over! Play it again?" :
+                                "Game over!");
+
+  last_game_over = game_over;
+}
+
+boolean checkGameSolved(void)
+{
+  // set for all game engines if level was solved
+  return game.LevelSolved_GameEnd;
+}
+
+boolean checkGameFailed(void)
+{
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    return (game_em.game_over && !game_em.level_solved);
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    return (game_sp.game_over && !game_sp.level_solved);
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+    return (game_mm.game_over && !game_mm.level_solved);
+  else                         // GAME_ENGINE_TYPE_RND
+    return (game.GameOver && !game.LevelSolved);
+}
+
+boolean checkGameEnded(void)
+{
+  return (checkGameSolved() || checkGameFailed());
+}
+
+
+// ----------------------------------------------------------------------------
+// random generator functions
+// ----------------------------------------------------------------------------
+
+unsigned int InitEngineRandom_RND(int seed)
+{
+  game.num_random_calls = 0;
+
+  return InitEngineRandom(seed);
+}
+
+unsigned int RND(int max)
+{
+  if (max > 0)
+  {
+    game.num_random_calls++;
+
+    return GetEngineRandom(max);
+  }
+
+  return 0;
+}
+
+
+// ----------------------------------------------------------------------------
+// game engine snapshot handling functions
+// ----------------------------------------------------------------------------
+
+struct EngineSnapshotInfo
+{
+  // runtime values for custom element collect score
+  int collect_score[NUM_CUSTOM_ELEMENTS];
+
+  // runtime values for group element choice position
+  int choice_pos[NUM_GROUP_ELEMENTS];
+
+  // runtime values for belt position animations
+  int belt_graphic[4][NUM_BELT_PARTS];
+  int belt_anim_mode[4][NUM_BELT_PARTS];
+};
+
+static struct EngineSnapshotInfo engine_snapshot_rnd;
+static char *snapshot_level_identifier = NULL;
+static int snapshot_level_nr = -1;
+
+static void SaveEngineSnapshotValues_RND(void)
+{
+  static int belt_base_active_element[4] =
+  {
+    EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+  };
+  int i, j;
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+
+    engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
+  }
+
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+  {
+    int element = EL_GROUP_START + i;
+
+    engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
+  }
+
+  for (i = 0; i < 4; i++)
+  {
+    for (j = 0; j < NUM_BELT_PARTS; j++)
+    {
+      int element = belt_base_active_element[i] + j;
+      int graphic = el2img(element);
+      int anim_mode = graphic_info[graphic].anim_mode;
+
+      engine_snapshot_rnd.belt_graphic[i][j] = graphic;
+      engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
+    }
+  }
+}
+
+static void LoadEngineSnapshotValues_RND(void)
 {
-  if (AllPlayersGone ||
-      !ask_if_really_quit ||
-      level_editor_test_game ||
-      Request("Do you really want to quit the game ?",
-             REQ_ASK | REQ_STAY_CLOSED))
+  unsigned int num_random_calls = game.num_random_calls;
+  int i, j;
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
-#if defined(NETWORK_AVALIABLE)
-    if (options.network)
-      SendToServer_StopPlaying();
-    else
-#endif
+    int element = EL_CUSTOM_START + i;
+
+    element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
+  }
+
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+  {
+    int element = EL_GROUP_START + i;
+
+    element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
+  }
+
+  for (i = 0; i < 4; i++)
+  {
+    for (j = 0; j < NUM_BELT_PARTS; j++)
     {
-      game_status = GAME_MODE_MAIN;
-      DrawMainMenu();
+      int graphic = engine_snapshot_rnd.belt_graphic[i][j];
+      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
+
+      graphic_info[graphic].anim_mode = anim_mode;
     }
   }
-  else
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
   {
-    if (tape.playing && tape.deactivate_display)
-      TapeDeactivateDisplayOff(TRUE);
+    InitRND(tape.random_seed);
+    for (i = 0; i < num_random_calls; i++)
+      RND(1);
+  }
 
-    OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
+  if (game.num_random_calls != num_random_calls)
+  {
+    Error(ERR_INFO, "number of random calls out of sync");
+    Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
+    Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
+    Error(ERR_EXIT, "this should not happen -- please debug");
+  }
+}
 
-    if (tape.playing && tape.deactivate_display)
-      TapeDeactivateDisplayOn();
+void FreeEngineSnapshotSingle(void)
+{
+  FreeSnapshotSingle();
+
+  setString(&snapshot_level_identifier, NULL);
+  snapshot_level_nr = -1;
+}
+
+void FreeEngineSnapshotList(void)
+{
+  FreeSnapshotList();
+}
+
+static ListNode *SaveEngineSnapshotBuffers(void)
+{
+  ListNode *buffers = NULL;
+
+  // copy some special values to a structure better suited for the snapshot
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    SaveEngineSnapshotValues_RND();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    SaveEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    SaveEngineSnapshotValues_SP(&buffers);
+  if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+    SaveEngineSnapshotValues_MM(&buffers);
+
+  // save values stored in special snapshot structure
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
+
+  // save further RND engine values
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
+
+#if 0
+  ListNode *node = engine_snapshot_list_rnd;
+  int num_bytes = 0;
+
+  while (node != NULL)
+  {
+    num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
+
+    node = node->next;
   }
+
+  printf("::: size of engine snapshot: %d bytes\n", num_bytes);
+#endif
+
+  return buffers;
+}
+
+void SaveEngineSnapshotSingle(void)
+{
+  ListNode *buffers = SaveEngineSnapshotBuffers();
+
+  // finally save all snapshot buffers to single snapshot
+  SaveSnapshotSingle(buffers);
+
+  // save level identification information
+  setString(&snapshot_level_identifier, leveldir_current->identifier);
+  snapshot_level_nr = level_nr;
+}
+
+boolean CheckSaveEngineSnapshotToList(void)
+{
+  boolean save_snapshot =
+    ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
+     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
+      game.snapshot.changed_action) ||
+     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
+      game.snapshot.collected_item));
+
+  game.snapshot.changed_action = FALSE;
+  game.snapshot.collected_item = FALSE;
+  game.snapshot.save_snapshot = save_snapshot;
+
+  return save_snapshot;
+}
+
+void SaveEngineSnapshotToList(void)
+{
+  if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
+      tape.quick_resume)
+    return;
+
+  ListNode *buffers = SaveEngineSnapshotBuffers();
+
+  // finally save all snapshot buffers to snapshot list
+  SaveSnapshotToList(buffers);
+}
+
+void SaveEngineSnapshotToListInitial(void)
+{
+  FreeEngineSnapshotList();
+
+  SaveEngineSnapshotToList();
+}
+
+static void LoadEngineSnapshotValues(void)
+{
+  // restore special values from snapshot structure
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    LoadEngineSnapshotValues_RND();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    LoadEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    LoadEngineSnapshotValues_SP();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+    LoadEngineSnapshotValues_MM();
+}
+
+void LoadEngineSnapshotSingle(void)
+{
+  LoadSnapshotSingle();
+
+  LoadEngineSnapshotValues();
+}
+
+static void LoadEngineSnapshot_Undo(int steps)
+{
+  LoadSnapshotFromList_Older(steps);
+
+  LoadEngineSnapshotValues();
+}
+
+static void LoadEngineSnapshot_Redo(int steps)
+{
+  LoadSnapshotFromList_Newer(steps);
+
+  LoadEngineSnapshotValues();
 }
 
+boolean CheckEngineSnapshotSingle(void)
+{
+  return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
+         snapshot_level_nr == level_nr);
+}
 
-/* ---------- new game button stuff ---------------------------------------- */
+boolean CheckEngineSnapshotList(void)
+{
+  return CheckSnapshotList();
+}
 
-/* graphic position values for game buttons */
-#define GAME_BUTTON_XSIZE      30
-#define GAME_BUTTON_YSIZE      30
-#define GAME_BUTTON_XPOS       5
-#define GAME_BUTTON_YPOS       215
-#define SOUND_BUTTON_XPOS      5
-#define SOUND_BUTTON_YPOS      (GAME_BUTTON_YPOS + GAME_BUTTON_YSIZE)
 
-#define GAME_BUTTON_STOP_XPOS  (GAME_BUTTON_XPOS + 0 * GAME_BUTTON_XSIZE)
-#define GAME_BUTTON_PAUSE_XPOS (GAME_BUTTON_XPOS + 1 * GAME_BUTTON_XSIZE)
-#define GAME_BUTTON_PLAY_XPOS  (GAME_BUTTON_XPOS + 2 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_MUSIC_XPOS        (SOUND_BUTTON_XPOS + 0 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_LOOPS_XPOS        (SOUND_BUTTON_XPOS + 1 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_SIMPLE_XPOS (SOUND_BUTTON_XPOS + 2 * GAME_BUTTON_XSIZE)
+// ---------- new game button stuff -------------------------------------------
 
 static struct
 {
-  int x, y;
+  int graphic;
+  struct XY *pos;
   int gadget_id;
+  boolean *setup_value;
+  boolean allowed_on_tape;
   char *infotext;
 } gamebutton_info[NUM_GAME_BUTTONS] =
 {
   {
-    GAME_BUTTON_STOP_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_STOP,
-    "stop game"
+    IMG_GFX_GAME_BUTTON_STOP,                  &game.button.stop,
+    GAME_CTRL_ID_STOP,                         NULL,
+    TRUE,                                      "stop game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PAUSE,                 &game.button.pause,
+    GAME_CTRL_ID_PAUSE,                                NULL,
+    TRUE,                                      "pause game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PLAY,                  &game.button.play,
+    GAME_CTRL_ID_PLAY,                         NULL,
+    TRUE,                                      "play game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_UNDO,                  &game.button.undo,
+    GAME_CTRL_ID_UNDO,                         NULL,
+    TRUE,                                      "undo step"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_REDO,                  &game.button.redo,
+    GAME_CTRL_ID_REDO,                         NULL,
+    TRUE,                                      "redo step"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_SAVE,                  &game.button.save,
+    GAME_CTRL_ID_SAVE,                         NULL,
+    TRUE,                                      "save game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PAUSE2,                        &game.button.pause2,
+    GAME_CTRL_ID_PAUSE2,                       NULL,
+    TRUE,                                      "pause game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_LOAD,                  &game.button.load,
+    GAME_CTRL_ID_LOAD,                         NULL,
+    TRUE,                                      "load game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PANEL_STOP,            &game.button.panel_stop,
+    GAME_CTRL_ID_PANEL_STOP,                   NULL,
+    FALSE,                                     "stop game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PANEL_PAUSE,           &game.button.panel_pause,
+    GAME_CTRL_ID_PANEL_PAUSE,                  NULL,
+    FALSE,                                     "pause game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PANEL_PLAY,            &game.button.panel_play,
+    GAME_CTRL_ID_PANEL_PLAY,                   NULL,
+    FALSE,                                     "play game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_SOUND_MUSIC,           &game.button.sound_music,
+    SOUND_CTRL_ID_MUSIC,                       &setup.sound_music,
+    TRUE,                                      "background music on/off"
   },
   {
-    GAME_BUTTON_PAUSE_XPOS,    GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PAUSE,
-    "pause game"
+    IMG_GFX_GAME_BUTTON_SOUND_LOOPS,           &game.button.sound_loops,
+    SOUND_CTRL_ID_LOOPS,                       &setup.sound_loops,
+    TRUE,                                      "sound loops on/off"
   },
   {
-    GAME_BUTTON_PLAY_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PLAY,
-    "play game"
+    IMG_GFX_GAME_BUTTON_SOUND_SIMPLE,          &game.button.sound_simple,
+    SOUND_CTRL_ID_SIMPLE,                      &setup.sound_simple,
+    TRUE,                                      "normal sounds on/off"
   },
   {
-    SOUND_BUTTON_MUSIC_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_MUSIC,
-    "background music on/off"
+    IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC,     &game.button.panel_sound_music,
+    SOUND_CTRL_ID_PANEL_MUSIC,                 &setup.sound_music,
+    FALSE,                                     "background music on/off"
   },
   {
-    SOUND_BUTTON_LOOPS_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_LOOPS,
-    "sound loops on/off"
+    IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS,     &game.button.panel_sound_loops,
+    SOUND_CTRL_ID_PANEL_LOOPS,                 &setup.sound_loops,
+    FALSE,                                     "sound loops on/off"
   },
   {
-    SOUND_BUTTON_SIMPLE_XPOS,  SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_SIMPLE,
-    "normal sounds on/off"
+    IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE,    &game.button.panel_sound_simple,
+    SOUND_CTRL_ID_PANEL_SIMPLE,                        &setup.sound_simple,
+    FALSE,                                     "normal sounds on/off"
   }
 };
 
-void CreateGameButtons()
+void CreateGameButtons(void)
 {
   int i;
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
   {
-    Bitmap *gd_bitmap = graphic_info[IMG_GLOBAL_DOOR].bitmap;
+    int graphic = gamebutton_info[i].graphic;
+    struct GraphicInfo *gfx = &graphic_info[graphic];
+    struct XY *pos = gamebutton_info[i].pos;
     struct GadgetInfo *gi;
     int button_type;
     boolean checked;
-    unsigned long event_mask;
-    int gd_xoffset, gd_yoffset;
-    int gd_x1, gd_x2, gd_y1, gd_y2;
+    unsigned int event_mask;
+    boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
+    boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
+    int base_x = (on_tape ? VX : DX);
+    int base_y = (on_tape ? VY : DY);
+    int gd_x   = gfx->src_x;
+    int gd_y   = gfx->src_y;
+    int gd_xp  = gfx->src_x + gfx->pressed_xoffset;
+    int gd_yp  = gfx->src_y + gfx->pressed_yoffset;
+    int gd_xa  = gfx->src_x + gfx->active_xoffset;
+    int gd_ya  = gfx->src_y + gfx->active_yoffset;
+    int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
+    int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
     int id = i;
 
-    gd_xoffset = gamebutton_info[i].x;
-    gd_yoffset = gamebutton_info[i].y;
-    gd_x1 = DOOR_GFX_PAGEX4 + gd_xoffset;
-    gd_x2 = DOOR_GFX_PAGEX3 + gd_xoffset;
+    if (gfx->bitmap == NULL)
+    {
+      game_gadget[id] = NULL;
+
+      continue;
+    }
 
     if (id == GAME_CTRL_ID_STOP ||
-       id == GAME_CTRL_ID_PAUSE ||
-       id == GAME_CTRL_ID_PLAY)
+       id == GAME_CTRL_ID_PANEL_STOP ||
+       id == GAME_CTRL_ID_PLAY ||
+       id == GAME_CTRL_ID_PANEL_PLAY ||
+       id == GAME_CTRL_ID_SAVE ||
+       id == GAME_CTRL_ID_LOAD)
     {
       button_type = GD_TYPE_NORMAL_BUTTON;
       checked = FALSE;
       event_mask = GD_EVENT_RELEASED;
-      gd_y1  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
-      gd_y2  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
+    }
+    else if (id == GAME_CTRL_ID_UNDO ||
+            id == GAME_CTRL_ID_REDO)
+    {
+      button_type = GD_TYPE_NORMAL_BUTTON;
+      checked = FALSE;
+      event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
     }
     else
     {
       button_type = GD_TYPE_CHECK_BUTTON;
-      checked =
-       ((id == SOUND_CTRL_ID_MUSIC && setup.sound_music) ||
-        (id == SOUND_CTRL_ID_LOOPS && setup.sound_loops) ||
-        (id == SOUND_CTRL_ID_SIMPLE && setup.sound_simple) ? TRUE : FALSE);
+      checked = (gamebutton_info[i].setup_value != NULL ?
+                *gamebutton_info[i].setup_value : FALSE);
       event_mask = GD_EVENT_PRESSED;
-      gd_y1  = DOOR_GFX_PAGEY1 + gd_yoffset;
-      gd_y2  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
     }
 
     gi = CreateGadget(GDI_CUSTOM_ID, id,
+                     GDI_IMAGE_ID, graphic,
                      GDI_INFO_TEXT, gamebutton_info[i].infotext,
-                     GDI_X, DX + gd_xoffset,
-                     GDI_Y, DY + gd_yoffset,
-                     GDI_WIDTH, GAME_BUTTON_XSIZE,
-                     GDI_HEIGHT, GAME_BUTTON_YSIZE,
+                     GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
+                     GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
+                     GDI_WIDTH, gfx->width,
+                     GDI_HEIGHT, gfx->height,
                      GDI_TYPE, button_type,
                      GDI_STATE, GD_BUTTON_UNPRESSED,
                      GDI_CHECKED, checked,
-                     GDI_DESIGN_UNPRESSED, gd_bitmap, gd_x1, gd_y1,
-                     GDI_DESIGN_PRESSED, gd_bitmap, gd_x2, gd_y1,
-                     GDI_ALT_DESIGN_UNPRESSED, gd_bitmap, gd_x1, gd_y2,
-                     GDI_ALT_DESIGN_PRESSED, gd_bitmap, gd_x2, gd_y2,
+                     GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
+                     GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
+                     GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
+                     GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
+                     GDI_DIRECT_DRAW, FALSE,
                      GDI_EVENT_MASK, event_mask,
                      GDI_CALLBACK_ACTION, HandleGameButtons,
                      GDI_END);
@@ -11406,7 +15642,7 @@ void CreateGameButtons()
   }
 }
 
-void FreeGameButtons()
+void FreeGameButtons(void)
 {
   int i;
 
@@ -11414,68 +15650,287 @@ void FreeGameButtons()
     FreeGadget(game_gadget[i]);
 }
 
-static void MapGameButtons()
+static void UnmapGameButtonsAtSamePosition(int id)
 {
   int i;
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
-    MapGadget(game_gadget[i]);
+    if (i != id &&
+       gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
+       gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
+      UnmapGadget(game_gadget[i]);
+}
+
+static void UnmapGameButtonsAtSamePosition_All(void)
+{
+  if (setup.show_snapshot_buttons)
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
+  }
+  else
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
+
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
+  }
 }
 
-void UnmapGameButtons()
+static void MapGameButtonsAtSamePosition(int id)
 {
   int i;
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
-    UnmapGadget(game_gadget[i]);
+    if (i != id &&
+       gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
+       gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
+      MapGadget(game_gadget[i]);
+
+  UnmapGameButtonsAtSamePosition_All();
 }
 
-static void HandleGameButtons(struct GadgetInfo *gi)
+void MapUndoRedoButtons(void)
+{
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+
+  MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
+  MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
+
+  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
+}
+
+void UnmapUndoRedoButtons(void)
+{
+  UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
+  UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
+
+  MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
+  MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+
+  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
+}
+
+static void MapGameButtonsExt(boolean on_tape)
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
+       i != GAME_CTRL_ID_UNDO &&
+       i != GAME_CTRL_ID_REDO)
+      MapGadget(game_gadget[i]);
+
+  UnmapGameButtonsAtSamePosition_All();
+
+  RedrawGameButtons();
+}
+
+static void UnmapGameButtonsExt(boolean on_tape)
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    if (!on_tape || gamebutton_info[i].allowed_on_tape)
+      UnmapGadget(game_gadget[i]);
+}
+
+static void RedrawGameButtonsExt(boolean on_tape)
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    if (!on_tape || gamebutton_info[i].allowed_on_tape)
+      RedrawGadget(game_gadget[i]);
+
+  // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
+  redraw_mask &= ~REDRAW_ALL;
+}
+
+static void SetGadgetState(struct GadgetInfo *gi, boolean state)
+{
+  if (gi == NULL)
+    return;
+
+  gi->checked = state;
+}
+
+static void RedrawSoundButtonGadget(int id)
+{
+  int id2 = (id == SOUND_CTRL_ID_MUSIC        ? SOUND_CTRL_ID_PANEL_MUSIC :
+            id == SOUND_CTRL_ID_LOOPS        ? SOUND_CTRL_ID_PANEL_LOOPS :
+            id == SOUND_CTRL_ID_SIMPLE       ? SOUND_CTRL_ID_PANEL_SIMPLE :
+            id == SOUND_CTRL_ID_PANEL_MUSIC  ? SOUND_CTRL_ID_MUSIC :
+            id == SOUND_CTRL_ID_PANEL_LOOPS  ? SOUND_CTRL_ID_LOOPS :
+            id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
+            id);
+
+  SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
+  RedrawGadget(game_gadget[id2]);
+}
+
+void MapGameButtons(void)
+{
+  MapGameButtonsExt(FALSE);
+}
+
+void UnmapGameButtons(void)
+{
+  UnmapGameButtonsExt(FALSE);
+}
+
+void RedrawGameButtons(void)
+{
+  RedrawGameButtonsExt(FALSE);
+}
+
+void MapGameButtonsOnTape(void)
+{
+  MapGameButtonsExt(TRUE);
+}
+
+void UnmapGameButtonsOnTape(void)
 {
-  int id = gi->custom_id;
+  UnmapGameButtonsExt(TRUE);
+}
+
+void RedrawGameButtonsOnTape(void)
+{
+  RedrawGameButtonsExt(TRUE);
+}
+
+static void GameUndoRedoExt(void)
+{
+  ClearPlayerAction();
+
+  tape.pausing = TRUE;
+
+  RedrawPlayfield();
+  UpdateAndDisplayGameControlValues();
+
+  DrawCompleteVideoDisplay();
+  DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+  DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
+  DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
+
+  BackToFront();
+}
+
+static void GameUndo(int steps)
+{
+  if (!CheckEngineSnapshotList())
+    return;
+
+  LoadEngineSnapshot_Undo(steps);
 
-  if (game_status != GAME_MODE_PLAYING)
+  GameUndoRedoExt();
+}
+
+static void GameRedo(int steps)
+{
+  if (!CheckEngineSnapshotList())
+    return;
+
+  LoadEngineSnapshot_Redo(steps);
+
+  GameUndoRedoExt();
+}
+
+static void HandleGameButtonsExt(int id, int button)
+{
+  static boolean game_undo_executed = FALSE;
+  int steps = BUTTON_STEPSIZE(button);
+  boolean handle_game_buttons =
+    (game_status == GAME_MODE_PLAYING ||
+     (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
+
+  if (!handle_game_buttons)
     return;
 
   switch (id)
   {
     case GAME_CTRL_ID_STOP:
-      RequestQuitGame(TRUE);
+    case GAME_CTRL_ID_PANEL_STOP:
+      if (game_status == GAME_MODE_MAIN)
+       break;
+
+      if (tape.playing)
+       TapeStop();
+      else
+       RequestQuitGame(TRUE);
+
       break;
 
     case GAME_CTRL_ID_PAUSE:
-      if (options.network)
+    case GAME_CTRL_ID_PAUSE2:
+    case GAME_CTRL_ID_PANEL_PAUSE:
+      if (network.enabled && game_status == GAME_MODE_PLAYING)
       {
-#if defined(NETWORK_AVALIABLE)
        if (tape.pausing)
          SendToServer_ContinuePlaying();
        else
          SendToServer_PausePlaying();
-#endif
       }
       else
        TapeTogglePause(TAPE_TOGGLE_MANUAL);
+
+      game_undo_executed = FALSE;
+
       break;
 
     case GAME_CTRL_ID_PLAY:
-      if (tape.pausing)
+    case GAME_CTRL_ID_PANEL_PLAY:
+      if (game_status == GAME_MODE_MAIN)
+      {
+        StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+      }
+      else if (tape.pausing)
       {
-#if defined(NETWORK_AVALIABLE)
-       if (options.network)
+       if (network.enabled)
          SendToServer_ContinuePlaying();
        else
-#endif
-       {
-         tape.pausing = FALSE;
-         DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF,0);
-       }
+         TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
       }
       break;
 
+    case GAME_CTRL_ID_UNDO:
+      // Important: When using "save snapshot when collecting an item" mode,
+      // load last (current) snapshot for first "undo" after pressing "pause"
+      // (else the last-but-one snapshot would be loaded, because the snapshot
+      // pointer already points to the last snapshot when pressing "pause",
+      // which is fine for "every step/move" mode, but not for "every collect")
+      if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
+         !game_undo_executed)
+       steps--;
+
+      game_undo_executed = TRUE;
+
+      GameUndo(steps);
+      break;
+
+    case GAME_CTRL_ID_REDO:
+      GameRedo(steps);
+      break;
+
+    case GAME_CTRL_ID_SAVE:
+      TapeQuickSave();
+      break;
+
+    case GAME_CTRL_ID_LOAD:
+      TapeQuickLoad();
+      break;
+
     case SOUND_CTRL_ID_MUSIC:
+    case SOUND_CTRL_ID_PANEL_MUSIC:
       if (setup.sound_music)
       { 
        setup.sound_music = FALSE;
+
        FadeMusic();
       }
       else if (audio.music_available)
@@ -11484,31 +15939,60 @@ static void HandleGameButtons(struct GadgetInfo *gi)
 
        SetAudioMode(setup.sound);
 
-       PlayLevelMusic();
+       if (game_status == GAME_MODE_PLAYING)
+         PlayLevelMusic();
       }
+
+      RedrawSoundButtonGadget(id);
+
       break;
 
     case SOUND_CTRL_ID_LOOPS:
+    case SOUND_CTRL_ID_PANEL_LOOPS:
       if (setup.sound_loops)
        setup.sound_loops = FALSE;
       else if (audio.loops_available)
       {
        setup.sound = setup.sound_loops = TRUE;
+
        SetAudioMode(setup.sound);
       }
+
+      RedrawSoundButtonGadget(id);
+
       break;
 
     case SOUND_CTRL_ID_SIMPLE:
+    case SOUND_CTRL_ID_PANEL_SIMPLE:
       if (setup.sound_simple)
        setup.sound_simple = FALSE;
       else if (audio.sound_available)
       {
        setup.sound = setup.sound_simple = TRUE;
+
        SetAudioMode(setup.sound);
       }
+
+      RedrawSoundButtonGadget(id);
+
       break;
 
     default:
       break;
   }
 }
+
+static void HandleGameButtons(struct GadgetInfo *gi)
+{
+  HandleGameButtonsExt(gi->custom_id, gi->event.button);
+}
+
+void HandleSoundButtonKeys(Key key)
+{
+  if (key == setup.shortcut.sound_simple)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
+  else if (key == setup.shortcut.sound_loops)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
+  else if (key == setup.shortcut.sound_music)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);
+}