rnd-20051126-1-src
[rocksndiamonds.git] / src / game.c
index 75508923566c856c7708a3ab612ab9006189a287..7949da60c770145cb4d447fd1329c6b36e6a98b9 100644 (file)
 #include "tape.h"
 #include "network.h"
 
-/* this switch controls how rocks move horizontally */
-#define OLD_GAME_BEHAVIOUR     FALSE
-
 /* EXPERIMENTAL STUFF */
 #define USE_NEW_AMOEBA_CODE    FALSE
 
+/* EXPERIMENTAL STUFF */
+#define USE_NEW_STUFF                  (TRUE                           * 1)
+
+#define USE_NEW_MOVE_STYLE             (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_MOVE_DELAY             (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_PUSH_DELAY             (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_BLOCK_STYLE            (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_SP_SLIPPERY            (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_RANDOMIZE              (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_CAN_MOVE_NOT_MOVING                (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_PREVIOUS_MOVE_DIR          (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_PUSH_BUGFIX                        (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_GRAVITY_BUGFIX_NEW         (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_GRAVITY_BUGFIX_OLD         (TRUE   * USE_NEW_STUFF         * 0)
+
+#define USE_PENGUIN_COLLECT_BUGFIX     (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_IMPACT_BUGFIX              (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_HITTING_SOMETHING_BUGFIX   (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_HIT_BY_SOMETHING_BUGFIX    (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_DROP_BUGFIX                        (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_CHANGE_TO_TRIGGERED                (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_BACK_WALKABLE_BUGFIX       (TRUE   * USE_NEW_STUFF         * 1)
+
+
 /* for DigField() */
 #define DF_NO_PUSH             0
 #define DF_DIG                 1
 
 /* for Explode() */
 #define EX_PHASE_START         0
-#define EX_NO_EXPLOSION                0
-#define EX_NORMAL              1
-#define EX_CENTER              2
-#define EX_BORDER              3
+#define EX_TYPE_NONE           0
+#define EX_TYPE_NORMAL         (1 << 0)
+#define EX_TYPE_CENTER         (1 << 1)
+#define EX_TYPE_BORDER         (1 << 2)
+#define EX_TYPE_CROSS          (1 << 3)
+#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
@@ -59,7 +89,8 @@
 #define YY_KEYS                        123
 #define XX_SCORE               15
 #define YY_SCORE               159
-#define XX_TIME                        29
+#define XX_TIME1               29
+#define XX_TIME2               30
 #define YY_TIME                        194
 
 /* special positions in the game control window (relative to main window) */
 #define DY_KEYS                        (DY + YY_KEYS)
 #define DX_SCORE               (DX + XX_SCORE)
 #define DY_SCORE               (DY + YY_SCORE)
-#define DX_TIME                        (DX + XX_TIME)
+#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) */
 /* values for other actions */
 #define MOVE_STEPSIZE_NORMAL   (TILEX / MOVE_DELAY_NORMAL_SPEED)
 
+#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_CHANGE_DELAY(c)    (   ((c)->delay_fixed  * (c)->delay_frames) + \
+                                RND((c)->delay_random * (c)->delay_frames))
+
+#define GET_TARGET_ELEMENT(e, ch)                                      \
+       ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element :     \
+        (e) == EL_TRIGGER_PLAYER  ? (ch)->actual_trigger_player : (e))
+
+#define GET_VALID_PLAYER_ELEMENT(e)                                    \
+       ((e) >= EL_PLAYER_1 && (e) <= EL_PLAYER_4 ? (e) : EL_PLAYER_1)
 
-#define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
+#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) ||                  \
-                                       (DONT_COLLIDE_WITH(e) &&        \
-                                        IS_PLAYER(x, y) &&             \
-                                        !PLAYER_ENEMY_PROTECTED(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_GENERIC_X(e, x, y, 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) &&       \
                                         IS_PLAYER(x, y) &&             \
                                         !PLAYER_ENEMY_PROTECTED(x, y))))
 
-#define ELEMENT_CAN_ENTER_FIELD_GENERIC_2(x, y, condition)             \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (condition)))
-
 #define ELEMENT_CAN_ENTER_FIELD(e, x, y)                               \
-       ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, 0)
+       ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
 
-#define ELEMENT_CAN_ENTER_FIELD_OR_ACID(e, x, y)                       \
-       ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, (Feld[x][y] == EL_ACID))
+#define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
 
-#define ELEMENT_CAN_ENTER_FIELD_OR_ACID_2(x, y)                                \
-       ELEMENT_CAN_ENTER_FIELD_GENERIC_2(x, y, (Feld[x][y] == EL_ACID))
+#define ENEMY_CAN_ENTER_FIELD(e, x, y)                                 \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
-#define ENEMY_CAN_ENTER_FIELD(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
+#define YAMYAM_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
 
-#define YAMYAM_CAN_ENTER_FIELD(x, y)                                   \
-               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(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 DARK_YAMYAM_CAN_ENTER_FIELD(x, y)                              \
-               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(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 PACMAN_CAN_ENTER_FIELD(x, y)                                   \
-               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(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 PIG_CAN_ENTER_FIELD(x, y)                                      \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(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 PENGUIN_CAN_ENTER_FIELD(x, y)                                  \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       IS_FOOD_PENGUIN(Feld[x][y]) ||  \
-                                       Feld[x][y] == EL_EXIT_OPEN ||   \
-                                       Feld[x][y] == EL_ACID))
+#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)
        (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) &&                                    \
-                (Feld[x][y] == EL_ACID ||                              \
-                 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e))))
-
-#define CE_ENTER_FIELD_COND_X(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_GENERIC_X(e, x, y, CE_ENTER_FIELD_COND_X(e, x, y))
-
-#define MOLE_CAN_ENTER_FIELD(x, y, condition)                          \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || (condition)))
+       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
 
 /* forward declaration for internal use */
 
+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);
@@ -203,18 +256,40 @@ 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 KillHeroUnlessEnemyProtected(int, int);
 static void KillHeroUnlessExplosionProtected(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 CheckTriggeredElementSideChange(int, int, int, int, int);
-static boolean CheckTriggeredElementChange(int, int, int, int);
-static boolean CheckElementSideChange(int, int, int, int, int, int);
-static boolean CheckElementChange(int, 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, int);
+#define CheckElementChange(x, y, e, te, ev)                            \
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
+#define CheckElementChangeByPlayer(x, y, e, ev, p, s)                  \
+       CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s, -1)
+#define CheckElementChangeBySide(x, y, e, te, ev, s)                   \
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s, -1)
+#define CheckElementChangeByPage(x, y, e, te, ev, p)                   \
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
 
 static void PlayLevelSound(int, int, int);
 static void PlayLevelSoundNearest(int, int, int);
@@ -489,27 +564,46 @@ struct
   int element;
   int direction;
 }
-tube_access[] =
-{
-  { 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_UNDEFINED,              0                                    }
+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_NO_MOVING                         }
 };
 
-static unsigned long trigger_events[MAX_NUM_ELEMENTS];
+static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
 
-#define IS_AUTO_CHANGING(e)    (element_info[e].change_events & \
-                                CH_EVENT_BIT(CE_DELAY))
+#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))
@@ -620,6 +714,21 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
                                level.sp_block_last_field :
                                level.block_last_field);
 
+#if USE_NEW_BLOCK_STYLE
+    /* ---------- 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);
+#endif
+
     if (!options.network || player->connected)
     {
       player->active = TRUE;
@@ -640,6 +749,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
     }
 
     Feld[x][y] = EL_EMPTY;
+
     player->jx = player->last_jx = x;
     player->jy = player->last_jy = y;
   }
@@ -757,27 +867,6 @@ static void InitField(int x, int y, boolean init_game)
       GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
       break;
 
-#if 0
-    case EL_SP_EMPTY:
-      Feld[x][y] = EL_EMPTY;
-      break;
-#endif
-
-#if 0
-    case EL_EM_KEY_1_FILE:
-      Feld[x][y] = EL_EM_KEY_1;
-      break;
-    case EL_EM_KEY_2_FILE:
-      Feld[x][y] = EL_EM_KEY_2;
-      break;
-    case EL_EM_KEY_3_FILE:
-      Feld[x][y] = EL_EM_KEY_3;
-      break;
-    case EL_EM_KEY_4_FILE:
-      Feld[x][y] = EL_EM_KEY_4;
-      break;
-#endif
-
     case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
     case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
     case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
@@ -852,7 +941,7 @@ static inline 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,0,9,0) &&
+  if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
       CAN_MOVE(Feld[x][y]))
     InitMovDir(x, y);
 }
@@ -864,7 +953,7 @@ static inline void InitField_WithBug2(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,0,9,0) &&
+  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);
@@ -872,29 +961,115 @@ static inline void InitField_WithBug2(int x, int y, boolean init_game)
   /* 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)
+     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"
+  */
+}
+
+inline void DrawGameValue_Emeralds(int value)
+{
+  DrawText(DX_EMERALDS, DY_EMERALDS, int2str(value, 3), FONT_TEXT_2);
+}
+
+inline void DrawGameValue_Dynamite(int value)
+{
+  DrawText(DX_DYNAMITE, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2);
+}
+
+inline void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
+{
+  int i;
+
+  /* currently only 4 of 8 possible keys are displayed */
+  for (i = 0; i < STD_NUM_KEYS; i++)
+  {
+    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);
+  }
+}
+
+inline void DrawGameValue_Score(int value)
+{
+  DrawText(DX_SCORE, DY_SCORE, int2str(value, 5), FONT_TEXT_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);
+}
+
+inline void DrawGameValue_Level(int value)
+{
+  if (level_nr < 100)
+    DrawText(DX_LEVEL, DY_LEVEL, int2str(value, 2), FONT_TEXT_2);
+  else
+  {
+    /* misuse area for displaying emeralds to draw bigger level number */
+    DrawTextExt(drawto, DX_EMERALDS, DY_EMERALDS,
+               int2str(value, 3), FONT_LEVEL_NUMBER, BLIT_OPAQUE);
+
+    /* 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);
+
+    /* restore the area for displaying emeralds */
+    DrawGameValue_Emeralds(local_player->gems_still_needed);
+
+    /* yes, this is all really ugly :-) */
+  }
+}
+
+void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
+                      int key_bits)
+{
+  int key[MAX_NUM_KEYS];
+  int i;
+
+  for (i = 0; i < MAX_NUM_KEYS; i++)
+    key[i] = key_bits & (1 << i);
+
+  DrawGameValue_Level(level_nr);
 
- */
+  DrawGameValue_Emeralds(emeralds);
+  DrawGameValue_Dynamite(dynamite);
+  DrawGameValue_Score(score);
+  DrawGameValue_Time(time);
+
+  DrawGameValue_Keys(key);
 }
 
 void DrawGameDoorValues()
 {
-  int i, j;
+  int i;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    for (j = 0; j < 4; j++)
-      if (stored_player[i].key[j])
-       DrawMiniGraphicExt(drawto, DX_KEYS + j * MINI_TILEX, DY_KEYS,
-                          el2edimg(EL_KEY_1 + j));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    DrawGameDoorValues_EM();
+
+    return;
+  }
+
+  DrawGameValue_Level(level_nr);
 
-  DrawText(DX + XX_EMERALDS, DY + YY_EMERALDS,
-          int2str(local_player->gems_still_needed, 3), FONT_TEXT_2);
-  DrawText(DX + XX_DYNAMITE, DY + YY_DYNAMITE,
-          int2str(local_player->inventory_size, 3), FONT_TEXT_2);
-  DrawText(DX + XX_SCORE, DY + YY_SCORE,
-          int2str(local_player->score, 5), FONT_TEXT_2);
-  DrawText(DX + XX_TIME, DY + YY_TIME,
-          int2str(TimeLeft, 3), FONT_TEXT_2);
+  DrawGameValue_Emeralds(local_player->gems_still_needed);
+  DrawGameValue_Dynamite(local_player->inventory_size);
+  DrawGameValue_Score(local_player->score);
+  DrawGameValue_Time(TimeLeft);
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    DrawGameValue_Keys(stored_player[i].key);
 }
 
 static void resolve_group_element(int group_element, int recursion_depth)
@@ -939,17 +1114,6 @@ static void resolve_group_element(int group_element, int recursion_depth)
       element_info[element].in_group[group_nr] = TRUE;
     }
   }
-
-#if 0
-  if (recursion_depth == 0 && group_element <= EL_GROUP_4)
-  {
-    printf("::: group %d: %d resolved elements\n",
-          group_element - EL_GROUP_START, group->num_elements_resolved);
-    for (i = 0; i < group->num_elements_resolved; i++)
-      printf("::: - %d ['%s']\n", group->element_resolved[i],
-            element_info[group->element_resolved[i]].token_name);
-  }
-#endif
 }
 
 
@@ -963,12 +1127,80 @@ static void resolve_group_element(int group_element, int recursion_depth)
 
 static void InitGameEngine()
 {
-  int i, j, k;
+  int i, j, k, l;
 
   /* 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 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));
+
+  /* ---------------------------------------------------------------------- */
+
   /* dynamically adjust element properties according to game engine version */
   InitElementPropertiesEngine(game.engine_version);
 
@@ -991,6 +1223,15 @@ static void InitGameEngine()
 
   /* ---------- initialize player's initial move delay --------------------- */
 
+#if USE_NEW_MOVE_DELAY
+  /* dynamically adjust player properties according to level information */
+  game.initial_move_delay_value =
+    (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
+
+  /* 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);
+#else
   /* dynamically adjust player properties according to game engine version */
   game.initial_move_delay =
     (game.engine_version <= VERSION_IDENT(2,0,1,0) ? INITIAL_MOVE_DELAY_ON :
@@ -999,6 +1240,7 @@ static void InitGameEngine()
   /* dynamically adjust player properties according to level information */
   game.initial_move_delay_value =
     (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
+#endif
 
   /* ---------- initialize player's initial push delay --------------------- */
 
@@ -1024,9 +1266,10 @@ static void InitGameEngine()
       ei->change->delay_frames = 1;
     }
 
-    ei->change_events = CE_BITMASK_DEFAULT;
     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];
     }
@@ -1045,10 +1288,12 @@ static void InitGameEngine()
     ei->change->change_function      = ch_delay->change_function;
     ei->change->post_change_function = ch_delay->post_change_function;
 
-    ei->change_events |= CH_EVENT_BIT(CE_DELAY);
+    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);
   }
 
-#if 1
   /* add change events from custom element configuration */
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
@@ -1062,10 +1307,10 @@ static void InitGameEngine()
       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].events & CH_EVENT_BIT(k) &&
-           !(ei->change_events & CH_EVENT_BIT(k)))
+       if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
        {
-         ei->change_events |= CH_EVENT_BIT(k);
+         ei->has_change_event[k] = TRUE;
+
          ei->event_page_nr[k] = j;
          ei->event_page[k] = &ei->change_page[j];
        }
@@ -1073,26 +1318,40 @@ static void InitGameEngine()
     }
   }
 
-#else
+  /* ---------- initialize internal run-time variables ------------- */
 
-  /* add change events from custom element configuration */
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
-    int element = EL_CUSTOM_START + i;
+    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
 
-    /* only add custom elements that change after fixed/random frame delay */
-    if (CAN_CHANGE(element) && HAS_CHANGE_EVENT(element, CE_DELAY))
-      element_info[element].change_events |= CH_EVENT_BIT(CE_DELAY);
+    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);
+    }
+  }
+
+  /* ---------- 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_PLAYER_1;
+    }
   }
-#endif
 
   /* ---------- initialize trigger events ---------------------------------- */
 
   /* initialize trigger events information */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    trigger_events[i] = EP_BITMASK_DEFAULT;
+    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+      trigger_events[i][j] = FALSE;
 
-#if 1
   /* add trigger events from element change event properties */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
   {
@@ -1103,30 +1362,29 @@ static void InitGameEngine()
       if (!ei->change_page[j].can_change)
        continue;
 
-      if (ei->change_page[j].events & CH_EVENT_BIT(CE_BY_OTHER_ACTION))
+      if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
       {
        int trigger_element = ei->change_page[j].trigger_element;
 
-       if (IS_GROUP_ELEMENT(trigger_element))
+       for (k = 0; k < NUM_CHANGE_EVENTS; k++)
        {
-         struct ElementGroupInfo *group = element_info[trigger_element].group;
+         if (ei->change_page[j].has_event[k])
+         {
+           if (IS_GROUP_ELEMENT(trigger_element))
+           {
+             struct ElementGroupInfo *group =
+               element_info[trigger_element].group;
 
-         for (k = 0; k < group->num_elements_resolved; k++)
-           trigger_events[group->element_resolved[k]]
-             |= ei->change_page[j].events;
+             for (l = 0; l < group->num_elements_resolved; l++)
+               trigger_events[group->element_resolved[l]][k] = TRUE;
+           }
+           else
+             trigger_events[trigger_element][k] = TRUE;
+         }
        }
-       else
-         trigger_events[trigger_element] |= ei->change_page[j].events;
       }
     }
   }
-#else
-  /* add trigger events from element change event properties */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    if (HAS_CHANGE_EVENT(i, CE_BY_OTHER_ACTION))
-      trigger_events[element_info[i].change->trigger_element] |=
-       element_info[i].change->events;
-#endif
 
   /* ---------- initialize push delay -------------------------------------- */
 
@@ -1150,14 +1408,22 @@ static void InitGameEngine()
   }
 
   /* set push delay value for Supaplex elements for newer engine versions */
-  if (game.engine_version >= VERSION_IDENT(3,0,9,0))
+  if (game.engine_version >= VERSION_IDENT(3,1,0,0))
   {
     for (i = 0; i < MAX_NUM_ELEMENTS; i++)
     {
       if (IS_SP_ELEMENT(i))
       {
-       element_info[i].push_delay_fixed  = 6;
+#if USE_NEW_MOVE_STYLE
+       /* 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;
+#else
+       element_info[i].push_delay_fixed  = 6;  /* just enough to escape ... */
+       element_info[i].push_delay_random = 0;  /* ... from falling zonk     */
+#endif
       }
     }
   }
@@ -1177,14 +1443,6 @@ static void InitGameEngine()
     element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
   }
 
-  /* ---------- initialize move dig/leave ---------------------------------- */
-
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-  {
-    element_info[i].can_leave_element = FALSE;
-    element_info[i].can_leave_element_last = FALSE;
-  }
-
   /* ---------- initialize gem count --------------------------------------- */
 
   /* initialize gem count values for each element */
@@ -1199,15 +1457,15 @@ static void InitGameEngine()
 
   /* ---------- initialize access direction -------------------------------- */
 
-  /* initialize access direction values to default */
+  /* 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; tube_access[i].element != EL_UNDEFINED; i++)
-    element_info[tube_access[i].element].access_direction =
-      tube_access[i].direction;
+  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;
 }
 
 
@@ -1228,16 +1486,6 @@ void InitGame()
 
   InitGameEngine();
 
-#if 0
-#if DEBUG
-#if USE_NEW_AMOEBA_CODE
-  printf("Using new amoeba code.\n");
-#else
-  printf("Using old amoeba code.\n");
-#endif
-#endif
-#endif
-
   /* don't play tapes over network */
   network_playing = (options.network && !tape.playing);
 
@@ -1246,6 +1494,7 @@ void InitGame()
     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;
@@ -1261,7 +1510,7 @@ void InitGame()
     player->lights_still_needed = 0;
     player->friends_still_needed = 0;
 
-    for (j = 0; j < 4; j++)
+    for (j = 0; j < MAX_NUM_KEYS; j++)
       player->key[j] = FALSE;
 
     player->dynabomb_count = 0;
@@ -1279,7 +1528,10 @@ void InitGame()
 
     player->use_murphy_graphic = FALSE;
 
-    player->block_last_field = FALSE;
+    player->block_last_field = FALSE;  /* initialized in InitPlayerField() */
+    player->block_delay_adjustment = 0;        /* initialized in InitPlayerField() */
+
+    player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
 
     player->actual_frame_counter = 0;
 
@@ -1289,6 +1541,7 @@ void InitGame()
 
     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;
@@ -1346,6 +1599,11 @@ void InitGame()
     player->switch_x = -1;
     player->switch_y = -1;
 
+#if USE_DROP_BUGFIX
+    player->drop_x = -1;
+    player->drop_y = -1;
+#endif
+
     player->show_envelope = 0;
 
     player->move_delay       = game.initial_move_delay;
@@ -1353,8 +1611,13 @@ void InitGame()
 
     player->move_delay_reset_counter = 0;
 
-    player->push_delay = 0;
+#if USE_NEW_PUSH_DELAY
+    player->push_delay       = -1;     /* initialized when pushing starts */
     player->push_delay_value = game.initial_push_delay_value;
+#else
+    player->push_delay       = 0;
+    player->push_delay_value = game.initial_push_delay_value;
+#endif
 
     player->drop_delay = 0;
 
@@ -1364,6 +1627,7 @@ void InitGame()
     player->shield_normal_time_left = 0;
     player->shield_deadly_time_left = 0;
 
+    player->inventory_infinite_element = EL_UNDEFINED;
     player->inventory_size = 0;
 
     DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
@@ -1375,18 +1639,20 @@ void InitGame()
 
   network_player_action_received = FALSE;
 
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
   /* initial null action */
   if (network_playing)
     SendToServer_MovePlayer(MV_NO_MOVING);
 #endif
 
   ZX = ZY = -1;
+  ExitX = ExitY = -1;
 
   FrameCounter = 0;
   TimeFrames = 0;
   TimePlayed = 0;
   TimeLeft = level.time;
+  TapeTime = 0;
 
   ScreenMovDir = MV_NO_MOVING;
   ScreenMovPos = 0;
@@ -1408,7 +1674,7 @@ void InitGame()
 
   game.envelope_active = FALSE;
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_BELTS; i++)
   {
     game.belt_dir[i] = MV_NO_MOVING;
     game.belt_dir_nr[i] = 3;           /* not moving, next moving left */
@@ -1429,15 +1695,16 @@ void InitGame()
       AmoebaNr[x][y] = 0;
       WasJustMoving[x][y] = 0;
       WasJustFalling[x][y] = 0;
+      CheckCollision[x][y] = 0;
       Stop[x][y] = FALSE;
       Pushed[x][y] = FALSE;
 
-      Changed[x][y] = CE_BITMASK_DEFAULT;
-      ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+      Changed[x][y] = FALSE;
+      ChangeEvent[x][y] = -1;
 
       ExplodePhase[x][y] = 0;
       ExplodeDelay[x][y] = 0;
-      ExplodeField[x][y] = EX_NO_EXPLOSION;
+      ExplodeField[x][y] = EX_TYPE_NONE;
 
       RunnerVisit[x][y] = 0;
       PlayerVisit[x][y] = 0;
@@ -1476,27 +1743,31 @@ void InitGame()
   {
     if (!IS_CUSTOM_ELEMENT(i))
     {
-      int num_phase = 9;
-      int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
-      int last_phase = num_phase * delay;
+      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;
+      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;
     }
 
-    if (element_info[i].explosion_delay < 2)   /* !!! check again !!! */
-      element_info[i].explosion_delay = 2;
+#if 0
+    if (element_info[i].explosion_delay < 1)   /* !!! check again !!! */
+      element_info[i].explosion_delay = 1;
 
     if (element_info[i].ignition_delay < 1)    /* !!! check again !!! */
       element_info[i].ignition_delay = 1;
+#endif
   }
 
   /* correct non-moving belts to start moving left */
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_BELTS; i++)
     if (game.belt_dir[i] == MV_NO_MOVING)
       game.belt_dir_nr[i] = 3;         /* not moving, next moving left */
 
@@ -1521,6 +1792,15 @@ void InitGame()
          some_player->present = FALSE;
          some_player->active = FALSE;
 
+#if 0
+         player->element_nr = some_player->element_nr;
+#endif
+
+#if USE_NEW_BLOCK_STYLE
+         player->block_last_field       = some_player->block_last_field;
+         player->block_delay_adjustment = some_player->block_delay_adjustment;
+#endif
+
          StorePlayer[jx][jy] = player->element_nr;
          player->jx = player->last_jx = jx;
          player->jy = player->last_jy = jy;
@@ -1674,7 +1954,7 @@ void InitGame()
 
        for (i = 0; i < element_info[element].num_change_pages; i++)
        {
-         content = element_info[element].change_page[i].content[xx][yy];
+         content= element_info[element].change_page[i].target_content[xx][yy];
          is_player = ELEM_IS_PLAYER(content);
 
          if (is_player && (found_rating < 1 || element < found_element))
@@ -1699,7 +1979,6 @@ void InitGame()
   }
   else
   {
-#if 1
     scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
                local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
                local_player->jx - MIDPOSX);
@@ -1707,85 +1986,83 @@ void InitGame()
     scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
                local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
                local_player->jy - MIDPOSY);
-#else
-    scroll_x = SBX_Left;
-    scroll_y = SBY_Upper;
-    if (local_player->jx >= SBX_Left + MIDPOSX)
-      scroll_x = (local_player->jx <= SBX_Right + MIDPOSX ?
-                 local_player->jx - MIDPOSX :
-                 SBX_Right);
-    if (local_player->jy >= SBY_Upper + MIDPOSY)
-      scroll_y = (local_player->jy <= SBY_Lower + MIDPOSY ?
-                 local_player->jy - MIDPOSY :
-                 SBY_Lower);
-#endif
   }
 
-  CloseDoor(DOOR_CLOSE_1);
-
-  DrawLevel();
-  DrawAllPlayers();
+  if (!game.restart_level)
+    CloseDoor(DOOR_CLOSE_1);
 
-  /* after drawing the level, correct some elements */
-  if (game.timegate_time_left == 0)
-    CloseAllOpenTimegates();
+  /* !!! FIX THIS (START) !!! */
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    InitGameEngine_EM();
+  }
+  else
+  {
+    DrawLevel();
+    DrawAllPlayers();
 
-  if (setup.soft_scrolling)
-    BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
+    /* after drawing the level, correct some elements */
+    if (game.timegate_time_left == 0)
+      CloseAllOpenTimegates();
 
-  redraw_mask |= REDRAW_FROM_BACKBUFFER;
-  FadeToFront();
+    if (setup.soft_scrolling)
+      BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
 
-  /* 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);
+    redraw_mask |= REDRAW_FROM_BACKBUFFER;
+    FadeToFront();
+  }
+  /* !!! FIX THIS (END) !!! */
 
-  if (level_nr < 100)
-    DrawText(DX + XX_LEVEL, DY + YY_LEVEL, int2str(level_nr, 2), FONT_TEXT_2);
-  else
+  if (!game.restart_level)
   {
-    DrawTextExt(drawto, DX + XX_EMERALDS, DY + YY_EMERALDS,
-               int2str(level_nr, 3), FONT_LEVEL_NUMBER, BLIT_OPAQUE);
-    BlitBitmap(drawto, drawto,
-              DX + XX_EMERALDS, DY + YY_EMERALDS + 1,
-              getFontWidth(FONT_LEVEL_NUMBER) * 3,
-              getFontHeight(FONT_LEVEL_NUMBER) - 1,
-              DX + XX_LEVEL - 1, DY + YY_LEVEL + 1);
+    /* 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);
   }
 
   DrawGameDoorValues();
 
-  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();
+  if (!game.restart_level)
+  {
+    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();
 
-  /* 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);
+    /* 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);
 
-  OpenDoor(DOOR_OPEN_ALL);
+    OpenDoor(DOOR_OPEN_ALL);
 
-  PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
+    PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
 
-  if (setup.sound_music)
-    PlayLevelMusic();
+    if (setup.sound_music)
+      PlayLevelMusic();
 
-  KeyboardAutoRepeatOffUnlessAutoplay();
+    KeyboardAutoRepeatOffUnlessAutoplay();
 
-  if (options.debug)
-  {
-    for (i = 0; i < 4; i++)
-      printf("Player %d %sactive.\n",
-            i + 1, (stored_player[i].active ? "" : "not "));
+    if (options.debug)
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       printf("Player %d %sactive.\n",
+              i + 1, (stored_player[i].active ? "" : "not "));
+    }
   }
 
-#if 0
-  printf("::: starting game [%d]\n", FrameCounter);
-#endif
+  game.restart_level = FALSE;
+}
+
+void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y)
+{
+  /* this is used for non-R'n'D game engines to update certain engine values */
+
+  /* needed to determine if sounds are played within the visible screen area */
+  scroll_x = actual_scroll_x;
+  scroll_y = actual_scroll_y;
 }
 
 void InitMovDir(int x, int y)
@@ -1898,7 +2175,11 @@ void InitMovDir(int x, int y)
        else if (move_pattern == MV_ALONG_LEFT_SIDE ||
                 move_pattern == MV_ALONG_RIGHT_SIDE)
        {
-         for (i = 0; i < 4; i++)
+         /* use random direction as default start direction */
+         if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+           MovDir[x][y] = 1 << RND(4);
+
+         for (i = 0; i < NUM_DIRECTIONS; i++)
          {
            int x1 = x + xy[i][0];
            int y1 = y + xy[i][1];
@@ -1925,7 +2206,7 @@ void InitMovDir(int x, int y)
            element != EL_BD_FIREFLY)
          break;
 
-       for (i = 0; i < 4; i++)
+       for (i = 0; i < NUM_DIRECTIONS; i++)
        {
          int x1 = x + xy[i][0];
          int y1 = y + xy[i][1];
@@ -1982,13 +2263,8 @@ void GameWon()
   if (local_player->MovPos)
     return;
 
-#if 1
   if (tape.auto_play)          /* tape might already be stopped here */
     tape.auto_play_level_solved = TRUE;
-#else
-  if (tape.playing && tape.auto_play)
-    tape.auto_play_level_solved = TRUE;
-#endif
 
   local_player->LevelSolved = FALSE;
 
@@ -2010,7 +2286,9 @@ void GameWon()
        TimeLeft -= 10;
       else
        TimeLeft--;
-      DrawText(DX_TIME, DY_TIME, int2str(TimeLeft, 3), FONT_TEXT_2);
+
+      DrawGameValue_Time(TimeLeft);
+
       BackToFront();
 
       if (!tape.playing)
@@ -2036,7 +2314,9 @@ void GameWon()
        TimePlayed += 10;
       else
        TimePlayed++;
-      DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
+
+      DrawGameValue_Time(TimePlayed);
+
       BackToFront();
 
       if (!tape.playing)
@@ -2048,8 +2328,9 @@ void GameWon()
   }
 
   /* close exit door after last player */
-  if ((Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
-       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN) && AllPlayersGone)
+  if (AllPlayersGone && ExitX >= 0 && ExitY >= 0 &&
+      (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
+       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN))
   {
     int element = Feld[ExitX][ExitY];
 
@@ -2060,7 +2341,9 @@ void GameWon()
   }
 
   /* Hero disappears */
-  DrawLevelField(ExitX, ExitY);
+  if (ExitX >= 0 && ExitY >= 0)
+    DrawLevelField(ExitX, ExitY);
+
   BackToFront();
 
   if (tape.playing)
@@ -2169,16 +2452,47 @@ int NewHiScore()
   return position;
 }
 
-void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
+inline static int getElementMoveStepsize(int x, int y)
 {
-  if (player->GfxAction != action || player->GfxDir != dir)
-  {
-#if 0
-    printf("Player frame reset! (%d => %d, %d => %d)\n",
-          player->GfxAction, action, player->GfxDir, dir);
-#endif
-
-    player->GfxAction = action;
+  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;
+
+  /* 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
+  }
+
+  return step;
+}
+
+void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
+{
+  if (player->GfxAction != action || player->GfxDir != dir)
+  {
+#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;
@@ -2208,6 +2522,27 @@ void InitMovingField(int x, int y, int direction)
   if (!WasJustMoving[x][y] || direction != MovDir[x][y])
     ResetGfxAnimation(x, y);
 
+#if USE_CAN_MOVE_NOT_MOVING
+
+  MovDir[x][y] = direction;
+  GfxDir[x][y] = direction;
+  GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
+                    ACTION_FALLING : ACTION_MOVING);
+
+  if (getElementMoveStepsize(x, y) != 0)       /* moving or being moved */
+  {
+    if (Feld[newx][newy] == EL_EMPTY)
+      Feld[newx][newy] = EL_BLOCKED;
+
+    MovDir[newx][newy] = MovDir[x][y];
+    GfxFrame[newx][newy] = GfxFrame[x][y];
+    GfxRandom[newx][newy] = GfxRandom[x][y];
+    GfxAction[newx][newy] = GfxAction[x][y];
+    GfxDir[newx][newy] = GfxDir[x][y];
+  }
+
+#else
+
   MovDir[newx][newy] = MovDir[x][y] = direction;
   GfxDir[x][y] = direction;
 
@@ -2223,6 +2558,7 @@ void InitMovingField(int x, int y, int direction)
   GfxRandom[newx][newy] = GfxRandom[x][y];
   GfxAction[newx][newy] = GfxAction[x][y];
   GfxDir[newx][newy] = GfxDir[x][y];
+#endif
 }
 
 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
@@ -2304,6 +2640,10 @@ static void RemoveField(int x, int y)
   ChangePage[x][y] = -1;
   Pushed[x][y] = FALSE;
 
+#if 0
+  ExplodeField[x][y] = EX_TYPE_NONE;
+#endif
+
   GfxElement[x][y] = EL_UNDEFINED;
   GfxAction[x][y] = ACTION_DEFAULT;
   GfxDir[x][y] = MV_NO_MOVING;
@@ -2321,8 +2661,21 @@ void RemoveMovingField(int x, int y)
   if (IS_MOVING(x, y))
   {
     Moving2Blocked(x, y, &newx, &newy);
+
     if (Feld[newx][newy] != EL_BLOCKED)
+    {
+      /* 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;
+
+      DrawLevelField(oldx, oldy);
+
       return;
+    }
   }
   else if (element == EL_BLOCKED)
   {
@@ -2369,19 +2722,10 @@ void DrawDynamite(int x, int y)
 
   frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
-#if 1
   if (Back[x][y] || Store[x][y])
     DrawGraphicThruMask(sx, sy, graphic, frame);
   else
     DrawGraphic(sx, sy, graphic, frame);
-#else
-  if (game.emulation == EMU_SUPAPLEX)
-    DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
-  else if (Store[x][y])
-    DrawGraphicThruMask(sx, sy, graphic, frame);
-  else
-    DrawGraphic(sx, sy, graphic, frame);
-#endif
 }
 
 void CheckDynamite(int x, int y)
@@ -2399,63 +2743,61 @@ void CheckDynamite(int x, int y)
     }
   }
 
-#if 1
   StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
-#else
-  if (Feld[x][y] == EL_DYNAMITE_ACTIVE ||
-      Feld[x][y] == EL_SP_DISK_RED_ACTIVE)
-    StopSound(SND_DYNAMITE_ACTIVE);
-  else
-    StopSound(SND_DYNABOMB_ACTIVE);
-#endif
 
   Bang(x, y);
 }
 
-void RelocatePlayer(int x, int y, int element_raw)
+void DrawRelocatePlayer(struct PlayerInfo *player)
 {
-  int element = (element_raw == EL_SP_MURPHY ? EL_PLAYER_1 : element_raw);
-  struct PlayerInfo *player = &stored_player[element - EL_PLAYER_1];
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
-  boolean no_delay = (tape.index_search);
+  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;
 
-  if (player->GameOver)                /* do not reanimate dead player */
-    return;
-
-#if 1
-  RemoveField(x, y);           /* temporarily remove newly placed player */
-  DrawLevelField(x, y);
-#endif
-
-  if (player->present)
+  if (level.instant_relocation)
   {
-    while (player->MovPos)
-    {
-      ScrollPlayer(player, SCROLL_GO_ON);
-      ScrollScreen(NULL, SCROLL_GO_ON);
-      FrameCounter++;
+    int offset = (setup.scroll_delay ? 3 : 0);
 
-      DrawPlayer(player);
+    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+    {
+      scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
+                 local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+                 local_player->jx - MIDPOSX);
 
-      BackToFront();
-      Delay(wait_delay_value);
+      scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+                 local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+                 local_player->jy - MIDPOSY);
     }
+    else
+    {
+      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);
 
-    DrawPlayer(player);                /* needed here only to cleanup last field */
-    DrawLevelField(player->jx, player->jy);    /* remove player graphic */
+      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);
 
-    player->is_moving = FALSE;
-  }
+      /* 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);
 
-  Feld[x][y] = element;
-  InitPlayerField(x, y, element, TRUE);
+      /* 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 == local_player)
+    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)
     {
       int dx = 0, dy = 0;
@@ -2472,6 +2814,9 @@ void RelocatePlayer(int x, int y, int element_raw)
       dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
       dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
 
+      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
+       break;
+
       scroll_x -= dx;
       scroll_y -= dy;
 
@@ -2490,28 +2835,115 @@ void RelocatePlayer(int x, int y, int element_raw)
       BackToFront();
       Delay(wait_delay_value);
     }
+
+    DrawPlayer(player);
+    BackToFront();
+    Delay(wait_delay_value);
   }
 }
 
-void Explode(int ex, int ey, int phase, int mode)
+void RelocatePlayer(int jx, int jy, int el_player_raw)
 {
-  int x, y;
-#if 0
-  int num_phase = 9;
-#endif
+  int el_player = GET_VALID_PLAYER_ELEMENT(el_player_raw);
+  struct PlayerInfo *player = &stored_player[el_player - EL_PLAYER_1];
+  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;
 
-  /* !!! eliminate this variable !!! */
-  int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+  if (player->GameOver)                /* do not reanimate dead player */
+    return;
 
-#if 1
-  int last_phase;
+  if (!player_relocated)       /* no need to relocate the player */
+    return;
+
+  if (IS_PLAYER(jx, jy))       /* player already placed at new position */
+  {
+    RemoveField(jx, jy);       /* temporarily remove newly placed player */
+    DrawLevelField(jx, jy);
+  }
+
+  if (player->present)
+  {
+    while (player->MovPos)
+    {
+      ScrollPlayer(player, SCROLL_GO_ON);
+      ScrollScreen(NULL, SCROLL_GO_ON);
+
+#if USE_NEW_MOVE_DELAY
+      AdvanceFrameAndPlayerCounters(player->index_nr);
 #else
-  int last_phase = num_phase * delay;
-  int half_phase = (num_phase / 2) * delay;
-  int first_phase_after_start = EX_PHASE_START + 1;
+      FrameCounter++;
 #endif
+
+      DrawPlayer(player);
+
+      BackToFront();
+      Delay(wait_delay_value);
+    }
+
+    DrawPlayer(player);                /* needed here only to cleanup last field */
+    DrawLevelField(player->jx, player->jy);    /* remove player graphic */
+
+    player->is_moving = FALSE;
+  }
+
+  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 */
+  {
+    Feld[jx][jy] = element;
+    InitField(jx, jy, FALSE);
+  }
+
+  if (player == local_player)  /* only visually relocate local player */
+    DrawRelocatePlayer(player);
+
+  TestIfHeroTouchesBadThing(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);
+
   if (game.explosions_delayed)
   {
     ExplodeField[ex][ey] = mode;
@@ -2526,11 +2958,14 @@ void Explode(int ex, int ey, int phase, int mode)
     /* --- 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_NORMAL || mode == EX_CENTER))
+       center_element == EL_EMPTY &&
+       (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER))
       return;
 #endif
 
-    if (mode == EX_NORMAL || mode == EX_CENTER)
+    if (mode == EX_TYPE_NORMAL ||
+       mode == EX_TYPE_CENTER ||
+       mode == EX_TYPE_CROSS)
       PlayLevelSoundAction(ex, ey, ACTION_EXPLODING);
 
     /* remove things displayed in background while burning dynamite */
@@ -2545,9 +2980,7 @@ void Explode(int ex, int ey, int phase, int mode)
       Feld[ex][ey] = center_element;
     }
 
-#if 1
-    last_phase = element_info[center_element].explosion_delay;
-#endif
+    last_phase = element_info[center_element].explosion_delay + 1;
 
     for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
     {
@@ -2556,8 +2989,8 @@ void Explode(int ex, int ey, int phase, int mode)
       int element;
 
       if (!IN_LEV_FIELD(x, y) ||
-         ((mode != EX_NORMAL || center_element == EL_AMOEBA_TO_DIAMOND) &&
-          (x != ex || y != ey)))
+         (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
+         (mode == EX_TYPE_CROSS      && (x != ex && y != ey)))
        continue;
 
       element = Feld[x][y];
@@ -2570,52 +3003,43 @@ void Explode(int ex, int ey, int phase, int mode)
          RemoveMovingField(x, y);
       }
 
-#if 1
-
-#if 0
-      if (IS_EXPLOSION_PROOF(element))
-       continue;
-#else
       /* indestructible elements can only explode in center (but not flames) */
-      if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey)) ||
+      if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
+                                          mode == EX_TYPE_BORDER)) ||
          element == EL_FLAMES)
        continue;
-#endif
 
+      /* 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_INDESTRUCTIBLE(element) &&
-          (game.engine_version < VERSION_IDENT(2,2,0,0) ||
-           (!IS_WALKABLE_OVER(element) && !IS_WALKABLE_UNDER(element)))) ||
-         element == EL_FLAMES)
-       continue;
-#endif
-
       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] = (Store[x][y] ? Store[x][y] : EL_EMPTY);
-         Store[x][y] = 0;
+         Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
+         Back[x][y] = 0;
        }
 
        continue;
       }
 
       /* save walkable background elements while explosion on same tile */
-#if 0
-      if (IS_INDESTRUCTIBLE(element))
+      if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
+         (x != ex || y != ey || mode == EX_TYPE_BORDER))
        Back[x][y] = element;
-#else
-      if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element))
-       Back[x][y] = element;
-#endif
 
       /* ignite explodable elements reached by other explosion */
       if (element == EL_EXPLOSION)
        element = Store2[x][y];
 
-#if 1
       if (AmoebaNr[x][y] &&
          (element == EL_AMOEBA_FULL ||
           element == EL_BD_AMOEBA ||
@@ -2626,7 +3050,6 @@ void Explode(int ex, int ey, int phase, int mode)
       }
 
       RemoveField(x, y);
-#endif
 
       if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
       {
@@ -2647,7 +3070,7 @@ void Explode(int ex, int ey, int phase, int mode)
            break;
        }
 
-       if (game.emulation == EMU_SUPAPLEX)
+       if (PLAYERINFO(ex, ey)->use_murphy_graphic)
          Store[x][y] = EL_EMPTY;
       }
       else if (center_element == EL_MOLE)
@@ -2688,40 +3111,16 @@ void Explode(int ex, int ey, int phase, int mode)
       else
        Store[x][y] = EL_EMPTY;
 
-      if (x != ex || y != ey ||
-         center_element == EL_AMOEBA_TO_DIAMOND || mode == EX_BORDER)
+      if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
+         center_element == EL_AMOEBA_TO_DIAMOND)
        Store2[x][y] = element;
 
-#if 0
-      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 1
-      RemoveField(x, y);
-#else
-      MovDir[x][y] = MovPos[x][y] = 0;
-      GfxDir[x][y] = MovDir[x][y];
-      AmoebaNr[x][y] = 0;
-#endif
-#endif
-
       Feld[x][y] = EL_EXPLOSION;
-#if 1
       GfxElement[x][y] = center_element;
-#else
-      GfxElement[x][y] = EL_UNDEFINED;
-#endif
 
       ExplodePhase[x][y] = 1;
-#if 1
       ExplodeDelay[x][y] = last_phase;
-#endif
+
       Stop[x][y] = TRUE;
     }
 
@@ -2738,9 +3137,10 @@ void Explode(int ex, int ey, int phase, int mode)
   x = ex;
   y = ey;
 
-#if 1
+  if (phase == 1)
+    GfxFrame[x][y] = 0;                /* restart explosion animation */
+
   last_phase = ExplodeDelay[x][y];
-#endif
 
   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
 
@@ -2748,6 +3148,7 @@ void Explode(int ex, int ey, int phase, int mode)
 
   /* activate this even in non-DEBUG version until cause for crash in
      getGraphicAnimationFrame() (see below) is found and eliminated */
+
 #endif
 #if 1
 
@@ -2762,10 +3163,8 @@ void Explode(int ex, int ey, int phase, int mode)
   }
 #endif
 
-#if 1
-
   border_element = Store2[x][y];
-  if (IS_PLAYER(x, y))
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
     border_element = StorePlayer[x][y];
 
   if (phase == element_info[border_element].ignition_delay ||
@@ -2773,19 +3172,11 @@ void Explode(int ex, int ey, int phase, int mode)
   {
     boolean border_explosion = FALSE;
 
-#if 1
-    if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present)
-#else
-    if (IS_PLAYER(x, y))
-#endif
+    if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
+       !PLAYER_EXPLOSION_PROTECTED(x, y))
     {
       KillHeroUnlessExplosionProtected(x, y);
       border_explosion = TRUE;
-
-#if 0
-      if (phase == last_phase)
-       printf("::: IS_PLAYER\n");
-#endif
     }
     else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
     {
@@ -2793,65 +3184,20 @@ void Explode(int ex, int ey, int phase, int mode)
       Store2[x][y] = 0;
       Bang(x, y);
       border_explosion = TRUE;
-
-#if 0
-      if (phase == last_phase)
-       printf("::: CAN_EXPLODE_BY_EXPLOSION\n");
-#endif
     }
     else if (border_element == EL_AMOEBA_TO_DIAMOND)
     {
       AmoebeUmwandeln(x, y);
       Store2[x][y] = 0;
       border_explosion = TRUE;
-
-#if 0
-      if (phase == last_phase)
-       printf("::: EL_AMOEBA_TO_DIAMOND [%d, %d] [%d]\n",
-              element_info[border_element].explosion_delay,
-              element_info[border_element].ignition_delay,
-              phase);
-#endif
     }
 
-#if 1
     /* 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;
-#endif
-  }
-
-#else
-
-  if (phase == first_phase_after_start)
-  {
-    int element = Store2[x][y];
-
-    if (element == EL_BLACK_ORB)
-    {
-      Feld[x][y] = Store2[x][y];
-      Store2[x][y] = 0;
-      Bang(x, y);
-    }
-  }
-  else if (phase == half_phase)
-  {
-    int element = Store2[x][y];
-
-    if (IS_PLAYER(x, y))
-      KillHeroUnlessExplosionProtected(x, y);
-    else if (CAN_EXPLODE_BY_EXPLOSION(element))
-    {
-      Feld[x][y] = Store2[x][y];
-      Store2[x][y] = 0;
-      Bang(x, y);
-    }
-    else if (element == EL_AMOEBA_TO_DIAMOND)
-      AmoebeUmwandeln(x, y);
   }
-#endif
 
   if (phase == last_phase)
   {
@@ -2881,22 +3227,8 @@ void Explode(int ex, int ey, int phase, int mode)
     ChangeDelay[x][y] = 0;
     ChangePage[x][y] = -1;
 
-#if 1
     InitField_WithBug2(x, y, FALSE);
-#else
-    InitField(x, y, FALSE);
-#if 1
-    /* !!! not needed !!! */
-#if 1
-    if (game.engine_version < VERSION_IDENT(3,0,9,0) &&
-       CAN_MOVE(Feld[x][y]) && Feld[x][y] != EL_MOLE)
-      InitMovDir(x, y);
-#else
-    if (CAN_MOVE(element))
-      InitMovDir(x, y);
-#endif
-#endif
-#endif
+
     DrawLevelField(x, y);
 
     TestIfElementTouchesCustomElement(x, y);
@@ -2910,27 +3242,10 @@ void Explode(int ex, int ey, int phase, int mode)
     if (ELEM_IS_PLAYER(element))
       RelocatePlayer(x, y, element);
   }
-#if 1
   else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
-#else
-  else if (phase >= delay && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
-#endif
   {
-#if 1
     int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
-#else
-    int stored = Store[x][y];
-    int graphic = (game.emulation != EMU_SUPAPLEX ? IMG_EXPLOSION :
-                  stored == EL_SP_INFOTRON ? IMG_SP_EXPLOSION_INFOTRON :
-                  IMG_SP_EXPLOSION);
-#endif
-    int frame = getGraphicAnimationFrame(graphic, phase - delay);
-
-#if 0
-    printf("::: %d ['%s'] -> %d\n", GfxElement[x][y],
-          element_info[GfxElement[x][y]].token_name,
-          graphic);
-#endif
+    int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
     if (phase == delay)
       DrawLevelFieldCrumbledSand(x, y);
@@ -2973,14 +3288,14 @@ void DynaExplode(int ex, int ey)
     player->dynabombs_left++;
   }
 
-  Explode(ex, ey, EX_PHASE_START, EX_CENTER);
+  Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     for (j = 1; j <= dynabomb_size; j++)
     {
-      int x = ex + j * xy[i % 4][0];
-      int y = ey + j * xy[i % 4][1];
+      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]))
@@ -2992,13 +3307,10 @@ void DynaExplode(int ex, int ey)
       if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
        continue;
 
-      Explode(x, y, EX_PHASE_START, EX_BORDER);
+      Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
 
-      /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
-      if (element != EL_EMPTY &&
-         element != EL_SAND &&
-         element != EL_EXPLOSION &&
-         !dynabomb_xl)
+      if (element != EL_EMPTY && element != EL_EXPLOSION &&
+         !IS_DIGGABLE(element) && !dynabomb_xl)
        break;
     }
   }
@@ -3006,17 +3318,9 @@ void DynaExplode(int ex, int ey)
 
 void Bang(int x, int y)
 {
-#if 1
   int element = MovingOrBlocked2Element(x, y);
-#else
-  int element = Feld[x][y];
-#endif
 
-#if 1
   if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
-#else
-  if (IS_PLAYER(x, y))
-#endif
   {
     struct PlayerInfo *player = PLAYERINFO(x, y);
 
@@ -3024,22 +3328,6 @@ void Bang(int x, int y)
                            player->element_nr);
   }
 
-#if 0
-#if 1
-  PlayLevelSoundAction(x, y, ACTION_EXPLODING);
-#else
-  if (game.emulation == EMU_SUPAPLEX)
-    PlayLevelSound(x, y, SND_SP_ELEMENT_EXPLODING);
-  else
-    PlayLevelSound(x, y, SND_ELEMENT_EXPLODING);
-#endif
-#endif
-
-#if 0
-  if (IS_PLAYER(x, y)) /* remove objects that might cause smaller explosion */
-    element = EL_EMPTY;
-#endif
-
   switch(element)
   {
     case EL_BUG:
@@ -3052,7 +3340,7 @@ void Bang(int x, int y)
     case EL_PACMAN:
     case EL_MOLE:
       RaiseScoreElement(element);
-      Explode(x, y, EX_PHASE_START, EX_NORMAL);
+      Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
       break;
     case EL_DYNABOMB_PLAYER_1_ACTIVE:
     case EL_DYNABOMB_PLAYER_2_ACTIVE:
@@ -3066,43 +3354,38 @@ void Bang(int x, int y)
     case EL_PENGUIN:
     case EL_LAMP:
     case EL_LAMP_ACTIVE:
+    case EL_AMOEBA_TO_DIAMOND:
       if (IS_PLAYER(x, y))
-       Explode(x, y, EX_PHASE_START, EX_NORMAL);
+       Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
       else
-       Explode(x, y, EX_PHASE_START, EX_CENTER);
+       Explode(x, y, EX_PHASE_START, EX_TYPE_CENTER);
       break;
     default:
-      if (CAN_EXPLODE_DYNA(element))
-       DynaExplode(x, y);
-      else if (CAN_EXPLODE_1X1(element))
-       Explode(x, y, EX_PHASE_START, EX_CENTER);
+      if (element_info[element].explosion_type == EXPLODES_CROSS)
+       Explode(x, y, EX_PHASE_START, EX_TYPE_CROSS);
+      else if (element_info[element].explosion_type == EXPLODES_1X1)
+       Explode(x, y, EX_PHASE_START, EX_TYPE_CENTER);
       else
-       Explode(x, y, EX_PHASE_START, EX_NORMAL);
+       Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
       break;
   }
 
-  CheckTriggeredElementChange(x, y, element, CE_OTHER_IS_EXPLODING);
+  CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
 }
 
 void SplashAcid(int x, int y)
 {
-  int element = Feld[x][y];
-
-  if (element != EL_ACID_SPLASH_LEFT &&
-      element != EL_ACID_SPLASH_RIGHT)
-  {
-    PlayLevelSound(x, y, SND_ACID_SPLASHING);
+  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;
 
-    if (IN_LEV_FIELD(x-1, y) && IS_FREE(x-1, y) &&
-       (!IN_LEV_FIELD(x-1, y-1) ||
-        !CAN_FALL(MovingOrBlocked2Element(x-1, y-1))))
-      Feld[x-1][y] = EL_ACID_SPLASH_LEFT;
+  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;
 
-    if (IN_LEV_FIELD(x+1, y) && IS_FREE(x+1, y) &&
-       (!IN_LEV_FIELD(x+1, y-1) ||
-        !CAN_FALL(MovingOrBlocked2Element(x+1, y-1))))
-      Feld[x+1][y] = EL_ACID_SPLASH_RIGHT;
-  }
+  PlayLevelSound(x, y, SND_ACID_SPLASHING);
 }
 
 static void InitBeltMovement()
@@ -3125,11 +3408,11 @@ static void InitBeltMovement()
   int x, y, i, j;
 
   /* set frame order for belt animation graphic according to belt direction */
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_BELTS; i++)
   {
     int belt_nr = i;
 
-    for (j = 0; j < 3; j++)
+    for (j = 0; j < NUM_BELT_PARTS; j++)
     {
       int element = belt_base_active_element[belt_nr] + j;
       int graphic = el2img(element);
@@ -3147,7 +3430,7 @@ static void InitBeltMovement()
     {
       int element = Feld[x][y];
 
-      for (i = 0; i < 4; i++)
+      for (i = 0; i < NUM_BELTS; i++)
       {
        if (IS_BELT(element) && game.belt_dir[i] != MV_NO_MOVING)
        {
@@ -3213,7 +3496,7 @@ static void ToggleBeltSwitch(int x, int y)
     belt_dir_nr = 1;
 
   /* set frame order for belt animation graphic according to belt direction */
-  for (i = 0; i < 3; i++)
+  for (i = 0; i < NUM_BELT_PARTS; i++)
   {
     int element = belt_base_active_element[belt_nr] + i;
     int graphic = el2img(element);
@@ -3290,21 +3573,15 @@ static void ToggleSwitchgateSwitch(int x, int y)
               element == EL_SWITCHGATE_OPENING)
       {
        Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
-#if 1
+
        PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
-#else
-       PlayLevelSound(xx, yy, SND_SWITCHGATE_CLOSING);
-#endif
       }
       else if (element == EL_SWITCHGATE_CLOSED ||
               element == EL_SWITCHGATE_CLOSING)
       {
        Feld[xx][yy] = EL_SWITCHGATE_OPENING;
-#if 1
+
        PlayLevelSoundAction(xx, yy, ACTION_OPENING);
-#else
-       PlayLevelSound(xx, yy, SND_SWITCHGATE_OPENING);
-#endif
       }
     }
   }
@@ -3356,6 +3633,10 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
          Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
 
        DrawLevelField(x, y);
+
+       /* 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 ||
@@ -3365,6 +3646,10 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
          Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
 
        DrawLevelField(x, y);
+
+       /* re-crumble neighbour fields, if needed */
+       if (element == EL_INVISIBLE_SAND)
+         DrawLevelFieldCrumbledSandNeighbours(x, y);
       }
     }
   }
@@ -3414,46 +3699,15 @@ static void ActivateTimegateSwitch(int x, int y)
   Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE;
 }
 
-inline static int getElementMoveStepsize(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;
-
-  /* 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
-  }
-
-  return step;
-}
-
 void Impact(int x, int y)
 {
-  boolean lastline = (y == lev_fieldy-1);
+  boolean last_line = (y == lev_fieldy - 1);
   boolean object_hit = FALSE;
-  boolean impact = (lastline || object_hit);
+  boolean impact = (last_line || object_hit);
   int element = Feld[x][y];
-  int smashed = EL_UNDEFINED;
+  int smashed = EL_STEELWALL;
 
-  if (!lastline)       /* check if element below was hit */
+  if (!last_line)      /* check if element below was hit */
   {
     if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
       return;
@@ -3462,10 +3716,6 @@ void Impact(int x, int y)
                                         MovDir[x][y + 1] != MV_DOWN ||
                                         MovPos[x][y + 1] <= TILEY / 2));
 
-#if 0
-    object_hit = !IS_FREE(x, y + 1);
-#endif
-
     /* 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)
@@ -3474,15 +3724,16 @@ void Impact(int x, int y)
     if (object_hit)
       smashed = MovingOrBlocked2Element(x, y + 1);
 
-    impact = (lastline || object_hit);
+    impact = (last_line || object_hit);
   }
 
-  if (!lastline && smashed == EL_ACID) /* element falls into acid */
+  if (!last_line && smashed == EL_ACID)        /* element falls into acid */
   {
-    SplashAcid(x, y);
+    SplashAcid(x, y + 1);
     return;
   }
 
+  /* !!! 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))
@@ -3498,11 +3749,13 @@ void Impact(int x, int y)
   }
   else if (impact && element == EL_PEARL)
   {
+    ResetGfxAnimation(x, y);
+
     Feld[x][y] = EL_PEARL_BREAKING;
     PlayLevelSound(x, y, SND_PEARL_BREAKING);
     return;
   }
-  else if (impact && CheckElementChange(x, y, element, CE_IMPACT))
+  else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
   {
     PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
 
@@ -3585,13 +3838,6 @@ void Impact(int x, int y)
       Bang(x, y + 1);
       return;
     }
-#if 0
-    else if (CAN_SMASH_ENEMIES(element) && IS_CLASSIC_ENEMY(smashed))
-    {
-      Bang(x, y + 1);
-      return;
-    }
-#endif
     else if (CAN_SMASH_EVERYTHING(element))
     {
       if (IS_CLASSIC_ENEMY(smashed) ||
@@ -3617,6 +3863,8 @@ void Impact(int x, int y)
        }
        else if (smashed == EL_PEARL)
        {
+         ResetGfxAnimation(x, y);
+
          Feld[x][y + 1] = EL_PEARL_BREAKING;
          PlayLevelSound(x, y, SND_PEARL_BREAKING);
          return;
@@ -3643,23 +3891,27 @@ void Impact(int x, int y)
        }
        else
        {
-         CheckElementChange(x, y + 1, smashed, CE_SMASHED);
+#if 0
+         TestIfElementSmashesCustomElement(x, y, MV_DOWN);
+#endif
 
-         CheckTriggeredElementSideChange(x, y + 1, smashed, CH_SIDE_TOP,
-                                         CE_OTHER_IS_SWITCHING);
-         CheckElementSideChange(x, y + 1, smashed, CH_SIDE_TOP,
-                                CE_SWITCHED, -1);
+         CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
+
+         CheckElementChangeBySide(x, y + 1, smashed, element,
+                                  CE_SWITCHED, CH_SIDE_TOP);
+         CheckTriggeredElementChangeBySide(x, y + 1, smashed,
+                                           CE_SWITCH_OF_X, CH_SIDE_TOP);
        }
       }
       else
       {
-       CheckElementChange(x, y + 1, smashed, CE_SMASHED);
+       CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
       }
     }
   }
 
   /* play sound of magic wall / mill */
-  if (!lastline &&
+  if (!last_line &&
       (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
        Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
   {
@@ -3672,7 +3924,7 @@ void Impact(int x, int y)
   }
 
   /* play sound of object that hits the ground */
-  if (lastline || object_hit)
+  if (last_line || object_hit)
     PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
 }
 
@@ -3730,9 +3982,9 @@ inline static void TurnRoundExt(int x, int y)
   {
     TestIfBadThingTouchesOtherBadThing(x, y);
 
-    if (ENEMY_CAN_ENTER_FIELD(right_x, right_y))
+    if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
       MovDir[x][y] = right_dir;
-    else if (!ENEMY_CAN_ENTER_FIELD(move_x, move_y))
+    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = left_dir;
 
     if (element == EL_BUG && MovDir[x][y] != old_move_dir)
@@ -3740,28 +3992,36 @@ inline static void TurnRoundExt(int x, int y)
     else if (element == EL_BD_BUTTERFLY)     /* && MovDir[x][y] == left_dir) */
       MovDelay[x][y] = 1;
   }
-  else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
-          element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
+  else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
   {
     TestIfBadThingTouchesOtherBadThing(x, y);
 
-    if (ENEMY_CAN_ENTER_FIELD(left_x, left_y))
+    if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
       MovDir[x][y] = left_dir;
-    else if (!ENEMY_CAN_ENTER_FIELD(move_x, move_y))
+    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = right_dir;
 
-    if ((element == EL_SPACESHIP ||
-        element == EL_SP_SNIKSNAK ||
-        element == EL_SP_ELECTRON)
-       && MovDir[x][y] != old_move_dir)
+    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);
+
+    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 (MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = 9;
+  }
   else if (element == EL_YAMYAM)
   {
-    boolean can_turn_left  = YAMYAM_CAN_ENTER_FIELD(left_x, left_y);
-    boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(right_x, right_y);
+    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);
@@ -3776,8 +4036,10 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (element == EL_DARK_YAMYAM)
   {
-    boolean can_turn_left  = DARK_YAMYAM_CAN_ENTER_FIELD(left_x, left_y);
-    boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(right_x, right_y);
+    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);
@@ -3792,8 +4054,8 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (element == EL_PACMAN)
   {
-    boolean can_turn_left  = PACMAN_CAN_ENTER_FIELD(left_x, left_y);
-    boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(right_x, right_y);
+    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 (can_turn_left && can_turn_right)
       MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
@@ -3808,9 +4070,9 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (element == EL_PIG)
   {
-    boolean can_turn_left  = PIG_CAN_ENTER_FIELD(left_x, left_y);
-    boolean can_turn_right = PIG_CAN_ENTER_FIELD(right_x, right_y);
-    boolean can_move_on    = PIG_CAN_ENTER_FIELD(move_x, move_y);
+    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);
@@ -3864,24 +4126,20 @@ inline static void TurnRoundExt(int x, int y)
     xx = x + move_xy[MovDir[x][y]].x;
     yy = y + move_xy[MovDir[x][y]].y;
 
-    if (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy]))
+    if (!IN_LEV_FIELD(xx, yy) ||
+        (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
       MovDir[x][y] = old_move_dir;
 
     MovDelay[x][y] = 0;
   }
   else if (element == EL_DRAGON)
   {
-    boolean can_turn_left  = IN_LEV_FIELD_AND_IS_FREE(left_x, left_y);
-    boolean can_turn_right = IN_LEV_FIELD_AND_IS_FREE(right_x, right_y);
-    boolean can_move_on    = IN_LEV_FIELD_AND_IS_FREE(move_x, move_y);
+    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 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":2: %d/%d: %d [%d]\n", x, y, MovDir[x][y], FrameCounter);
-#endif
-
     if (can_move_on && rnd > rnd_value / 8)
       MovDir[x][y] = old_move_dir;
     else if (can_turn_left && can_turn_right)
@@ -3896,42 +4154,25 @@ inline static void TurnRoundExt(int x, int y)
     xx = x + move_xy[MovDir[x][y]].x;
     yy = y + move_xy[MovDir[x][y]].y;
 
-#if 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":3: %d/%d: %d (%d/%d: %d) [%d]\n", x, y, MovDir[x][y],
-            xx, yy, Feld[xx][yy],
-            FrameCounter);
-#endif
-
-#if 1
     if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
       MovDir[x][y] = old_move_dir;
-#else
-    if (!IS_FREE(xx, yy))
-      MovDir[x][y] = old_move_dir;
-#endif
-
-#if 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":4: %d/%d: %d [%d]\n", x, y, MovDir[x][y], FrameCounter);
-#endif
 
     MovDelay[x][y] = 0;
   }
   else if (element == EL_MOLE)
   {
     boolean can_move_on =
-      (MOLE_CAN_ENTER_FIELD(move_x, move_y,
+      (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(left_x, left_y,
+       (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
                              IS_AMOEBOID(Feld[left_x][left_y])));
 
       boolean can_turn_right =
-       (MOLE_CAN_ENTER_FIELD(right_x, right_y,
+       (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
                              IS_AMOEBOID(Feld[right_x][right_y])));
 
       if (can_turn_left && can_turn_right)
@@ -3953,8 +4194,8 @@ inline static void TurnRoundExt(int x, int y)
   else if (element == EL_SPRING)
   {
     if (MovDir[x][y] & MV_HORIZONTAL &&
-       (!IN_LEV_FIELD_AND_IS_FREE(move_x, move_y) ||
-        IN_LEV_FIELD_AND_IS_FREE(x, y + 1)))
+       (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
+        SPRING_CAN_ENTER_FIELD(element, x, y + 1)))
       MovDir[x][y] = MV_NO_MOVING;
 
     MovDelay[x][y] = 0;
@@ -3991,7 +4232,9 @@ inline static void TurnRoundExt(int x, int y)
       }
     }
 
-    if (element == EL_ROBOT && ZX >= 0 && ZY >= 0)
+    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;
@@ -4008,10 +4251,10 @@ inline static void TurnRoundExt(int x, int y)
        { 0, +1 }
       };
 
-      for (i = 0; i < 4; i++)
+      for (i = 0; i < NUM_DIRECTIONS; i++)
       {
-       int ex = x + xy[i % 4][0];
-       int ey = y + xy[i % 4][1];
+       int ex = x + xy[i][0];
+       int ey = y + xy[i][1];
 
        if (IN_LEV_FIELD(ex, ey) && Feld[ex][ey] == EL_EXIT_OPEN)
        {
@@ -4060,14 +4303,14 @@ inline static void TurnRoundExt(int x, int y)
          new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
-       if (PENGUIN_CAN_ENTER_FIELD(newx, newy))
+       if (PENGUIN_CAN_ENTER_FIELD(EL_PENGUIN, newx, newy))
          return;
 
        MovDir[x][y] =
          new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
-       if (PENGUIN_CAN_ENTER_FIELD(newx, newy))
+       if (PENGUIN_CAN_ENTER_FIELD(EL_PENGUIN, newx, newy))
          return;
 
        MovDir[x][y] = old_move_dir;
@@ -4089,14 +4332,14 @@ inline static void TurnRoundExt(int x, int y)
          new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
-       if (ELEMENT_CAN_ENTER_FIELD_OR_ACID_2(newx, newy))
+       if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
          return;
 
        MovDir[x][y] =
          new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
-       if (ELEMENT_CAN_ENTER_FIELD_OR_ACID_2(newx, newy))
+       if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
          return;
 
        MovDir[x][y] = old_move_dir;
@@ -4116,6 +4359,11 @@ inline static void TurnRoundExt(int x, int y)
     boolean can_turn_right =
       CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
 
+#if USE_CAN_MOVE_NOT_MOVING
+    if (element_info[element].move_stepsize == 0)      /* not moving */
+      return;
+#endif
+
     if (move_pattern == MV_TURNING_LEFT)
       MovDir[x][y] = left_dir;
     else if (move_pattern == MV_TURNING_RIGHT)
@@ -4226,6 +4474,16 @@ inline static void TurnRoundExt(int x, int y)
       boolean first_horiz = RND(2);
       int new_move_dir = MovDir[x][y];
 
+#if USE_CAN_MOVE_NOT_MOVING
+      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);
+
+       return;
+      }
+#endif
+
       MovDir[x][y] =
        new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
       Moving2Blocked(x, y, &newx, &newy);
@@ -4246,7 +4504,7 @@ inline static void TurnRoundExt(int x, int y)
   else if (move_pattern == MV_WHEN_PUSHED ||
           move_pattern == MV_WHEN_DROPPED)
   {
-    if (!IN_LEV_FIELD_AND_IS_FREE(move_x, move_y))
+    if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = MV_NO_MOVING;
 
     MovDelay[x][y] = 0;
@@ -4279,7 +4537,7 @@ inline static void TurnRoundExt(int x, int y)
     int start_test = RND(4);
     int i;
 
-    for (i = 0; i < 4; i++)
+    for (i = 0; i < NUM_DIRECTIONS; i++)
     {
       int move_dir = test_dir[start_test + i];
       int move_dir_preference;
@@ -4319,7 +4577,7 @@ inline static void TurnRoundExt(int x, int y)
 
     MovDir[x][y] = new_move_dir;
     if (old_move_dir != new_move_dir)
-      MovDelay[x][y] = 9;
+      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
   }
 }
 
@@ -4327,26 +4585,15 @@ static void TurnRound(int x, int y)
 {
   int direction = MovDir[x][y];
 
-#if 0
-  GfxDir[x][y] = MovDir[x][y];
-#endif
-
   TurnRoundExt(x, y);
 
-#if 1
   GfxDir[x][y] = MovDir[x][y];
-#endif
 
   if (direction != MovDir[x][y])
     GfxFrame[x][y] = 0;
 
-#if 1
   if (MovDelay[x][y])
     GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_BIT(direction);
-#else
-  if (MovDelay[x][y])
-    GfxAction[x][y] = ACTION_WAITING;
-#endif
 }
 
 static boolean JustBeingPushed(int x, int y)
@@ -4372,23 +4619,14 @@ static boolean JustBeingPushed(int x, int y)
 
 void StartMoving(int x, int y)
 {
-#if 0
-  boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0,0));
-#endif
   boolean started_moving = FALSE;      /* some elements can fall _and_ move */
   int element = Feld[x][y];
 
   if (Stop[x][y])
     return;
 
-#if 1
   if (MovDelay[x][y] == 0)
     GfxAction[x][y] = ACTION_DEFAULT;
-#else
-  /* !!! this should be handled more generic (not only for mole) !!! */
-  if (element != EL_MOLE && GfxAction[x][y] != ACTION_DIGGING)
-    GfxAction[x][y] = ACTION_DEFAULT;
-#endif
 
   if (CAN_FALL(element) && y < lev_fieldy - 1)
   {
@@ -4406,11 +4644,8 @@ void StartMoving(int x, int y)
 
        Feld[x][y] = EL_QUICKSAND_EMPTYING;
        Store[x][y] = EL_ROCK;
-#if 1
+
        PlayLevelSoundAction(x, y, ACTION_EMPTYING);
-#else
-       PlayLevelSound(x, y, SND_QUICKSAND_EMPTYING);
-#endif
       }
       else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
       {
@@ -4428,11 +4663,8 @@ void StartMoving(int x, int y)
        Feld[x][y + 1] = EL_QUICKSAND_FULL;
        Store[x][y + 1] = Store[x][y];
        Store[x][y] = 0;
-#if 1
+
        PlayLevelSoundAction(x, y, ACTION_FILLING);
-#else
-       PlayLevelSound(x, y, SND_QUICKSAND_FILLING);
-#endif
       }
     }
     else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
@@ -4443,11 +4675,8 @@ void StartMoving(int x, int y)
 
       Feld[x][y] = EL_QUICKSAND_FILLING;
       Store[x][y] = element;
-#if 1
+
       PlayLevelSoundAction(x, y, ACTION_FILLING);
-#else
-      PlayLevelSound(x, y, SND_QUICKSAND_FILLING);
-#endif
     }
     else if (element == EL_MAGIC_WALL_FULL)
     {
@@ -4517,42 +4746,35 @@ void StartMoving(int x, int y)
         EL_BD_MAGIC_WALL_FILLING);
       Store[x][y] = element;
     }
-#if 0
-    else if (CAN_SMASH(element) && Feld[x][y + 1] == EL_ACID)
-#else
     else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
-#endif
     {
-      SplashAcid(x, y);
+      SplashAcid(x, y + 1);
 
       InitMovingField(x, y, MV_DOWN);
       started_moving = TRUE;
 
       Store[x][y] = EL_ACID;
-#if 0
-      /* !!! TEST !!! better use "_FALLING" etc. !!! */
-      GfxAction[x][y + 1] = ACTION_ACTIVE;
-#endif
     }
-#if 1
-    else if ((game.engine_version < VERSION_IDENT(2,2,0,7) &&
-             CAN_SMASH(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
-             (Feld[x][y + 1] == EL_BLOCKED)) ||
+    else if ((game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+             CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
+
+#if USE_IMPACT_BUGFIX
             (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
-             CAN_SMASH(element) && WasJustFalling[x][y] &&
-             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))))
+             CAN_FALL(element) && WasJustFalling[x][y] &&
+             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
 
+            (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)))
 #else
-#if 1
-    else if (game.engine_version < VERSION_IDENT(2,2,0,7) &&
-            CAN_SMASH(element) && Feld[x][y + 1] == EL_BLOCKED &&
-            WasJustMoving[x][y] && !Pushed[x][y + 1])
-#else
-    else if (CAN_SMASH(element) && Feld[x][y + 1] == EL_BLOCKED &&
-            WasJustMoving[x][y])
-#endif
-#endif
+            (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
+             CAN_SMASH(element) && WasJustFalling[x][y] &&
+             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
 
+            (game.engine_version < VERSION_IDENT(2,2,0,7) &&
+             CAN_SMASH(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
+             (Feld[x][y + 1] == EL_BLOCKED)))
+#endif
     {
       /* this is needed for a special case not covered by calling "Impact()"
         from "ContinueMoving()": if an element moves to a tile directly below
@@ -4564,10 +4786,7 @@ void StartMoving(int x, int y)
         element; also, the case of the player being the element to smash was
         simply not covered here... :-/ ) */
 
-#if 0
-      WasJustMoving[x][y] = 0;
-      WasJustFalling[x][y] = 0;
-#endif
+      CheckCollision[x][y] = 0;
 
       Impact(x, y);
     }
@@ -4592,23 +4811,10 @@ void StartMoving(int x, int y)
       Feld[x][y] = EL_AMOEBA_GROWING;
       Store[x][y] = EL_AMOEBA_WET;
     }
-    /* Store[x][y + 1] must be zero, because:
-       (EL_QUICKSAND_FULL -> EL_ROCK): Store[x][y + 1] == EL_QUICKSAND_EMPTY
-    */
-#if 0
-#if OLD_GAME_BEHAVIOUR
-    else if (IS_SLIPPERY(Feld[x][y + 1]) && !Store[x][y + 1])
-#else
-    else if (IS_SLIPPERY(Feld[x][y + 1]) && !Store[x][y + 1] &&
-            !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
-            element != EL_DX_SUPABOMB)
-#endif
-#else
     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)
-#endif
     {
       boolean can_fall_left  = (x > 0 && IS_FREE(x - 1, y) &&
                                (IS_FREE(x - 1, y + 1) ||
@@ -4636,13 +4842,30 @@ void StartMoving(int x, int y)
        can_fall_both = (can_fall_left && can_fall_right);
       }
 
-      if (can_fall_any)
+#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
+
+      if (can_fall_both)
       {
-       if (can_fall_both &&
-           (game.emulation != EMU_BOULDERDASH &&
-            element != EL_BD_ROCK && element != EL_BD_DIAMOND))
+       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));
 
+       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;
       }
@@ -4661,17 +4884,13 @@ void StartMoving(int x, int y)
       if ((belt_dir == MV_LEFT  && left_is_free) ||
          (belt_dir == MV_RIGHT && right_is_free))
       {
-#if 1
        int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
-#endif
 
        InitMovingField(x, y, belt_dir);
        started_moving = TRUE;
 
-#if 1
        Pushed[x][y] = TRUE;
        Pushed[nextx][y] = TRUE;
-#endif
 
        GfxAction[x][y] = ACTION_DEFAULT;
       }
@@ -4683,79 +4902,42 @@ void StartMoving(int x, int 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_NO_MOVING)
+#else
   if (CAN_MOVE(element) && !started_moving)
+#endif
   {
     int move_pattern = element_info[element].move_pattern;
     int newx, newy;
 
+#if 0
+#if DEBUG
+    if (MovDir[x][y] == MV_NO_MOVING)
+    {
+      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
+
     Moving2Blocked(x, y, &newx, &newy);
 
-#if 1
     if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
       return;
-#else
-    if ((element == EL_SATELLITE ||
-        element == EL_BALLOON ||
-        element == EL_SPRING)
-       && JustBeingPushed(x, y))
-      return;
-#endif
 
-#if 1
-    if (game.engine_version >= VERSION_IDENT(3,0,9,0) &&
-       WasJustMoving[x][y] && IN_LEV_FIELD(newx, newy) &&
-       (Feld[newx][newy] == EL_BLOCKED || IS_PLAYER(newx, newy)))
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+       CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
     {
-#if 0
-      printf("::: element %d '%s' WasJustMoving %d [%d, %d, %d, %d]\n",
-            element, element_info[element].token_name,
-            WasJustMoving[x][y],
-            HAS_ANY_CHANGE_EVENT(element, CE_HITTING_SOMETHING),
-            HAS_ANY_CHANGE_EVENT(element, CE_HIT_BY_SOMETHING),
-            HAS_ANY_CHANGE_EVENT(element, CE_OTHER_IS_HITTING),
-            HAS_ANY_CHANGE_EVENT(element, CE_OTHER_GETS_HIT));
-#endif
-
-#if 1
       WasJustMoving[x][y] = 0;
-#endif
+      CheckCollision[x][y] = 0;
 
       TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
 
-#if 0
-      if (Feld[x][y] != element)       /* element has changed */
-      {
-       element = Feld[x][y];
-       move_pattern = element_info[element].move_pattern;
-
-       if (!CAN_MOVE(element))
-         return;
-      }
-#else
       if (Feld[x][y] != element)       /* element has changed */
        return;
-#endif
     }
-#endif
-
-#if 0
-#if 0
-    if (element == EL_SPRING && MovDir[x][y] == MV_DOWN)
-      Feld[x][y + 1] = EL_EMPTY;       /* was set to EL_BLOCKED above */
-#else
-    if (element == EL_SPRING && MovDir[x][y] != MV_NO_MOVING)
-    {
-      Moving2Blocked(x, y, &newx, &newy);
-      if (Feld[newx][newy] == EL_BLOCKED)
-       Feld[newx][newy] = EL_EMPTY;    /* was set to EL_BLOCKED above */
-    }
-#endif
-#endif
-
-#if 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":1: %d/%d: %d [%d]\n", x, y, MovDir[x][y], FrameCounter);
-#endif
 
     if (!MovDelay[x][y])       /* start new movement phase */
     {
@@ -4774,11 +4956,6 @@ void StartMoving(int x, int y)
       {
        TurnRound(x, y);
 
-#if 0
-       if (FrameCounter < 1 && x == 0 && y == 29)
-         printf(":9: %d: %d [%d]\n", y, MovDir[x][y], FrameCounter);
-#endif
-
        if (MovDelay[x][y] && (element == EL_BUG ||
                               element == EL_SPACESHIP ||
                               element == EL_SP_SNIKSNAK ||
@@ -4792,47 +4969,16 @@ void StartMoving(int x, int y)
     {
       MovDelay[x][y]--;
 
-#if 0
-      if (element == EL_YAMYAM)
+      if (element == EL_ROBOT ||
+         element == EL_YAMYAM ||
+         element == EL_DARK_YAMYAM)
       {
-       printf("::: %d\n",
-              el_act_dir2img(EL_YAMYAM, ACTION_WAITING, MV_LEFT));
-       DrawLevelElementAnimation(x, y, element);
+       DrawLevelElementAnimationIfNeeded(x, y, element);
+       PlayLevelSoundAction(x, y, ACTION_WAITING);
       }
-#endif
-
-      if (MovDelay[x][y])      /* element still has to wait some time */
-      {
-#if 0
-       /* !!! PLACE THIS SOMEWHERE AFTER "TurnRound()" !!! */
-       ResetGfxAnimation(x, y);
-#endif
-
-#if 0
-       if (GfxAction[x][y] != ACTION_WAITING)
-         printf("::: %d: %d != ACTION_WAITING\n", element, GfxAction[x][y]);
-
-       GfxAction[x][y] = ACTION_WAITING;
-#endif
-      }
-
-      if (element == EL_ROBOT ||
-#if 0
-         element == EL_PACMAN ||
-#endif
-         element == EL_YAMYAM ||
-         element == EL_DARK_YAMYAM)
-      {
-#if 0
-       DrawLevelElementAnimation(x, y, element);
-#else
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-#endif
-       PlayLevelSoundAction(x, y, ACTION_WAITING);
-      }
-      else if (element == EL_SP_ELECTRON)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-      else if (element == EL_DRAGON)
+      else if (element == EL_SP_ELECTRON)
+       DrawLevelElementAnimationIfNeeded(x, y, element);
+      else if (element == EL_DRAGON)
       {
        int i;
        int dir = MovDir[x][y];
@@ -4844,10 +4990,6 @@ void StartMoving(int x, int y)
                       dir == MV_DOWN   ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
        int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
-#if 0
-       printf("::: %d, %d\n", GfxAction[x][y], GfxFrame[x][y]);
-#endif
-
        GfxAction[x][y] = ACTION_ATTACKING;
 
        if (IS_PLAYER(x, y))
@@ -4872,12 +5014,25 @@ void StartMoving(int x, int y)
          {
            int flamed = MovingOrBlocked2Element(xx, yy);
 
+           /* !!! */
+#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);
+#endif
+
+           ChangeDelay[xx][yy] = 0;
 
            Feld[xx][yy] = EL_FLAMES;
+
            if (IN_SCR_FIELD(sx, sy))
            {
              DrawLevelFieldCrumbledSand(xx, yy);
@@ -4899,13 +5054,6 @@ void StartMoving(int x, int y)
 
        return;
       }
-
-#if 0
-      /* special case of "moving" animation of waiting elements (FIX THIS !!!);
-        for all other elements GfxAction will be set by InitMovingField() */
-      if (element == EL_BD_BUTTERFLY || element == EL_BD_FIREFLY)
-       GfxAction[x][y] = ACTION_MOVING;
-#endif
     }
 
     /* now make next step */
@@ -4916,47 +5064,25 @@ void StartMoving(int x, int y)
        IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
        !PLAYER_ENEMY_PROTECTED(newx, newy))
     {
-#if 1
       TestIfBadThingRunsIntoHero(x, y, MovDir[x][y]);
 
       return;
-#else
-      /* player killed by element which is deadly when colliding with */
-      MovDir[x][y] = 0;
-      KillHero(PLAYERINFO(newx, newy));
-      return;
-#endif
-
     }
-#if 1
-    else if (CAN_MOVE_INTO_ACID(element) && MovDir[x][y] == MV_DOWN &&
-            IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID)
-
-#else
 
-    else if ((element == EL_PENGUIN ||
-             element == EL_ROBOT ||
-             element == EL_SATELLITE ||
-             element == EL_BALLOON ||
-             IS_CUSTOM_ELEMENT(element)) &&
-            IN_LEV_FIELD(newx, newy) &&
-            MovDir[x][y] == MV_DOWN && Feld[newx][newy] == EL_ACID)
-#endif
+    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)))
     {
-      SplashAcid(x, y);
+      SplashAcid(newx, newy);
       Store[x][y] = EL_ACID;
     }
     else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
     {
       if (Feld[newx][newy] == EL_EXIT_OPEN)
       {
-#if 1
        RemoveField(x, y);
        DrawLevelField(x, y);
-#else
-       Feld[x][y] = EL_EMPTY;
-       DrawLevelField(x, y);
-#endif
 
        PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
        if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
@@ -5012,32 +5138,11 @@ void StartMoving(int x, int y)
        return;
       }
     }
-
-#if 1
-
-    /*
-    else if (move_pattern & MV_MAZE_RUNNER_STYLE && IN_LEV_FIELD(newx, newy))
-    */
-
     else if (IS_CUSTOM_ELEMENT(element) &&
-            CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy)
-
-#if 0
- &&
-            !IS_FREE(newx, newy)
-#endif
-
-)
+            CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
     {
       int new_element = Feld[newx][newy];
 
-#if 0
-      printf("::: '%s' digs '%s' [%d]\n",
-            element_info[element].token_name,
-            element_info[Feld[newx][newy]].token_name,
-            StorePlayer[newx][newy]);
-#endif
-
       if (!IS_FREE(newx, newy))
       {
        int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING :
@@ -5067,11 +5172,24 @@ void StartMoving(int x, int y)
          DrawLevelField(newx, newy);
        }
 
+       /* if digged element was about to explode, prevent the explosion */
+       ExplodeField[newx][newy] = EX_TYPE_NONE;
+
        PlayLevelSoundAction(x, y, action);
       }
 
-      if (new_element == element_info[element].move_enter_element)
-       element_info[element].can_leave_element = TRUE;
+      Store[newx][newy] = EL_EMPTY;
+      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
+      {
+#if USE_CHANGE_TO_TRIGGERED
+       int move_leave_element = element_info[element].move_leave_element;
+
+       Store[newx][newy] = (move_leave_element == EL_TRIGGER_ELEMENT ?
+                            new_element : move_leave_element);
+#else
+       Store[newx][newy] = element_info[element].move_leave_element;
+#endif
+      }
 
       if (move_pattern & MV_MAZE_RUNNER_STYLE)
       {
@@ -5079,9 +5197,6 @@ void StartMoving(int x, int y)
        PlayerVisit[x][y] /= 8;         /* expire player visit path */
       }
     }
-
-#endif
-
     else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
     {
       if (!IS_FREE(newx, newy))
@@ -5110,10 +5225,8 @@ void StartMoving(int x, int y)
            element1 != EL_DRAGON && element2 != EL_DRAGON &&
            element1 != EL_FLAMES && element2 != EL_FLAMES)
        {
-#if 1
          ResetGfxAnimation(x, y);
          GfxAction[x][y] = ACTION_ATTACKING;
-#endif
 
          if (IS_PLAYER(x, y))
            DrawPlayerField(x, y);
@@ -5124,11 +5237,25 @@ void StartMoving(int x, int y)
 
          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;
        }
@@ -5158,8 +5285,18 @@ void StartMoving(int x, int y)
          AmoebaCnt[AmoebaNr[newx][newy]]--;
       }
 
+#if 0
+      /* !!! test !!! */
+      if (IS_MOVING(newx, newy) || IS_BLOCKED(newx, newy))
+      {
+       RemoveMovingField(newx, newy);
+      }
+#else
       if (IS_MOVING(newx, newy))
+      {
        RemoveMovingField(newx, newy);
+      }
+#endif
       else
       {
        Feld[newx][newy] = EL_EMPTY;
@@ -5212,32 +5349,27 @@ void StartMoving(int x, int y)
 
       TurnRound(x, y);
 
-#if 1
+#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);
+
+       CheckElementChangeBySide(x, y, element, blocking_element, CE_BLOCKED,
+                                MovDir[x][y]);
+
+       element = Feld[x][y];   /* element might have changed */
+      }
+#endif
+
       if (GFX_ELEMENT(element) != EL_SAND)     /* !!! FIX THIS (crumble) !!! */
        DrawLevelElementAnimation(x, y, element);
-#else
-      if (element == EL_BUG ||
-         element == EL_SPACESHIP ||
-         element == EL_SP_SNIKSNAK)
-       DrawLevelField(x, y);
-      else if (element == EL_MOLE)
-       DrawLevelField(x, y);
-      else if (element == EL_BD_BUTTERFLY ||
-              element == EL_BD_FIREFLY)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-      else if (element == EL_SATELLITE)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-      else if (element == EL_SP_ELECTRON)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-#endif
 
       if (DONT_TOUCH(element))
        TestIfBadThingTouchesHero(x, y);
 
-#if 0
-      PlayLevelSoundAction(x, y, ACTION_WAITING);
-#endif
-
       return;
     }
 
@@ -5250,36 +5382,28 @@ void StartMoving(int x, int y)
     ContinueMoving(x, y);
 }
 
+/* (emacs is confused here for some reason; this makes it happy again ;-) ) */
+void dummy()
+{
+}
+
 void ContinueMoving(int x, int y)
 {
   int element = Feld[x][y];
+  int stored = Store[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;
-#if 0
-  int nextx = newx + dx, nexty = newy + dy;
-#endif
-#if 1
   boolean pushed_by_player   = (Pushed[x][y] && IS_PLAYER(x, y));
   boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
-#else
-  boolean pushed_by_player = Pushed[x][y];
-#endif
+  boolean last_line = (newy == lev_fieldy - 1);
 
   MovPos[x][y] += getElementMoveStepsize(x, y);
 
-#if 0
-  if (pushed_by_player && IS_PLAYER(x, y))
-  {
-    /* special case: moving object pushed by player */
-    MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
-  }
-#else
   if (pushed_by_player)        /* special case: moving object pushed by player */
     MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
-#endif
 
   if (ABS(MovPos[x][y]) < TILEX)
   {
@@ -5294,7 +5418,11 @@ void ContinueMoving(int x, int y)
   Feld[newx][newy] = element;
   MovPos[x][y] = 0;    /* force "not moving" for "crumbled sand" */
 
-  if (element == EL_MOLE)
+  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;
 
@@ -5353,25 +5481,26 @@ void ContinueMoving(int x, int y)
 
     Back[x][y] = Back[newx][newy] = 0;
   }
-  else if (Store[x][y] == EL_ACID)
-  {
-    element = Feld[newx][newy] = EL_ACID;
-  }
 
-  Store[x][y] = 0;
-  MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
+  Store[x][y] = EL_EMPTY;
+  MovPos[x][y] = 0;
+  MovDir[x][y] = 0;
+  MovDelay[x][y] = 0;
   MovDelay[newx][newy] = 0;
 
-  /* copy element change control values to new field */
-  ChangeDelay[newx][newy] = ChangeDelay[x][y];
-  ChangePage[newx][newy] = ChangePage[x][y];
-  Changed[newx][newy] = Changed[x][y];
-  ChangeEvent[newx][newy] = ChangeEvent[x][y];
+  if (CAN_CHANGE(element))
+  {
+    /* copy element change control values to new field */
+    ChangeDelay[newx][newy] = ChangeDelay[x][y];
+    ChangePage[newx][newy]  = ChangePage[x][y];
+    Changed[newx][newy]     = Changed[x][y];
+    ChangeEvent[newx][newy] = ChangeEvent[x][y];
+  }
 
   ChangeDelay[x][y] = 0;
   ChangePage[x][y] = -1;
-  Changed[x][y] = CE_BITMASK_DEFAULT;
-  ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+  Changed[x][y] = FALSE;
+  ChangeEvent[x][y] = -1;
 
   /* copy animation control values to new field */
   GfxFrame[newx][newy]  = GfxFrame[x][y];
@@ -5381,50 +5510,45 @@ void ContinueMoving(int x, int y)
 
   Pushed[x][y] = Pushed[newx][newy] = FALSE;
 
-  ResetGfxAnimation(x, y);     /* reset animation values for old field */
-
-#if 1
   /* some elements can leave other elements behind after moving */
-  if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
-      ei->move_leave_element != EL_EMPTY &&
-      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED ||
-       ei->can_leave_element_last))
+  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)))
   {
-    Feld[x][y] = ei->move_leave_element;
+    int move_leave_element = ei->move_leave_element;
+
+#if USE_CHANGE_TO_TRIGGERED
+    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 USE_PREVIOUS_MOVE_DIR
+    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = direction;
+#endif
+
     InitField(x, y, FALSE);
 
     if (GFX_CRUMBLED(Feld[x][y]))
       DrawLevelFieldCrumbledSandNeighbours(x, y);
-  }
-
-  ei->can_leave_element_last = ei->can_leave_element;
-  ei->can_leave_element = FALSE;
-#endif
 
-#if 0
-  /* 2.1.1 (does not work correctly for spring) */
-  if (!CAN_MOVE(element))
-    MovDir[newx][newy] = 0;
-#else
+    if (ELEM_IS_PLAYER(move_leave_element))
+      RelocatePlayer(x, y, move_leave_element);
+  }
 
-#if 0
-  /* (does not work for falling objects that slide horizontally) */
-  if (CAN_FALL(element) && MovDir[newx][newy] == MV_DOWN)
-    MovDir[newx][newy] = 0;
-#else
-  /*
-  if (!CAN_MOVE(element) ||
-      (element == EL_SPRING && MovDir[newx][newy] == MV_DOWN))
-    MovDir[newx][newy] = 0;
-  */
+  /* 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))
+      (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;
 
-#endif
-#endif
-
   DrawLevelField(x, y);
   DrawLevelField(newx, newy);
 
@@ -5436,19 +5560,23 @@ void ContinueMoving(int x, int y)
       !(element_info[element].move_pattern & direction))
     TurnRound(newx, newy);
 
-#if 1
   /* 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;
-#endif
 
   if (!pushed_by_player)
   {
+    int nextx = newx + dx, nexty = newy + dy;
+    boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
+
     WasJustMoving[newx][newy] = 3;
 
     if (CAN_FALL(element) && direction == MV_DOWN)
       WasJustFalling[newx][newy] = 3;
+
+    if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
+      CheckCollision[newx][newy] = 2;
   }
 
   if (DONT_TOUCH(element))     /* object may be nasty to player or others */
@@ -5462,96 +5590,38 @@ void ContinueMoving(int x, int y)
   else if (element == EL_PENGUIN)
     TestIfFriendTouchesBadThing(newx, newy);
 
+#if USE_NEW_MOVE_STYLE
+  /* give the player one last chance (one more frame) to move away */
   if (CAN_FALL(element) && direction == MV_DOWN &&
-      (newy == lev_fieldy - 1 || !IS_FREE(x, newy + 1)))
+      (last_line || (!IS_FREE(x, newy + 1) &&
+                    (!IS_PLAYER(x, newy + 1) ||
+                     game.engine_version < VERSION_IDENT(3,1,1,0)))))
+    Impact(x, newy);
+#else
+  if (CAN_FALL(element) && direction == MV_DOWN &&
+      (last_line || !IS_FREE(x, newy + 1)))
     Impact(x, newy);
-
-#if 1
-  TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
-#endif
-
-#if 0
-  if (ChangePage[newx][newy] != -1)                    /* delayed change */
-    ChangeElement(newx, newy, ChangePage[newx][newy]);
 #endif
 
-#if 1
-
-  TestIfElementHitsCustomElement(newx, newy, direction);
-
+#if USE_PUSH_BUGFIX
+  if (pushed_by_player && !game.use_change_when_pushing_bug)
 #else
+  if (pushed_by_player)
+#endif
 
-  if (!IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty))
   {
-    int hitting_element = Feld[newx][newy];
-
-    /* !!! fix side (direction) orientation here and elsewhere !!! */
-    CheckElementSideChange(newx, newy, hitting_element,
-                          direction, CE_HITTING_SOMETHING, -1);
-
-#if 0
-    if (IN_LEV_FIELD(nextx, nexty))
-    {
-      int opposite_direction = MV_DIR_OPPOSITE(direction);
-      int hitting_side = direction;
-      int touched_side = opposite_direction;
-      int touched_element = MovingOrBlocked2Element(nextx, nexty);
-      boolean object_hit = (!IS_MOVING(nextx, nexty) ||
-                           MovDir[nextx][nexty] != direction ||
-                           ABS(MovPos[nextx][nexty]) <= TILEY / 2);
-
-      if (object_hit)
-      {
-       int i;
-
-       CheckElementSideChange(nextx, nexty, touched_element,
-                              opposite_direction, CE_HIT_BY_SOMETHING, -1);
-
-       if (IS_CUSTOM_ELEMENT(hitting_element) &&
-           HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_HITTING))
-       {
-         for (i = 0; i < element_info[hitting_element].num_change_pages; i++)
-         {
-           struct ElementChangeInfo *change =
-             &element_info[hitting_element].change_page[i];
-
-           if (change->can_change &&
-               change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) &&
-               change->sides & touched_side &&
-               change->trigger_element == touched_element)
-           {
-             CheckElementSideChange(newx, newy, hitting_element,
-                                    CH_SIDE_ANY, CE_OTHER_IS_HITTING, i);
-             break;
-           }
-         }
-       }
-
-       if (IS_CUSTOM_ELEMENT(touched_element) &&
-           HAS_ANY_CHANGE_EVENT(touched_element, CE_OTHER_GETS_HIT))
-       {
-         for (i = 0; i < element_info[touched_element].num_change_pages; i++)
-         {
-           struct ElementChangeInfo *change =
-             &element_info[touched_element].change_page[i];
+    int dig_side = MV_DIR_OPPOSITE(direction);
+    struct PlayerInfo *player = PLAYERINFO(x, y);
 
-           if (change->can_change &&
-               change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) &&
-               change->sides & hitting_side &&
-               change->trigger_element == hitting_element)
-           {
-             CheckElementSideChange(nextx, nexty, touched_element,
-                                    CH_SIDE_ANY, CE_OTHER_GETS_HIT, i);
-             break;
-           }
-         }
-       }
-      }
-    }
-#endif
+    CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
+                              player->index_bit, dig_side);
+    CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
+                                       player->index_bit, dig_side);
   }
-#endif
 
+  TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
+
+  TestIfElementHitsCustomElement(newx, newy, direction);
   TestIfPlayerTouchesCustomElement(newx, newy);
   TestIfElementTouchesCustomElement(newx, newy);
 }
@@ -5569,7 +5639,7 @@ int AmoebeNachbarNr(int ax, int ay)
     { 0, +1 }
   };
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int x = ax + xy[i][0];
     int y = ay + xy[i][1];
@@ -5599,7 +5669,7 @@ void AmoebenVereinigen(int ax, int ay)
   if (new_group_nr == 0)
     return;
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     x = ax + xy[i][0];
     y = ay + xy[i][1];
@@ -5677,7 +5747,7 @@ void AmoebeUmwandeln(int ax, int ay)
       { 0, +1 }
     };
 
-    for (i = 0; i < 4; i++)
+    for (i = 0; i < NUM_DIRECTIONS; i++)
     {
       x = ax + xy[i][0];
       y = ay + xy[i][1];
@@ -5746,14 +5816,7 @@ void AmoebeWaechst(int x, int y)
 
     if (DelayReached(&sound_delay, sound_delay_value))
     {
-#if 1
       PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
-#else
-      if (Store[x][y] == EL_BD_AMOEBA)
-       PlayLevelSound(x, y, SND_BD_AMOEBA_GROWING);
-      else
-       PlayLevelSound(x, y, SND_AMOEBA_GROWING);
-#endif
       sound_delay_value = 30;
     }
   }
@@ -5857,9 +5920,9 @@ void AmoebeAbleger(int ax, int ay)
     if (!IN_LEV_FIELD(x, y))
       return;
 
-    /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
     if (IS_FREE(x, y) ||
-       Feld[x][y] == EL_SAND || Feld[x][y] == EL_QUICKSAND_EMPTY)
+       CAN_GROW_INTO(Feld[x][y]) ||
+       Feld[x][y] == EL_QUICKSAND_EMPTY)
     {
       newax = x;
       neway = y;
@@ -5873,7 +5936,7 @@ void AmoebeAbleger(int ax, int ay)
     int start = RND(4);
     boolean waiting_for_player = FALSE;
 
-    for (i = 0; i < 4; i++)
+    for (i = 0; i < NUM_DIRECTIONS; i++)
     {
       int j = (start + i) % 4;
       int x = ax + xy[j][0];
@@ -5882,9 +5945,9 @@ void AmoebeAbleger(int ax, int ay)
       if (!IN_LEV_FIELD(x, y))
        continue;
 
-      /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
       if (IS_FREE(x, y) ||
-         Feld[x][y] == EL_SAND || Feld[x][y] == EL_QUICKSAND_EMPTY)
+         CAN_GROW_INTO(Feld[x][y]) ||
+         Feld[x][y] == EL_QUICKSAND_EMPTY)
       {
        newax = x;
        neway = y;
@@ -5896,7 +5959,7 @@ void AmoebeAbleger(int ax, int ay)
 
     if (newax == ax && neway == ay)            /* amoeba cannot grow */
     {
-      if (i == 4 && (!waiting_for_player || game.emulation == EMU_BOULDERDASH))
+      if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
       {
        Feld[ax][ay] = EL_AMOEBA_DEAD;
        DrawLevelField(ax, ay);
@@ -5951,11 +6014,8 @@ void AmoebeAbleger(int ax, int ay)
   else if (neway == ay)
   {
     Feld[newax][neway] = EL_AMOEBA_DROP;       /* drop left/right of amoeba */
-#if 1
+
     PlayLevelSoundAction(newax, neway, ACTION_GROWING);
-#else
-    PlayLevelSound(newax, neway, SND_AMOEBA_GROWING);
-#endif
   }
   else
   {
@@ -6027,8 +6087,7 @@ void Life(int ax, int ay)
        changed = TRUE;
       }
     }
-    /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
-    else if (IS_FREE(xx, yy) || Feld[xx][yy] == EL_SAND)
+    else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
     {                                  /* free border field */
       if (nachbarn >= life[2] && nachbarn <= life[3])
       {
@@ -6065,7 +6124,7 @@ static void StopRobotWheel(int x, int y)
 
 static void InitTimegateWheel(int x, int y)
 {
-  ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+  ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
 }
 
 static void RunTimegateWheel(int x, int y)
@@ -6130,11 +6189,8 @@ static void CloseAllOpenTimegates()
       if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
       {
        Feld[x][y] = EL_TIMEGATE_CLOSING;
-#if 1
+
        PlayLevelSoundAction(x, y, ACTION_CLOSING);
-#else
-       PlayLevelSound(x, y, SND_TIMEGATE_CLOSING);
-#endif
       }
     }
   }
@@ -6336,11 +6392,7 @@ void MauerAbleger(int ax, int ay)
     Feld[ax][ay] = EL_WALL;
 
   if (new_wall)
-#if 1
     PlayLevelSoundAction(ax, ay, ACTION_GROWING);
-#else
-    PlayLevelSound(ax, ay, SND_EXPANDABLE_WALL_GROWING);
-#endif
 }
 
 void CheckForDragon(int x, int y)
@@ -6355,11 +6407,11 @@ void CheckForDragon(int x, int y)
     { 0, +1 }
   };
 
-  for (i = 0; i < 4; i++)
+  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];
+      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))
@@ -6374,11 +6426,11 @@ void CheckForDragon(int x, int y)
 
   if (!dragon_found)
   {
-    for (i = 0; i < 4; i++)
+    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];
+       int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
   
        if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
        {
@@ -6417,7 +6469,7 @@ static void WarnBuggyBase(int x, int y)
     { 0, +1 }
   };
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int xx = x + xy[i][0], yy = y + xy[i][1];
 
@@ -6449,71 +6501,393 @@ static void ChangeActiveTrap(int x, int y)
     DrawLevelFieldCrumbledSand(x, y);
 }
 
-static void ChangeElementNowExt(int x, int y, int target_element)
+static int getSpecialActionElement(int element, int number, int base_element)
 {
-  int previous_move_direction = MovDir[x][y];
+  return (element != EL_EMPTY ? element :
+         number != -1 ? base_element + number - 1 :
+         EL_EMPTY);
+}
 
-  /* 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;
-  }
+static int getModifiedActionNumber(int value_old, int value_min, int value_max,
+                                  int operator, int operand)
+{
+  int value_new = (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_SET      ? operand :
+                  value_old);
 
-  RemoveField(x, y);
-  Feld[x][y] = target_element;
+  return (value_new < value_min ? value_min :
+         value_new > value_max ? value_max :
+         value_new);
+}
 
-  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+static void ExecuteCustomElementAction(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;
 
-  ResetGfxAnimation(x, y);
-  ResetRandomAnimationValue(x, y);
+  if (!change->has_action)
+    return;
 
-  if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
-    MovDir[x][y] = previous_move_direction;
+  /* ---------- determine action paramater values ---------- */
+
+  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);
+
+  int action_arg_number =
+    (action_arg <= CA_ARG_MAX ? action_arg :
+     action_arg == CA_ARG_NUMBER_MIN ? CA_ARG_MIN :
+     action_arg == CA_ARG_NUMBER_MAX ? CA_ARG_MAX :
+     action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
+     action_arg == CA_ARG_NUMBER_CE_COUNT ? ei->collect_count :
+     action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CHANGE_DELAY(change) :
+     -1);
+
+  /* (for explicit player choice, set invalid value to "no player") */
+  int action_arg_player_bits =
+    (action_arg == CA_ARG_PLAYER_ANY ? action_arg - CA_ARG_PLAYER :
+     action_arg >= CA_ARG_PLAYER_1 &&
+     action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
+     action_arg >= CA_ARG_1 &&
+     action_arg <= CA_ARG_PLAYER_4 ? (1 << (action_arg - 1)) :
+     action_arg_element >= EL_PLAYER_1 &&
+     action_arg_element <= EL_PLAYER_4 ?
+     (1 << (action_arg_element - EL_PLAYER_1)) :
+     0);
+
+  /* (for implicit player choice, set invalid value to "all players") */
+  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);
+
+  /* ---------- execute action  ---------- */
+
+  switch(action_type)
+  {
+    case CA_NO_ACTION:
+    {
+      return;
+    }
 
-#if 1
-  InitField_WithBug1(x, y, FALSE);
-#else
-  InitField(x, y, FALSE);
-  if (CAN_MOVE(Feld[x][y]))
-    InitMovDir(x, y);
+    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;
+
+      break;
+    }
+
+    case CA_KILL_PLAYER:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (action_arg_player_bits & (1 << i))
+         KillHero(&stored_player[i]);
+
+      break;
+    }
+
+    case CA_RESTART_LEVEL:
+    {
+      game.restart_level = TRUE;
+
+      break;
+    }
+
+    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_ADD_KEY:
+    {
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_KEY_1);
+
+      if (IS_KEY(element))
+      {
+       for (i = 0; i < MAX_PLAYERS; i++)
+       {
+         if (trigger_player_bits & (1 << i))
+         {
+           stored_player[i].key[KEY_NR(element)] = TRUE;
+
+           DrawGameValue_Keys(stored_player[i].key);
+
+           redraw_mask |= REDRAW_DOOR_1;
+         }
+       }
+      }
+
+      break;
+    }
+
+    case CA_DEL_KEY:
+    {
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_KEY_1);
+
+      if (IS_KEY(element))
+      {
+       for (i = 0; i < MAX_PLAYERS; i++)
+       {
+         if (trigger_player_bits & (1 << i))
+         {
+           stored_player[i].key[KEY_NR(element)] = FALSE;
+
+           DrawGameValue_Keys(stored_player[i].key);
+
+           redraw_mask |= REDRAW_DOOR_1;
+         }
+       }
+      }
+
+      break;
+    }
+
+    case CA_SET_PLAYER_SPEED:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (trigger_player_bits & (1 << i))
+       {
+         if (action_arg == CA_ARG_NUMBER_RESET)
+           stored_player[i].move_delay_value = game.initial_move_delay_value;
+         else if (action_arg == CA_ARG_NUMBER_NORMAL)
+           stored_player[i].move_delay_value = MOVE_DELAY_NORMAL_SPEED;
+         else if (action_arg == CA_ARG_NUMBER_MIN)
+           stored_player[i].move_delay_value = 16;
+         else if (action_arg == CA_ARG_NUMBER_MAX)
+           stored_player[i].move_delay_value = MOVE_DELAY_HIGH_SPEED;
+         else
+         {
+#if 0
+           if (action_mode == CA_MODE_ADD)
+           {
+             action_mode = CA_MODE_DIVIDE;
+             action_arg_number = (1 << action_arg_number);
+           }
+           else if (action_mode == CA_MODE_SUBTRACT)
+           {
+             action_mode = CA_MODE_MULTIPLY;
+             action_arg_number = (1 << action_arg_number);
+           }
+
+           int mode = (action_mode == CA_MODE_MULTIPLY ? CA_MODE_DIVIDE :
+                       action_mode == CA_MODE_DIVIDE   ? CA_MODE_MULTIPLY :
+                       action_mode);
+
+           stored_player[i].move_delay_value =
+             getModifiedActionNumber(stored_player[i].move_delay_value,
+                                     1, 16,
+                                     action_mode, action_arg_number);
 #endif
+         }
+       }
+      }
 
-  DrawLevelField(x, y);
+      break;
+    }
 
-  if (GFX_CRUMBLED(Feld[x][y]))
-    DrawLevelFieldCrumbledSandNeighbours(x, y);
+    case CA_SET_GEMS:
+    {
+      local_player->gems_still_needed =
+       getModifiedActionNumber(local_player->gems_still_needed, 0, 999,
+                               action_mode, action_arg_number);
 
-  TestIfBadThingTouchesHero(x, y);
-  TestIfPlayerTouchesCustomElement(x, y);
-  TestIfElementTouchesCustomElement(x, y);
+      DrawGameValue_Emeralds(local_player->gems_still_needed);
+
+      break;
+    }
+
+    case CA_SET_TIME:
+    {
+      if (level.time > 0)      /* only modify limited time value */
+      {
+       TimeLeft = getModifiedActionNumber(TimeLeft, 0, 9999,
+                                          action_mode, action_arg_number);
+
+       DrawGameValue_Time(TimeLeft);
+      }
+
+      break;
+    }
+
+    case CA_SET_SCORE:
+    {
+      local_player->score =
+       getModifiedActionNumber(local_player->score, 0, 99999,
+                               action_mode, action_arg_number);
+
+      DrawGameValue_Score(local_player->score);
+
+      break;
+    }
+
+    case CA_SET_CE_SCORE:
+    {
+      printf("::: CA_SET_CE_SCORE -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_SET_CE_COUNT:
+    {
+      printf("::: CA_SET_CE_COUNT -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_SET_DYNABOMB_NUMBER:
+    {
+      printf("::: CA_SET_DYNABOMB_NUMBER -- not yet implemented\n");
 
+      break;
+    }
+
+    case CA_SET_DYNABOMB_SIZE:
+    {
+      printf("::: CA_SET_DYNABOMB_SIZE -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_SET_DYNABOMB_POWER:
+    {
+      printf("::: CA_SET_DYNABOMB_POWER -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_TOGGLE_PLAYER_GRAVITY:
+    {
+      game.gravity = !game.gravity;
+
+      break;
+    }
+
+    case CA_ENABLE_PLAYER_GRAVITY:
+    {
+      game.gravity = TRUE;
+
+      break;
+    }
+
+    case CA_DISABLE_PLAYER_GRAVITY:
+    {
+      game.gravity = FALSE;
+
+      break;
+    }
+
+    default:
+      break;
+  }
+}
+
+static void ChangeElementNowExt(struct ElementChangeInfo *change,
+                               int x, int y, int target_element)
+{
+  int previous_move_direction = MovDir[x][y];
+  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+                       IS_WALKABLE(Feld[x][y]));
+
+  /* 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 (!add_player)
+  {
+    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+      RemoveMovingField(x, y);
+    else
+      RemoveField(x, y);
+
+    Feld[x][y] = target_element;
+
+    ResetGfxAnimation(x, y);
+    ResetRandomAnimationValue(x, y);
+
+    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = previous_move_direction;
+
+    InitField_WithBug1(x, y, FALSE);
+
+    DrawLevelField(x, y);
+
+    if (GFX_CRUMBLED(Feld[x][y]))
+      DrawLevelFieldCrumbledSandNeighbours(x, y);
+  }
+
+  /* "Changed[][]" not set yet to allow "entered by player" change one time */
   if (ELEM_IS_PLAYER(target_element))
     RelocatePlayer(x, y, target_element);
+
+#if 1
+  Changed[x][y] = TRUE;                /* ignore all further changes in this frame */
+#else
+  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+#endif
+
+  TestIfBadThingTouchesHero(x, y);
+  TestIfPlayerTouchesCustomElement(x, y);
+  TestIfElementTouchesCustomElement(x, y);
 }
 
 static boolean ChangeElementNow(int x, int y, int element, int page)
 {
   struct ElementChangeInfo *change = &element_info[element].change_page[page];
+  int target_element;
+  int old_element = Feld[x][y];
 
   /* always use default change event to prevent running into a loop */
-  if (ChangeEvent[x][y] == CE_BITMASK_DEFAULT)
-    ChangeEvent[x][y] = CH_EVENT_BIT(CE_DELAY);
+  if (ChangeEvent[x][y] == -1)
+    ChangeEvent[x][y] = CE_DELAY;
 
-  /* do not change already changed elements with same change event */
-#if 0
-  if (Changed[x][y] & ChangeEvent[x][y])
+  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_PLAYER_1;
+  }
+
+#if 1
+  /* do not change any elements that have already changed in this frame */
+  if (Changed[x][y])
     return FALSE;
 #else
-  if (Changed[x][y])
+  /* do not change already changed elements with same change event */
+  if (Changed[x][y] & ChangeEvent[x][y])
     return FALSE;
 #endif
 
+#if 1
+  Changed[x][y] = TRUE;                /* ignore all further changes in this frame */
+#else
   Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
-
-  CheckTriggeredElementChange(x, y, Feld[x][y], CE_OTHER_IS_CHANGING);
+#endif
 
   if (change->explode)
   {
@@ -6522,35 +6896,41 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
     return TRUE;
   }
 
-  if (change->use_content)
+  if (change->use_target_content)
   {
-    boolean complete_change = TRUE;
-    boolean can_change[3][3];
+    boolean complete_replace = TRUE;
+    boolean can_replace[3][3];
     int xx, yy;
 
     for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
     {
-      boolean half_destructible;
+      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[xx][yy];
       int e;
 
-      can_change[xx][yy] = TRUE;
+      can_replace[xx][yy] = TRUE;
 
       if (ex == x && ey == y)  /* do not check changing element itself */
        continue;
 
-      if (change->content[xx][yy] == EL_EMPTY_SPACE)
+      if (content_element == EL_EMPTY_SPACE)
       {
-       can_change[xx][yy] = FALSE;     /* do not change empty borders */
+       can_replace[xx][yy] = FALSE;    /* do not replace border with space */
 
        continue;
       }
 
       if (!IN_LEV_FIELD(ex, ey))
       {
-       can_change[xx][yy] = FALSE;
-       complete_change = FALSE;
+       can_replace[xx][yy] = FALSE;
+       complete_replace = FALSE;
 
        continue;
       }
@@ -6560,39 +6940,54 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
       if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
        e = MovingOrBlocked2Element(ex, ey);
 
-      half_destructible = (IS_FREE(ex, ey) || IS_DIGGABLE(e));
+      is_empty = (IS_FREE(ex, ey) ||
+                 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
 
-      if ((change->power <= CP_NON_DESTRUCTIVE  && !IS_FREE(ex, ey)) ||
-         (change->power <= CP_HALF_DESTRUCTIVE && !half_destructible) ||
-         (change->power <= CP_FULL_DESTRUCTIVE && IS_INDESTRUCTIBLE(e)))
-      {
-       can_change[xx][yy] = FALSE;
-       complete_change = FALSE;
-      }
+      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;
     }
 
-    if (!change->only_complete || complete_change)
+    if (!change->only_if_complete || complete_replace)
     {
       boolean something_has_changed = FALSE;
 
-      if (change->only_complete && change->use_random_change &&
-         RND(100) < change->random)
+      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++)
       {
        int ex = x + xx - 1;
        int ey = y + yy - 1;
+       int content_element;
 
-       if (can_change[xx][yy] && (!change->use_random_change ||
-                                  RND(100) < change->random))
+       if (can_replace[xx][yy] && (!change->use_random_replace ||
+                                   RND(100) < change->random_percentage))
        {
          if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
            RemoveMovingField(ex, ey);
 
          ChangeEvent[ex][ey] = ChangeEvent[x][y];
 
-         ChangeElementNowExt(ex, ey, change->content[xx][yy]);
+         content_element = change->target_content[xx][yy];
+         target_element = GET_TARGET_ELEMENT(content_element, change);
+
+         ChangeElementNowExt(change, ex, ey, target_element);
 
          something_has_changed = TRUE;
 
@@ -6608,11 +7003,16 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
   }
   else
   {
-    ChangeElementNowExt(x, y, change->target_element);
+    target_element = GET_TARGET_ELEMENT(change->target_element, change);
+
+    ChangeElementNowExt(change, x, y, target_element);
 
     PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
   }
 
+  /* this uses direct change before indirect change */
+  CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
+
   return TRUE;
 }
 
@@ -6622,9 +7022,8 @@ static void ChangeElement(int x, int y, int page)
   struct ElementInfo *ei = &element_info[element];
   struct ElementChangeInfo *change = &ei->change_page[page];
 
-#if 0
 #ifdef DEBUG
-  if (!CAN_CHANGE(element))
+  if (!CAN_CHANGE(element) && !CAN_CHANGE(Back[x][y]))
   {
     printf("\n\n");
     printf("ChangeElement(): %d,%d: element = %d ('%s')\n",
@@ -6633,12 +7032,21 @@ static void ChangeElement(int x, int y, int page)
     printf("\n\n");
   }
 #endif
+
+  /* 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
 
+    return;
+  }
+
   if (ChangeDelay[x][y] == 0)          /* initialize element change */
   {
-    ChangeDelay[x][y] = (    change->delay_fixed  * change->delay_frames +
-                        RND(change->delay_random * change->delay_frames)) + 1;
+    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
 
     ResetGfxAnimation(x, y);
     ResetRandomAnimationValue(x, y);
@@ -6665,13 +7073,11 @@ static void ChangeElement(int x, int y, int page)
     {
       page = ChangePage[x][y];
       ChangePage[x][y] = -1;
+
+      change = &ei->change_page[page];
     }
 
-#if 0
-    if (IS_MOVING(x, y) && !change->explode)
-#else
     if (IS_MOVING(x, y))               /* never change a running system ;-) */
-#endif
     {
       ChangeDelay[x][y] = 1;           /* try change after next move step */
       ChangePage[x][y] = page;         /* remember page to use for change */
@@ -6687,70 +7093,59 @@ static void ChangeElement(int x, int y, int page)
   }
 }
 
-static boolean CheckTriggeredElementSideChange(int lx, int ly,
-                                              int trigger_element,
-                                              int trigger_side,
-                                              int trigger_event)
+static boolean CheckTriggeredElementChangeExt(int lx, int ly,
+                                             int trigger_element,
+                                             int trigger_event,
+                                             int trigger_player,
+                                             int trigger_side,
+                                             int trigger_page)
 {
   int i, j, x, y;
+  int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
 
-  if (!(trigger_events[trigger_element] & CH_EVENT_BIT(trigger_event)))
+  if (!(trigger_events[trigger_element][trigger_event]))
     return FALSE;
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
+    boolean change_found = FALSE;
 
-    boolean change_element = FALSE;
-    int page = 0;
-
-    if (!CAN_CHANGE(element) || !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+    if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+       !HAS_ANY_CHANGE_EVENT(element, trigger_event))
       continue;
 
     for (j = 0; j < element_info[element].num_change_pages; j++)
     {
       struct ElementChangeInfo *change = &element_info[element].change_page[j];
 
-      if (change->can_change &&
-#if 1
-         change->events & CH_EVENT_BIT(trigger_event) &&
-#endif
-         change->sides & trigger_side &&
-#if 1
-         IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)
-#else
-         change->trigger_element == trigger_element
-#endif
-         )
+      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 0
-       if (!(change->events & CH_EVENT_BIT(trigger_event)))
-         printf("::: !!! %d triggers %d: using wrong page %d [event %d]\n",
-                trigger_element-EL_CUSTOM_START+1, i+1, j, trigger_event);
-#endif
-
-       change_element = TRUE;
-       page = j;
-
-       break;
-      }
-    }
+       change->actual_trigger_element = trigger_element;
+       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
 
-    if (!change_element)
-      continue;
+       if (change->can_change && !change_found)
+       {
+         change_found = TRUE;
 
-    for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-    {
-#if 0
-      if (x == lx && y == ly)  /* do not change trigger element itself */
-       continue;
-#endif
+         for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+         {
+           if (Feld[x][y] == element)
+           {
+             ChangeDelay[x][y] = 1;
+             ChangeEvent[x][y] = trigger_event;
+             ChangeElement(x, y, j);
+           }
+         }
+       }
 
-      if (Feld[x][y] == element)
-      {
-       ChangeDelay[x][y] = 1;
-       ChangeEvent[x][y] = CH_EVENT_BIT(trigger_event);
-       ChangeElement(x, y, page);
+       if (change->has_action)
+         ExecuteCustomElementAction(element, j);
       }
     }
   }
@@ -6758,15 +7153,13 @@ static boolean CheckTriggeredElementSideChange(int lx, int ly,
   return TRUE;
 }
 
-static boolean CheckTriggeredElementChange(int lx, int ly, int trigger_element,
-                                          int trigger_event)
-{
-  return CheckTriggeredElementSideChange(lx, ly, trigger_element, CH_SIDE_ANY,
-                                        trigger_event);
-}
-
-static boolean CheckElementSideChange(int x, int y, int element, int side,
-                                     int trigger_event, int page)
+static boolean CheckElementChangeExt(int x, int y,
+                                    int element,
+                                    int trigger_element,
+                                    int trigger_event,
+                                    int trigger_player,
+                                    int trigger_side,
+                                    int trigger_page)
 {
   if (!CAN_CHANGE(element) || !HAS_ANY_CHANGE_EVENT(element, trigger_event))
     return FALSE;
@@ -6777,8 +7170,10 @@ static boolean CheckElementSideChange(int x, int y, int element, int side,
     element = Feld[x][y];
   }
 
-#if 1
-  if (page < 0)
+  if (Feld[x][y] != element)   /* check if element has already changed */
+    return FALSE;
+
+  if (trigger_page < 0)
   {
     boolean change_element = FALSE;
     int i;
@@ -6787,12 +7182,23 @@ static boolean CheckElementSideChange(int x, int y, int element, int side,
     {
       struct ElementChangeInfo *change = &element_info[element].change_page[i];
 
+      boolean check_trigger_element =
+       (trigger_event == CE_TOUCHING_X ||
+        trigger_event == CE_HITTING_X ||
+        trigger_event == CE_HIT_BY_X);
+
       if (change->can_change &&
-         change->events & CH_EVENT_BIT(trigger_event) &&
-         change->sides & side)
+         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_element = TRUE;
-       page = i;
+       trigger_page = i;
+
+       change->actual_trigger_element = trigger_element;
+       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
 
        break;
       }
@@ -6801,30 +7207,22 @@ static boolean CheckElementSideChange(int x, int y, int element, int side,
     if (!change_element)
       return FALSE;
   }
+  else
+  {
+    struct ElementInfo *ei = &element_info[element];
+    struct ElementChangeInfo *change = &ei->change_page[trigger_page];
 
-#else
-
-  /* !!! this check misses pages with same event, but different side !!! */
-
-  if (page < 0)
-    page = element_info[element].event_page_nr[trigger_event];
-
-  if (!(element_info[element].change_page[page].sides & side))
-    return FALSE;
-#endif
+    change->actual_trigger_element = trigger_element;
+    change->actual_trigger_player = EL_PLAYER_1;       /* unused */
+  }
 
   ChangeDelay[x][y] = 1;
-  ChangeEvent[x][y] = CH_EVENT_BIT(trigger_event);
-  ChangeElement(x, y, page);
+  ChangeEvent[x][y] = trigger_event;
+  ChangeElement(x, y, trigger_page);
 
   return TRUE;
 }
 
-static boolean CheckElementChange(int x, int y, int element, int trigger_event)
-{
-  return CheckElementSideChange(x, y, element, CH_SIDE_ANY, trigger_event, -1);
-}
-
 static void PlayPlayerSound(struct PlayerInfo *player)
 {
   int jx = player->jx, jy = player->jy;
@@ -6987,13 +7385,8 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
   }
 }
 
-#if 1
 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 {
-#if 0
-  static byte stored_player_action[MAX_PLAYERS];
-  static int num_stored_actions = 0;
-#endif
   boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
   int left     = player_action & JOY_LEFT;
   int right    = player_action & JOY_RIGHT;
@@ -7004,29 +7397,11 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
   int dx       = (left ? -1    : right ? 1     : 0);
   int dy       = (up   ? -1    : down  ? 1     : 0);
 
-#if 0
-  stored_player_action[player->index_nr] = 0;
-  num_stored_actions++;
-#endif
-
-#if 0
-  printf("::: player %d [%d]\n", player->index_nr, FrameCounter);
-#endif
-
   if (!player->active || tape.pausing)
     return 0;
 
-#if 0
-  printf("::: [%d %d %d %d] [%d %d]\n",
-        left, right, up, down, button1, button2);
-#endif
-
   if (player_action)
   {
-#if 0
-    printf("::: player %d acts [%d]\n", player->index_nr, FrameCounter);
-#endif
-
     if (button1)
       snapped = SnapField(player, dx, dy);
     else
@@ -7048,23 +7423,15 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 
     SetPlayerWaiting(player, FALSE);
 
-#if 1
     return player_action;
-#else
-    stored_player_action[player->index_nr] = player_action;
-#endif
   }
   else
   {
-#if 0
-    printf("::: player %d waits [%d]\n", player->index_nr, FrameCounter);
-#endif
-
     /* 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);
-    CheckGravityMovement(player);
+    CheckGravityMovementWhenNotMoving(player);
 
     if (player->MovPos == 0)
       SetPlayerWaiting(player, TRUE);
@@ -7076,155 +7443,98 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 
     return 0;
   }
-
-#if 0
-  if (tape.recording && num_stored_actions >= MAX_PLAYERS)
-  {
-    printf("::: player %d recorded [%d]\n", player->index_nr, FrameCounter);
-
-    TapeRecordAction(stored_player_action);
-    num_stored_actions = 0;
-  }
-#endif
 }
 
-#else
-
-static void PlayerActions(struct PlayerInfo *player, byte player_action)
+void AdvanceFrameAndPlayerCounters(int player_nr)
 {
-  static byte stored_player_action[MAX_PLAYERS];
-  static int num_stored_actions = 0;
-  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);
-
-  stored_player_action[player->index_nr] = 0;
-  num_stored_actions++;
-
-  printf("::: player %d [%d]\n", player->index_nr, FrameCounter);
-
-  if (!player->active || tape.pausing)
-    return;
-
-  if (player_action)
-  {
-    printf("::: player %d acts [%d]\n", player->index_nr, FrameCounter);
-
-    if (button1)
-      snapped = SnapField(player, dx, dy);
-    else
-    {
-      if (button2)
-       dropped = DropElement(player);
-
-      moved = MovePlayer(player, dx, dy);
-    }
+  int i;
 
-    if (tape.single_step && tape.recording && !tape.pausing)
-    {
-      if (button1 || (dropped && !moved))
-      {
-       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
-       SnapField(player, 0, 0);                /* stop snapping */
-      }
-    }
+  /* advance frame counters (global frame counter and time frame counter) */
+  FrameCounter++;
+  TimeFrames++;
 
-    stored_player_action[player->index_nr] = player_action;
-  }
-  else
+  /* advance player counters (counters for move delay, move animation etc.) */
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    printf("::: player %d waits [%d]\n", player->index_nr, FrameCounter);
+    boolean advance_player_counters = (player_nr == -1 || player_nr == i);
+    int move_frames =
+      MOVE_DELAY_NORMAL_SPEED /  stored_player[i].move_delay_value;
 
-    /* no actions for this player (no input at player's configured device) */
+    if (!advance_player_counters)      /* not all players may be affected */
+      continue;
 
-    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
-    SnapField(player, 0, 0);
-    CheckGravityMovement(player);
+    stored_player[i].Frame += move_frames;
 
-    if (player->MovPos == 0)
-      InitPlayerGfxAnimation(player, ACTION_DEFAULT, player->MovDir);
+    if (stored_player[i].MovPos != 0)
+      stored_player[i].StepFrame += move_frames;
 
-    if (player->MovPos == 0)   /* needed for tape.playing */
-      player->is_moving = FALSE;
-  }
+#if USE_NEW_MOVE_DELAY
+    if (stored_player[i].move_delay > 0)
+      stored_player[i].move_delay--;
+#endif
 
-  if (tape.recording && num_stored_actions >= MAX_PLAYERS)
-  {
-    printf("::: player %d recorded [%d]\n", player->index_nr, FrameCounter);
+#if USE_NEW_PUSH_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++;
+#endif
 
-    TapeRecordAction(stored_player_action);
-    num_stored_actions = 0;
+    if (stored_player[i].drop_delay > 0)
+      stored_player[i].drop_delay--;
   }
 }
-#endif
 
 void GameActions()
 {
-  static unsigned long action_delay = 0;
-  unsigned long action_delay_value;
+  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;
-#if 1
   byte tape_action[MAX_PLAYERS];
-#endif
 
   if (game_status != GAME_MODE_PLAYING)
     return;
 
-  action_delay_value =
+  game_frame_delay_value =
     (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
 
-  if (tape.playing && tape.index_search && !tape.pausing)
-    action_delay_value = 0;
+  if (tape.playing && tape.warp_forward && !tape.pausing)
+    game_frame_delay_value = 0;
 
   /* ---------- main game synchronization point ---------- */
 
-  WaitUntilDelayReached(&action_delay, action_delay_value);
+  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
 
   if (network_playing && !network_player_action_received)
   {
-    /*
-#ifdef DEBUG
-    printf("DEBUG: try to get network player actions in time\n");
-#endif
-    */
+    /* try to get network player actions in time */
 
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
     /* last chance to get network player actions without main loop delay */
     HandleNetworking();
 #endif
 
+    /* game was quit by network peer */
     if (game_status != GAME_MODE_PLAYING)
       return;
 
     if (!network_player_action_received)
-    {
-      /*
-#ifdef DEBUG
-      printf("DEBUG: failed to get network player actions in time\n");
-#endif
-      */
-      return;
-    }
+      return;          /* failed to get network player actions in time */
   }
 
   if (tape.pausing)
     return;
 
-#if 0
-  printf("::: getting new tape action [%d]\n", FrameCounter);
-#endif
-
   recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
 
+#if 1
+  /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
+  if (recorded_player_action == NULL && tape.pausing)
+    return;
+#endif
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     summarized_player_action |= stored_player[i].action;
@@ -7233,7 +7543,7 @@ void GameActions()
       stored_player[i].effective_action = stored_player[i].action;
   }
 
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
   if (network_playing)
     SendToServer_MovePlayer(summarized_player_action);
 #endif
@@ -7241,43 +7551,58 @@ void GameActions()
   if (!options.network && !setup.team_mode)
     local_player->effective_action = summarized_player_action;
 
+  if (recorded_player_action != NULL)
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action = recorded_player_action[i];
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    tape_action[i] = stored_player[i].effective_action;
+
+    if (tape.recording && tape_action[i] && !tape.player_participates[i])
+      tape.player_participates[i] = TRUE;    /* player just appeared from CE */
+  }
+
+  /* only save actions from input devices, but not programmed actions */
+  if (tape.recording)
+    TapeRecordAction(tape_action);
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     int actual_player_action = stored_player[i].effective_action;
 
+#if 1
+    /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
+       - rnd_equinox_tetrachloride 048
+       - rnd_equinox_tetrachloride_ii 096
+       - rnd_emanuel_schmieg 002
+       - doctor_sloan_ww 001, 020
+    */
+    if (stored_player[i].MovPos == 0)
+      CheckGravityMovement(&stored_player[i]);
+#endif
+
+    /* overwrite programmed action with tape action */
     if (stored_player[i].programmed_action)
       actual_player_action = stored_player[i].programmed_action;
 
-    if (recorded_player_action)
-      actual_player_action = recorded_player_action[i];
-
+#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);
   }
 
-#if 1
-  if (tape.recording)
-    TapeRecordAction(tape_action);
-#endif
-
   network_player_action_received = FALSE;
 
   ScrollScreen(NULL, SCROLL_GO_ON);
 
-#if 0
-  FrameCounter++;
-  TimeFrames++;
-
-  for (i = 0; i < MAX_PLAYERS; i++)
-    stored_player[i].Frame++;
-#endif
-
-#if 1
-  /* for downwards compatibility, the following code emulates a fixed bug that
+  /* for backwards compatibility, the following code emulates a fixed bug that
      occured when pushing elements (causing elements that just made their last
      pushing step to already (if possible) make their first falling step in the
      same game frame, which is bad); this code is also needed to use the famous
@@ -7285,11 +7610,7 @@ void GameActions()
      used also in newer levels, but in this case the buggy pushing code is only
      affecting the "spring" element and no other elements */
 
-#if 1
   if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
-#else
-  if (game.engine_version < VERSION_IDENT(2,2,0,7))
-#endif
   {
     for (i = 0; i < MAX_PLAYERS; i++)
     {
@@ -7297,15 +7618,10 @@ void GameActions()
       int x = player->jx;
       int y = player->jy;
 
-#if 1
       if (player->active && player->is_pushing && player->is_moving &&
          IS_MOVING(x, y) &&
          (game.engine_version < VERSION_IDENT(2,2,0,7) ||
           Feld[x][y] == EL_SPRING))
-#else
-      if (player->active && player->is_pushing && player->is_moving &&
-         IS_MOVING(x, y))
-#endif
       {
        ContinueMoving(x, y);
 
@@ -7317,12 +7633,21 @@ void GameActions()
       }
     }
   }
-#endif
 
   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
   {
-    Changed[x][y] = CE_BITMASK_DEFAULT;
-    ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+    Changed[x][y] = FALSE;
+    ChangeEvent[x][y] = -1;
+
+#if USE_NEW_BLOCK_STYLE
+    /* this must be handled before main playfield loop */
+    if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
+    {
+      MovDelay[x][y]--;
+      if (MovDelay[x][y] <= 0)
+       RemoveField(x, y);
+    }
+#endif
 
 #if DEBUG
     if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
@@ -7339,10 +7664,11 @@ void GameActions()
       WasJustMoving[x][y]--;
     if (WasJustFalling[x][y] > 0)
       WasJustFalling[x][y]--;
+    if (CheckCollision[x][y] > 0)
+      CheckCollision[x][y]--;
 
     GfxFrame[x][y]++;
 
-#if 1
     /* reset finished pushing action (not done in ContinueMoving() to allow
        continous pushing animation for elements with zero push delay) */
     if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
@@ -7350,7 +7676,6 @@ void GameActions()
       ResetGfxAnimation(x, y);
       DrawLevelField(x, y);
     }
-#endif
 
 #if DEBUG
     if (IS_BLOCKED(x, y))
@@ -7372,20 +7697,7 @@ void GameActions()
   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
   {
     element = Feld[x][y];
-#if 1
     graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-#else
-    graphic = el2img(element);
-#endif
-
-#if 0
-    if (element == -1)
-    {
-      printf("::: %d,%d: %d [%d]\n", x, y, element, FrameCounter);
-
-      element = graphic = 0;
-    }
-#endif
 
     if (graphic_info[graphic].anim_global_sync)
       GfxFrame[x][y] = FrameCounter;
@@ -7396,9 +7708,7 @@ void GameActions()
 
     SetRandomAnimationValue(x, y);
 
-#if 1
     PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
-#endif
 
     if (IS_INACTIVE(element))
     {
@@ -7408,14 +7718,9 @@ void GameActions()
       continue;
     }
 
-#if 1
     /* this may take place after moving, so 'element' may have changed */
-#if 0
-    if (IS_CHANGING(x, y))
-#else
     if (IS_CHANGING(x, y) &&
        (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
-#endif
     {
 #if 0
       ChangeElement(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] :
@@ -7427,45 +7732,19 @@ void GameActions()
       element = Feld[x][y];
       graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
     }
-#endif
 
     if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
     {
       StartMoving(x, y);
 
-#if 1
       element = Feld[x][y];
       graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-#if 0
-      if (element == EL_MOLE)
-       printf("::: %d, %d, %d [%d]\n",
-              IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y],
-              GfxAction[x][y]);
-#endif
-#if 0
-      if (element == EL_YAMYAM)
-       printf("::: %d, %d, %d\n",
-              IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y]);
-#endif
-#endif
 
       if (IS_ANIMATED(graphic) &&
          !IS_MOVING(x, y) &&
          !Stop[x][y])
-      {
        DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-#if 0
-       if (element == EL_BUG)
-         printf("::: %d, %d\n", graphic, GfxFrame[x][y]);
-#endif
-
-#if 0
-       if (element == EL_MOLE)
-         printf("::: %d, %d\n", graphic, GfxFrame[x][y]);
-#endif
-      }
-
       if (IS_GEM(element) || element == EL_SP_INFOTRON)
        EdelsteinFunkeln(x, y);
     }
@@ -7483,10 +7762,6 @@ void GameActions()
       ContinueMoving(x, y);
     else if (IS_ACTIVE_BOMB(element))
       CheckDynamite(x, y);
-#if 0
-    else if (element == EL_EXPLOSION && !game.explosions_delayed)
-      Explode(x, y, ExplodePhase[x][y], EX_NORMAL);
-#endif
     else if (element == EL_AMOEBA_GROWING)
       AmoebeWaechst(x, y);
     else if (element == EL_AMOEBA_SHRINKING)
@@ -7512,21 +7787,11 @@ void GameActions()
       MauerAbleger(x, y);
     else if (element == EL_FLAMES)
       CheckForDragon(x, y);
-#if 0
-    else if (IS_AUTO_CHANGING(element))
-      ChangeElement(x, y);
-#endif
     else if (element == EL_EXPLOSION)
       ;        /* drawing of correct explosion animation is handled separately */
     else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-#if 0
-    /* this may take place after moving, so 'element' may have changed */
-    if (IS_AUTO_CHANGING(Feld[x][y]))
-      ChangeElement(x, y);
-#endif
-
     if (IS_BELT_ACTIVE(element))
       PlayLevelSoundAction(x, y, ACTION_ACTIVE);
 
@@ -7551,27 +7816,19 @@ void GameActions()
 
 #if USE_NEW_AMOEBA_CODE
   /* new experimental amoeba growth stuff */
-#if 1
   if (!(FrameCounter % 8))
-#endif
   {
     static unsigned long random = 1684108901;
 
     for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
     {
-#if 0
-      x = (random >> 10) % lev_fieldx;
-      y = (random >> 20) % lev_fieldy;
-#else
       x = RND(lev_fieldx);
       y = RND(lev_fieldy);
-#endif
       element = Feld[x][y];
 
-      /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
       if (!IS_PLAYER(x,y) &&
          (element == EL_EMPTY ||
-          element == EL_SAND ||
+          CAN_GROW_INTO(element) ||
           element == EL_QUICKSAND_EMPTY ||
           element == EL_ACID_SPLASH_LEFT ||
           element == EL_ACID_SPLASH_RIGHT))
@@ -7601,9 +7858,9 @@ void GameActions()
       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_NORMAL);
+       Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
 
-      ExplodeField[x][y] = EX_NO_EXPLOSION;
+      ExplodeField[x][y] = EX_TYPE_NONE;
     }
 
     game.explosions_delayed = TRUE;
@@ -7683,7 +7940,7 @@ void GameActions()
   if (TimeFrames >= FRAMES_PER_SECOND)
   {
     TimeFrames = 0;
-    TimePlayed++;
+    TapeTime++;
 
     for (i = 0; i < MAX_PLAYERS; i++)
     {
@@ -7698,24 +7955,29 @@ void GameActions()
       }
     }
 
-    if (tape.recording || tape.playing)
-      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TimePlayed);
-
-    if (TimeLeft > 0)
+    if (!level.use_step_counter)
     {
-      TimeLeft--;
+      TimePlayed++;
 
-      if (TimeLeft <= 10 && setup.time_limit)
-       PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+      if (TimeLeft > 0)
+      {
+       TimeLeft--;
 
-      DrawText(DX_TIME, DY_TIME, int2str(TimeLeft, 3), FONT_TEXT_2);
+       if (TimeLeft <= 10 && setup.time_limit)
+         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
 
-      if (!TimeLeft && setup.time_limit)
-       for (i = 0; i < MAX_PLAYERS; i++)
-         KillHero(&stored_player[i]);
+       DrawGameValue_Time(TimeLeft);
+
+       if (!TimeLeft && setup.time_limit)
+         for (i = 0; i < MAX_PLAYERS; i++)
+           KillHero(&stored_player[i]);
+      }
+      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+       DrawGameValue_Time(TimePlayed);
     }
-    else if (level.time == 0 && !AllPlayersGone) /* level without time limit */
-      DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
+
+    if (tape.recording || tape.playing)
+      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
   }
 
   DrawAllPlayers();
@@ -7740,18 +8002,9 @@ void GameActions()
     redraw_mask |= REDRAW_FPS;
   }
 
-#if 0
-  if (stored_player[0].jx != stored_player[0].last_jx ||
-      stored_player[0].jy != stored_player[0].last_jy)
-    printf("::: %d, %d, %d, %d, %d\n",
-          stored_player[0].MovDir,
-          stored_player[0].MovPos,
-          stored_player[0].GfxPos,
-          stored_player[0].Frame,
-          stored_player[0].StepFrame);
-#endif
-
-#if 1
+#if USE_NEW_MOVE_DELAY
+  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+#else
   FrameCounter++;
   TimeFrames++;
 
@@ -7765,18 +8018,27 @@ void GameActions()
     if (stored_player[i].MovPos != 0)
       stored_player[i].StepFrame += move_frames;
 
+#if USE_NEW_MOVE_DELAY
+    if (stored_player[i].move_delay > 0)
+      stored_player[i].move_delay--;
+#endif
+
     if (stored_player[i].drop_delay > 0)
       stored_player[i].drop_delay--;
   }
 #endif
 
-#if 1
   if (local_player->show_envelope != 0 && local_player->MovPos == 0)
   {
     ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
 
     local_player->show_envelope = 0;
   }
+
+#if USE_NEW_RANDOMIZE
+  /* use random number generator in every frame to make it less predictable */
+  if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+    RND(1);
 #endif
 }
 
@@ -7849,70 +8111,88 @@ void ScrollLevel(int dx, int dy)
   redraw_mask |= REDRAW_FIELD;
 }
 
-static boolean canEnterSupaplexPort(int x, int y, int dx, int dy)
+static boolean canFallDown(struct PlayerInfo *player)
 {
-  int nextx = x + dx, nexty = y + dy;
+  int jx = player->jx, jy = player->jy;
+
+  return (IN_LEV_FIELD(jx, jy + 1) &&
+         (IS_FREE(jx, jy + 1) ||
+#if USE_NEW_BLOCK_STYLE
+#if USE_GRAVITY_BUGFIX_OLD
+          Feld[jx][jy + 1] == EL_PLAYER_IS_LEAVING ||
+#endif
+#endif
+          (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
+         IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
+         !IS_WALKABLE_INSIDE(Feld[jx][jy]));
+}
+
+static boolean canPassField(int x, int y, int move_dir)
+{
+  int opposite_dir = MV_DIR_OPPOSITE(move_dir);
+  int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+  int dy = (move_dir & MV_UP   ? -1 : move_dir & MV_DOWN  ? +1 : 0);
+  int nextx = x + dx;
+  int nexty = y + dy;
   int element = Feld[x][y];
 
-  if ((dx == -1 &&
-       element != EL_SP_PORT_LEFT &&
-       element != EL_SP_GRAVITY_PORT_LEFT &&
-       element != EL_SP_PORT_HORIZONTAL &&
-       element != EL_SP_PORT_ANY) ||
-      (dx == +1 &&
-       element != EL_SP_PORT_RIGHT &&
-       element != EL_SP_GRAVITY_PORT_RIGHT &&
-       element != EL_SP_PORT_HORIZONTAL &&
-       element != EL_SP_PORT_ANY) ||
-      (dy == -1 &&
-       element != EL_SP_PORT_UP &&
-       element != EL_SP_GRAVITY_PORT_UP &&
-       element != EL_SP_PORT_VERTICAL &&
-       element != EL_SP_PORT_ANY) ||
-      (dy == +1 &&
-       element != EL_SP_PORT_DOWN &&
-       element != EL_SP_GRAVITY_PORT_DOWN &&
-       element != EL_SP_PORT_VERTICAL &&
-       element != EL_SP_PORT_ANY) ||
-      !IN_LEV_FIELD(nextx, nexty) ||
-      !IS_FREE(nextx, nexty))
-    return FALSE;
+  return (IS_PASSABLE_FROM(element, opposite_dir) &&
+         !CAN_MOVE(element) &&
+         IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
+         IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
+         (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
+}
 
-  return TRUE;
+static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
+{
+  int opposite_dir = MV_DIR_OPPOSITE(move_dir);
+  int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+  int dy = (move_dir & MV_UP   ? -1 : move_dir & MV_DOWN  ? +1 : 0);
+  int newx = x + dx;
+  int newy = y + dy;
+
+  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+         IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
+         (IS_DIGGABLE(Feld[newx][newy]) ||
+          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+          canPassField(newx, newy, move_dir)));
 }
 
 static void CheckGravityMovement(struct PlayerInfo *player)
 {
   if (game.gravity && !player->programmed_action)
   {
-    int move_dir_vertical = player->action & (MV_UP | MV_DOWN);
-    int move_dir_horizontal = player->action & (MV_LEFT | MV_RIGHT);
-    int move_dir =
-      (player->last_move_dir & (MV_LEFT | MV_RIGHT) ?
-       (move_dir_vertical ? move_dir_vertical : move_dir_horizontal) :
-       (move_dir_horizontal ? move_dir_horizontal : move_dir_vertical));
+    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;
     int jx = player->jx, jy = player->jy;
-    int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
-    int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
-    int new_jx = jx + dx, new_jy = jy + dy;
-    boolean field_under_player_is_free =
-      (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
     boolean player_is_moving_to_valid_field =
-      (IN_LEV_FIELD(new_jx, new_jy) &&
-       (Feld[new_jx][new_jy] == EL_SP_BASE ||
-       Feld[new_jx][new_jy] == EL_SAND ||
-       (IS_SP_PORT(Feld[new_jx][new_jy]) &&
-        canEnterSupaplexPort(new_jx, new_jy, dx, dy))));
-    /* !!! extend EL_SAND to anything diggable !!! */
+      (!player_is_snapping &&
+       (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
+       canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
+    boolean player_can_fall_down = canFallDown(player);
+
+    if (player_can_fall_down &&
+       !player_is_moving_to_valid_field)
+      player->programmed_action = MV_DOWN;
+  }
+}
+
+static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
+{
+  return CheckGravityMovement(player);
 
+  if (game.gravity && !player->programmed_action)
+  {
+    int jx = player->jx, jy = player->jy;
+    boolean field_under_player_is_free =
+      (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
     boolean player_is_standing_on_valid_field =
       (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
        (IS_WALKABLE(Feld[jx][jy]) &&
        !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
 
-    if (field_under_player_is_free &&
-       !player_is_standing_on_valid_field &&
-       !player_is_moving_to_valid_field)
+    if (field_under_player_is_free && !player_is_standing_on_valid_field)
       player->programmed_action = MV_DOWN;
   }
 }
@@ -7927,22 +8207,6 @@ static void CheckGravityMovement(struct PlayerInfo *player)
 boolean MovePlayerOneStep(struct PlayerInfo *player,
                          int dx, int dy, int real_dx, int real_dy)
 {
-#if 0
-  static int change_sides[4][2] =
-  {
-    /* enter side        leave side */
-    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* moving left  */
-    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* moving right */
-    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     },      /* moving up    */
-    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  }       /* moving down  */
-  };
-  int move_direction = (dx == -1 ? MV_LEFT :
-                       dx == +1 ? MV_RIGHT :
-                       dy == -1 ? MV_UP :
-                       dy == +1 ? MV_DOWN : MV_NO_MOVING);
-  int enter_side = change_sides[MV_DIR_BIT(move_direction)][0];
-  int leave_side = change_sides[MV_DIR_BIT(move_direction)][1];
-#endif
   int jx = player->jx, jy = player->jy;
   int new_jx = jx + dx, new_jy = jy + dy;
   int element;
@@ -7962,17 +8226,13 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
   if (!options.network && !AllPlayersInSight(player, new_jx, new_jy))
     return MF_NO_ACTION;
 
-#if 0
-  element = MovingOrBlocked2Element(new_jx, new_jy);
-#else
   element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
-#endif
 
   if (DONT_RUN_INTO(element))
   {
     if (element == EL_ACID && dx == 0 && dy == 1)
     {
-      SplashAcid(jx, jy);
+      SplashAcid(new_jx, new_jy);
       Feld[jx][jy] = EL_PLAYER_1;
       InitMovingField(jx, jy, MV_DOWN);
       Store[jx][jy] = EL_ACID;
@@ -7991,7 +8251,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 
   /* check if DigField() has caused relocation of the player */
   if (player->jx != jx || player->jy != jy)
-    return MF_NO_ACTION;
+    return MF_NO_ACTION;       /* <-- !!! CHECK THIS [-> MF_ACTION ?] !!! */
 
   StorePlayer[jx][jy] = 0;
   player->last_jx = jx;
@@ -8005,30 +8265,10 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 
   player->step_counter++;
 
-  player->drop_delay = 0;
-
   PlayerVisit[jx][jy] = FrameCounter;
 
   ScrollPlayer(player, SCROLL_INIT);
 
-#if 0
-  if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
-  {
-    CheckTriggeredElementSideChange(jx, jy, Feld[jx][jy], leave_side,
-                                   CE_OTHER_GETS_LEFT);
-    CheckElementSideChange(jx, jy, Feld[jx][jy], leave_side,
-                          CE_LEFT_BY_PLAYER, -1);
-  }
-
-  if (IS_CUSTOM_ELEMENT(Feld[new_jx][new_jy]))
-  {
-    CheckTriggeredElementSideChange(new_jx, new_jy, Feld[new_jx][new_jy],
-                                   enter_side, CE_OTHER_GETS_ENTERED);
-    CheckElementSideChange(new_jx, new_jy, Feld[new_jx][new_jy], enter_side,
-                          CE_ENTERED_BY_PLAYER, -1);
-  }
-#endif
-
   return MF_MOVING;
 }
 
@@ -8038,7 +8278,6 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
   int old_jx = jx, old_jy = jy;
   int moved = MF_NO_ACTION;
 
-#if 1
   if (!player->active)
     return FALSE;
 
@@ -8055,21 +8294,23 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
     return FALSE;
   }
-#else
-  if (!player->active || (!dx && !dy))
-    return FALSE;
-#endif
 
-#if 0
-  if (!FrameReached(&player->move_delay, player->move_delay_value) &&
-      !tape.playing)
-    return FALSE;
+#if USE_NEW_MOVE_DELAY
+  if (player->move_delay > 0)
 #else
-  if (!FrameReached(&player->move_delay, player->move_delay_value) &&
-      !(tape.playing && tape.file_version < FILE_VERSION_2_0))
+  if (!FrameReached(&player->move_delay, player->move_delay_value))
+#endif
+  {
     return FALSE;
+  }
+
+#if USE_NEW_MOVE_DELAY
+  player->move_delay = -1;             /* set to "uninitialized" value */
 #endif
 
+  /* store if player is automatically moved to next field */
+  player->is_auto_moving = (player->programmed_action != MV_NO_MOVING);
+
   /* remove the last programmed player action */
   player->programmed_action = 0;
 
@@ -8092,7 +8333,13 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     {
       ScrollPlayer(player, SCROLL_GO_ON);
       ScrollScreen(NULL, SCROLL_GO_ON);
+
+#if USE_NEW_MOVE_DELAY
+      AdvanceFrameAndPlayerCounters(player->index_nr);
+#else
       FrameCounter++;
+#endif
+
       DrawAllPlayers();
       BackToFront();
     }
@@ -8100,7 +8347,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     player->move_delay_value = original_move_delay_value;
   }
 
-  if (player->last_move_dir & (MV_LEFT | MV_RIGHT))
+  if (player->last_move_dir & MV_HORIZONTAL)
   {
     if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
       moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
@@ -8132,7 +8379,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     {
       if (jx != old_jx)                /* player has moved horizontally */
       {
-       if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
+       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);
 
@@ -8144,13 +8391,13 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
        scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
 
        /* don't scroll against the player's moving direction */
-       if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
+       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 */
       {
-       if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + 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);
 
@@ -8162,7 +8409,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
        scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
 
        /* don't scroll against the player's moving direction */
-       if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
+       if ((player->MovDir == MV_UP   && scroll_y > old_scroll_y) ||
            (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
          scroll_y = old_scroll_y;
       }
@@ -8183,15 +8430,6 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     }
   }
 
-#if 0
-#if 1
-  InitPlayerGfxAnimation(player, ACTION_DEFAULT);
-#else
-  if (!(moved & MF_MOVING) && !player->is_pushing)
-    player->Frame = 0;
-#endif
-#endif
-
   player->StepFrame = 0;
 
   if (moved & MF_MOVING)
@@ -8205,64 +8443,35 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
     player->last_move_dir = player->MovDir;
     player->is_moving = TRUE;
-#if 1
     player->is_snapping = FALSE;
-#endif
-
-#if 1
     player->is_switching = FALSE;
-#endif
-
     player->is_dropping = FALSE;
-
-
-#if 1
-    {
-      static int change_sides[4][2] =
-      {
-       /* enter side           leave side */
-       { CH_SIDE_RIGHT,        CH_SIDE_LEFT    },      /* moving left  */
-       { CH_SIDE_LEFT,         CH_SIDE_RIGHT   },      /* moving right */
-       { CH_SIDE_BOTTOM,       CH_SIDE_TOP     },      /* moving up    */
-       { CH_SIDE_TOP,          CH_SIDE_BOTTOM  }       /* moving down  */
-      };
-      int move_direction = player->MovDir;
-      int enter_side = change_sides[MV_DIR_BIT(move_direction)][0];
-      int leave_side = change_sides[MV_DIR_BIT(move_direction)][1];
-
-#if 1
-      if (IS_CUSTOM_ELEMENT(Feld[old_jx][old_jy]))
-      {
-       CheckTriggeredElementSideChange(old_jx, old_jy, Feld[old_jx][old_jy],
-                                       leave_side, CE_OTHER_GETS_LEFT);
-       CheckElementSideChange(old_jx, old_jy, Feld[old_jx][old_jy],
-                              leave_side, CE_LEFT_BY_PLAYER, -1);
-      }
-
-      if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
-      {
-       CheckTriggeredElementSideChange(jx, jy, Feld[jx][jy],
-                                       enter_side, CE_OTHER_GETS_ENTERED);
-       CheckElementSideChange(jx, jy, Feld[jx][jy],
-                              enter_side, CE_ENTERED_BY_PLAYER, -1);
-      }
-#endif
-
-    }
-#endif
-
-
   }
   else
   {
-    CheckGravityMovement(player);
+    CheckGravityMovementWhenNotMoving(player);
 
     /*
     player->last_move_dir = MV_NO_MOVING;
     */
     player->is_moving = FALSE;
+
+#if USE_NEW_MOVE_STYLE
+    /* player is ALLOWED to move, but CANNOT move (something blocks his way) */
+    /* ensure that the player is also allowed to move in the next frame */
+    /* (currently, the player is forced to wait eight frames before he can try
+       again!!!) */
+
+    if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+      player->move_delay = 0;  /* allow direct movement in the next frame */
+#endif
   }
 
+#if USE_NEW_MOVE_DELAY
+  if (player->move_delay == -1)                /* not yet initialized by DigField() */
+    player->move_delay = player->move_delay_value;
+#endif
+
   if (game.engine_version < VERSION_IDENT(3,0,7,0))
   {
     TestIfHeroTouchesBadThing(jx, jy);
@@ -8289,11 +8498,42 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     player->actual_frame_counter = FrameCounter;
     player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
 
+#if USE_NEW_BLOCK_STYLE
+
+    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 block_delay_adjustment = player->block_delay_adjustment;
+
+      /* if player blocks last field, add delay for exactly one move */
+      if (player->block_last_field)
+      {
+       last_field_block_delay += player->move_delay_value;
+
+#if USE_GRAVITY_BUGFIX_NEW
+       /* when blocking enabled, prevent moving up despite gravity */
+       if (game.gravity && player->MovDir == MV_UP)
+         block_delay_adjustment = -1;
+#endif
+      }
+
+      /* 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;
+    }
+#else
+#if USE_NEW_MOVE_STYLE
+    if ((game.engine_version < VERSION_IDENT(3,1,1,0) ||
+        player->block_last_field) &&
+       Feld[last_jx][last_jy] == EL_EMPTY)
+      Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
+#else
     if (Feld[last_jx][last_jy] == EL_EMPTY)
       Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
-
-#if 0
-    DrawPlayer(player);
+#endif
 #endif
 
     return;
@@ -8304,21 +8544,20 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
   player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
 
+#if USE_NEW_BLOCK_STYLE
+#else
   if (!player->block_last_field &&
       Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
-    Feld[last_jx][last_jy] = EL_EMPTY;
+
+    RemoveField(last_jx, last_jy);
+#endif
 
   /* before DrawPlayer() to draw correct player graphic for this case */
   if (player->MovPos == 0)
     CheckGravityMovement(player);
 
-#if 0
-  DrawPlayer(player);  /* needed here only to cleanup last field */
-#endif
-
   if (player->MovPos == 0)     /* player reached destination field */
   {
-#if 1
     if (player->move_delay_reset_counter > 0)
     {
       player->move_delay_reset_counter--;
@@ -8332,20 +8571,14 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        player->move_delay = 0;
       }
     }
-#else
-    if (IS_PASSABLE(Feld[last_jx][last_jy]))
-    {
-      /* continue with normal speed after quickly moving through gate */
-      HALVE_PLAYER_SPEED(player);
-
-      /* be able to make the next move without delay */
-      player->move_delay = 0;
-    }
-#endif
 
+#if USE_NEW_BLOCK_STYLE
+#else
     if (player->block_last_field &&
        Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
-      Feld[last_jx][last_jy] = EL_EMPTY;
+
+      RemoveField(last_jx, last_jy);
+#endif
 
     player->last_jx = jx;
     player->last_jy = jy;
@@ -8362,18 +8595,71 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        player->LevelSolved = player->GameOver = TRUE;
     }
 
+    /* this breaks one level: "machine", level 000 */
+    {
+      int move_direction = player->MovDir;
+      int enter_side = MV_DIR_OPPOSITE(move_direction);
+      int leave_side = move_direction;
+      int old_jx = last_jx;
+      int old_jy = last_jy;
+      int old_element = Feld[old_jx][old_jy];
+      int new_element = Feld[jx][jy];
+
+      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 (IS_CUSTOM_ELEMENT(new_element))
+       CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
+                                  player->index_bit, enter_side);
+
+      CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
+                                         CE_PLAYER_ENTERS_X,
+                                         player->index_bit, enter_side);
+    }
+
     if (game.engine_version >= VERSION_IDENT(3,0,7,0))
     {
       TestIfHeroTouchesBadThing(jx, jy);
       TestIfPlayerTouchesCustomElement(jx, jy);
-#if 1
-      TestIfElementTouchesCustomElement(jx, jy);       /* for empty space */
-#endif
+
+      /* 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 */
 
       if (!player->active)
        RemoveHero(player);
     }
 
+    if (level.use_step_counter)
+    {
+      int i;
+
+      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++)
+           KillHero(&stored_player[i]);
+      }
+      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+       DrawGameValue_Time(TimePlayed);
+    }
+
     if (tape.single_step && tape.recording && !tape.pausing &&
        !player->programmed_action)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
@@ -8417,7 +8703,7 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
     { +1, 0 },
     { 0, +1 }
   };
-  static int change_sides[4][2] =
+  static int trigger_sides[4][2] =
   {
     /* center side       border side */
     { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      /* check top    */
@@ -8435,12 +8721,12 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
   int center_element = Feld[x][y];     /* should always be non-moving! */
   int i;
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int xx = x + xy[i][0];
     int yy = y + xy[i][1];
-    int center_side = change_sides[i][0];
-    int border_side = change_sides[i][1];
+    int center_side = trigger_sides[i][0];
+    int border_side = trigger_sides[i][1];
     int border_element;
 
     if (!IN_LEV_FIELD(xx, yy))
@@ -8448,6 +8734,8 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
 
     if (IS_PLAYER(x, y))
     {
+      struct PlayerInfo *player = PLAYERINFO(x, y);
+
       if (game.engine_version < VERSION_IDENT(3,0,7,0))
        border_element = Feld[xx][yy];          /* may be moving! */
       else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
@@ -8457,26 +8745,27 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
       else
        continue;               /* center and border element do not touch */
 
-      CheckTriggeredElementSideChange(xx, yy, border_element, border_side,
-                                     CE_OTHER_GETS_TOUCHED);
-      CheckElementSideChange(xx, yy, border_element, border_side,
-                            CE_TOUCHED_BY_PLAYER, -1);
+      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);
     }
     else if (IS_PLAYER(xx, yy))
     {
+      struct PlayerInfo *player = PLAYERINFO(xx, yy);
+
       if (game.engine_version >= VERSION_IDENT(3,0,7,0))
       {
-       struct PlayerInfo *player = PLAYERINFO(xx, yy);
-
        if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
          continue;             /* center and border element do not touch */
       }
 
-      CheckTriggeredElementSideChange(x, y, center_element, center_side,
-                                     CE_OTHER_GETS_TOUCHED);
-      CheckElementSideChange(x, y, center_element, center_side,
-                            CE_TOUCHED_BY_PLAYER, -1);
-
+      CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
+                                player->index_bit, center_side);
+      CheckTriggeredElementChangeByPlayer(x, y, center_element,
+                                         CE_PLAYER_TOUCHES_X,
+                                         player->index_bit, center_side);
       break;
     }
   }
@@ -8491,7 +8780,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
     { +1, 0 },
     { 0, +1 }
   };
-  static int change_sides[4][2] =
+  static int trigger_sides[4][2] =
   {
     /* center side     border side */
     { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      /* check top    */
@@ -8507,16 +8796,15 @@ void TestIfElementTouchesCustomElement(int x, int y)
     MV_LEFT | MV_RIGHT
   };
   boolean change_center_element = FALSE;
-  int center_element_change_page = 0;
   int center_element = Feld[x][y];     /* should always be non-moving! */
-  int i, j;
+  int i;
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int xx = x + xy[i][0];
     int yy = y + xy[i][1];
-    int center_side = change_sides[i][0];
-    int border_side = change_sides[i][1];
+    int center_side = trigger_sides[i][0];
+    int border_side = trigger_sides[i][1];
     int border_element;
 
     if (!IN_LEV_FIELD(xx, yy))
@@ -8532,71 +8820,83 @@ void TestIfElementTouchesCustomElement(int x, int y)
       continue;                        /* center and border element do not touch */
 
     /* check for change of center element (but change it only once) */
-    if (IS_CUSTOM_ELEMENT(center_element) &&
-       HAS_ANY_CHANGE_EVENT(center_element, CE_OTHER_IS_TOUCHING) &&
-       !change_center_element)
-    {
-      for (j = 0; j < element_info[center_element].num_change_pages; j++)
-      {
-       struct ElementChangeInfo *change =
-         &element_info[center_element].change_page[j];
+    if (!change_center_element)
+      change_center_element =
+       CheckElementChangeBySide(x, y, center_element, border_element,
+                                CE_TOUCHING_X, border_side);
 
-       if (change->can_change &&
-           change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
-           change->sides & border_side &&
-#if 1
-           IS_EQUAL_OR_IN_GROUP(border_element, change->trigger_element)
-#else
-           change->trigger_element == border_element
+    /* check for change of border element */
+    CheckElementChangeBySide(xx, yy, border_element, center_element,
+                            CE_TOUCHING_X, center_side);
+  }
+}
+
+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 (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
+    return;
+
+  touched_element = (IN_LEV_FIELD(hitx, hity) ?
+                    MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
+
+#if !USE_HITTING_SOMETHING_BUGFIX
+  /* "hitting something" is also true when hitting the playfield border */
+  CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                          CE_HITTING_SOMETHING, direction);
 #endif
-           )
-       {
-         change_center_element = TRUE;
-         center_element_change_page = j;
 
-         break;
-       }
-      }
-    }
+  if (IN_LEV_FIELD(hitx, hity))
+  {
+    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);
 
-    /* check for change of border element */
-    if (IS_CUSTOM_ELEMENT(border_element) &&
-       HAS_ANY_CHANGE_EVENT(border_element, CE_OTHER_IS_TOUCHING))
+    object_hit = TRUE;
+
+    if (object_hit)
     {
-      for (j = 0; j < element_info[border_element].num_change_pages; j++)
-      {
-       struct ElementChangeInfo *change =
-         &element_info[border_element].change_page[j];
+#if !USE_HIT_BY_SOMETHING_BUGFIX
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_HIT_BY_SOMETHING, opposite_direction);
+#endif
 
-       if (change->can_change &&
-           change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
-           change->sides & center_side &&
-#if 1
-           IS_EQUAL_OR_IN_GROUP(center_element, change->trigger_element)
-#else
-           change->trigger_element == center_element
+      CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                              CE_HITTING_X, touched_side);
+
+      CheckElementChangeBySide(hitx, hity, touched_element,
+                              hitting_element, CE_HIT_BY_X, hitting_side);
+
+#if USE_HIT_BY_SOMETHING_BUGFIX
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_HIT_BY_SOMETHING, opposite_direction);
 #endif
-           )
-       {
-         CheckElementSideChange(xx, yy, border_element, CH_SIDE_ANY,
-                                CE_OTHER_IS_TOUCHING, j);
-         break;
-       }
-      }
     }
   }
 
-  if (change_center_element)
-    CheckElementSideChange(x, y, center_element, CH_SIDE_ANY,
-                          CE_OTHER_IS_TOUCHING, center_element_change_page);
+#if USE_HITTING_SOMETHING_BUGFIX
+  /* "hitting something" is also true when hitting the playfield border */
+  CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                          CE_HITTING_SOMETHING, direction);
+#endif
 }
 
-void TestIfElementHitsCustomElement(int x, int y, int direction)
+#if 0
+void TestIfElementSmashesCustomElement(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) &&
@@ -8613,87 +8913,49 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
     return;
 #endif
 
-  CheckElementSideChange(x, y, hitting_element,
-                        direction, CE_HITTING_SOMETHING, -1);
+  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;
-
-      CheckElementSideChange(hitx, hity, touched_element,
-                            opposite_direction, CE_HIT_BY_SOMETHING, -1);
-
-      if (IS_CUSTOM_ELEMENT(hitting_element) &&
-         HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_HITTING))
-      {
-       for (i = 0; i < element_info[hitting_element].num_change_pages; i++)
-       {
-         struct ElementChangeInfo *change =
-           &element_info[hitting_element].change_page[i];
-
-         if (change->can_change &&
-             change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) &&
-             change->sides & touched_side &&
-         
-#if 1
-             IS_EQUAL_OR_IN_GROUP(touched_element, change->trigger_element)
-#else
-             change->trigger_element == touched_element
-#endif
-             )
-         {
-           CheckElementSideChange(x, y, hitting_element,
-                                  CH_SIDE_ANY, CE_OTHER_IS_HITTING, i);
-           break;
-         }
-       }
-      }
-
-      if (IS_CUSTOM_ELEMENT(touched_element) &&
-         HAS_ANY_CHANGE_EVENT(touched_element, CE_OTHER_GETS_HIT))
-      {
-       for (i = 0; i < element_info[touched_element].num_change_pages; i++)
-       {
-         struct ElementChangeInfo *change =
-           &element_info[touched_element].change_page[i];
+                         ABS(MovPos[hitx][hity]) <= TILEY / 2);
 
-         if (change->can_change &&
-             change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) &&
-             change->sides & hitting_side &&
-#if 1
-             IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
-#else
-             change->trigger_element == hitting_element
+    object_hit = TRUE;
 #endif
-             )
-         {
-           CheckElementSideChange(hitx, hity, touched_element,
-                                  CH_SIDE_ANY, CE_OTHER_GETS_HIT, i);
-           break;
-         }
-       }
-      }
+
+    if (object_hit)
+    {
+      int i;
+
+      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);
+
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_OTHER_GETS_SMASHED, hitting_side);
     }
   }
 }
+#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] =
   {
     { 0, -1 },
@@ -8709,23 +8971,20 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
     MV_DOWN
   };
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int test_x, test_y, test_move_dir, test_element;
 
     test_x = good_x + test_xy[i][0];
     test_y = good_y + test_xy[i][1];
+
     if (!IN_LEV_FIELD(test_x, test_y))
       continue;
 
     test_move_dir =
       (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NO_MOVING);
 
-#if 0
-    test_element = Feld[test_x][test_y];
-#else
     test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
-#endif
 
     /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
        2nd case: DONT_TOUCH style bad thing does not move away from good thing
@@ -8735,6 +8994,8 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
     {
       kill_x = test_x;
       kill_y = test_y;
+      bad_element = test_element;
+
       break;
     }
   }
@@ -8745,7 +9006,8 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
     {
       struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
 
-      if (player->shield_deadly_time_left > 0)
+      if (player->shield_deadly_time_left > 0 &&
+         !IS_INDESTRUCTIBLE(bad_element))
        Bang(kill_x, kill_y);
       else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
        KillHero(player);
@@ -8784,7 +9046,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
   if (bad_element == EL_EXPLOSION)     /* skip just exploding bad things */
     return;
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int test_x, test_y, test_move_dir, test_element;
 
@@ -8837,7 +9099,8 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
     {
       struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
 
-      if (player->shield_deadly_time_left > 0)
+      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))
        KillHero(player);
@@ -8888,7 +9151,7 @@ void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
     { 0, +1 }
   };
 
-  for (i = 0; i < 4; i++)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int x, y, element;
 
@@ -8948,11 +9211,7 @@ void BuryHero(struct PlayerInfo *player)
   if (!player->active)
     return;
 
-#if 1
   PlayLevelSoundElementAction(jx, jy, player->element_nr, ACTION_DYING);
-#else
-  PlayLevelSound(jx, jy, SND_CLASS_PLAYER_DYING);
-#endif
   PlayLevelSound(jx, jy, SND_GAME_LOSING);
 
   player->GameOver = TRUE;
@@ -9022,16 +9281,8 @@ int DigField(struct PlayerInfo *player,
             int oldx, int oldy, int x, int y,
             int real_dx, int real_dy, int mode)
 {
-  static int change_sides[4] =
-  {
-    CH_SIDE_RIGHT,     /* moving left  */
-    CH_SIDE_LEFT,      /* moving right */
-    CH_SIDE_BOTTOM,    /* moving up    */
-    CH_SIDE_TOP,       /* moving down  */
-  };
-#if 0
-  boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0,0));
-#endif
+  boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
+  boolean player_was_pushing = player->is_pushing;
   int jx = oldx, jy = oldy;
   int dx = x - jx, dy = y - jy;
   int nextx = x + dx, nexty = y + dy;
@@ -9040,674 +9291,543 @@ int DigField(struct PlayerInfo *player,
                        dy == -1 ? MV_UP :
                        dy == +1 ? MV_DOWN : MV_NO_MOVING);
   int opposite_direction = MV_DIR_OPPOSITE(move_direction);
-  int dig_side = change_sides[MV_DIR_BIT(move_direction)];
+  int dig_side = MV_DIR_OPPOSITE(move_direction);
   int old_element = Feld[jx][jy];
   int element;
 
-  if (player->MovPos == 0)
-  {
-    player->is_digging = FALSE;
-    player->is_collecting = FALSE;
-  }
-
-  if (player->MovPos == 0)     /* last pushing move finished */
-    player->is_pushing = FALSE;
-
-  if (mode == DF_NO_PUSH)      /* player just stopped pushing */
+  if (is_player)               /* function can also be called by EL_PENGUIN */
   {
-    player->is_switching = FALSE;
-    player->push_delay = 0;
-
-    return MF_NO_ACTION;
-  }
-
-  if (IS_MOVING(x, y) || IS_PLAYER(x, y))
-    return MF_NO_ACTION;
+    if (player->MovPos == 0)
+    {
+      player->is_digging = FALSE;
+      player->is_collecting = FALSE;
+    }
 
-#if 0
+    if (player->MovPos == 0)   /* last pushing move finished */
+      player->is_pushing = FALSE;
 
-#if 0
-  if (IS_TUBE(Feld[jx][jy]) || IS_TUBE(Back[jx][jy]))
+    if (mode == DF_NO_PUSH)    /* player just stopped pushing */
+    {
+      player->is_switching = FALSE;
+#if USE_NEW_PUSH_DELAY
+      player->push_delay = -1;
 #else
-  if (IS_TUBE(Feld[jx][jy]) ||
-      (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0)))
+      player->push_delay = 0;
 #endif
-  {
-    int i = 0;
-    int tube_element = (IS_TUBE(Feld[jx][jy]) ? Feld[jx][jy] : Back[jx][jy]);
-    int tube_leave_directions[][2] =
-    {
-      { 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 },
-      { -1,                            MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN }
-    };
 
-    while (tube_leave_directions[i][0] != tube_element)
-    {
-      i++;
-      if (tube_leave_directions[i][0] == -1)   /* should not happen */
-       break;
+      return MF_NO_ACTION;
     }
-
-    if (!(tube_leave_directions[i][1] & move_direction))
-      return MF_NO_ACTION;     /* tube has no opening in this direction */
   }
 
-#else
+  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];
 
+#if USE_BACK_WALKABLE_BUGFIX
+  /* 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];
 #endif
 
-  if (IS_WALKABLE(old_element) &&
-      !(element_info[old_element].access_direction & move_direction))
+  if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
+    return MF_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 */
 
   element = Feld[x][y];
 
+  if (!is_player && !IS_COLLECTIBLE(element))  /* penguin cannot collect it */
+    return MF_NO_ACTION;
+
   if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
       game.engine_version >= VERSION_IDENT(2,2,0,0))
     return MF_NO_ACTION;
 
-  switch (element)
+  if (game.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 */
+
+  if (IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
   {
-    case EL_SP_PORT_LEFT:
-    case EL_SP_PORT_RIGHT:
-    case EL_SP_PORT_UP:
-    case EL_SP_PORT_DOWN:
-    case EL_SP_PORT_HORIZONTAL:
-    case EL_SP_PORT_VERTICAL:
-    case EL_SP_PORT_ANY:
-    case EL_SP_GRAVITY_PORT_LEFT:
-    case EL_SP_GRAVITY_PORT_RIGHT:
-    case EL_SP_GRAVITY_PORT_UP:
-    case EL_SP_GRAVITY_PORT_DOWN:
-#if 1
-      if (!canEnterSupaplexPort(x, y, dx, dy))
+    int sound_element = SND_ELEMENT(element);
+    int sound_action = ACTION_WALKING;
+
+    if (IS_RND_GATE(element))
+    {
+      if (!player->key[RND_GATE_NR(element)])
        return MF_NO_ACTION;
-#else
-      if ((dx == -1 &&
-          element != EL_SP_PORT_LEFT &&
-          element != EL_SP_GRAVITY_PORT_LEFT &&
-          element != EL_SP_PORT_HORIZONTAL &&
-          element != EL_SP_PORT_ANY) ||
-         (dx == +1 &&
-          element != EL_SP_PORT_RIGHT &&
-          element != EL_SP_GRAVITY_PORT_RIGHT &&
-          element != EL_SP_PORT_HORIZONTAL &&
-          element != EL_SP_PORT_ANY) ||
-         (dy == -1 &&
-          element != EL_SP_PORT_UP &&
-          element != EL_SP_GRAVITY_PORT_UP &&
-          element != EL_SP_PORT_VERTICAL &&
-          element != EL_SP_PORT_ANY) ||
-         (dy == +1 &&
-          element != EL_SP_PORT_DOWN &&
-          element != EL_SP_GRAVITY_PORT_DOWN &&
-          element != EL_SP_PORT_VERTICAL &&
-          element != EL_SP_PORT_ANY) ||
-         !IN_LEV_FIELD(nextx, nexty) ||
-         !IS_FREE(nextx, nexty))
+    }
+    else if (IS_RND_GATE_GRAY(element))
+    {
+      if (!player->key[RND_GATE_GRAY_NR(element)])
        return MF_NO_ACTION;
-#endif
+    }
+    else if (element == EL_EXIT_OPEN ||
+            element == EL_SP_EXIT_OPEN ||
+            element == EL_SP_EXIT_OPENING)
+    {
+      sound_action = ACTION_PASSING;   /* player is passing exit */
+    }
+    else if (element == EL_EMPTY)
+    {
+      sound_action = ACTION_MOVING;            /* nothing to walk on */
+    }
+
+    /* 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->element_nr, sound_action);
+  }
+  else if (IS_PASSABLE(element) && canPassField(x, y, move_direction))
+  {
+    if (!ACCESS_FROM(element, opposite_direction))
+      return MF_NO_ACTION;     /* field not accessible from this direction */
+
+    if (CAN_MOVE(element))     /* only fixed elements can be passed! */
+      return MF_NO_ACTION;
 
+    if (IS_EM_GATE(element))
+    {
+      if (!player->key[EM_GATE_NR(element)])
+       return MF_NO_ACTION;
+    }
+    else if (IS_EM_GATE_GRAY(element))
+    {
+      if (!player->key[EM_GATE_GRAY_NR(element)])
+       return MF_NO_ACTION;
+    }
+    else if (IS_SP_PORT(element))
+    {
       if (element == EL_SP_GRAVITY_PORT_LEFT ||
          element == EL_SP_GRAVITY_PORT_RIGHT ||
          element == EL_SP_GRAVITY_PORT_UP ||
          element == EL_SP_GRAVITY_PORT_DOWN)
        game.gravity = !game.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;
+      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;
+    }
 
-      /* automatically move to the next field with double speed */
-      player->programmed_action = move_direction;
-#if 1
-      if (player->move_delay_reset_counter == 0)
-      {
-       player->move_delay_reset_counter = 2;   /* two double speed steps */
+    /* automatically move to the next field with double speed */
+    player->programmed_action = move_direction;
 
-       DOUBLE_PLAYER_SPEED(player);
-      }
-#else
-      player->move_delay_reset_counter = 2;
+    if (player->move_delay_reset_counter == 0)
+    {
+      player->move_delay_reset_counter = 2;    /* two double speed steps */
 
       DOUBLE_PLAYER_SPEED(player);
-#endif
-
-      PlayLevelSound(x, y, SND_CLASS_SP_PORT_PASSING);
-      break;
-
-#if 0
-    case EL_TUBE_ANY:
-    case EL_TUBE_VERTICAL:
-    case EL_TUBE_HORIZONTAL:
-    case EL_TUBE_VERTICAL_LEFT:
-    case EL_TUBE_VERTICAL_RIGHT:
-    case EL_TUBE_HORIZONTAL_UP:
-    case EL_TUBE_HORIZONTAL_DOWN:
-    case EL_TUBE_LEFT_UP:
-    case EL_TUBE_LEFT_DOWN:
-    case EL_TUBE_RIGHT_UP:
-    case EL_TUBE_RIGHT_DOWN:
-      {
-       int i = 0;
-       int tube_enter_directions[][2] =
-       {
-         { 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_RIGHT | MV_UP | MV_DOWN },
-         { EL_TUBE_VERTICAL_RIGHT,     MV_LEFT            | MV_UP | MV_DOWN },
-         { EL_TUBE_HORIZONTAL_UP,      MV_LEFT | MV_RIGHT |         MV_DOWN },
-         { EL_TUBE_HORIZONTAL_DOWN,    MV_LEFT | MV_RIGHT | MV_UP           },
-         { EL_TUBE_LEFT_UP,                      MV_RIGHT |         MV_DOWN },
-         { EL_TUBE_LEFT_DOWN,                    MV_RIGHT | MV_UP           },
-         { EL_TUBE_RIGHT_UP,           MV_LEFT |                    MV_DOWN },
-         { EL_TUBE_RIGHT_DOWN,         MV_LEFT |            MV_UP           },
-         { -1,                         MV_NO_MOVING                         }
-       };
-
-       while (tube_enter_directions[i][0] != element)
-       {
-         i++;
-         if (tube_enter_directions[i][0] == -1)        /* should not happen */
-           break;
-       }
-
-       if (!(tube_enter_directions[i][1] & move_direction))
-         return MF_NO_ACTION;  /* tube has no opening in this direction */
-
-       PlayLevelSound(x, y, SND_CLASS_TUBE_WALKING);
-      }
-      break;
-#endif
-
-    default:
-
-      if (IS_WALKABLE(element))
-      {
-       int sound_action = ACTION_WALKING;
-
-       if (!(element_info[element].access_direction & opposite_direction))
-         return MF_NO_ACTION;  /* field not accessible from this direction */
-
-       if (element >= EL_GATE_1 && element <= EL_GATE_4)
-       {
-         if (!player->key[element - EL_GATE_1])
-           return MF_NO_ACTION;
-       }
-       else if (element >= EL_GATE_1_GRAY && element <= EL_GATE_4_GRAY)
-       {
-         if (!player->key[element - EL_GATE_1_GRAY])
-           return MF_NO_ACTION;
-       }
-       else if (element == EL_EXIT_OPEN ||
-                element == EL_SP_EXIT_OPEN ||
-                element == EL_SP_EXIT_OPENING)
-       {
-         sound_action = ACTION_PASSING;        /* player is passing exit */
-       }
-       else if (element == EL_EMPTY)
-       {
-         sound_action = ACTION_MOVING;         /* nothing to walk on */
-       }
-
-       /* play sound from background or player, whatever is available */
-       if (element_info[element].sound[sound_action] != SND_UNDEFINED)
-         PlayLevelSoundElementAction(x, y, element, sound_action);
-       else
-         PlayLevelSoundElementAction(x, y, player->element_nr, sound_action);
-
-       break;
-      }
-      else if (IS_PASSABLE(element))
-      {
-       if (!IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty))
-         return MF_NO_ACTION;
-
-       if (IS_CUSTOM_ELEMENT(element) &&
-           !(element_info[element].access_direction & opposite_direction))
-         return MF_NO_ACTION;  /* field not accessible from this direction */
-
-#if 1
-       if (CAN_MOVE(element))  /* only fixed elements can be passed! */
-         return MF_NO_ACTION;
-#endif
-
-       if (element >= EL_EM_GATE_1 && element <= EL_EM_GATE_4)
-       {
-         if (!player->key[element - EL_EM_GATE_1])
-           return MF_NO_ACTION;
-       }
-       else if (element >= EL_EM_GATE_1_GRAY && element <= EL_EM_GATE_4_GRAY)
-       {
-         if (!player->key[element - EL_EM_GATE_1_GRAY])
-           return MF_NO_ACTION;
-       }
-
-       /* automatically move to the next field with double speed */
-       player->programmed_action = move_direction;
-#if 1
-       if (player->move_delay_reset_counter == 0)
-       {
-         player->move_delay_reset_counter = 2; /* two double speed steps */
+    }
 
-         DOUBLE_PLAYER_SPEED(player);
-       }
-#else
-       player->move_delay_reset_counter = 2;
+    PlayLevelSoundAction(x, y, ACTION_PASSING);
+  }
+  else if (IS_DIGGABLE(element))
+  {
+    RemoveField(x, y);
 
-       DOUBLE_PLAYER_SPEED(player);
-#endif
+    if (mode != DF_SNAP)
+    {
+      GfxElement[x][y] = GFX_ELEMENT(element);
+      player->is_digging = TRUE;
+    }
 
-       PlayLevelSoundAction(x, y, ACTION_PASSING);
+    PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
 
-       break;
-      }
-      else if (IS_DIGGABLE(element))
-      {
-       RemoveField(x, y);
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
+                                       player->index_bit, dig_side);
 
-       if (mode != DF_SNAP)
-       {
-#if 1
-         GfxElement[x][y] = GFX_ELEMENT(element);
-#else
-         GfxElement[x][y] =
-           (GFX_CRUMBLED(element) ? EL_SAND : GFX_ELEMENT(element));
-#endif
-         player->is_digging = TRUE;
-       }
+    if (mode == DF_SNAP)
+      TestIfElementTouchesCustomElement(x, y); /* for empty space */
+  }
+  else if (IS_COLLECTIBLE(element))
+  {
+    RemoveField(x, y);
 
-       PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+    if (is_player && mode != DF_SNAP)
+    {
+      GfxElement[x][y] = element;
+      player->is_collecting = TRUE;
+    }
 
-       CheckTriggeredElementChange(x, y, element, CE_OTHER_GETS_DIGGED);
+    if (element == EL_SPEED_PILL)
+    {
+      player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
+    }
+    else if (element == EL_EXTRA_TIME && level.time > 0)
+    {
+      TimeLeft += 10;
+      DrawGameValue_Time(TimeLeft);
+    }
+    else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
+    {
+      player->shield_normal_time_left += 10;
+      if (element == EL_SHIELD_DEADLY)
+       player->shield_deadly_time_left += 10;
+    }
+    else if (element == EL_DYNAMITE || element == EL_SP_DISK_RED)
+    {
+      if (player->inventory_size < MAX_INVENTORY_SIZE)
+       player->inventory_element[player->inventory_size++] = element;
 
-#if 1
-       if (mode == DF_SNAP)
-         TestIfElementTouchesCustomElement(x, y);      /* for empty space */
-#endif
+      DrawGameValue_Dynamite(local_player->inventory_size);
+    }
+    else if (element == EL_DYNABOMB_INCREASE_NUMBER)
+    {
+      player->dynabomb_count++;
+      player->dynabombs_left++;
+    }
+    else if (element == EL_DYNABOMB_INCREASE_SIZE)
+    {
+      player->dynabomb_size++;
+    }
+    else if (element == EL_DYNABOMB_INCREASE_POWER)
+    {
+      player->dynabomb_xl = TRUE;
+    }
+    else if (IS_KEY(element))
+    {
+      player->key[KEY_NR(element)] = TRUE;
 
-       break;
-      }
-      else if (IS_COLLECTIBLE(element))
-      {
-       RemoveField(x, y);
+      DrawGameValue_Keys(player->key);
 
-       if (mode != DF_SNAP)
-       {
-         GfxElement[x][y] = element;
-         player->is_collecting = TRUE;
-       }
+      redraw_mask |= REDRAW_DOOR_1;
+    }
+    else if (IS_ENVELOPE(element))
+    {
+      player->show_envelope = element;
+    }
+    else if (IS_DROPPABLE(element) ||
+            IS_THROWABLE(element))     /* can be collected and dropped */
+    {
+      int i;
 
-       if (element == EL_SPEED_PILL)
-         player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
-       else if (element == EL_EXTRA_TIME && level.time > 0)
-       {
-         TimeLeft += 10;
-         DrawText(DX_TIME, DY_TIME, int2str(TimeLeft, 3), FONT_TEXT_2);
-       }
-       else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
-       {
-         player->shield_normal_time_left += 10;
-         if (element == EL_SHIELD_DEADLY)
-           player->shield_deadly_time_left += 10;
-       }
-       else if (element == EL_DYNAMITE || element == EL_SP_DISK_RED)
-       {
+      if (element_info[element].collect_count == 0)
+       player->inventory_infinite_element = element;
+      else
+       for (i = 0; i < element_info[element].collect_count; i++)
          if (player->inventory_size < MAX_INVENTORY_SIZE)
            player->inventory_element[player->inventory_size++] = element;
 
-         DrawText(DX_DYNAMITE, DY_DYNAMITE,
-                  int2str(local_player->inventory_size, 3), FONT_TEXT_2);
-       }
-       else if (element == EL_DYNABOMB_INCREASE_NUMBER)
-       {
-         player->dynabomb_count++;
-         player->dynabombs_left++;
-       }
-       else if (element == EL_DYNABOMB_INCREASE_SIZE)
-       {
-         player->dynabomb_size++;
-       }
-       else if (element == EL_DYNABOMB_INCREASE_POWER)
-       {
-         player->dynabomb_xl = TRUE;
-       }
-       else if ((element >= EL_KEY_1 && element <= EL_KEY_4) ||
-                (element >= EL_EM_KEY_1 && element <= EL_EM_KEY_4))
-       {
-         int key_nr = (element >= EL_KEY_1 && element <= EL_KEY_4 ?
-                       element - EL_KEY_1 : element - EL_EM_KEY_1);
-
-         player->key[key_nr] = TRUE;
-
-         DrawMiniGraphicExt(drawto, DX_KEYS + key_nr * MINI_TILEX, DY_KEYS,
-                            el2edimg(EL_KEY_1 + key_nr));
-         redraw_mask |= REDRAW_DOOR_1;
-       }
-       else if (IS_ENVELOPE(element))
-       {
-#if 1
-         player->show_envelope = element;
-#else
-         ShowEnvelope(element - EL_ENVELOPE_1);
-#endif
-       }
-       else if (IS_DROPPABLE(element)) /* can be collected and dropped */
-       {
-         int i;
+      DrawGameValue_Dynamite(local_player->inventory_size);
+    }
+    else if (element_info[element].collect_count > 0)
+    {
+      local_player->gems_still_needed -=
+       element_info[element].collect_count;
+      if (local_player->gems_still_needed < 0)
+       local_player->gems_still_needed = 0;
 
-         for (i = 0; i < element_info[element].collect_count; i++)
-           if (player->inventory_size < MAX_INVENTORY_SIZE)
-             player->inventory_element[player->inventory_size++] = element;
+      DrawGameValue_Emeralds(local_player->gems_still_needed);
+    }
 
-         DrawText(DX_DYNAMITE, DY_DYNAMITE,
-                  int2str(local_player->inventory_size, 3), FONT_TEXT_2);
-       }
-       else if (element_info[element].collect_count > 0)
-       {
-         local_player->gems_still_needed -=
-           element_info[element].collect_count;
-         if (local_player->gems_still_needed < 0)
-           local_player->gems_still_needed = 0;
+    RaiseScoreElement(element);
+    PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
 
-         DrawText(DX_EMERALDS, DY_EMERALDS,
-                  int2str(local_player->gems_still_needed, 3), FONT_TEXT_2);
-       }
+    if (is_player)
+      CheckTriggeredElementChangeByPlayer(x, y, element,
+                                         CE_PLAYER_COLLECTS_X,
+                                         player->index_bit, dig_side);
 
-       RaiseScoreElement(element);
-       PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
+    if (mode == DF_SNAP)
+      TestIfElementTouchesCustomElement(x, y); /* for empty space */
+  }
+  else if (IS_PUSHABLE(element))
+  {
+    if (mode == DF_SNAP && element != EL_BD_ROCK)
+      return MF_NO_ACTION;
 
-       CheckTriggeredElementChange(x, y, element, CE_OTHER_GETS_COLLECTED);
+    if (CAN_FALL(element) && dy)
+      return MF_NO_ACTION;
 
-#if 1
-       if (mode == DF_SNAP)
-         TestIfElementTouchesCustomElement(x, y);      /* for empty space */
-#endif
+    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;
 
-       break;
-      }
-      else if (IS_PUSHABLE(element))
-      {
-       if (mode == DF_SNAP && element != EL_BD_ROCK)
-         return MF_NO_ACTION;
+    if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
+       ((move_direction & MV_VERTICAL &&
+         ((element_info[element].move_pattern & MV_LEFT &&
+           IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
+          (element_info[element].move_pattern & MV_RIGHT &&
+           IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
+        (move_direction & MV_HORIZONTAL &&
+         ((element_info[element].move_pattern & MV_UP &&
+           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;
 
-       if (CAN_FALL(element) && dy)
-         return MF_NO_ACTION;
+    /* 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;
 
-       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;
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+    {
+      if (player->push_delay_value == -1 || !player_was_pushing)
+       player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    }
+    else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+    {
+      if (player->push_delay_value == -1)
+       player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    }
+    else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
+    {
+      if (!player->is_pushing)
+       player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    }
 
-#if 1
-       if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
-           ((move_direction & MV_VERTICAL &&
-             ((element_info[element].move_pattern & MV_LEFT &&
-               IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
-              (element_info[element].move_pattern & MV_RIGHT &&
-               IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
-            (move_direction & MV_HORIZONTAL &&
-             ((element_info[element].move_pattern & MV_UP &&
-               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;
-#endif
+    player->is_pushing = TRUE;
 
-#if 1
-       /* 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;
-#else
-       if (element == EL_SPRING && MovDir[x][y] != MV_NO_MOVING)
-         return MF_NO_ACTION;
-#endif
+    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;
 
-#if 1
-       if (game.engine_version >= VERSION_IDENT(3,0,7,1))
-       {
-         if (player->push_delay_value == -1)
-           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       }
-       else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
-       {
-         if (!player->is_pushing)
-           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       }
+    if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
+      return MF_NO_ACTION;
 
-       /*
-       if (game.engine_version >= VERSION_IDENT(2,2,0,7) &&
-           (game.engine_version < VERSION_IDENT(3,0,7,1) ||
-            !player_is_pushing))
-         player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       */
+#if USE_NEW_PUSH_DELAY
+    if (player->push_delay == -1)      /* new pushing; restart delay */
+      player->push_delay = 0;
 #else
-       if (!player->is_pushing &&
-           game.engine_version >= VERSION_IDENT(2,2,0,7))
-         player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    if (player->push_delay == 0)       /* new pushing; restart delay */
+      player->push_delay = FrameCounter;
 #endif
 
-#if 0
-       printf("::: push delay: %ld [%d, %d] [%d]\n",
-              player->push_delay_value, FrameCounter, game.engine_version,
-              player->is_pushing);
+#if USE_NEW_PUSH_DELAY
+    if (player->push_delay < player->push_delay_value &&
+       !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
+       element != EL_SPRING && element != EL_BALLOON)
+#else
+    if (!FrameReached(&player->push_delay, player->push_delay_value) &&
+       !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
+       element != EL_SPRING && element != EL_BALLOON)
+#endif
+    {
+      /* make sure that there is no move delay before next try to push */
+#if USE_NEW_MOVE_DELAY
+      if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+       player->move_delay = 0;
+#else
+      if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+       player->move_delay = INITIAL_MOVE_DELAY_OFF;
 #endif
 
-       player->is_pushing = TRUE;
+      return MF_NO_ACTION;
+    }
 
-       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;
+    if (IS_SB_ELEMENT(element))
+    {
+      if (element == EL_SOKOBAN_FIELD_FULL)
+      {
+       Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
+       local_player->sokobanfields_still_needed++;
+      }
 
-       if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
-         return MF_NO_ACTION;
+      if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
+      {
+       Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
+       local_player->sokobanfields_still_needed--;
+      }
 
-       if (player->push_delay == 0)    /* new pushing; restart delay */
-         player->push_delay = FrameCounter;
+      Feld[x][y] = EL_SOKOBAN_OBJECT;
 
-       if (!FrameReached(&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 */
-         if (game.engine_version >= VERSION_IDENT(3,0,7,1))
-           player->move_delay = INITIAL_MOVE_DELAY_OFF;
+      if (Back[x][y] == Back[nextx][nexty])
+       PlayLevelSoundAction(x, y, ACTION_PUSHING);
+      else if (Back[x][y] != 0)
+       PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
+                                   ACTION_EMPTYING);
+      else
+       PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
+                                   ACTION_FILLING);
 
-         return MF_NO_ACTION;
-       }
+      if (local_player->sokobanfields_still_needed == 0 &&
+         game.emulation == EMU_SOKOBAN)
+      {
+       player->LevelSolved = player->GameOver = TRUE;
+       PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
+      }
+    }
+    else
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
 
-#if 0
-       printf("::: NOW PUSHING... [%d]\n", FrameCounter);
-#endif
+    InitMovingField(x, y, move_direction);
+    GfxAction[x][y] = ACTION_PUSHING;
 
-       if (IS_SB_ELEMENT(element))
-       {
-         if (element == EL_SOKOBAN_FIELD_FULL)
-         {
-           Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
-           local_player->sokobanfields_still_needed++;
-         }
+    if (mode == DF_SNAP)
+      ContinueMoving(x, y);
+    else
+      MovPos[x][y] = (dx != 0 ? dx : dy);
 
-         if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
-         {
-           Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
-           local_player->sokobanfields_still_needed--;
-         }
+    Pushed[x][y] = TRUE;
+    Pushed[nextx][nexty] = TRUE;
 
-         Feld[x][y] = EL_SOKOBAN_OBJECT;
+    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 */
 
-         if (Back[x][y] == Back[nextx][nexty])
-           PlayLevelSoundAction(x, y, ACTION_PUSHING);
-         else if (Back[x][y] != 0)
-           PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
-                                       ACTION_EMPTYING);
-         else
-           PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
-                                       ACTION_FILLING);
+#if USE_PUSH_BUGFIX
+    /* now: check for element change _after_ element has been pushed! */
+    if (game.use_change_when_pushing_bug)
+    {
+      CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
+                                player->index_bit, dig_side);
+      CheckTriggeredElementChangeByPlayer(x,y, element, CE_PLAYER_PUSHES_X,
+                                         player->index_bit, dig_side);
+    }
 
-         if (local_player->sokobanfields_still_needed == 0 &&
-             game.emulation == EMU_SOKOBAN)
-         {
-           player->LevelSolved = player->GameOver = TRUE;
-           PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
-         }
-       }
-       else
-         PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
+#else
+    /* check for element change _after_ element has been pushed! */
+#endif
+  }
+  else if (IS_SWITCHABLE(element))
+  {
+    if (PLAYER_SWITCHING(player, x, y))
+    {
+      CheckTriggeredElementChangeByPlayer(x,y, element,
+                                         CE_PLAYER_PRESSES_X,
+                                         player->index_bit, dig_side);
 
-       InitMovingField(x, y, move_direction);
-       GfxAction[x][y] = ACTION_PUSHING;
+      return MF_ACTION;
+    }
 
-       if (mode == DF_SNAP)
-         ContinueMoving(x, y);
-       else
-         MovPos[x][y] = (dx != 0 ? dx : dy);
+    player->is_switching = TRUE;
+    player->switch_x = x;
+    player->switch_y = y;
 
-       Pushed[x][y] = TRUE;
-       Pushed[nextx][nexty] = TRUE;
+    PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
 
-       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 */
+    if (element == EL_ROBOT_WHEEL)
+    {
+      Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
+      ZX = x;
+      ZY = y;
 
-       CheckTriggeredElementSideChange(x, y, element, dig_side,
-                                       CE_OTHER_GETS_PUSHED);
-       CheckElementSideChange(x, y, element, dig_side,
-                              CE_PUSHED_BY_PLAYER, -1);
+      DrawLevelField(x, y);
+    }
+    else if (element == EL_SP_TERMINAL)
+    {
+      int xx, yy;
 
-       break;
-      }
-      else if (IS_SWITCHABLE(element))
+      for (yy = 0; yy < lev_fieldy; yy++) for (xx=0; xx < lev_fieldx; xx++)
       {
-       if (PLAYER_SWITCHING(player, x, y))
-         return MF_ACTION;
-
-       player->is_switching = TRUE;
-       player->switch_x = x;
-       player->switch_y = y;
-
-       PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
-
-       if (element == EL_ROBOT_WHEEL)
-       {
-         Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
-         ZX = x;
-         ZY = y;
-
-         DrawLevelField(x, y);
-       }
-       else if (element == EL_SP_TERMINAL)
-       {
-         int xx, yy;
-
-         for (yy = 0; yy < lev_fieldy; yy++) for (xx=0; xx < lev_fieldx; xx++)
-         {
-           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;
-         }
-       }
-       else if (IS_BELT_SWITCH(element))
-       {
-         ToggleBeltSwitch(x, y);
-       }
-       else if (element == EL_SWITCHGATE_SWITCH_UP ||
-                element == EL_SWITCHGATE_SWITCH_DOWN)
-       {
-         ToggleSwitchgateSwitch(x, y);
-       }
-       else if (element == EL_LIGHT_SWITCH ||
-                element == EL_LIGHT_SWITCH_ACTIVE)
-       {
-         ToggleLightSwitch(x, y);
+       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;
+      }
+    }
+    else if (IS_BELT_SWITCH(element))
+    {
+      ToggleBeltSwitch(x, y);
+    }
+    else if (element == EL_SWITCHGATE_SWITCH_UP ||
+            element == EL_SWITCHGATE_SWITCH_DOWN)
+    {
+      ToggleSwitchgateSwitch(x, y);
+    }
+    else if (element == EL_LIGHT_SWITCH ||
+            element == EL_LIGHT_SWITCH_ACTIVE)
+    {
+      ToggleLightSwitch(x, y);
+    }
+    else if (element == EL_TIMEGATE_SWITCH)
+    {
+      ActivateTimegateSwitch(x, y);
+    }
+    else if (element == EL_BALLOON_SWITCH_LEFT ||
+            element == EL_BALLOON_SWITCH_RIGHT ||
+            element == EL_BALLOON_SWITCH_UP ||
+            element == EL_BALLOON_SWITCH_DOWN ||
+            element == EL_BALLOON_SWITCH_ANY)
+    {
+      if (element == EL_BALLOON_SWITCH_ANY)
+       game.balloon_dir = move_direction;
+      else
+       game.balloon_dir = (element == EL_BALLOON_SWITCH_LEFT  ? MV_LEFT :
+                           element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
+                           element == EL_BALLOON_SWITCH_UP    ? MV_UP :
+                           element == EL_BALLOON_SWITCH_DOWN  ? MV_DOWN :
+                           MV_NO_MOVING);
+    }
+    else if (element == EL_LAMP)
+    {
+      Feld[x][y] = EL_LAMP_ACTIVE;
+      local_player->lights_still_needed--;
 
-#if 0
-         PlayLevelSound(x, y, element == EL_LIGHT_SWITCH ?
-                        SND_LIGHT_SWITCH_ACTIVATING :
-                        SND_LIGHT_SWITCH_DEACTIVATING);
-#endif
-       }
-       else if (element == EL_TIMEGATE_SWITCH)
-       {
-         ActivateTimegateSwitch(x, y);
-       }
-       else if (element == EL_BALLOON_SWITCH_LEFT ||
-                element == EL_BALLOON_SWITCH_RIGHT ||
-                element == EL_BALLOON_SWITCH_UP ||
-                element == EL_BALLOON_SWITCH_DOWN ||
-                element == EL_BALLOON_SWITCH_ANY)
-       {
-         if (element == EL_BALLOON_SWITCH_ANY)
-           game.balloon_dir = move_direction;
-         else
-           game.balloon_dir = (element == EL_BALLOON_SWITCH_LEFT  ? MV_LEFT :
-                               element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
-                               element == EL_BALLOON_SWITCH_UP    ? MV_UP :
-                               element == EL_BALLOON_SWITCH_DOWN  ? MV_DOWN :
-                               MV_NO_MOVING);
-       }
-       else if (element == EL_LAMP)
-       {
-         Feld[x][y] = EL_LAMP_ACTIVE;
-         local_player->lights_still_needed--;
+      ResetGfxAnimation(x, y);
+      DrawLevelField(x, y);
+    }
+    else if (element == EL_TIME_ORB_FULL)
+    {
+      Feld[x][y] = EL_TIME_ORB_EMPTY;
+      TimeLeft += 10;
+      DrawGameValue_Time(TimeLeft);
 
-         DrawLevelField(x, y);
-       }
-       else if (element == EL_TIME_ORB_FULL)
-       {
-         Feld[x][y] = EL_TIME_ORB_EMPTY;
-         TimeLeft += 10;
-         DrawText(DX_TIME, DY_TIME, int2str(TimeLeft, 3), FONT_TEXT_2);
+      ResetGfxAnimation(x, y);
+      DrawLevelField(x, y);
+    }
 
-         DrawLevelField(x, y);
+    CheckTriggeredElementChangeByPlayer(x, y, element,
+                                       CE_SWITCH_OF_X,
+                                       player->index_bit, dig_side);
 
-#if 0
-         PlaySoundStereo(SND_TIME_ORB_FULL_COLLECTING, SOUND_MIDDLE);
-#endif
-       }
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
+                                       player->index_bit, dig_side);
 
-       return MF_ACTION;
-      }
-      else
-      {
-       if (!PLAYER_SWITCHING(player, x, y))
-       {
-         player->is_switching = TRUE;
-         player->switch_x = x;
-         player->switch_y = y;
+    return MF_ACTION;
+  }
+  else
+  {
+    if (!PLAYER_SWITCHING(player, x, y))
+    {
+      player->is_switching = TRUE;
+      player->switch_x = x;
+      player->switch_y = y;
 
-         CheckTriggeredElementSideChange(x, y, element, dig_side,
-                                         CE_OTHER_IS_SWITCHING);
-         CheckElementSideChange(x, y, element, dig_side, CE_SWITCHED, -1);
-       }
+      CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
+                                player->index_bit, dig_side);
+      CheckTriggeredElementChangeByPlayer(x, y, element,
+                                         CE_SWITCH_OF_X,
+                                         player->index_bit, dig_side);
+    }
 
-       CheckTriggeredElementSideChange(x, y, element, dig_side,
-                                       CE_OTHER_GETS_PRESSED);
-       CheckElementSideChange(x, y, element, dig_side,
-                              CE_PRESSED_BY_PLAYER, -1);
-      }
+    CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
+                              player->index_bit, dig_side);
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
+                                       player->index_bit, dig_side);
 
-      return MF_NO_ACTION;
+    return MF_NO_ACTION;
   }
 
+#if USE_NEW_PUSH_DELAY
+  player->push_delay = -1;
+#else
   player->push_delay = 0;
+#endif
 
-  if (Feld[x][y] != element)           /* really digged/collected something */
-    player->is_collecting = !player->is_digging;
+#if USE_PENGUIN_COLLECT_BUGFIX
+  if (is_player)               /* function can also be called by EL_PENGUIN */
+#endif
+  {
+    if (Feld[x][y] != element)         /* really digged/collected something */
+      player->is_collecting = !player->is_digging;
+  }
 
   return MF_MOVING;
 }
@@ -9721,7 +9841,7 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
                        dy == -1 ? MV_UP :
                        dy == +1 ? MV_DOWN : MV_NO_MOVING);
 
-  if (player->MovPos && game.engine_version >= VERSION_IDENT(2,2,0,0))
+  if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
     return FALSE;
 
   if (!player->active || !IN_LEV_FIELD(x, y))
@@ -9752,9 +9872,7 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 
   player->MovDir = snap_direction;
 
-#if 1
   if (player->MovPos == 0)
-#endif
   {
     player->is_moving = FALSE;
     player->is_digging = FALSE;
@@ -9768,33 +9886,62 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 
   player->is_snapping = TRUE;
 
-#if 1
   if (player->MovPos == 0)
-#endif
   {
     player->is_moving = FALSE;
     player->is_digging = FALSE;
     player->is_collecting = FALSE;
   }
 
+  if (player->MovPos != 0)     /* prevent graphic bugs in versions < 2.2.0 */
+    DrawLevelField(player->last_jx, player->last_jy);
+
   DrawLevelField(x, y);
-  BackToFront();
 
   return TRUE;
 }
 
 boolean DropElement(struct PlayerInfo *player)
 {
-  int jx = player->jx, jy = player->jy;
-  int old_element = Feld[jx][jy];
-  int new_element;
+  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);
+
+#if USE_DROP_BUGFIX
+  /* 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;
+#endif
+
+  if (IS_THROWABLE(drop_element))
+  {
+    dropx += GET_DX_FROM_DIR(drop_direction);
+    dropy += GET_DY_FROM_DIR(drop_direction);
+
+    if (!IN_LEV_FIELD(dropx, dropy))
+      return FALSE;
+  }
+
+  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 */
   if (!player->active || player->MovPos || player->drop_delay > 0)
     return FALSE;
 
   /* check if player has anything that can be dropped */
-  if (player->inventory_size == 0 && player->dynabombs_left == 0)
+  if (new_element == EL_UNDEFINED)
     return FALSE;
 
   /* check if anything can be dropped at the current position */
@@ -9802,124 +9949,90 @@ boolean DropElement(struct PlayerInfo *player)
     return FALSE;
 
   /* collected custom elements can only be dropped on empty fields */
-  if (player->inventory_size > 0 &&
-      IS_CUSTOM_ELEMENT(player->inventory_element[player->inventory_size - 1])
-      && old_element != EL_EMPTY)
+  if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
     return FALSE;
 
   if (old_element != EL_EMPTY)
-    Back[jx][jy] = old_element;                /* store old element on this field */
+    Back[dropx][dropy] = old_element;  /* store old element on this field */
 
-  ResetGfxAnimation(jx, jy);
-  ResetRandomAnimationValue(jx, jy);
+  ResetGfxAnimation(dropx, dropy);
+  ResetRandomAnimationValue(dropx, dropy);
 
-  if (player->inventory_size > 0)
+  if (player->inventory_size > 0 ||
+      player->inventory_infinite_element != EL_UNDEFINED)
   {
-    player->inventory_size--;
-    new_element = player->inventory_element[player->inventory_size];
+    if (player->inventory_size > 0)
+    {
+      player->inventory_size--;
 
-    if (new_element == EL_DYNAMITE)
-      new_element = EL_DYNAMITE_ACTIVE;
-    else if (new_element == EL_SP_DISK_RED)
-      new_element = EL_SP_DISK_RED_ACTIVE;
+      DrawGameValue_Dynamite(local_player->inventory_size);
 
-    Feld[jx][jy] = new_element;
+      if (new_element == EL_DYNAMITE)
+       new_element = EL_DYNAMITE_ACTIVE;
+      else if (new_element == EL_SP_DISK_RED)
+       new_element = EL_SP_DISK_RED_ACTIVE;
+    }
 
-    DrawText(DX_DYNAMITE, DY_DYNAMITE,
-            int2str(local_player->inventory_size, 3), FONT_TEXT_2);
+    Feld[dropx][dropy] = new_element;
 
-    if (IN_SCR_FIELD(SCREENX(jx), SCREENY(jy)))
-      DrawGraphicThruMask(SCREENX(jx), SCREENY(jy), el2img(Feld[jx][jy]), 0);
+    if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
+      DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
+                         el2img(Feld[dropx][dropy]), 0);
 
-    PlayLevelSoundAction(jx, jy, ACTION_DROPPING);
+    PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
 
-#if 1
     /* needed if previous element just changed to "empty" in the last frame */
-    Changed[jx][jy] = 0;               /* allow another change */
-#endif
+    Changed[dropx][dropy] = FALSE;             /* allow another change */
 
-    CheckTriggeredElementChange(jx, jy, new_element, CE_OTHER_GETS_DROPPED);
-    CheckElementChange(jx, jy, new_element, CE_DROPPED_BY_PLAYER);
+    CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
+                              player->index_bit, drop_side);
+    CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
+                                       CE_PLAYER_DROPS_X,
+                                       player->index_bit, drop_side);
 
-    TestIfElementTouchesCustomElement(jx, jy);
+    TestIfElementTouchesCustomElement(dropx, dropy);
   }
   else         /* player is dropping a dyna bomb */
   {
     player->dynabombs_left--;
-    new_element = EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr;
 
-    Feld[jx][jy] = new_element;
+    Feld[dropx][dropy] = new_element;
 
-    if (IN_SCR_FIELD(SCREENX(jx), SCREENY(jy)))
-      DrawGraphicThruMask(SCREENX(jx), SCREENY(jy), el2img(Feld[jx][jy]), 0);
+    if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
+      DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
+                         el2img(Feld[dropx][dropy]), 0);
 
-    PlayLevelSoundAction(jx, jy, ACTION_DROPPING);
+    PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
   }
 
+  if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
+    InitField_WithBug1(dropx, dropy, FALSE);
 
-
-#if 1
-
-  if (Feld[jx][jy] == new_element)     /* uninitialized unless CE change */
-  {
-#if 1
-    InitField_WithBug1(jx, jy, FALSE);
-#else
-    InitField(jx, jy, FALSE);
-    if (CAN_MOVE(Feld[jx][jy]))
-      InitMovDir(jx, jy);
-#endif
-  }
-
-  new_element = Feld[jx][jy];
+  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_stepsize = element_info[new_element].move_stepsize;
-    int direction, dx, dy, nextx, nexty;
+    int move_direction, nextx, nexty;
 
     if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
-      MovDir[jx][jy] = player->MovDir;
-
-    direction = MovDir[jx][jy];
-    dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-    dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-    nextx = jx + dx;
-    nexty = jy + dy;
-
-    if (IN_LEV_FIELD(nextx, nexty) && IS_FREE(nextx, nexty))
-    {
-#if 0
-      WasJustMoving[jx][jy] = 3;
-#else
-      InitMovingField(jx, jy, direction);
-      ContinueMoving(jx, jy);
-#endif
-    }
-    else
-    {
-      Changed[jx][jy] = 0;             /* allow another change */
+      MovDir[dropx][dropy] = drop_direction;
 
-#if 1
-      TestIfElementHitsCustomElement(jx, jy, direction);
-#else
-      CheckElementSideChange(jx, jy, new_element,
-                            direction, CE_HITTING_SOMETHING, -1);
-#endif
-    }
+    move_direction = MovDir[dropx][dropy];
+    nextx = dropx + GET_DX_FROM_DIR(move_direction);
+    nexty = dropy + GET_DY_FROM_DIR(move_direction);
 
-    player->drop_delay = 2 * TILEX / move_stepsize + 1;
+    Changed[dropx][dropy] = FALSE;             /* allow another change */
+    CheckCollision[dropx][dropy] = 2;
   }
 
-#if 0
-  player->drop_delay = 8 + 8 + 8;
-#endif
-
-#endif
-
+  player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
   player->is_dropping = TRUE;
 
+#if USE_DROP_BUGFIX
+  player->drop_x = dropx;
+  player->drop_y = dropy;
+#endif
 
   return TRUE;
 }
@@ -10003,7 +10116,7 @@ static void PlayLevelSoundAction(int x, int y, int action)
 
 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
 {
-  int sound_effect = element_info[element].sound[action];
+  int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
 
   if (sound_effect != SND_UNDEFINED)
     PlayLevelSound(x, y, sound_effect);
@@ -10012,7 +10125,7 @@ static void PlayLevelSoundElementAction(int x, int y, int element, int action)
 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
                                              int action)
 {
-  int sound_effect = element_info[element].sound[action];
+  int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
 
   if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
     PlayLevelSound(x, y, sound_effect);
@@ -10020,7 +10133,7 @@ static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
 
 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
 {
-  int sound_effect = element_info[Feld[x][y]].sound[action];
+  int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
 
   if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
     PlayLevelSound(x, y, sound_effect);
@@ -10028,7 +10141,7 @@ static void PlayLevelSoundActionIfLoop(int x, int y, int action)
 
 static void StopLevelSoundActionIfLoop(int x, int y, int action)
 {
-  int sound_effect = element_info[Feld[x][y]].sound[action];
+  int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
 
   if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
     StopSound(sound_effect);
@@ -10042,10 +10155,168 @@ static void PlayLevelMusic()
     PlayMusic(MAP_NOCONF_MUSIC(level_nr));     /* from music dir */
 }
 
+void PlayLevelSound_EM(int x, int y, int element_em, int sample)
+{
+  int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
+
+  switch (sample)
+  {
+    case SAMPLE_blank:
+      PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
+      break;
+
+    case SAMPLE_roll:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
+      break;
+
+    case SAMPLE_stone:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_nut:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_crack:
+      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
+      break;
+
+    case SAMPLE_bug:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_tank:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_android_clone:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_android_move:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_spring:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_slurp:
+      PlayLevelSoundElementAction(x, y, element, ACTION_SLURPED_BY_SPRING);
+      break;
+
+    case SAMPLE_eater:
+      PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
+      break;
+
+    case SAMPLE_eater_eat:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+      break;
+
+    case SAMPLE_alien:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_collect:
+      PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
+      break;
+
+    case SAMPLE_diamond:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_squash:
+      /* !!! CHECK THIS !!! */
+#if 1
+      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
+#else
+      PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
+#endif
+      break;
+
+    case SAMPLE_wonderfall:
+      PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
+      break;
+
+    case SAMPLE_drip:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_push:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
+      break;
+
+    case SAMPLE_dirt:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+      break;
+
+    case SAMPLE_acid:
+      PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
+      break;
+
+    case SAMPLE_ball:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_grow:
+      PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
+      break;
+
+    case SAMPLE_wonder:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_door:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
+      break;
+
+    case SAMPLE_exit_open:
+      PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
+      break;
+
+    case SAMPLE_exit_leave:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
+      break;
+
+    case SAMPLE_dynamite:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_tick:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_press:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
+      break;
+
+    case SAMPLE_wheel:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_boom:
+      PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
+      break;
+
+    case SAMPLE_die:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
+      break;
+
+    case SAMPLE_time:
+      PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+      break;
+
+    default:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
+      break;
+  }
+}
+
 void RaiseScore(int value)
 {
   local_player->score += value;
-  DrawText(DX_SCORE, DY_SCORE, int2str(local_player->score, 5), FONT_TEXT_2);
+
+  DrawGameValue_Score(local_player->score);
 }
 
 void RaiseScoreElement(int element)
@@ -10110,6 +10381,14 @@ void RaiseScoreElement(int element)
     case EL_KEY_2:
     case EL_KEY_3:
     case EL_KEY_4:
+    case EL_EM_KEY_1:
+    case EL_EM_KEY_2:
+    case EL_EM_KEY_3:
+    case EL_EM_KEY_4:
+    case EL_EMC_KEY_5:
+    case EL_EMC_KEY_6:
+    case EL_EMC_KEY_7:
+    case EL_EMC_KEY_8:
       RaiseScore(level.score[SC_KEY]);
       break;
     default:
@@ -10126,7 +10405,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
       Request("Do you really want to quit the game ?",
              REQ_ASK | REQ_STAY_CLOSED))
   {
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
     if (options.network)
       SendToServer_StopPlaying();
     else
@@ -10138,7 +10417,13 @@ void RequestQuitGame(boolean ask_if_really_quit)
   }
   else
   {
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOff(TRUE);
+
     OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
+
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOn();
   }
 }
 
@@ -10305,7 +10590,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
     case GAME_CTRL_ID_PAUSE:
       if (options.network)
       {
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
        if (tape.pausing)
          SendToServer_ContinuePlaying();
        else
@@ -10319,7 +10604,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
     case GAME_CTRL_ID_PLAY:
       if (tape.pausing)
       {
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
        if (options.network)
          SendToServer_ContinuePlaying();
        else