rnd-20040411-1-src
[rocksndiamonds.git] / src / game.c
index 02eb0df91c098eb6a837efd7f5bfa705e42b012e..59ced41619717c82f6fc88b6e1a34d074651ccb7 100644 (file)
 
 /* 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
 /* 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 ELEMENT_CAN_ENTER_FIELD_BASE(e, x, y, condition)               \
+#define GET_TARGET_ELEMENT(e, ch)                                      \
+       ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element :     \
+        (e) == EL_TRIGGER_PLAYER  ? (ch)->actual_trigger_player : (e))
+
+#define CAN_GROW_INTO(e)                                               \
+       (e == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
+
+#define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition)                        \
+               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
+                                       (condition)))
+
+#define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition)             \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID) ||      \
                                        (condition)))
 
-#if 0
-#define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
+#define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition)             \
+               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
+                                        Feld[x][y] == EL_ACID) ||      \
+                                       (condition)))
+
+#define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition)             \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        (condition) ||                  \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
+                                        Feld[x][y] == EL_ACID) ||      \
                                        (DONT_COLLIDE_WITH(e) &&        \
                                         IS_PLAYER(x, y) &&             \
                                         !PLAYER_ENEMY_PROTECTED(x, y))))
-#else
+
+#if 0
 #define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        (condition) ||                  \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
                                        (DONT_COLLIDE_WITH(e) &&        \
                                         IS_PLAYER(x, y) &&             \
                                         !PLAYER_ENEMY_PROTECTED(x, y))))
 #endif
 
-#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)
-
-#define ELEMENT_CAN_ENTER_FIELD_OR_ACID(e, x, y)                       \
-       ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, (Feld[x][y] == EL_ACID))
+       ELEMENT_CAN_ENTER_FIELD_BASE_4(e, 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))
+#if 1
+#define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
+#else
+#define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, Feld[x][y] == EL_ACID)
+#endif
 
 #if 0
 #define ENEMY_CAN_ENTER_FIELD(e, x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
-#else
-#define ENEMY_CAN_ENTER_FIELD(e, x, y) ELEMENT_CAN_ENTER_FIELD_BASE(e, x, y, 0)
 #endif
 
-#define YAMYAM_CAN_ENTER_FIELD(x, y)                                   \
+#define ENEMY_CAN_ENTER_FIELD(e, x, y)                                 \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
+
+#if 1
+
+#define YAMYAM_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
+
+#define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y)                           \
+       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
+
+#define PACMAN_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
+
+#define PIG_CAN_ENTER_FIELD(e, x, y)                                   \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
+
+#define PENGUIN_CAN_ENTER_FIELD(e, x, y)                               \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN ||\
+                                                IS_FOOD_PENGUIN(Feld[x][y])))
+#define DRAGON_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
+
+#define MOLE_CAN_ENTER_FIELD(e, x, y, condition)                       \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
+
+#define SPRING_CAN_ENTER_FIELD(e, x, y)                                        \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
+
+#else
+
+#define YAMYAM_CAN_ENTER_FIELD(e, x, y)                                        \
                (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
-                                       (CAN_MOVE_INTO_ACID(EL_YAMYAM) && \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID) ||      \
                                        Feld[x][y] == EL_DIAMOND))
 
-#define DARK_YAMYAM_CAN_ENTER_FIELD(x, y)                              \
+#define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y)                           \
                (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
-                                       (CAN_MOVE_INTO_ACID(EL_DARK_YAMYAM) &&\
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID) ||      \
                                        IS_FOOD_DARK_YAMYAM(Feld[x][y])))
 
-#define PACMAN_CAN_ENTER_FIELD(x, y)                                   \
+#define PACMAN_CAN_ENTER_FIELD(e, x, y)                                        \
                (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
-                                       (CAN_MOVE_INTO_ACID(EL_PACMAN) && \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID) ||      \
                                        IS_AMOEBOID(Feld[x][y])))
 
-#define PIG_CAN_ENTER_FIELD(x, y)                                      \
+#define PIG_CAN_ENTER_FIELD(e, x, y)                                   \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(EL_PIG) &&  \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID) ||      \
                                        IS_FOOD_PIG(Feld[x][y])))
 
-#define PENGUIN_CAN_ENTER_FIELD(x, y)                                  \
+#define PENGUIN_CAN_ENTER_FIELD(e, x, y)                               \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(EL_PENGUIN) && \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID) ||      \
                                        IS_FOOD_PENGUIN(Feld[x][y]) ||  \
                                        Feld[x][y] == EL_EXIT_OPEN))
 
-#define DRAGON_CAN_ENTER_FIELD(x, y)                                   \
+#define DRAGON_CAN_ENTER_FIELD(e, x, y)                                        \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(EL_DRAGON) && \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID)))
 
-#define MOLE_CAN_ENTER_FIELD(x, y, condition)                          \
+#define MOLE_CAN_ENTER_FIELD(e, x, y, condition)                       \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(EL_MOLE) && \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID) ||      \
                                        (condition)))
 
-#define SPRING_CAN_ENTER_FIELD(x, y)                                   \
+#define SPRING_CAN_ENTER_FIELD(e, x, y)                                        \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(EL_SPRING) && \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
                                         Feld[x][y] == EL_ACID)))
 
+#endif
+
 #define GROUP_NR(e)            ((e) - EL_GROUP_START)
 #define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
 #define IS_IN_GROUP(e, nr)     (element_info[e].in_group[nr] == TRUE)
 #endif
 
 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y)                                \
-       ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
+       ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
 
 #define IN_LEV_FIELD_AND_IS_FREE(x, y)  (IN_LEV_FIELD(x, y) &&  IS_FREE(x, y))
 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
 
+#define ACCESS_FROM(e, d)              (element_info[e].access_direction &(d))
+#define IS_WALKABLE_FROM(e, d)         (IS_WALKABLE(e)   && ACCESS_FROM(e, d))
+#define IS_PASSABLE_FROM(e, d)         (IS_PASSABLE(e)   && ACCESS_FROM(e, d))
+#define IS_ACCESSIBLE_FROM(e, d)       (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
+
 /* game button identifiers */
 #define GAME_CTRL_ID_STOP              0
 #define GAME_CTRL_ID_PAUSE             1
@@ -241,18 +299,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, CH_PAGE_ANY)
+#define CheckElementChangeBySide(x, y, e, te, ev, s)                   \
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s, CH_PAGE_ANY)
+#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);
@@ -527,7 +607,7 @@ struct
   int element;
   int direction;
 }
-tube_access[] =
+access_direction_list[] =
 {
   { EL_TUBE_ANY,               MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
   { EL_TUBE_VERTICAL,                               MV_UP | MV_DOWN },
@@ -541,7 +621,19 @@ tube_access[] =
   { EL_TUBE_RIGHT_UP,                    MV_RIGHT | MV_UP           },
   { EL_TUBE_RIGHT_DOWN,                          MV_RIGHT |         MV_DOWN },
 
-  { EL_UNDEFINED,              0                                    }
+  { EL_SP_PORT_LEFT,                     MV_RIGHT                   },
+  { EL_SP_PORT_RIGHT,          MV_LEFT                              },
+  { EL_SP_PORT_UP,                                          MV_DOWN },
+  { EL_SP_PORT_DOWN,                                MV_UP           },
+  { EL_SP_PORT_HORIZONTAL,     MV_LEFT | MV_RIGHT                   },
+  { EL_SP_PORT_VERTICAL,                            MV_UP | MV_DOWN },
+  { EL_SP_PORT_ANY,            MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
+  { EL_SP_GRAVITY_PORT_LEFT,             MV_RIGHT                   },
+  { EL_SP_GRAVITY_PORT_RIGHT,  MV_LEFT                              },
+  { EL_SP_GRAVITY_PORT_UP,                                  MV_DOWN },
+  { EL_SP_GRAVITY_PORT_DOWN,                        MV_UP           },
+
+  { EL_UNDEFINED,              MV_NO_MOVING                         }
 };
 
 static unsigned long trigger_events[MAX_NUM_ELEMENTS];
@@ -890,7 +982,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);
 }
@@ -902,7 +994,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);
@@ -910,9 +1002,10 @@ 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)
@@ -1136,6 +1229,10 @@ static void InitGameEngine()
     ei->change->post_change_function = ch_delay->post_change_function;
 
     ei->change_events |= CH_EVENT_BIT(CE_DELAY);
+
+#if 1
+    SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
+#endif
   }
 
 #if 1
@@ -1176,6 +1273,19 @@ static void InitGameEngine()
   }
 #endif
 
+  /* ---------- 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;
+    }
+  }
+
   /* ---------- initialize trigger events ---------------------------------- */
 
   /* initialize trigger events information */
@@ -1240,14 +1350,14 @@ 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;
-       element_info[i].push_delay_random = 0;
+       element_info[i].push_delay_fixed  = 6;  /* just enough to escape ... */
+       element_info[i].push_delay_random = 0;  /* ... from falling zonk     */
       }
     }
   }
@@ -1267,6 +1377,7 @@ static void InitGameEngine()
     element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
   }
 
+#if 0
   /* ---------- initialize move dig/leave ---------------------------------- */
 
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
@@ -1274,6 +1385,7 @@ static void InitGameEngine()
     element_info[i].can_leave_element = FALSE;
     element_info[i].can_leave_element_last = FALSE;
   }
+#endif
 
   /* ---------- initialize gem count --------------------------------------- */
 
@@ -1289,15 +1401,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;
 }
 
 
@@ -1336,6 +1448,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;
@@ -1370,6 +1483,7 @@ void InitGame()
     player->use_murphy_graphic = FALSE;
 
     player->block_last_field = FALSE;
+    player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
 
     player->actual_frame_counter = 0;
 
@@ -1379,6 +1493,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;
@@ -1454,6 +1569,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);
@@ -1477,6 +1593,7 @@ void InitGame()
   TimeFrames = 0;
   TimePlayed = 0;
   TimeLeft = level.time;
+  TapeTime = 0;
 
   ScreenMovDir = MV_NO_MOVING;
   ScreenMovPos = 0;
@@ -1519,6 +1636,7 @@ 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;
 
@@ -1527,7 +1645,7 @@ void InitGame()
 
       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;
@@ -1566,23 +1684,32 @@ 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 0
+      if (i == EL_BLACK_ORB)
+       element_info[i].ignition_delay = 0;
+#else
       if (i == EL_BLACK_ORB)
        element_info[i].ignition_delay = 1;
+#endif
     }
 
-    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 */
@@ -1611,6 +1738,10 @@ void InitGame()
          some_player->present = FALSE;
          some_player->active = FALSE;
 
+#if 0
+         player->element_nr = some_player->element_nr;
+#endif
+
          StorePlayer[jx][jy] = player->element_nr;
          player->jx = player->last_jx = jx;
          player->jy = player->last_jy = jy;
@@ -1764,7 +1895,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))
@@ -1975,6 +2106,12 @@ void InitMovDir(int x, int y)
        else if (move_pattern == MV_ALONG_LEFT_SIDE ||
                 move_pattern == MV_ALONG_RIGHT_SIDE)
        {
+#if 1
+         /* use random direction as default start direction */
+         if (game.engine_version >= VERSION_IDENT(3,1,0,2))
+           MovDir[x][y] = 1 << RND(4);
+#endif
+
          for (i = 0; i < NUM_DIRECTIONS; i++)
          {
            int x1 = x + xy[i][0];
@@ -2385,6 +2522,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;
@@ -2515,17 +2656,16 @@ void RelocatePlayer(int x, int y, int element_raw)
   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 old_jx, old_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)
   {
@@ -2547,13 +2687,124 @@ void RelocatePlayer(int x, int y, int element_raw)
     player->is_moving = FALSE;
   }
 
+  old_jx = player->jx;
+  old_jy = player->jy;
+
   Feld[x][y] = element;
   InitPlayerField(x, y, element, TRUE);
 
-  if (player == local_player)
+  if (player != local_player)  /* do not visually relocate other players */
+    return;
+
+  if (level.instant_relocation)
+  {
+#if 1
+    int offset = (setup.scroll_delay ? 3 : 0);
+    int jx = local_player->jx;
+    int jy = local_player->jy;
+
+    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);
+
+      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);
+
+      if ((player->MovDir == MV_UP  && scroll_y > jy - MIDPOSY + offset) ||
+         (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
+       scroll_y = jy - MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
+
+      /* don't scroll over playfield boundaries */
+      if (scroll_x < SBX_Left || scroll_x > SBX_Right)
+       scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
+
+      /* don't scroll over playfield boundaries */
+      if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
+       scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+    }
+#else
+    scroll_x += (local_player->jx - old_jx);
+    scroll_y += (local_player->jy - old_jy);
+
+    /* don't scroll over playfield boundaries */
+    if (scroll_x < SBX_Left || scroll_x > SBX_Right)
+      scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
+
+    /* don't scroll over playfield boundaries */
+    if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
+      scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+#endif
+
+    RedrawPlayfield(TRUE, 0,0,0,0);
+  }
+  else
   {
+#if 1
+#if 0
+    int offset = (setup.scroll_delay ? 3 : 0);
+    int jx = local_player->jx;
+    int jy = local_player->jy;
+#endif
+    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;
+      int fx = FX, fy = FY;
+
+      scroll_xx = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
+                  local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+                  local_player->jx - MIDPOSX);
+
+      scroll_yy = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+                  local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+                  local_player->jy - MIDPOSY);
+
+      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 1
+      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
+       break;
+#else
+      if (scroll_xx == scroll_x && scroll_yy == scroll_y)
+       break;
+#endif
+
+      scroll_x -= dx;
+      scroll_y -= dy;
+
+      fx += dx * TILEX / 2;
+      fy += dy * TILEY / 2;
+
+      ScrollLevel(dx, dy);
+      DrawAllPlayers();
+
+      /* scroll in two steps of half tile size to make things smoother */
+      BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
+      FlushDisplay();
+      Delay(wait_delay_value);
+
+      /* scroll second step to align at full tile size */
+      BackToFront();
+      Delay(wait_delay_value);
+    }
+#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;
@@ -2570,6 +2821,14 @@ 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 1
+      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
+       break;
+#else
+      if (scroll_xx == scroll_x && scroll_yy == scroll_y)
+       break;
+#endif
+
       scroll_x -= dx;
       scroll_y -= dy;
 
@@ -2588,6 +2847,7 @@ void RelocatePlayer(int x, int y, int element_raw)
       BackToFront();
       Delay(wait_delay_value);
     }
+#endif
   }
 }
 
@@ -2620,15 +2880,20 @@ void Explode(int ex, int ey, int phase, int mode)
   {
     int center_element = Feld[ex][ey];
 
+#if 0
+    printf("::: start explosion %d,%d [%d]\n", ex, ey, FrameCounter);
+#endif
+
 #if 0
     /* --- This is only really needed (and now handled) in "Impact()". --- */
     /* do not explode moving elements that left the explode field in time */
     if (game.engine_version >= VERSION_IDENT(2,2,0,7) &&
-       center_element == EL_EMPTY && (mode == EX_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)
       PlayLevelSoundAction(ex, ey, ACTION_EXPLODING);
 
     /* remove things displayed in background while burning dynamite */
@@ -2644,19 +2909,42 @@ void Explode(int ex, int ey, int phase, int mode)
     }
 
 #if 1
+
+#if 1
+    last_phase = element_info[center_element].explosion_delay + 1;
+#else
     last_phase = element_info[center_element].explosion_delay;
 #endif
 
+#if 0
+    printf("::: %d -> %d\n", center_element, last_phase);
+#endif
+#endif
+
     for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
     {
       int xx = x - ex + 1;
       int yy = y - ey + 1;
       int element;
 
+#if 1
+#if 1
+      if (!IN_LEV_FIELD(x, y) ||
+         (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
+         (mode == EX_TYPE_CROSS      && (x != ex && y != ey)))
+       continue;
+#else
+      if (!IN_LEV_FIELD(x, y) ||
+         (mode != EX_TYPE_NORMAL && (x != ex || y != ey)))
+       continue;
+#endif
+#else
       if (!IN_LEV_FIELD(x, y) ||
-         ((mode != EX_NORMAL || center_element == EL_AMOEBA_TO_DIAMOND) &&
+         ((mode != EX_TYPE_NORMAL ||
+           center_element == EL_AMOEBA_TO_DIAMOND) &&
           (x != ex || y != ey)))
        continue;
+#endif
 
       element = Feld[x][y];
 
@@ -2745,8 +3033,13 @@ void Explode(int ex, int ey, int phase, int mode)
            break;
        }
 
+#if 1
+       if (PLAYERINFO(ex, ey)->use_murphy_graphic)
+         Store[x][y] = EL_EMPTY;
+#else
        if (game.emulation == EMU_SUPAPLEX)
          Store[x][y] = EL_EMPTY;
+#endif
       }
       else if (center_element == EL_MOLE)
        Store[x][y] = EL_EMERALD_RED;
@@ -2787,7 +3080,7 @@ void Explode(int ex, int ey, int phase, int mode)
        Store[x][y] = EL_EMPTY;
 
       if (x != ex || y != ey ||
-         center_element == EL_AMOEBA_TO_DIAMOND || mode == EX_BORDER)
+         center_element == EL_AMOEBA_TO_DIAMOND || mode == EX_TYPE_BORDER)
        Store2[x][y] = element;
 
 #if 0
@@ -2820,6 +3113,15 @@ void Explode(int ex, int ey, int phase, int mode)
 #if 1
       ExplodeDelay[x][y] = last_phase;
 #endif
+
+#if 0
+#if 1
+      GfxFrame[x][y] = 0;      /* animation does not start until next frame */
+#else
+      GfxFrame[x][y] = -1;     /* animation does not start until next frame */
+#endif
+#endif
+
       Stop[x][y] = TRUE;
     }
 
@@ -2836,6 +3138,15 @@ 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 */
+#endif
+
+#if 0
+  printf(":X: phase == %d [%d]\n", phase, GfxFrame[x][y]);
+#endif
+
 #if 1
   last_phase = ExplodeDelay[x][y];
 #endif
@@ -2866,6 +3177,10 @@ void Explode(int ex, int ey, int phase, int mode)
   if (IS_PLAYER(x, y))
     border_element = StorePlayer[x][y];
 
+#if 0
+  printf("::: phase == %d\n", phase);
+#endif
+
   if (phase == element_info[border_element].ignition_delay ||
       phase == last_phase)
   {
@@ -2955,6 +3270,14 @@ void Explode(int ex, int ey, int phase, int mode)
   {
     int element;
 
+#if 0
+  printf("::: done: phase == %d\n", phase);
+#endif
+
+#if 0
+    printf("::: explosion %d,%d done [%d]\n", x, y, FrameCounter);
+#endif
+
     element = Feld[x][y] = Store[x][y];
     Store[x][y] = Store2[x][y] = 0;
     GfxElement[x][y] = EL_UNDEFINED;
@@ -2986,7 +3309,7 @@ void Explode(int ex, int ey, int phase, int mode)
 #if 1
     /* !!! not needed !!! */
 #if 1
-    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]) && Feld[x][y] != EL_MOLE)
       InitMovDir(x, y);
 #else
@@ -3022,7 +3345,20 @@ void Explode(int ex, int ey, int phase, int mode)
                   stored == EL_SP_INFOTRON ? IMG_SP_EXPLOSION_INFOTRON :
                   IMG_SP_EXPLOSION);
 #endif
+#if 1
+    int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+#else
     int frame = getGraphicAnimationFrame(graphic, phase - delay);
+#endif
+
+#if 0
+  printf("::: phase == %d [%d]\n", phase, GfxFrame[x][y]);
+#endif
+
+#if 0
+    printf("::: %d / %d [%d - %d]\n",
+          GfxFrame[x][y], phase - delay, phase, delay);
+#endif
 
 #if 0
     printf("::: %d ['%s'] -> %d\n", GfxElement[x][y],
@@ -3071,7 +3407,7 @@ 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 < NUM_DIRECTIONS; i++)
   {
@@ -3090,14 +3426,18 @@ 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);
 
+#if 1
+      if (element != EL_EMPTY && element != EL_EXPLOSION &&
+         !CAN_GROW_INTO(element) && !dynabomb_xl)
+       break;
+#else
       /* !!! 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 &&
+         element != EL_SAND && !dynabomb_xl)
        break;
+#endif
     }
   }
 }
@@ -3150,7 +3490,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:
@@ -3164,23 +3504,25 @@ void Bang(int x, int y)
     case EL_PENGUIN:
     case EL_LAMP:
     case EL_LAMP_ACTIVE:
-
 #if 1
     case EL_AMOEBA_TO_DIAMOND:
 #endif
-
       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))
+      if (CAN_EXPLODE_CROSS(element))
+#if 1
+       Explode(x, y, EX_PHASE_START, EX_TYPE_CROSS);
+#else
        DynaExplode(x, y);
+#endif
       else if (CAN_EXPLODE_1X1(element))
-       Explode(x, y, EX_PHASE_START, EX_CENTER);
+       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;
   }
 
@@ -3573,7 +3915,7 @@ void Impact(int x, int y)
   boolean object_hit = FALSE;
   boolean impact = (lastline || object_hit);
   int element = Feld[x][y];
-  int smashed = EL_UNDEFINED;
+  int smashed = EL_STEELWALL;
 
   if (!lastline)       /* check if element below was hit */
   {
@@ -3605,6 +3947,7 @@ void Impact(int x, int y)
     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))
@@ -3620,11 +3963,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);
 
@@ -3739,6 +4084,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;
@@ -3765,17 +4112,29 @@ 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);
+
+#if 1
+         /* !!! TEST ONLY !!! */
+         CheckElementChangeBySide(x, y + 1, smashed, element,
+                                  CE_SWITCHED, CH_SIDE_TOP);
+         CheckTriggeredElementChangeBySide(x, y + 1, smashed,
+                                           CE_OTHER_IS_SWITCHING,CH_SIDE_TOP);
+#else
+         CheckTriggeredElementChangeBySide(x, y + 1, smashed,
+                                           CE_OTHER_IS_SWITCHING,CH_SIDE_TOP);
+         CheckElementChangeBySide(x, y + 1, smashed, element,
+                                  CE_SWITCHED, CH_SIDE_TOP);
+#endif
        }
       }
       else
       {
-       CheckElementChange(x, y + 1, smashed, CE_SMASHED);
+       CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
       }
     }
   }
@@ -3862,6 +4221,7 @@ inline static void TurnRoundExt(int x, int y)
     else if (element == EL_BD_BUTTERFLY)     /* && MovDir[x][y] == left_dir) */
       MovDelay[x][y] = 1;
   }
+#if 0
   else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
           element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
   {
@@ -3880,26 +4240,56 @@ inline static void TurnRoundExt(int x, int y)
     else if (element == EL_BD_FIREFLY)     /* && MovDir[x][y] == right_dir) */
       MovDelay[x][y] = 1;
   }
-  else if (element == EL_YAMYAM)
+#else
+  else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
   {
-    boolean can_turn_left  = YAMYAM_CAN_ENTER_FIELD(left_x, left_y);
-    boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(right_x, right_y);
-
-    if (can_turn_left && can_turn_right)
-      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
-    else if (can_turn_left)
-      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
-    else if (can_turn_right)
-      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
-    else
-      MovDir[x][y] = back_dir;
+    TestIfBadThingTouchesOtherBadThing(x, y);
 
-    MovDelay[x][y] = 16 + 16 * RND(3);
+    if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
+      MovDir[x][y] = left_dir;
+    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
+      MovDir[x][y] = right_dir;
+
+    if (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;
+  }
+#endif
+  else if (element == EL_YAMYAM)
+  {
+    boolean can_turn_left  = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
+
+    if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+    else if (can_turn_left)
+      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+    else if (can_turn_right)
+      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+    else
+      MovDir[x][y] = back_dir;
+
+    MovDelay[x][y] = 16 + 16 * RND(3);
   }
   else if (element == EL_DARK_YAMYAM)
   {
-    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);
@@ -3914,8 +4304,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);
@@ -3930,9 +4320,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);
@@ -3993,9 +4383,9 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (element == EL_DRAGON)
   {
-    boolean can_turn_left  = DRAGON_CAN_ENTER_FIELD(left_x, left_y);
-    boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(right_x, right_y);
-    boolean can_move_on    = DRAGON_CAN_ENTER_FIELD(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);
 
@@ -4043,17 +4433,17 @@ inline static void TurnRoundExt(int x, int y)
   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)
@@ -4076,12 +4466,12 @@ inline static void TurnRoundExt(int x, int y)
   {
 #if 0
     if (MovDir[x][y] & MV_HORIZONTAL &&
-       !SPRING_CAN_ENTER_FIELD(move_x, move_y))
+       !SPRING_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = MV_NO_MOVING;
 #else
     if (MovDir[x][y] & MV_HORIZONTAL &&
-       (!SPRING_CAN_ENTER_FIELD(move_x, move_y) ||
-        SPRING_CAN_ENTER_FIELD(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;
 #endif
 
@@ -4119,7 +4509,13 @@ inline static void TurnRoundExt(int x, int y)
       }
     }
 
+#if 1
+    if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
+       (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
+        game.engine_version < VERSION_IDENT(3,1,0,0)))
+#else
     if (element == EL_ROBOT && ZX >= 0 && ZY >= 0)
+#endif
     {
       attr_x = ZX;
       attr_y = ZY;
@@ -4188,14 +4584,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;
@@ -4217,14 +4613,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;
@@ -4663,12 +5059,16 @@ void StartMoving(int x, int y)
 #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)) ||
+
             (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))))
+             (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)))
 
 #else
 #if 1
@@ -4830,9 +5230,15 @@ void StartMoving(int x, int y)
 #endif
 
 #if 1
-    if (game.engine_version >= VERSION_IDENT(3,0,9,0) &&
+
+#if 1
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+       CheckCollision[x][y] && IN_LEV_FIELD_AND_NOT_FREE(newx, newy))
+#else
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
        WasJustMoving[x][y] && IN_LEV_FIELD(newx, newy) &&
        (Feld[newx][newy] == EL_BLOCKED || IS_PLAYER(newx, newy)))
+#endif
     {
 #if 0
       printf("::: element %d '%s' WasJustMoving %d [%d, %d, %d, %d]\n",
@@ -5000,11 +5406,30 @@ 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
+
+#if 0
+           if (ChangeDelay[xx][yy])
+             printf("::: !!! [%d]\n", (IS_MOVING(xx, yy) ||
+                                       Feld[xx][yy] == EL_BLOCKED));
+#endif
 
+#if 1
+           ChangeDelay[xx][yy] = 0;
+#endif
            Feld[xx][yy] = EL_FLAMES;
            if (IN_SCR_FIELD(sx, sy))
            {
@@ -5061,7 +5486,7 @@ void StartMoving(int x, int y)
     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,0,8,0)))
+             game.engine_version >= VERSION_IDENT(3,1,0,0)))
 #else
     else if (CAN_MOVE_INTO_ACID(element) && MovDir[x][y] == MV_DOWN &&
             IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID)
@@ -5201,11 +5626,27 @@ 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 1
+#if 1
+      Store[newx][newy] = EL_EMPTY;
+      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
+       Store[newx][newy] = element_info[element].move_leave_element;
+#else
+      Store[newx][newy] = EL_EMPTY;
+      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)) ||
+         element_info[element].move_leave_type == LEAVE_TYPE_UNLIMITED)
+       Store[newx][newy] = element_info[element].move_leave_element;
+#endif
+#else
       if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
        element_info[element].can_leave_element = TRUE;
+#endif
 
       if (move_pattern & MV_MAZE_RUNNER_STYLE)
       {
@@ -5258,11 +5699,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;
        }
@@ -5394,6 +5849,7 @@ void StartMoving(int x, int y)
 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);
@@ -5498,16 +5954,34 @@ void ContinueMoving(int x, int y)
   {
     element = Feld[newx][newy] = EL_ACID;
   }
+#if 0
+  else if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
+          ei->move_leave_element != EL_EMPTY &&
+          (ei->move_leave_type == LEAVE_TYPE_UNLIMITED ||
+           Store[x][y] != EL_EMPTY))
+  {
+    /* some elements can leave other elements behind after moving */
+
+    Feld[x][y] = ei->move_leave_element;
+    InitField(x, y, FALSE);
+
+    if (GFX_CRUMBLED(Feld[x][y]))
+      DrawLevelFieldCrumbledSandNeighbours(x, y);
+  }
+#endif
 
-  Store[x][y] = 0;
+  Store[x][y] = EL_EMPTY;
   MovPos[x][y] = MovDir[x][y] = 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;
@@ -5525,6 +5999,22 @@ void ContinueMoving(int x, int y)
   ResetGfxAnimation(x, y);     /* reset animation values for old field */
 
 #if 1
+  if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
+      ei->move_leave_element != EL_EMPTY &&
+      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED ||
+       stored != EL_EMPTY))
+  {
+    /* some elements can leave other elements behind after moving */
+
+    Feld[x][y] = ei->move_leave_element;
+    InitField(x, y, FALSE);
+
+    if (GFX_CRUMBLED(Feld[x][y]))
+      DrawLevelFieldCrumbledSandNeighbours(x, y);
+  }
+#endif
+
+#if 0
   /* some elements can leave other elements behind after moving */
   if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
       ei->move_leave_element != EL_EMPTY &&
@@ -5559,9 +6049,18 @@ void ContinueMoving(int x, int y)
     MovDir[newx][newy] = 0;
   */
 
+#if 0
   if (!CAN_MOVE(element) ||
       (CAN_FALL(element) && direction == MV_DOWN))
     GfxDir[x][y] = MovDir[newx][newy] = 0;
+#else
+  if (!CAN_MOVE(element) ||
+      (CAN_FALL(element) && direction == MV_DOWN &&
+       (element == EL_SPRING ||
+       element_info[element].move_pattern == MV_WHEN_PUSHED ||
+       element_info[element].move_pattern == MV_WHEN_DROPPED)))
+    GfxDir[x][y] = MovDir[newx][newy] = 0;
+#endif
 
 #endif
 #endif
@@ -5586,10 +6085,16 @@ void ContinueMoving(int x, int y)
 
   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 */
@@ -5607,6 +6112,26 @@ void ContinueMoving(int x, int y)
       (newy == lev_fieldy - 1 || !IS_FREE(x, newy + 1)))
     Impact(x, newy);
 
+#if 1
+  if (pushed_by_player)
+  {
+    static int trigger_sides[4] =
+    {
+      CH_SIDE_RIGHT,   /* moving left  */
+      CH_SIDE_LEFT,    /* moving right */
+      CH_SIDE_BOTTOM,  /* moving up    */
+      CH_SIDE_TOP,     /* moving down  */
+    };
+    int dig_side = trigger_sides[MV_DIR_BIT(direction)];
+    struct PlayerInfo *player = PLAYERINFO(x, y);
+
+    CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
+                              player->index_bit, dig_side);
+    CheckTriggeredElementChangeByPlayer(newx,newy,element,CE_OTHER_GETS_PUSHED,
+                                       player->index_bit, dig_side);
+  }
+#endif
+
 #if 1
   TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
 #endif
@@ -5627,8 +6152,8 @@ void ContinueMoving(int x, int y)
     int hitting_element = Feld[newx][newy];
 
     /* !!! fix side (direction) orientation here and elsewhere !!! */
-    CheckElementSideChange(newx, newy, hitting_element,
-                          direction, CE_HITTING_SOMETHING, -1);
+    CheckElementChangeBySide(newx, newy, hitting_element, CE_HITTING_SOMETHING,
+                            direction);
 
 #if 0
     if (IN_LEV_FIELD(nextx, nexty))
@@ -5645,8 +6170,8 @@ void ContinueMoving(int x, int y)
       {
        int i;
 
-       CheckElementSideChange(nextx, nexty, touched_element,
-                              opposite_direction, CE_HIT_BY_SOMETHING, -1);
+       CheckElementChangeBySide(nextx, nexty, touched_element,
+                                CE_HIT_BY_SOMETHING, opposite_direction);
 
        if (IS_CUSTOM_ELEMENT(hitting_element) &&
            HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_HITTING))
@@ -5658,11 +6183,11 @@ void ContinueMoving(int x, int y)
 
            if (change->can_change &&
                change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) &&
-               change->sides & touched_side &&
+               change->trigger_side & touched_side &&
                change->trigger_element == touched_element)
            {
-             CheckElementSideChange(newx, newy, hitting_element,
-                                    CH_SIDE_ANY, CE_OTHER_IS_HITTING, i);
+             CheckElementChangeByPage(newx, newy, hitting_element,
+                                      touched_element, CE_OTHER_IS_HITTING,i);
              break;
            }
          }
@@ -5678,11 +6203,11 @@ void ContinueMoving(int x, int y)
 
            if (change->can_change &&
                change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) &&
-               change->sides & hitting_side &&
+               change->trigger_side & hitting_side &&
                change->trigger_element == hitting_element)
            {
-             CheckElementSideChange(nextx, nexty, touched_element,
-                                    CH_SIDE_ANY, CE_OTHER_GETS_HIT, i);
+             CheckElementChangeByPage(nextx, nexty, touched_element,
+                                      hitting_element, CE_OTHER_GETS_HIT, i);
              break;
            }
          }
@@ -5998,6 +6523,15 @@ void AmoebeAbleger(int ax, int ay)
     if (!IN_LEV_FIELD(x, y))
       return;
 
+#if 1
+    if (IS_FREE(x, y) ||
+       CAN_GROW_INTO(Feld[x][y]) ||
+       Feld[x][y] == EL_QUICKSAND_EMPTY)
+    {
+      newax = x;
+      neway = y;
+    }
+#else
     /* !!! 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)
@@ -6005,6 +6539,7 @@ void AmoebeAbleger(int ax, int ay)
       newax = x;
       neway = y;
     }
+#endif
 
     if (newax == ax && neway == ay)
       return;
@@ -6023,6 +6558,16 @@ void AmoebeAbleger(int ax, int ay)
       if (!IN_LEV_FIELD(x, y))
        continue;
 
+#if 1
+      if (IS_FREE(x, y) ||
+         CAN_GROW_INTO(Feld[x][y]) ||
+         Feld[x][y] == EL_QUICKSAND_EMPTY)
+      {
+       newax = x;
+       neway = y;
+       break;
+      }
+#else
       /* !!! 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)
@@ -6031,13 +6576,18 @@ void AmoebeAbleger(int ax, int ay)
        neway = y;
        break;
       }
+#endif
       else if (IS_PLAYER(x, y))
        waiting_for_player = TRUE;
     }
 
     if (newax == ax && neway == ay)            /* amoeba cannot grow */
     {
+#if 1
+      if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
+#else
       if (i == 4 && (!waiting_for_player || game.emulation == EMU_BOULDERDASH))
+#endif
       {
        Feld[ax][ay] = EL_AMOEBA_DEAD;
        DrawLevelField(ax, ay);
@@ -6168,6 +6718,20 @@ void Life(int ax, int ay)
        changed = TRUE;
       }
     }
+#if 1
+    else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
+    {                                  /* free border field */
+      if (nachbarn >= life[2] && nachbarn <= life[3])
+      {
+       Feld[xx][yy] = element;
+       MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
+       if (!Stop[xx][yy])
+         DrawLevelField(xx, yy);
+       Stop[xx][yy] = TRUE;
+       changed = TRUE;
+      }
+    }
+#else
     /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
     else if (IS_FREE(xx, yy) || Feld[xx][yy] == EL_SAND)
     {                                  /* free border field */
@@ -6181,6 +6745,7 @@ void Life(int ax, int ay)
        changed = TRUE;
       }
     }
+#endif
   }
 
   if (changed)
@@ -6206,7 +6771,12 @@ static void StopRobotWheel(int x, int y)
 
 static void InitTimegateWheel(int x, int y)
 {
+#if 1
+  ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
+#else
+  /* another brainless, "type style" bug ... :-( */
   ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+#endif
 }
 
 static void RunTimegateWheel(int x, int y)
@@ -6638,11 +7208,19 @@ static void ChangeElementNowExt(int x, int y, int target_element)
 static boolean ChangeElementNow(int x, int y, int element, int page)
 {
   struct ElementChangeInfo *change = &element_info[element].change_page[page];
+  int target_element;
 
   /* 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] == CH_EVENT_BIT(CE_DELAY))
+  {
+    /* reset actual trigger element and player */
+    change->actual_trigger_element = EL_EMPTY;
+    change->actual_trigger_player = EL_PLAYER_1;
+  }
+
   /* do not change already changed elements with same change event */
 #if 0
   if (Changed[x][y] & ChangeEvent[x][y])
@@ -6654,7 +7232,7 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
 
   Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
 
-  CheckTriggeredElementChange(x, y, Feld[x][y], CE_OTHER_IS_CHANGING);
+  CheckTriggeredElementChangeByPage(x,y,Feld[x][y], CE_OTHER_IS_CHANGING,page);
 
   if (change->explode)
   {
@@ -6663,35 +7241,40 @@ 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_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;
       }
@@ -6701,39 +7284,74 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
       if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
        e = MovingOrBlocked2Element(ex, ey);
 
+#if 1
+
+#if 1
+      is_empty = (IS_FREE(ex, ey) || (IS_FREE_OR_PLAYER(ex, ey) &&
+                                     IS_WALKABLE(content_element)));
+#else
+      is_empty = (IS_FREE(ex, ey) || (IS_PLAYER(ex, ey) &&
+                                     IS_WALKABLE(content_element)));
+#endif
+      is_diggable = (is_empty || IS_DIGGABLE(e));
+      is_collectible = (is_empty || IS_COLLECTIBLE(e));
+      is_removable = (is_diggable || is_collectible);
+      is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
+
+      can_replace[xx][yy] =
+       ((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
+        (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));
+
+      if (!can_replace[xx][yy])
+       complete_replace = FALSE;
+#else
+      empty_for_element = (IS_FREE(ex, ey) || (IS_FREE_OR_PLAYER(ex, ey) &&
+                                              IS_WALKABLE(content_element)));
+#if 1
+      half_destructible = (empty_for_element || IS_DIGGABLE(e));
+#else
       half_destructible = (IS_FREE(ex, ey) || IS_DIGGABLE(e));
+#endif
 
-      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)))
+      if ((change->replace_when <= CP_WHEN_EMPTY  && !empty_for_element) ||
+         (change->replace_when <= CP_WHEN_DIGGABLE && !half_destructible) ||
+         (change->replace_when <= CP_WHEN_DESTRUCTIBLE && IS_INDESTRUCTIBLE(e)))
       {
-       can_change[xx][yy] = FALSE;
-       complete_change = FALSE;
+       can_replace[xx][yy] = FALSE;
+       complete_replace = FALSE;
       }
+#endif
     }
 
-    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(ex, ey, target_element);
 
          something_has_changed = TRUE;
 
@@ -6749,7 +7367,9 @@ 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(x, y, target_element);
 
     PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
   }
@@ -6763,9 +7383,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",
@@ -6774,8 +7393,18 @@ 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 +
@@ -6806,6 +7435,8 @@ static void ChangeElement(int x, int y, int page)
     {
       page = ChangePage[x][y];
       ChangePage[x][y] = -1;
+
+      change = &ei->change_page[page];
     }
 
 #if 0
@@ -6828,12 +7459,15 @@ 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)))
     return FALSE;
@@ -6853,16 +7487,11 @@ static boolean CheckTriggeredElementSideChange(int lx, int ly,
       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
-         )
+         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)))
@@ -6873,6 +7502,9 @@ static boolean CheckTriggeredElementSideChange(int lx, int ly,
        change_element = TRUE;
        page = j;
 
+       change->actual_trigger_element = trigger_element;
+       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+
        break;
       }
     }
@@ -6899,15 +7531,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;
@@ -6919,7 +7549,21 @@ static boolean CheckElementSideChange(int x, int y, int element, int side,
   }
 
 #if 1
-  if (page < 0)
+  if (Feld[x][y] != element)   /* check if element has already changed */
+  {
+#if 0
+    printf("::: %d ('%s') != %d ('%s') [%d]\n",
+          Feld[x][y], element_info[Feld[x][y]].token_name,
+          element, element_info[element].token_name,
+          trigger_event);
+#endif
+
+    return FALSE;
+  }
+#endif
+
+#if 1
+  if (trigger_page < 0)
   {
     boolean change_element = FALSE;
     int i;
@@ -6930,10 +7574,14 @@ static boolean CheckElementSideChange(int x, int y, int element, int side,
 
       if (change->can_change &&
          change->events & CH_EVENT_BIT(trigger_event) &&
-         change->sides & side)
+         change->trigger_side & trigger_side &&
+         change->trigger_player & trigger_player)
       {
        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;
       }
@@ -6942,30 +7590,33 @@ 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];
+
+    change->actual_trigger_element = trigger_element;
+    change->actual_trigger_player = EL_PLAYER_1;       /* unused */
+  }
 
 #else
 
   /* !!! this check misses pages with same event, but different side !!! */
 
-  if (page < 0)
-    page = element_info[element].event_page_nr[trigger_event];
+  if (trigger_page < 0)
+    trigger_page = element_info[element].event_page_nr[trigger_event];
 
-  if (!(element_info[element].change_page[page].sides & side))
+  if (!(element_info[element].change_page[trigger_page].trigger_side & trigger_side))
     return FALSE;
 #endif
 
   ChangeDelay[x][y] = 1;
   ChangeEvent[x][y] = CH_EVENT_BIT(trigger_event);
-  ChangeElement(x, y, page);
+  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;
@@ -7168,6 +7819,11 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
     printf("::: player %d acts [%d]\n", player->index_nr, FrameCounter);
 #endif
 
+#if 0
+    /* !!! TEST !!! */
+    if (player->MovPos == 0)
+      CheckGravityMovement(player);
+#endif
     if (button1)
       snapped = SnapField(player, dx, dy);
     else
@@ -7205,7 +7861,7 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 
     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);
@@ -7286,7 +7942,7 @@ static void PlayerActions(struct PlayerInfo *player, byte player_action)
 
     DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
     SnapField(player, 0, 0);
-    CheckGravityMovement(player);
+    CheckGravityMovementWhenNotMoving(player);
 
     if (player->MovPos == 0)
       InitPlayerGfxAnimation(player, ACTION_DEFAULT, player->MovDir);
@@ -7323,7 +7979,7 @@ void GameActions()
   action_delay_value =
     (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
 
-  if (tape.playing && tape.index_search && !tape.pausing)
+  if (tape.playing && tape.warp_forward && !tape.pausing)
     action_delay_value = 0;
 
   /* ---------- main game synchronization point ---------- */
@@ -7366,6 +8022,21 @@ void GameActions()
 
   recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
 
+#if 1
+  if (recorded_player_action == NULL && tape.pausing)
+    return;
+#endif
+
+#if 0
+  printf("::: %d\n", stored_player[0].action);
+#endif
+
+#if 0
+  if (recorded_player_action != NULL)
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].action = recorded_player_action[i];
+#endif
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     summarized_player_action |= stored_player[i].action;
@@ -7382,46 +8053,112 @@ void GameActions()
   if (!options.network && !setup.team_mode)
     local_player->effective_action = summarized_player_action;
 
+#if 1
+  if (recorded_player_action != NULL)
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action = recorded_player_action[i];
+#endif
+
+#if 1
   for (i = 0; i < MAX_PLAYERS; i++)
   {
-    int actual_player_action = stored_player[i].effective_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];
-
-    tape_action[i] = PlayerActions(&stored_player[i], actual_player_action);
+    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 */
-
-    ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
   }
 
-#if 1
+  /* only save actions from input devices, but not programmed actions */
   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++;
+  {
+    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
 
 #if 1
-  /* for downwards 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
+    /* overwrite programmed action with tape action */
+    if (stored_player[i].programmed_action)
+      actual_player_action = stored_player[i].programmed_action;
+#endif
+
+#if 0
+    if (stored_player[i].programmed_action)
+      printf("::: %d\n", stored_player[i].programmed_action);
+#endif
+
+    if (recorded_player_action)
+    {
+#if 0
+      if (stored_player[i].programmed_action &&
+         stored_player[i].programmed_action != recorded_player_action[i])
+       printf("::: %d: %d <-> %d\n", i,
+              stored_player[i].programmed_action, recorded_player_action[i]);
+#endif
+
+#if 0
+      actual_player_action = recorded_player_action[i];
+#endif
+    }
+
+#if 0
+    /* overwrite tape action with programmed action */
+    if (stored_player[i].programmed_action)
+      actual_player_action = stored_player[i].programmed_action;
+#endif
+
+#if 0
+    if (i == 0)
+      printf("::: action: %d: %x [%d]\n",
+            stored_player[i].MovPos, actual_player_action, FrameCounter);
+#endif
+
+#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 0
+  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
+     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
      "spring push bug" which is used in older levels and might be wanted to be
      used also in newer levels, but in this case the buggy pushing code is only
      affecting the "spring" element and no other elements */
@@ -7480,6 +8217,8 @@ void GameActions()
       WasJustMoving[x][y]--;
     if (WasJustFalling[x][y] > 0)
       WasJustFalling[x][y]--;
+    if (CheckCollision[x][y] > 0)
+      CheckCollision[x][y]--;
 
     GfxFrame[x][y]++;
 
@@ -7626,7 +8365,7 @@ void GameActions()
       CheckDynamite(x, y);
 #if 0
     else if (element == EL_EXPLOSION && !game.explosions_delayed)
-      Explode(x, y, ExplodePhase[x][y], EX_NORMAL);
+      Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
 #endif
     else if (element == EL_AMOEBA_GROWING)
       AmoebeWaechst(x, y);
@@ -7709,6 +8448,21 @@ void GameActions()
 #endif
       element = Feld[x][y];
 
+#if 1
+      if (!IS_PLAYER(x,y) &&
+         (element == EL_EMPTY ||
+          CAN_GROW_INTO(element) ||
+          element == EL_QUICKSAND_EMPTY ||
+          element == EL_ACID_SPLASH_LEFT ||
+          element == EL_ACID_SPLASH_RIGHT))
+      {
+       if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
+           (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
+           (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
+           (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
+         Feld[x][y] = EL_AMOEBA_DROP;
+      }
+#else
       /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
       if (!IS_PLAYER(x,y) &&
          (element == EL_EMPTY ||
@@ -7723,6 +8477,7 @@ void GameActions()
            (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
          Feld[x][y] = EL_AMOEBA_DROP;
       }
+#endif
 
       random = random * 129 + 1;
     }
@@ -7742,9 +8497,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;
@@ -7824,39 +8579,44 @@ void GameActions()
   if (TimeFrames >= FRAMES_PER_SECOND)
   {
     TimeFrames = 0;
-    TimePlayed++;
+    TapeTime++;
 
-    for (i = 0; i < MAX_PLAYERS; i++)
+    if (!level.use_step_counter)
     {
-      struct PlayerInfo *player = &stored_player[i];
+      TimePlayed++;
 
-      if (SHIELD_ON(player))
+      for (i = 0; i < MAX_PLAYERS; i++)
       {
-       player->shield_normal_time_left--;
+       struct PlayerInfo *player = &stored_player[i];
 
-       if (player->shield_deadly_time_left > 0)
-         player->shield_deadly_time_left--;
-      }
-    }
+       if (SHIELD_ON(player))
+       {
+         player->shield_normal_time_left--;
 
-    if (tape.recording || tape.playing)
-      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TimePlayed);
+         if (player->shield_deadly_time_left > 0)
+           player->shield_deadly_time_left--;
+       }
+      }
 
-    if (TimeLeft > 0)
-    {
-      TimeLeft--;
+      if (TimeLeft > 0)
+      {
+       TimeLeft--;
 
-      if (TimeLeft <= 10 && setup.time_limit)
-       PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+       if (TimeLeft <= 10 && setup.time_limit)
+         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
 
-      DrawGameValue_Time(TimeLeft);
+       DrawGameValue_Time(TimeLeft);
 
-      if (!TimeLeft && setup.time_limit)
-       for (i = 0; i < MAX_PLAYERS; i++)
-         KillHero(&stored_player[i]);
+       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 */
-      DrawGameValue_Time(TimePlayed);
+
+    if (tape.recording || tape.playing)
+      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
   }
 
   DrawAllPlayers();
@@ -7990,6 +8750,7 @@ void ScrollLevel(int dx, int dy)
   redraw_mask |= REDRAW_FIELD;
 }
 
+#if 0
 static boolean canEnterSupaplexPort(int x, int y, int dx, int dy)
 {
   int nextx = x + dx, nexty = y + dy;
@@ -8021,39 +8782,195 @@ static boolean canEnterSupaplexPort(int x, int y, int dx, int dy)
 
   return TRUE;
 }
+#endif
+
+static boolean canFallDown(struct PlayerInfo *player)
+{
+  int jx = player->jx, jy = player->jy;
+
+  return (IN_LEV_FIELD(jx, jy + 1) &&
+         (IS_FREE(jx, jy + 1) ||
+          (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];
+
+  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)));
+}
+
+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;
+#if 0
+  int nextx = newx + dx;
+  int nexty = newy + dy;
+#endif
+
+#if 1
+  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+         (IS_DIGGABLE(Feld[newx][newy]) ||
+          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+          canPassField(newx, newy, move_dir)));
+#else
+  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+         (IS_DIGGABLE(Feld[newx][newy]) ||
+          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+          (IS_PASSABLE_FROM(Feld[newx][newy], opposite_dir) &&
+           !CAN_MOVE(Feld[newx][newy]) &&
+           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)))));
+#endif
+}
 
 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);
+#if 1
+    int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
+    int move_dir_vertical   = player->effective_action & MV_VERTICAL;
+#else
+    int move_dir_horizontal = player->action & MV_HORIZONTAL;
+    int move_dir_vertical   = player->action & MV_VERTICAL;
+#endif
+
+#if 1
+    boolean player_is_snapping = player->effective_action & JOY_BUTTON_1;
+#else
+    boolean player_is_snapping = player->action & JOY_BUTTON_1;
+#endif
+
+    int jx = player->jx, jy = player->jy;
+
+    boolean player_is_moving_to_valid_field =
+      (!player_is_snapping &&
+       (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
+       canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
+
+#if 0
     int move_dir =
-      (player->last_move_dir & (MV_LEFT | MV_RIGHT) ?
+      (player->last_move_dir & MV_HORIZONTAL ?
        (move_dir_vertical ? move_dir_vertical : move_dir_horizontal) :
        (move_dir_horizontal ? move_dir_horizontal : move_dir_vertical));
-    int jx = player->jx, jy = player->jy;
+#endif
+
+#if 0
+    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 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));
+    int nextx = new_jx + dx, nexty = new_jy + dy;
+#endif
+
+#if 1
+
+#if 1
+    boolean player_can_fall_down = canFallDown(player);
+#else
+    boolean player_can_fall_down =
+      (IN_LEV_FIELD(jx, jy + 1) &&
+       (IS_FREE(jx, jy + 1) ||
+       (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)));
+#endif
+
+#else
+    boolean player_can_fall_down =
+      (IN_LEV_FIELD(jx, jy + 1) &&
+       (IS_FREE(jx, jy + 1)));
+#endif
+
+#if 0
     boolean player_is_moving_to_valid_field =
-      (IN_LEV_FIELD(new_jx, new_jy) &&
+      (
+#if 1
+       !player_is_snapping &&
+#endif
+
+#if 1
+       IN_LEV_FIELD(new_jx, new_jy) &&
+       (IS_DIGGABLE(Feld[new_jx][new_jy]) ||
+       (IS_SP_PORT(Feld[new_jx][new_jy]) &&
+        element_info[Feld[new_jx][new_jy]].access_direction & opposite_dir &&
+        IN_LEV_FIELD(nextx, nexty) &&
+        element_info[Feld[nextx][nexty]].access_direction & move_dir))
+#else
+       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))));
+        canEnterSupaplexPort(new_jx, new_jy, dx, dy)))
     /* !!! extend EL_SAND to anything diggable !!! */
+#endif
+       );
+#endif
 
+#if 0
     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)));
+       (IS_WALKABLE(Feld[jx][jy]) && !ACCESS_FROM(Feld[jx][jy], MV_DOWN)));
+#endif
 
-    if (field_under_player_is_free &&
+#if 0
+    printf("::: checking gravity NOW [%d, %d, %d] [%d] [%d / %d] ...\n",
+          player_can_fall_down,
+          player_is_standing_on_valid_field,
+          player_is_moving_to_valid_field,
+          (player_is_moving_to_valid_field ? Feld[new_jx][new_jy] : -1),
+          player->effective_action,
+          player->can_fall_into_acid);
+#endif
+
+    if (player_can_fall_down &&
+#if 0
        !player_is_standing_on_valid_field &&
+#endif
        !player_is_moving_to_valid_field)
+    {
+#if 0
+      printf("::: setting programmed_action to MV_DOWN [%d,%d - %d] ...\n",
+            jx, jy, FrameCounter);
+#endif
+
+      player->programmed_action = MV_DOWN;
+    }
+  }
+}
+
+static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
+{
+#if 1
+  return CheckGravityMovement(player);
+#endif
+
+  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->programmed_action = MV_DOWN;
   }
 }
@@ -8069,7 +8986,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
                          int dx, int dy, int real_dx, int real_dy)
 {
 #if 0
-  static int change_sides[4][2] =
+  static int trigger_sides[4][2] =
   {
     /* enter side        leave side */
     { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* moving left  */
@@ -8081,8 +8998,8 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
                        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];
+  int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
+  int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
 #endif
   int jx = player->jx, jy = player->jy;
   int new_jx = jx + dx, new_jy = jy + dy;
@@ -8146,7 +9063,9 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 
   player->step_counter++;
 
+#if 0
   player->drop_delay = 0;
+#endif
 
   PlayerVisit[jx][jy] = FrameCounter;
 
@@ -8155,18 +9074,17 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 #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);
+    CheckTriggeredElementChangeBySide(jx, jy, Feld[jx][jy], CE_OTHER_GETS_LEFT,
+                                     leave_side);
+    CheckElementChangeBySide(jx,jy, Feld[jx][jy],CE_LEFT_BY_PLAYER,leave_side);
   }
 
   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);
+    CheckTriggeredElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
+                                     CE_OTHER_GETS_ENTERED, enter_side);
+    CheckElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
+                            CE_ENTERED_BY_PLAYER, enter_side);
   }
 #endif
 
@@ -8206,11 +9124,21 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       !tape.playing)
     return FALSE;
 #else
+
+#if 1
+  if (!FrameReached(&player->move_delay, player->move_delay_value))
+    return FALSE;
+#else
   if (!FrameReached(&player->move_delay, player->move_delay_value) &&
       !(tape.playing && tape.file_version < FILE_VERSION_2_0))
     return FALSE;
 #endif
 
+#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;
 
@@ -8241,7 +9169,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);
@@ -8273,7 +9201,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);
 
@@ -8285,13 +9213,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);
 
@@ -8303,7 +9231,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;
       }
@@ -8357,9 +9285,14 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     player->is_dropping = FALSE;
 
 
+#if 0
+    /* !!! ENABLE THIS FOR OLD VERSIONS !!! */
+
 #if 1
+    if (game.engine_version < VERSION_IDENT(3,1,0,0))
+#endif
     {
-      static int change_sides[4][2] =
+      static int trigger_sides[4][2] =
       {
        /* enter side           leave side */
        { CH_SIDE_RIGHT,        CH_SIDE_LEFT    },      /* moving left  */
@@ -8368,25 +9301,27 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
        { 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];
+      int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
+      int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
 
 #if 1
+      /* !!! TEST ONLY !!! */
       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);
-      }
+       CheckElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+                                  CE_LEFT_BY_PLAYER,
+                                  player->index_bit, leave_side);
+
+      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+                                         CE_OTHER_GETS_LEFT,
+                                         player->index_bit, leave_side);
 
       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);
-      }
+       CheckElementChangeByPlayer(jx, jy, Feld[jx][jy], CE_ENTERED_BY_PLAYER,
+                                  player->index_bit, enter_side);
+
+      CheckTriggeredElementChangeByPlayer(jx, jy, Feld[jx][jy],
+                                         CE_OTHER_GETS_ENTERED,
+                                         player->index_bit, enter_side);
 #endif
 
     }
@@ -8396,7 +9331,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
   }
   else
   {
-    CheckGravityMovement(player);
+    CheckGravityMovementWhenNotMoving(player);
 
     /*
     player->last_move_dir = MV_NO_MOVING;
@@ -8503,6 +9438,50 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        player->LevelSolved = player->GameOver = TRUE;
     }
 
+#if 1
+    /* !!! ENABLE THIS FOR NEW VERSIONS !!! */
+    /* this breaks one level: "machine", level 000 */
+#if 0
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+#endif
+    {
+      static int trigger_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 = trigger_sides[MV_DIR_BIT(move_direction)][0];
+      int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
+      int old_jx = last_jx;
+      int old_jy = last_jy;
+
+#if 1
+      /* !!! TEST ONLY !!! */
+      if (IS_CUSTOM_ELEMENT(Feld[old_jx][old_jy]))
+       CheckElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+                                  CE_LEFT_BY_PLAYER,
+                                  player->index_bit, leave_side);
+
+      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+                                         CE_OTHER_GETS_LEFT,
+                                         player->index_bit, leave_side);
+
+      if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
+       CheckElementChangeByPlayer(jx, jy, Feld[jx][jy], CE_ENTERED_BY_PLAYER,
+                                  player->index_bit, enter_side);
+
+      CheckTriggeredElementChangeByPlayer(jx, jy, Feld[jx][jy],
+                                         CE_OTHER_GETS_ENTERED,
+                                         player->index_bit, enter_side);
+#endif
+
+    }
+#endif
+
     if (game.engine_version >= VERSION_IDENT(3,0,7,0))
     {
       TestIfHeroTouchesBadThing(jx, jy);
@@ -8515,6 +9494,42 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        RemoveHero(player);
     }
 
+    if (level.use_step_counter)
+    {
+      int i;
+
+      TimePlayed++;
+
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+
+       if (SHIELD_ON(player))
+       {
+         player->shield_normal_time_left--;
+
+         if (player->shield_deadly_time_left > 0)
+           player->shield_deadly_time_left--;
+       }
+      }
+
+      if (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);
@@ -8558,7 +9573,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    */
@@ -8580,8 +9595,8 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
   {
     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))
@@ -8589,6 +9604,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))
@@ -8598,25 +9615,45 @@ 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);
+#if 1
+      /* !!! TEST ONLY !!! */
+      CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
+                                player->index_bit, border_side);
+      CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
+                                         CE_OTHER_GETS_TOUCHED,
+                                         player->index_bit, border_side);
+#else
+      CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
+                                         CE_OTHER_GETS_TOUCHED,
+                                         player->index_bit, border_side);
+      CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
+                                player->index_bit, border_side);
+#endif
     }
     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);
+#if 1
+      /* !!! TEST ONLY !!! */
+      CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
+                                player->index_bit, center_side);
+      CheckTriggeredElementChangeByPlayer(x, y, center_element,
+                                         CE_OTHER_GETS_TOUCHED,
+                                         player->index_bit, center_side);
+#else
+      CheckTriggeredElementChangeByPlayer(x, y, center_element,
+                                         CE_OTHER_GETS_TOUCHED,
+                                         player->index_bit, center_side);
+      CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
+                                player->index_bit, center_side);
+#endif
 
       break;
     }
@@ -8632,7 +9669,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    */
@@ -8650,14 +9687,15 @@ void TestIfElementTouchesCustomElement(int x, int y)
   boolean change_center_element = FALSE;
   int center_element_change_page = 0;
   int center_element = Feld[x][y];     /* should always be non-moving! */
+  int border_trigger_element;
   int i, j;
 
   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))
@@ -8684,7 +9722,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
 
        if (change->can_change &&
            change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
-           change->sides & border_side &&
+           change->trigger_side & border_side &&
 #if 1
            IS_EQUAL_OR_IN_GROUP(border_element, change->trigger_element)
 #else
@@ -8694,6 +9732,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
        {
          change_center_element = TRUE;
          center_element_change_page = j;
+         border_trigger_element = border_element;
 
          break;
        }
@@ -8711,7 +9750,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
 
        if (change->can_change &&
            change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
-           change->sides & center_side &&
+           change->trigger_side & center_side &&
 #if 1
            IS_EQUAL_OR_IN_GROUP(center_element, change->trigger_element)
 #else
@@ -8719,8 +9758,12 @@ void TestIfElementTouchesCustomElement(int x, int y)
 #endif
            )
        {
-         CheckElementSideChange(xx, yy, border_element, CH_SIDE_ANY,
-                                CE_OTHER_IS_TOUCHING, j);
+#if 0
+         printf("::: border_element %d, %d\n", x, y);
+#endif
+
+         CheckElementChangeByPage(xx, yy, border_element, center_element,
+                                  CE_OTHER_IS_TOUCHING, j);
          break;
        }
       }
@@ -8728,8 +9771,14 @@ void TestIfElementTouchesCustomElement(int x, int y)
   }
 
   if (change_center_element)
-    CheckElementSideChange(x, y, center_element, CH_SIDE_ANY,
-                          CE_OTHER_IS_TOUCHING, center_element_change_page);
+  {
+#if 0
+    printf("::: center_element %d, %d\n", x, y);
+#endif
+
+    CheckElementChangeByPage(x, y, center_element, border_trigger_element,
+                            CE_OTHER_IS_TOUCHING, center_element_change_page);
+  }
 }
 
 void TestIfElementHitsCustomElement(int x, int y, int direction)
@@ -8738,6 +9787,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
   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) &&
@@ -8754,15 +9804,20 @@ 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,
+                          CE_HITTING_SOMETHING, 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 ||
@@ -8775,8 +9830,8 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
     {
       int i;
 
-      CheckElementSideChange(hitx, hity, touched_element,
-                            opposite_direction, CE_HIT_BY_SOMETHING, -1);
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_HIT_BY_SOMETHING, opposite_direction);
 
       if (IS_CUSTOM_ELEMENT(hitting_element) &&
          HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_HITTING))
@@ -8788,7 +9843,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
 
          if (change->can_change &&
              change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) &&
-             change->sides & touched_side &&
+             change->trigger_side & touched_side &&
          
 #if 1
              IS_EQUAL_OR_IN_GROUP(touched_element, change->trigger_element)
@@ -8797,8 +9852,8 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
 #endif
              )
          {
-           CheckElementSideChange(x, y, hitting_element,
-                                  CH_SIDE_ANY, CE_OTHER_IS_HITTING, i);
+           CheckElementChangeByPage(x, y, hitting_element, touched_element,
+                                    CE_OTHER_IS_HITTING, i);
            break;
          }
        }
@@ -8814,7 +9869,114 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
 
          if (change->can_change &&
              change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) &&
-             change->sides & hitting_side &&
+             change->trigger_side & hitting_side &&
+#if 1
+             IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
+#else
+             change->trigger_element == hitting_element
+#endif
+             )
+         {
+           CheckElementChangeByPage(hitx, hity, touched_element,
+                                    hitting_element, CE_OTHER_GETS_HIT, i);
+           break;
+         }
+       }
+      }
+    }
+  }
+}
+
+#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) &&
+                       (!IS_MOVING(hitx, hity) ||
+                        MovDir[hitx][hity] != direction ||
+                        ABS(MovPos[hitx][hity]) <= TILEY / 2));
+#endif
+
+  if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
+    return;
+
+#if 0
+  if (IN_LEV_FIELD(hitx, hity) && !object_hit)
+    return;
+#endif
+
+  touched_element = (IN_LEV_FIELD(hitx, hity) ?
+                    MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
+
+  CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                          EP_CAN_SMASH_EVERYTHING, direction);
+
+  if (IN_LEV_FIELD(hitx, hity))
+  {
+    int opposite_direction = MV_DIR_OPPOSITE(direction);
+    int hitting_side = direction;
+    int touched_side = opposite_direction;
+#if 0
+    int touched_element = MovingOrBlocked2Element(hitx, hity);
+#endif
+#if 1
+    boolean object_hit = (!IS_MOVING(hitx, hity) ||
+                         MovDir[hitx][hity] != direction ||
+                         ABS(MovPos[hitx][hity]) <= TILEY / 2);
+
+    object_hit = TRUE;
+#endif
+
+    if (object_hit)
+    {
+      int i;
+
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_SMASHED_BY_SOMETHING, opposite_direction);
+
+      if (IS_CUSTOM_ELEMENT(hitting_element) &&
+         HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_SMASHING))
+      {
+       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_SMASHING) &&
+             change->trigger_side & touched_side &&
+         
+#if 1
+             IS_EQUAL_OR_IN_GROUP(touched_element, change->trigger_element)
+#else
+             change->trigger_element == touched_element
+#endif
+             )
+         {
+           CheckElementChangeByPage(x, y, hitting_element, touched_element,
+                                    CE_OTHER_IS_SMASHING, i);
+           break;
+         }
+       }
+      }
+
+      if (IS_CUSTOM_ELEMENT(touched_element) &&
+         HAS_ANY_CHANGE_EVENT(touched_element, CE_OTHER_GETS_SMASHED))
+      {
+       for (i = 0; i < element_info[touched_element].num_change_pages; i++)
+       {
+         struct ElementChangeInfo *change =
+           &element_info[touched_element].change_page[i];
+
+         if (change->can_change &&
+             change->events & CH_EVENT_BIT(CE_OTHER_GETS_SMASHED) &&
+             change->trigger_side & hitting_side &&
 #if 1
              IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
 #else
@@ -8822,8 +9984,8 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
 #endif
              )
          {
-           CheckElementSideChange(hitx, hity, touched_element,
-                                  CH_SIDE_ANY, CE_OTHER_GETS_HIT, i);
+           CheckElementChangeByPage(hitx, hity, touched_element,
+                                    hitting_element, CE_OTHER_GETS_SMASHED,i);
            break;
          }
        }
@@ -8831,10 +9993,12 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
     }
   }
 }
+#endif
 
 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
 {
   int i, kill_x = -1, kill_y = -1;
+  int bad_element = -1;
   static int test_xy[4][2] =
   {
     { 0, -1 },
@@ -8856,6 +10020,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
 
     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;
 
@@ -8876,6 +10041,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;
     }
   }
@@ -8886,10 +10053,18 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
     {
       struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
 
+#if 1
+      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);
+#else
       if (player->shield_deadly_time_left > 0)
        Bang(kill_x, kill_y);
       else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
        KillHero(player);
+#endif
     }
     else
       Bang(good_x, good_y);
@@ -8978,10 +10153,18 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
     {
       struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
 
+#if 1
+      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);
+#else
       if (player->shield_deadly_time_left > 0)
        Bang(bad_x, bad_y);
       else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
        KillHero(player);
+#endif
     }
     else
       Bang(kill_x, kill_y);
@@ -9163,7 +10346,7 @@ 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] =
+  static int trigger_sides[4] =
   {
     CH_SIDE_RIGHT,     /* moving left  */
     CH_SIDE_LEFT,      /* moving right */
@@ -9181,7 +10364,7 @@ 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 = trigger_sides[MV_DIR_BIT(move_direction)];
   int old_element = Feld[jx][jy];
   int element;
 
@@ -9250,8 +10433,10 @@ int DigField(struct PlayerInfo *player,
 
 #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];
@@ -9262,6 +10447,7 @@ int DigField(struct PlayerInfo *player,
 
   switch (element)
   {
+#if 0
     case EL_SP_PORT_LEFT:
     case EL_SP_PORT_RIGHT:
     case EL_SP_PORT_UP:
@@ -9323,8 +10509,13 @@ int DigField(struct PlayerInfo *player,
       DOUBLE_PLAYER_SPEED(player);
 #endif
 
+#if 0
+      printf("::: passing port %d,%d [%d]\n", x, y, FrameCounter);
+#endif
+
       PlayLevelSound(x, y, SND_CLASS_SP_PORT_PASSING);
       break;
+#endif
 
 #if 0
     case EL_TUBE_ANY:
@@ -9373,19 +10564,32 @@ int DigField(struct PlayerInfo *player,
 
     default:
 
+#if 1
+      if (IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
+#else
       if (IS_WALKABLE(element))
+#endif
       {
        int sound_action = ACTION_WALKING;
 
-       if (!(element_info[element].access_direction & opposite_direction))
+#if 0
+       if (!ACCESS_FROM(element, opposite_direction))
          return MF_NO_ACTION;  /* field not accessible from this direction */
+#endif
 
-       if (element >= EL_GATE_1 && element <= EL_GATE_4)
+#if 1
+       if (element == EL_EMPTY_SPACE &&
+           game.gravity && !player->is_auto_moving &&
+           canFallDown(player) && move_direction != MV_DOWN)
+         return MF_NO_ACTION;  /* player cannot walk here due to gravity */
+#endif
+
+       if (IS_GATE(element))
        {
          if (!player->key[element - EL_GATE_1])
            return MF_NO_ACTION;
        }
-       else if (element >= EL_GATE_1_GRAY && element <= EL_GATE_4_GRAY)
+       else if (IS_GATE_GRAY(element))
        {
          if (!player->key[element - EL_GATE_1_GRAY])
            return MF_NO_ACTION;
@@ -9409,30 +10613,63 @@ int DigField(struct PlayerInfo *player,
 
        break;
       }
+#if 1
+      else if (IS_PASSABLE(element) && canPassField(x, y, move_direction))
+#else
       else if (IS_PASSABLE(element))
+#endif
       {
+#if 0
+       if (!canPassField(x, y, move_direction))
+         return MF_NO_ACTION;
+#else
+
+#if 0
+#if 1
+       if (!IN_LEV_FIELD(nextx, nexty) || IS_PLAYER(nextx, nexty) ||
+           !IS_WALKABLE_FROM(Feld[nextx][nexty], move_direction) ||
+           (!level.can_pass_to_walkable && !IS_FREE(nextx, nexty)))
+         return MF_NO_ACTION;
+#else
        if (!IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty))
          return MF_NO_ACTION;
+#endif
+#endif
 
+#if 1
+       if (!ACCESS_FROM(element, opposite_direction))
+         return MF_NO_ACTION;  /* field not accessible from this direction */
+#else
        if (IS_CUSTOM_ELEMENT(element) &&
-           !(element_info[element].access_direction & opposite_direction))
+           !ACCESS_FROM(element, opposite_direction))
          return MF_NO_ACTION;  /* field not accessible from this direction */
+#endif
 
 #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)
+#endif
+
+       if (IS_EM_GATE(element))
        {
          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)
+       else if (IS_EM_GATE_GRAY(element))
        {
          if (!player->key[element - EL_EM_GATE_1_GRAY])
            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;
+       }
 
        /* automatically move to the next field with double speed */
        player->programmed_action = move_direction;
@@ -9470,7 +10707,8 @@ int DigField(struct PlayerInfo *player,
 
        PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
 
-       CheckTriggeredElementChange(x, y, element, CE_OTHER_GETS_DIGGED);
+       CheckTriggeredElementChangeByPlayer(x, y, element,CE_OTHER_GETS_DIGGED,
+                                           player->index_bit, dig_side);
 
 #if 1
        if (mode == DF_SNAP)
@@ -9542,13 +10780,17 @@ int DigField(struct PlayerInfo *player,
          ShowEnvelope(element - EL_ENVELOPE_1);
 #endif
        }
-       else if (IS_DROPPABLE(element)) /* can be collected and dropped */
+       else if (IS_DROPPABLE(element) ||
+                IS_THROWABLE(element)) /* can be collected and dropped */
        {
          int i;
 
-         for (i = 0; i < element_info[element].collect_count; i++)
-           if (player->inventory_size < MAX_INVENTORY_SIZE)
-             player->inventory_element[player->inventory_size++] = element;
+         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;
 
          DrawGameValue_Dynamite(local_player->inventory_size);
        }
@@ -9565,7 +10807,9 @@ int DigField(struct PlayerInfo *player,
        RaiseScoreElement(element);
        PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
 
-       CheckTriggeredElementChange(x, y, element, CE_OTHER_GETS_COLLECTED);
+       CheckTriggeredElementChangeByPlayer(x, y, element,
+                                           CE_OTHER_GETS_COLLECTED,
+                                           player->index_bit, dig_side);
 
 #if 1
        if (mode == DF_SNAP)
@@ -9721,10 +10965,23 @@ int DigField(struct PlayerInfo *player,
        else
          player->push_delay_value = -1;        /* get new value later */
 
-       CheckTriggeredElementSideChange(x, y, element, dig_side,
-                                       CE_OTHER_GETS_PUSHED);
-       CheckElementSideChange(x, y, element, dig_side,
-                              CE_PUSHED_BY_PLAYER, -1);
+#if 1
+       /* check for element change _after_ element has been pushed! */
+#else
+
+#if 1
+      /* !!! TEST ONLY !!! */
+       CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
+                                  player->index_bit, dig_side);
+       CheckTriggeredElementChangeByPlayer(x, y, element,CE_OTHER_GETS_PUSHED,
+                                           player->index_bit, dig_side);
+#else
+       CheckTriggeredElementChangeByPlayer(x, y, element,CE_OTHER_GETS_PUSHED,
+                                           player->index_bit, dig_side);
+       CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
+                                  player->index_bit, dig_side);
+#endif
+#endif
 
        break;
       }
@@ -9828,15 +11085,34 @@ int DigField(struct PlayerInfo *player,
          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);
+#if 1
+         /* !!! TEST ONLY !!! */
+         CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
+                                    player->index_bit, dig_side);
+         CheckTriggeredElementChangeByPlayer(x, y, element,
+                                             CE_OTHER_IS_SWITCHING,
+                                             player->index_bit, dig_side);
+#else
+         CheckTriggeredElementChangeByPlayer(x, y, element,
+                                             CE_OTHER_IS_SWITCHING,
+                                             player->index_bit, dig_side);
+         CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
+                                    player->index_bit, dig_side);
+#endif
        }
 
-       CheckTriggeredElementSideChange(x, y, element, dig_side,
-                                       CE_OTHER_GETS_PRESSED);
-       CheckElementSideChange(x, y, element, dig_side,
-                              CE_PRESSED_BY_PLAYER, -1);
+#if 1
+       /* !!! TEST ONLY !!! (this breaks "machine", level 000) */
+       CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
+                                  player->index_bit, dig_side);
+       CheckTriggeredElementChangeByPlayer(x,y, element,CE_OTHER_GETS_PRESSED,
+                                           player->index_bit, dig_side);
+#else
+       CheckTriggeredElementChangeByPlayer(x,y, element,CE_OTHER_GETS_PRESSED,
+                                           player->index_bit, dig_side);
+       CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
+                                  player->index_bit, dig_side);
+#endif
       }
 
       return MF_NO_ACTION;
@@ -9859,8 +11135,13 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
                        dy == -1 ? MV_UP :
                        dy == +1 ? MV_DOWN : MV_NO_MOVING);
 
+#if 0
+  if (player->MovPos)
+    return FALSE;
+#else
   if (player->MovPos && game.engine_version >= VERSION_IDENT(2,2,0,0))
     return FALSE;
+#endif
 
   if (!player->active || !IN_LEV_FIELD(x, y))
     return FALSE;
@@ -9923,136 +11204,216 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 
 boolean DropElement(struct PlayerInfo *player)
 {
-  int jx = player->jx, jy = player->jy;
-  int old_element = Feld[jx][jy];
-  int new_element;
+  static int trigger_sides[4] =
+  {
+    CH_SIDE_LEFT,      /* dropping left  */
+    CH_SIDE_RIGHT,     /* dropping right */
+    CH_SIDE_TOP,       /* dropping up    */
+    CH_SIDE_BOTTOM,    /* dropping down  */
+  };
+  int old_element, new_element;
+  int dropx = player->jx, dropy = player->jy;
+  int drop_direction = player->MovDir;
+  int drop_side = trigger_sides[MV_DIR_BIT(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 (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 1
+  if (new_element == EL_UNDEFINED)
+    return FALSE;
+#else
+  if (player->inventory_size == 0 &&
+      player->inventory_infinite_element == EL_UNDEFINED &&
+      player->dynabombs_left == 0)
     return FALSE;
+#endif
 
   /* check if anything can be dropped at the current position */
   if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
     return FALSE;
 
   /* collected custom elements can only be dropped on empty fields */
+#if 1
+  if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
+    return FALSE;
+#else
   if (player->inventory_size > 0 &&
       IS_CUSTOM_ELEMENT(player->inventory_element[player->inventory_size - 1])
       && old_element != EL_EMPTY)
     return FALSE;
+#endif
 
   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;
+#if 0
+      new_element = player->inventory_element[player->inventory_size];
+#endif
 
-    Feld[jx][jy] = new_element;
+      DrawGameValue_Dynamite(local_player->inventory_size);
 
-    DrawGameValue_Dynamite(local_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;
+    }
+
+    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 */
+    Changed[dropx][dropy] = 0;         /* allow another change */
 #endif
 
-    CheckTriggeredElementChange(jx, jy, new_element, CE_OTHER_GETS_DROPPED);
-    CheckElementChange(jx, jy, new_element, CE_DROPPED_BY_PLAYER);
+#if 1
+    /* !!! TEST ONLY !!! */
+    CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
+                              player->index_bit, drop_side);
+    CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
+                                       CE_OTHER_GETS_DROPPED,
+                                       player->index_bit, drop_side);
+#else
+    CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
+                                       CE_OTHER_GETS_DROPPED,
+                                       player->index_bit, drop_side);
+    CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
+                              player->index_bit, drop_side);
+#endif
 
-    TestIfElementTouchesCustomElement(jx, jy);
+    TestIfElementTouchesCustomElement(dropx, dropy);
   }
   else         /* player is dropping a dyna bomb */
   {
     player->dynabombs_left--;
+
+#if 0
     new_element = EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr;
+#endif
 
-    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 1
 
-  if (Feld[jx][jy] == new_element)     /* uninitialized unless CE change */
+  if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
   {
 #if 1
-    InitField_WithBug1(jx, jy, FALSE);
+    InitField_WithBug1(dropx, dropy, FALSE);
 #else
-    InitField(jx, jy, FALSE);
-    if (CAN_MOVE(Feld[jx][jy]))
-      InitMovDir(jx, jy);
+    InitField(dropx, dropy, FALSE);
+    if (CAN_MOVE(Feld[dropx][dropy]))
+      InitMovDir(dropx, dropy);
 #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)
   {
+#if 0
     int move_stepsize = element_info[new_element].move_stepsize;
-    int direction, dx, dy, nextx, nexty;
+#endif
+    int move_direction, nextx, nexty;
 
     if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
-      MovDir[jx][jy] = player->MovDir;
+      MovDir[dropx][dropy] = drop_direction;
 
-    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;
+    move_direction = MovDir[dropx][dropy];
+    nextx = dropx + GET_DX_FROM_DIR(move_direction);
+    nexty = dropy + GET_DY_FROM_DIR(move_direction);
+
+#if 1
+      Changed[dropx][dropy] = 0;       /* allow another change */
+      CheckCollision[dropx][dropy] = 2;
+#else
 
     if (IN_LEV_FIELD(nextx, nexty) && IS_FREE(nextx, nexty))
     {
 #if 0
-      WasJustMoving[jx][jy] = 3;
+      WasJustMoving[dropx][dropy] = 3;
 #else
-      InitMovingField(jx, jy, direction);
-      ContinueMoving(jx, jy);
+#if 1
+      InitMovingField(dropx, dropy, move_direction);
+      ContinueMoving(dropx, dropy);
+#endif
 #endif
     }
+#if 1
     else
     {
-      Changed[jx][jy] = 0;             /* allow another change */
+      Changed[dropx][dropy] = 0;       /* allow another change */
 
 #if 1
-      TestIfElementHitsCustomElement(jx, jy, direction);
+      TestIfElementHitsCustomElement(dropx, dropy, move_direction);
 #else
-      CheckElementSideChange(jx, jy, new_element,
-                            direction, CE_HITTING_SOMETHING, -1);
+      CheckElementChangeBySide(dropx, dropy, new_element, touched_element,
+                              CE_HITTING_SOMETHING, move_direction);
 #endif
     }
+#endif
 
+#endif
+
+#if 0
     player->drop_delay = 2 * TILEX / move_stepsize + 1;
+#endif
   }
 
 #if 0
   player->drop_delay = 8 + 8 + 8;
 #endif
 
+#if 1
+  player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
+#endif
+
 #endif
 
   player->is_dropping = TRUE;
@@ -10276,7 +11637,19 @@ void RequestQuitGame(boolean ask_if_really_quit)
   }
   else
   {
+
+#if 1
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOff(TRUE);
+#endif
+
     OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
+
+#if 1
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOn();
+#endif
+
   }
 }