rnd-20060319-1-src
[rocksndiamonds.git] / src / game.c
index 052239346ffc65f545296b98070c120881393cf3..98751de41aaeca52086e4695ff2c67da64730a98 100644 (file)
 #define USE_NEW_DELAYED_ACTION         (USE_NEW_STUFF          * 1)
 #define USE_NEW_SNAP_DELAY             (USE_NEW_STUFF          * 1)
 #define USE_ONLY_ONE_CHANGE_PER_FRAME  (USE_NEW_STUFF          * 1)
+#define USE_ONE_MORE_CHANGE_PER_FRAME  (USE_NEW_STUFF          * 1)
+#define USE_FIXED_DONT_RUN_INTO                (USE_NEW_STUFF          * 1)
+#define USE_NEW_SPRING_BUMPER          (USE_NEW_STUFF          * 1)
+#define USE_STOP_CHANGED_ELEMENTS      (USE_NEW_STUFF          * 1)
+#define USE_ELEMENT_TOUCHING_BUGFIX    (USE_NEW_STUFF          * 1)
+#define USE_NEW_CONTINUOUS_SNAPPING    (USE_NEW_STUFF          * 1)
+
 #define USE_QUICKSAND_IMPACT_BUGFIX    (USE_NEW_STUFF          * 0)
 
 /* for DigField() */
 #define DF_SNAP                        2
 
 /* for MovePlayer() */
-#define MF_NO_ACTION           0
-#define MF_MOVING              1
-#define MF_ACTION              2
+#define MP_NO_ACTION           0
+#define MP_MOVING              1
+#define MP_ACTION              2
+#define MP_DONT_RUN_INTO       (MP_MOVING | MP_ACTION)
 
 /* for ScrollPlayer() */
 #define SCROLL_INIT            0
 #define DX_TIME2               (DX + XX_TIME2)
 #define DY_TIME                        (DY + YY_TIME)
 
+/* values for delayed check of falling and moving elements and for collision */
+#define CHECK_DELAY_MOVING     3
+#define CHECK_DELAY_FALLING    3
+#define CHECK_DELAY_COLLISION  2
+
 /* values for initial player move delay (initial delay counter value) */
 #define INITIAL_MOVE_DELAY_OFF -1
 #define INITIAL_MOVE_DELAY_ON  0
 #define GET_CE_DELAY_VALUE(c)  (   ((c)->delay_fixed) + \
                                 RND((c)->delay_random))
 
+#if 1
+#define GET_VALID_RUNTIME_ELEMENT(e)                                   \
+        ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
+#else
+#define GET_VALID_FILE_ELEMENT(e)                                      \
+       ((e) >= NUM_FILE_ELEMENTS ? EL_UNKNOWN : (e))
+#endif
+
 #define GET_TARGET_ELEMENT(e, ch)                                      \
-       ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element :     \
-        (e) == EL_TRIGGER_PLAYER  ? (ch)->actual_trigger_player : (e))
+       ((e) == EL_TRIGGER_PLAYER   ? (ch)->actual_trigger_player  :    \
+        (e) == EL_TRIGGER_ELEMENT  ? (ch)->actual_trigger_element :    \
+        (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value  : (e))
 
 #define CAN_GROW_INTO(e)                                               \
        ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
 #define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
        ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
 
+#define ANDROID_CAN_ENTER_FIELD(e, x, y)                               \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
+
+#define ANDROID_CAN_CLONE_FIELD(x, y)                                  \
+       (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
+                               CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
+
 #define ENEMY_CAN_ENTER_FIELD(e, x, y)                                 \
        ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
 #define SPRING_CAN_ENTER_FIELD(e, x, y)                                        \
        ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
+#define SPRING_CAN_BUMP_FROM_FIELD(x, y)                               \
+       (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER ||   \
+                               Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
+
+#if 0
 #define GROUP_NR(e)            ((e) - EL_GROUP_START)
-#define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
 #define IS_IN_GROUP(e, nr)     (element_info[e].in_group[nr] == TRUE)
 #define IS_IN_GROUP_EL(e, ge)  (IS_IN_GROUP(e, (ge) - EL_GROUP_START))
 
 #define IS_EQUAL_OR_IN_GROUP(e, ge)                                    \
        (IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge))
+#endif
+
+#define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
 
 #define CE_ENTER_FIELD_COND(e, x, y)                                   \
                (!IS_PLAYER(x, y) &&                                    \
@@ -264,6 +300,8 @@ static void TestIfElementSmashesCustomElement(int, int, int);
 #endif
 
 static void HandleElementChange(int, int, int);
+static void ExecuteCustomElementAction(int, int, int, int);
+static boolean ChangeElement(int, int, int, int);
 
 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
 #define CheckTriggeredElementChange(x, y, e, ev)                       \
@@ -295,6 +333,32 @@ static void PlayLevelMusic();
 static void MapGameButtons();
 static void HandleGameButtons(struct GadgetInfo *);
 
+int AmoebeNachbarNr(int, int);
+void AmoebeUmwandeln(int, int);
+void ContinueMoving(int, int);
+void Bang(int, int);
+void InitMovDir(int, int);
+void InitAmoebaNr(int, int);
+int NewHiScore(void);
+
+void TestIfGoodThingHitsBadThing(int, int, int);
+void TestIfBadThingHitsGoodThing(int, int, int);
+void TestIfPlayerTouchesBadThing(int, int);
+void TestIfPlayerRunsIntoBadThing(int, int, int);
+void TestIfBadThingTouchesPlayer(int, int);
+void TestIfBadThingRunsIntoPlayer(int, int, int);
+void TestIfFriendTouchesBadThing(int, int);
+void TestIfBadThingTouchesFriend(int, int);
+void TestIfBadThingTouchesOtherBadThing(int, int);
+
+void KillPlayer(struct PlayerInfo *);
+void BuryPlayer(struct PlayerInfo *);
+void RemovePlayer(struct PlayerInfo *);
+
+boolean SnapField(struct PlayerInfo *, int, int);
+boolean DropElement(struct PlayerInfo *);
+
+
 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 
 
@@ -321,6 +385,8 @@ static void RunTimegateWheel(int, int);
 static void InitMagicBallDelay(int, int);
 static void ActivateMagicBall(int, int);
 
+static void InitDiagonalMovingElement(int, int);
+
 struct ChangingElementInfo
 {
   int element;
@@ -494,6 +560,30 @@ static struct ChangingElementInfo change_delay_list[] =
     NULL,
     ActivateMagicBall
   },
+  {
+    EL_EMC_SPRING_BUMPER_ACTIVE,
+    EL_EMC_SPRING_BUMPER,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_DIAGONAL_SHRINKING,
+    EL_UNDEFINED,
+    0,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_DIAGONAL_GROWING,
+    EL_UNDEFINED,
+    0,
+    NULL,
+    NULL,
+    InitDiagonalMovingElement
+  },
 
   {
     EL_UNDEFINED,
@@ -626,6 +716,18 @@ static int playfield_scan_delta_y = 1;
                                     (x) >= 0 && (x) <= lev_fieldx - 1; \
                                     (x) += playfield_scan_delta_x)     \
 
+#ifdef DEBUG
+void DEBUG_SetMaximumDynamite()
+{
+  int i;
+
+  for (i = 0; i < MAX_INVENTORY_SIZE; i++)
+    if (local_player->inventory_size < MAX_INVENTORY_SIZE)
+      local_player->inventory_element[local_player->inventory_size++] =
+       EL_DYNAMITE;
+}
+#endif
+
 static void InitPlayfieldScanModeVars()
 {
   if (game.use_reverse_scan_direction)
@@ -654,6 +756,40 @@ static void InitPlayfieldScanMode(int mode)
   InitPlayfieldScanModeVars();
 }
 
+static int get_move_delay_from_stepsize(int move_stepsize)
+{
+  move_stepsize =
+    MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
+
+  /* make sure that stepsize value is always a power of 2 */
+  move_stepsize = (1 << log_2(move_stepsize));
+
+  return TILEX / move_stepsize;
+}
+
+static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
+                              boolean init_game)
+{
+  int move_delay = get_move_delay_from_stepsize(move_stepsize);
+  boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
+
+  /* do no immediately change move delay -- the player might just be moving */
+  player->move_delay_value_next = move_delay;
+
+  /* information if player can move must be set separately */
+  player->cannot_move = cannot_move;
+
+  if (init_game)
+  {
+    player->move_delay       = game.initial_move_delay;
+    player->move_delay_value = game.initial_move_delay_value;
+
+    player->move_delay_value_next = -1;
+
+    player->move_delay_reset_counter = 0;
+  }
+}
+
 void GetPlayerConfig()
 {
   if (!audio.sound_available)
@@ -740,6 +876,9 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
       else
       {
        stored_player[0].use_murphy = TRUE;
+
+       if (!level.use_artwork_element[0])
+         stored_player[0].artwork_element = EL_SP_MURPHY;
       }
 
       Feld[x][y] = EL_PLAYER_1;
@@ -835,41 +974,45 @@ static void InitField(int x, int y, boolean init_game)
        Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
       break;
 
+    case EL_BUG:
     case EL_BUG_RIGHT:
     case EL_BUG_UP:
     case EL_BUG_LEFT:
     case EL_BUG_DOWN:
-    case EL_BUG:
+    case EL_SPACESHIP:
     case EL_SPACESHIP_RIGHT:
     case EL_SPACESHIP_UP:
     case EL_SPACESHIP_LEFT:
     case EL_SPACESHIP_DOWN:
-    case EL_SPACESHIP:
+    case EL_BD_BUTTERFLY:
     case EL_BD_BUTTERFLY_RIGHT:
     case EL_BD_BUTTERFLY_UP:
     case EL_BD_BUTTERFLY_LEFT:
     case EL_BD_BUTTERFLY_DOWN:
-    case EL_BD_BUTTERFLY:
+    case EL_BD_FIREFLY:
     case EL_BD_FIREFLY_RIGHT:
     case EL_BD_FIREFLY_UP:
     case EL_BD_FIREFLY_LEFT:
     case EL_BD_FIREFLY_DOWN:
-    case EL_BD_FIREFLY:
     case EL_PACMAN_RIGHT:
     case EL_PACMAN_UP:
     case EL_PACMAN_LEFT:
     case EL_PACMAN_DOWN:
     case EL_YAMYAM:
+    case EL_YAMYAM_LEFT:
+    case EL_YAMYAM_RIGHT:
+    case EL_YAMYAM_UP:
+    case EL_YAMYAM_DOWN:
     case EL_DARK_YAMYAM:
     case EL_ROBOT:
     case EL_PACMAN:
     case EL_SP_SNIKSNAK:
     case EL_SP_ELECTRON:
+    case EL_MOLE:
     case EL_MOLE_LEFT:
     case EL_MOLE_RIGHT:
     case EL_MOLE_UP:
     case EL_MOLE_DOWN:
-    case EL_MOLE:
       InitMovDir(x, y);
       break;
 
@@ -895,6 +1038,10 @@ static void InitField(int x, int y, boolean init_game)
       MovDelay[x][y] = 96;
       break;
 
+    case EL_EM_DYNAMITE_ACTIVE:
+      MovDelay[x][y] = 32;
+      break;
+
     case EL_LAMP:
       local_player->lights_still_needed++;
       break;
@@ -1155,6 +1302,7 @@ void DrawGameDoorValues()
     DrawGameValue_Keys(stored_player[i].key);
 }
 
+#if 0
 static void resolve_group_element(int group_element, int recursion_depth)
 {
   static int group_nr;
@@ -1198,7 +1346,7 @@ static void resolve_group_element(int group_element, int recursion_depth)
     }
   }
 }
-
+#endif
 
 /*
   =============================================================================
@@ -1330,6 +1478,7 @@ static void InitGameEngine()
   printf("       => game.engine_version == %06d\n", game.engine_version);
 #endif
 
+#if 0
   /* ---------- recursively resolve group elements ------------------------- */
 
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
@@ -1338,12 +1487,19 @@ static void InitGameEngine()
 
   for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
     resolve_group_element(EL_GROUP_START + i, 0);
+#endif
 
   /* ---------- initialize player's initial move delay --------------------- */
 
+#if 1
+  /* dynamically adjust player properties according to level information */
+  game.initial_move_delay_value =
+    get_move_delay_from_stepsize(level.initial_player_stepsize);
+#else
   /* dynamically adjust player properties according to level information */
   game.initial_move_delay_value =
     (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
+#endif
 
   /* dynamically adjust player properties according to game engine version */
   game.initial_move_delay = (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
@@ -1635,6 +1791,10 @@ int get_num_special_action(int element, int action_first, int action_last)
       break;
   }
 
+#if 0
+  printf("::: %d->%d: %d\n", action_first, action_last, num_special_action);
+#endif
+
   return num_special_action;
 }
 
@@ -1720,23 +1880,26 @@ void InitGame()
     player->is_pushing = FALSE;
     player->is_switching = FALSE;
     player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
 
     player->is_bored = FALSE;
     player->is_sleeping = FALSE;
 
-    player->cannot_move = FALSE;
-
     player->frame_counter_bored = -1;
     player->frame_counter_sleeping = -1;
 
     player->anim_delay_counter = 0;
     player->post_delay_counter = 0;
 
+    player->dir_waiting = MV_NONE;
     player->action_waiting = ACTION_DEFAULT;
     player->last_action_waiting = ACTION_DEFAULT;
     player->special_action_bored = ACTION_DEFAULT;
     player->special_action_sleeping = ACTION_DEFAULT;
 
+#if 1
+    /* cannot be set here -- could be modified in Init[Player]Field() below */
+#else
     /* set number of special actions for bored and sleeping animation */
     player->num_special_action_bored =
       get_num_special_action(player->artwork_element,
@@ -1744,6 +1907,7 @@ void InitGame()
     player->num_special_action_sleeping =
       get_num_special_action(player->artwork_element,
                             ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+#endif
 
     player->switch_x = -1;
     player->switch_y = -1;
@@ -1753,6 +1917,9 @@ void InitGame()
 
     player->show_envelope = 0;
 
+#if 1
+    SetPlayerMoveSpeed(player, level.initial_player_stepsize, TRUE);
+#else
     player->move_delay       = game.initial_move_delay;
     player->move_delay_value = game.initial_move_delay_value;
 
@@ -1760,10 +1927,14 @@ void InitGame()
 
     player->move_delay_reset_counter = 0;
 
+    player->cannot_move = FALSE;
+#endif
+
     player->push_delay       = -1;     /* initialized when pushing starts */
     player->push_delay_value = game.initial_push_delay_value;
 
     player->drop_delay = 0;
+    player->drop_pressed_delay = 0;
 
     player->last_jx = player->last_jy = 0;
     player->jx = player->jy = 0;
@@ -1824,6 +1995,9 @@ void InitGame()
 
   game.envelope_active = FALSE;
 
+  game.centered_player_nr = game.centered_player_nr_next = -1; /* focus all */
+  game.set_centered_player = FALSE;
+
   for (i = 0; i < NUM_BELTS; i++)
   {
     game.belt_dir[i] = MV_NONE;
@@ -1889,6 +2063,22 @@ void InitGame()
 
   InitBeltMovement();
 
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+#if 1
+    /* set number of special actions for bored and sleeping animation */
+    player->num_special_action_bored =
+      get_num_special_action(player->artwork_element,
+                            ACTION_BORING_1, ACTION_BORING_LAST);
+    player->num_special_action_sleeping =
+      get_num_special_action(player->artwork_element,
+                            ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+#endif
+
+  }
+
   game.emulation = (emulate_bd ? EMU_BOULDERDASH :
                    emulate_sb ? EMU_SOKOBAN :
                    emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
@@ -1988,7 +2178,7 @@ void InitGame()
 
   if (tape.playing)
   {
-    /* when playing a tape, eliminate all players which do not participate */
+    /* when playing a tape, eliminate all players who do not participate */
 
     for (i = 0; i < MAX_PLAYERS; i++)
     {
@@ -2320,6 +2510,14 @@ void InitMovDir(int x, int y)
       MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
       break;
 
+    case EL_YAMYAM_LEFT:
+    case EL_YAMYAM_RIGHT:
+    case EL_YAMYAM_UP:
+    case EL_YAMYAM_DOWN:
+      Feld[x][y] = EL_YAMYAM;
+      MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
+      break;
+
     case EL_SP_SNIKSNAK:
       MovDir[x][y] = MV_UP;
       break;
@@ -2552,6 +2750,13 @@ void GameWon()
 
   BackToFront();
 
+#if 0
+  if (tape.playing)
+    printf("::: TAPE PLAYING -> DO NOT SAVE SCORE\n");
+  else
+    printf("::: NO TAPE PLAYING -> SAVING SCORE\n");
+#endif
+
   if (tape.playing)
     return;
 
@@ -2712,14 +2917,33 @@ static void ResetRandomAnimationValue(int x, int y)
 
 static void ResetGfxAnimation(int x, int y)
 {
+#if 0
+  int element, graphic;
+#endif
+
   GfxFrame[x][y] = 0;
   GfxAction[x][y] = ACTION_DEFAULT;
   GfxDir[x][y] = MovDir[x][y];
+
+#if 0
+  element = Feld[x][y];
+  graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+  if (graphic_info[graphic].anim_global_sync)
+    GfxFrame[x][y] = FrameCounter;
+  else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+    GfxFrame[x][y] = CustomValue[x][y];
+  else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+    GfxFrame[x][y] = element_info[element].collect_score;
+#endif
 }
 
 void InitMovingField(int x, int y, int direction)
 {
   int element = Feld[x][y];
+#if 0
+  int graphic;
+#endif
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
   int newx = x + dx;
@@ -2733,6 +2957,17 @@ void InitMovingField(int x, int y, int direction)
   GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
                     ACTION_FALLING : ACTION_MOVING);
 
+#if 0
+  graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+  if (graphic_info[graphic].anim_global_sync)
+    GfxFrame[x][y] = FrameCounter;
+  else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+    GfxFrame[x][y] = CustomValue[x][y];
+  else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+    GfxFrame[x][y] = element_info[element].collect_score;
+#endif
+
   /* this is needed for CEs with property "can move" / "not moving" */
 
   if (getElementMoveStepsize(x, y) != 0)       /* moving or being moved */
@@ -2756,8 +2991,13 @@ void InitMovingField(int x, int y, int direction)
 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
 {
   int direction = MovDir[x][y];
+#if 1
+  int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
+  int newy = y + (direction & MV_UP   ? -1 : direction & MV_DOWN  ? +1 : 0);
+#else
   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
+#endif
 
   *goes_to_x = newx;
   *goes_to_y = newy;
@@ -2944,38 +3184,121 @@ void CheckDynamite(int x, int y)
   Bang(x, y);
 }
 
-void DrawRelocatePlayer(struct PlayerInfo *player)
+#if 1
+
+static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
+{
+  boolean num_checked_players = 0;
+  int i;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    if (stored_player[i].active)
+    {
+      int sx = stored_player[i].jx;
+      int sy = stored_player[i].jy;
+
+      if (num_checked_players == 0)
+      {
+       *sx1 = *sx2 = sx;
+       *sy1 = *sy2 = sy;
+      }
+      else
+      {
+       *sx1 = MIN(*sx1, sx);
+       *sy1 = MIN(*sy1, sy);
+       *sx2 = MAX(*sx2, sx);
+       *sy2 = MAX(*sy2, sy);
+      }
+
+      num_checked_players++;
+    }
+  }
+}
+
+static boolean checkIfAllPlayersFitToScreen_RND()
+{
+  int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
+
+  setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+  return (sx2 - sx1 < SCR_FIELDX &&
+         sy2 - sy1 < SCR_FIELDY);
+}
+
+static void setScreenCenteredToAllPlayers(int *sx, int *sy)
+{
+  int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
+
+  setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+  *sx = (sx1 + sx2) / 2;
+  *sy = (sy1 + sy2) / 2;
+}
+
+#if 0
+static void setMaxCenterDistanceForAllPlayers(int *max_dx, int *max_dy,
+                                             int center_x, int center_y)
+{
+  int sx1 = center_x, sy1 = center_y, sx2 = center_x, sy2 = center_y;
+
+  setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+  *max_dx = MAX(ABS(sx1 - center_x), ABS(sx2 - center_x));
+  *max_dy = MAX(ABS(sy1 - center_y), ABS(sy2 - center_y));
+}
+
+static boolean checkIfAllPlayersAreVisible(int center_x, int center_y)
+{
+  int max_dx, max_dy;
+
+  setMaxCenterDistanceForAllPlayers(&max_dx, &max_dy, center_x, center_y);
+
+  return (max_dx <= SCR_FIELDX / 2 &&
+         max_dy <= SCR_FIELDY / 2);
+}
+#endif
+
+#endif
+
+#if 1
+
+void DrawRelocateScreen(int x, int y, int move_dir, boolean center_screen,
+                       boolean quick_relocation)
 {
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int wait_delay_value = (no_delay ? 0 : frame_delay_value);
-  int jx = player->jx;
-  int jy = player->jy;
 
-  if (level.instant_relocation)
+  if (quick_relocation)
   {
     int offset = (setup.scroll_delay ? 3 : 0);
 
-    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+#if 0
+    if (center_screen)
+      offset = 0;
+#endif
+
+    if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
     {
-      scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-                 local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-                 local_player->jx - MIDPOSX);
+      scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+                 x > SBX_Right + MIDPOSX ? SBX_Right :
+                 x - MIDPOSX);
 
-      scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
-                 local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
-                 local_player->jy - MIDPOSY);
+      scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                 y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                 y - 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 ((move_dir == MV_LEFT  && scroll_x > x - MIDPOSX + offset) ||
+         (move_dir == MV_RIGHT && scroll_x < x - MIDPOSX - offset))
+       scroll_x = x - MIDPOSX + (scroll_x < x - 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);
+      if ((move_dir == MV_UP   && scroll_y > y - MIDPOSY + offset) ||
+         (move_dir == MV_DOWN && scroll_y < y - MIDPOSY - offset))
+       scroll_y = y - MIDPOSY + (scroll_y < y - MIDPOSY ? -offset : +offset);
 
       /* don't scroll over playfield boundaries */
       if (scroll_x < SBX_Left || scroll_x > SBX_Right)
@@ -2990,23 +3313,21 @@ void DrawRelocatePlayer(struct PlayerInfo *player)
   }
   else
   {
-    int scroll_xx = -999, scroll_yy = -999;
+    int scroll_xx = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+                    x > SBX_Right + MIDPOSX ? SBX_Right :
+                    x - MIDPOSX);
+
+    int scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                    y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                    y - MIDPOSY);
 
     ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
 
-    while (scroll_xx != scroll_x || scroll_yy != scroll_y)
+    while (scroll_x != scroll_xx || scroll_y != scroll_yy)
     {
       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);
 
@@ -3032,61 +3353,157 @@ void DrawRelocatePlayer(struct PlayerInfo *player)
       Delay(wait_delay_value);
     }
 
-    DrawPlayer(player);
+    DrawAllPlayers();
     BackToFront();
     Delay(wait_delay_value);
   }
 }
 
-void RelocatePlayer(int jx, int jy, int el_player_raw)
+#else
+
+void DrawRelocatePlayer(struct PlayerInfo *player, boolean quick_relocation)
 {
-  int el_player = GET_PLAYER_ELEMENT(el_player_raw);
-  int player_nr = GET_PLAYER_NR(el_player);
-  struct PlayerInfo *player = &stored_player[player_nr];
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int wait_delay_value = (no_delay ? 0 : frame_delay_value);
-  int old_jx = player->jx;
-  int old_jy = player->jy;
-  int old_element = Feld[old_jx][old_jy];
-  int element = Feld[jx][jy];
-  boolean player_relocated = (old_jx != jx || old_jy != jy);
-
-  int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
-  int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
-  int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
-  int enter_side_vert  = MV_DIR_OPPOSITE(move_dir_vert);
-  int leave_side_horiz = move_dir_horiz;
-  int leave_side_vert  = move_dir_vert;
-  int enter_side = enter_side_horiz | enter_side_vert;
-  int leave_side = leave_side_horiz | leave_side_vert;
-
-  if (player->GameOver)                /* do not reanimate dead player */
-    return;
-
-  if (!player_relocated)       /* no need to relocate the player */
-    return;
+  int jx = player->jx;
+  int jy = player->jy;
 
-  if (IS_PLAYER(jx, jy))       /* player already placed at new position */
+  if (quick_relocation)
   {
-    RemoveField(jx, jy);       /* temporarily remove newly placed player */
-    DrawLevelField(jx, jy);
-  }
+    int offset = (setup.scroll_delay ? 3 : 0);
 
-  if (player->present)
-  {
-    while (player->MovPos)
+    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
     {
-      ScrollPlayer(player, SCROLL_GO_ON);
-      ScrollScreen(NULL, SCROLL_GO_ON);
-
-      AdvanceFrameAndPlayerCounters(player->index_nr);
-
-      DrawPlayer(player);
+      scroll_x = (player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
+                 player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+                 player->jx - MIDPOSX);
 
-      BackToFront();
-      Delay(wait_delay_value);
+      scroll_y = (player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+                 player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+                 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);
+    }
+
+    RedrawPlayfield(TRUE, 0,0,0,0);
+  }
+  else
+  {
+    int scroll_xx = (player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
+                    player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+                    player->jx - MIDPOSX);
+
+    int scroll_yy = (player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+                    player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+                    player->jy - MIDPOSY);
+
+    ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
+
+    while (scroll_x != scroll_xx || scroll_y != scroll_yy)
+    {
+      int dx = 0, dy = 0;
+      int fx = FX, fy = FY;
+
+      dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
+      dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
+
+      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
+       break;
+
+      scroll_x -= dx;
+      scroll_y -= dy;
+
+      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);
+    }
+
+    DrawPlayer(player);
+    BackToFront();
+    Delay(wait_delay_value);
+  }
+}
+
+#endif
+
+void RelocatePlayer(int jx, int jy, int el_player_raw)
+{
+  int el_player = GET_PLAYER_ELEMENT(el_player_raw);
+  int player_nr = GET_PLAYER_NR(el_player);
+  struct PlayerInfo *player = &stored_player[player_nr];
+  boolean ffwd_delay = (tape.playing && tape.fast_forward);
+  boolean no_delay = (tape.warp_forward);
+  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+  int old_jx = player->jx;
+  int old_jy = player->jy;
+  int old_element = Feld[old_jx][old_jy];
+  int element = Feld[jx][jy];
+  boolean player_relocated = (old_jx != jx || old_jy != jy);
+
+  int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
+  int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
+  int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
+  int enter_side_vert  = MV_DIR_OPPOSITE(move_dir_vert);
+  int leave_side_horiz = move_dir_horiz;
+  int leave_side_vert  = move_dir_vert;
+  int enter_side = enter_side_horiz | enter_side_vert;
+  int leave_side = leave_side_horiz | leave_side_vert;
+
+  if (player->GameOver)                /* do not reanimate dead player */
+    return;
+
+  if (!player_relocated)       /* no need to relocate the player */
+    return;
+
+  if (IS_PLAYER(jx, jy))       /* player already placed at new position */
+  {
+    RemoveField(jx, jy);       /* temporarily remove newly placed player */
+    DrawLevelField(jx, jy);
+  }
+
+  if (player->present)
+  {
+    while (player->MovPos)
+    {
+      ScrollPlayer(player, SCROLL_GO_ON);
+      ScrollScreen(NULL, SCROLL_GO_ON);
+
+      AdvanceFrameAndPlayerCounters(player->index_nr);
+
+      DrawPlayer(player);
+
+      BackToFront();
+      Delay(wait_delay_value);
     }
 
     DrawPlayer(player);                /* needed here only to cleanup last field */
@@ -3113,8 +3530,19 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
     InitField(jx, jy, FALSE);
   }
 
+#if 1
+  /* only visually relocate centered player */
+#if 1
+  DrawRelocateScreen(player->jx, player->jy, player->MovDir, FALSE,
+                    level.instant_relocation);
+#else
+  if (player->index_nr == game.centered_player_nr)
+    DrawRelocatePlayer(player, level.instant_relocation);
+#endif
+#else
   if (player == local_player)  /* only visually relocate local player */
-    DrawRelocatePlayer(player);
+    DrawRelocatePlayer(player, level.instant_relocation);
+#endif
 
   TestIfPlayerTouchesBadThing(jx, jy);
   TestIfPlayerTouchesCustomElement(jx, jy);
@@ -3371,6 +3799,11 @@ void Explode(int ex, int ey, int phase, int mode)
       Feld[x][y] = EL_EXPLOSION;
       GfxElement[x][y] = artwork_element;
 
+#if 0
+      printf(":: setting gfx(%d,%d) to %d ['%s']\n",
+            x, y, artwork_element, EL_NAME(artwork_element));
+#endif
+
       ExplodePhase[x][y] = 1;
       ExplodeDelay[x][y] = last_phase;
 
@@ -3405,6 +3838,11 @@ void Explode(int ex, int ey, int phase, int mode)
 #endif
 #if 1
 
+#if 1
+  /* this can happen if the player leaves an explosion just in time */
+  if (GfxElement[x][y] == EL_UNDEFINED)
+    GfxElement[x][y] = EL_EMPTY;
+#else
   if (GfxElement[x][y] == EL_UNDEFINED)
   {
     printf("\n\n");
@@ -3414,6 +3852,8 @@ void Explode(int ex, int ey, int phase, int mode)
 
     GfxElement[x][y] = EL_EMPTY;
   }
+#endif
+
 #endif
 
   border_element = Store2[x][y];
@@ -4606,16 +5046,35 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (element == EL_SPRING)
   {
+#if USE_NEW_SPRING_BUMPER
+    if (MovDir[x][y] & MV_HORIZONTAL)
+    {
+      if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
+         !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+      {
+       Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
+       ResetGfxAnimation(move_x, move_y);
+       DrawLevelField(move_x, move_y);
+
+       MovDir[x][y] = back_dir;
+      }
+      else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
+              SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+       MovDir[x][y] = MV_NONE;
+    }
+#else
     if (MovDir[x][y] & MV_HORIZONTAL &&
        (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
         SPRING_CAN_ENTER_FIELD(element, x, y + 1)))
       MovDir[x][y] = MV_NONE;
+#endif
 
     MovDelay[x][y] = 0;
   }
   else if (element == EL_ROBOT ||
           element == EL_SATELLITE ||
-          element == EL_PENGUIN)
+          element == EL_PENGUIN ||
+          element == EL_EMC_ANDROID)
   {
     int attr_x = -1, attr_y = -1;
 
@@ -4716,21 +5175,21 @@ 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(EL_PENGUIN, newx, newy))
+       if (PENGUIN_CAN_ENTER_FIELD(element, 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(EL_PENGUIN, newx, newy))
+       if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
          return;
 
        MovDir[x][y] = old_move_dir;
        return;
       }
     }
-    else       /* (element == EL_SATELLITE) */
+    else if (element == EL_SATELLITE)
     {
       int newx, newy;
 
@@ -4759,6 +5218,144 @@ inline static void TurnRoundExt(int x, int y)
        return;
       }
     }
+    else if (element == EL_EMC_ANDROID)
+    {
+      static int check_pos[16] =
+      {
+       -1,             /*  0 => (invalid)          */
+       7,              /*  1 => MV_LEFT            */
+       3,              /*  2 => MV_RIGHT           */
+       -1,             /*  3 => (invalid)          */
+       1,              /*  4 =>            MV_UP   */
+       0,              /*  5 => MV_LEFT  | MV_UP   */
+       2,              /*  6 => MV_RIGHT | MV_UP   */
+       -1,             /*  7 => (invalid)          */
+       5,              /*  8 =>            MV_DOWN */
+       6,              /*  9 => MV_LEFT  | MV_DOWN */
+       4,              /* 10 => MV_RIGHT | MV_DOWN */
+       -1,             /* 11 => (invalid)          */
+       -1,             /* 12 => (invalid)          */
+       -1,             /* 13 => (invalid)          */
+       -1,             /* 14 => (invalid)          */
+       -1,             /* 15 => (invalid)          */
+      };
+      static struct
+      {
+       int dx, dy;
+       int dir;
+      } check_xy[8] =
+      {
+        { -1, -1,      MV_LEFT  | MV_UP   },
+               {  0, -1,                  MV_UP   },
+       { +1, -1,       MV_RIGHT | MV_UP   },
+       { +1,  0,       MV_RIGHT           },
+       { +1, +1,       MV_RIGHT | MV_DOWN },
+       {  0, +1,                  MV_DOWN },
+       { -1, +1,       MV_LEFT  | MV_DOWN },
+       { -1,  0,       MV_LEFT            },
+      };
+      int start_pos, check_order;
+      boolean can_clone = FALSE;
+      int i;
+
+      /* check if there is any free field around current position */
+      for (i = 0; i < 8; i++)
+      {
+       int newx = x + check_xy[i].dx;
+       int newy = y + check_xy[i].dy;
+
+       if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+       {
+         can_clone = TRUE;
+
+         break;
+       }
+      }
+
+      if (can_clone)           /* randomly find an element to clone */
+      {
+       can_clone = FALSE;
+
+       start_pos = check_pos[RND(8)];
+       check_order = (RND(2) ? -1 : +1);
+
+       for (i = 0; i < 8; i++)
+       {
+         int pos_raw = start_pos + i * check_order;
+         int pos = (pos_raw + 8) % 8;
+         int newx = x + check_xy[pos].dx;
+         int newy = y + check_xy[pos].dy;
+
+         if (ANDROID_CAN_CLONE_FIELD(newx, newy))
+         {
+           element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
+           element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
+
+           Store[x][y] = Feld[newx][newy];
+
+           can_clone = TRUE;
+
+           break;
+         }
+       }
+      }
+
+      if (can_clone)           /* randomly find a direction to move */
+      {
+       can_clone = FALSE;
+
+       start_pos = check_pos[RND(8)];
+       check_order = (RND(2) ? -1 : +1);
+
+       for (i = 0; i < 8; i++)
+       {
+         int pos_raw = start_pos + i * check_order;
+         int pos = (pos_raw + 8) % 8;
+         int newx = x + check_xy[pos].dx;
+         int newy = y + check_xy[pos].dy;
+         int new_move_dir = check_xy[pos].dir;
+
+         if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+         {
+           MovDir[x][y] = new_move_dir;
+           MovDelay[x][y] = level.android_clone_time * 8 + 1;
+
+           can_clone = TRUE;
+
+           break;
+         }
+       }
+      }
+
+      if (can_clone)           /* cloning and moving successful */
+       return;
+
+      /* cannot clone -- try to move towards player */
+
+      start_pos = check_pos[MovDir[x][y] & 0x0f];
+      check_order = (RND(2) ? -1 : +1);
+
+      for (i = 0; i < 3; i++)
+      {
+       /* first check start_pos, then previous/next or (next/previous) pos */
+       int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
+       int pos = (pos_raw + 8) % 8;
+       int newx = x + check_xy[pos].dx;
+       int newy = y + check_xy[pos].dy;
+       int new_move_dir = check_xy[pos].dir;
+
+       if (IS_PLAYER(newx, newy))
+         break;
+
+       if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+       {
+         MovDir[x][y] = new_move_dir;
+         MovDelay[x][y] = level.android_move_time * 8 + 1;
+
+         break;
+       }
+      }
+    }
   }
   else if (move_pattern == MV_TURNING_LEFT ||
           move_pattern == MV_TURNING_RIGHT ||
@@ -4998,6 +5595,9 @@ inline static void TurnRoundExt(int x, int y)
 static void TurnRound(int x, int y)
 {
   int direction = MovDir[x][y];
+#if 1
+  int element, graphic;
+#endif
 
   TurnRoundExt(x, y);
 
@@ -5007,7 +5607,19 @@ static void TurnRound(int x, int y)
     GfxFrame[x][y] = 0;
 
   if (MovDelay[x][y])
-    GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_BIT(direction);
+    GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
+
+#if 1
+  element = Feld[x][y];
+  graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+  if (graphic_info[graphic].anim_global_sync)
+    GfxFrame[x][y] = FrameCounter;
+  else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+    GfxFrame[x][y] = CustomValue[x][y];
+  else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+    GfxFrame[x][y] = element_info[element].collect_score;
+#endif
 }
 
 static boolean JustBeingPushed(int x, int y)
@@ -5506,6 +6118,7 @@ void StartMoving(int x, int y)
 
     else if (CAN_MOVE_INTO_ACID(element) &&
             IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
+            !IS_MV_DIAGONAL(MovDir[x][y]) &&
             (MovDir[x][y] == MV_DOWN ||
              game.engine_version >= VERSION_IDENT(3,1,0,0)))
     {
@@ -5532,7 +6145,7 @@ void StartMoving(int x, int y)
       }
       else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
       {
-       if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MF_MOVING)
+       if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
          DrawLevelField(newx, newy);
        else
          GfxDir[x][y] = MovDir[x][y] = MV_NONE;
@@ -5573,6 +6186,97 @@ void StartMoving(int x, int y)
        return;
       }
     }
+    else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
+    {
+      if (Store[x][y] != EL_EMPTY)
+      {
+       boolean can_clone = FALSE;
+       int xx, yy;
+
+       /* check if element to clone is still there */
+       for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
+       {
+         if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
+         {
+           can_clone = TRUE;
+
+           break;
+         }
+       }
+
+       /* cannot clone or target field not free anymore -- do not clone */
+       if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+         Store[x][y] = EL_EMPTY;
+      }
+
+      if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+      {
+       if (IS_MV_DIAGONAL(MovDir[x][y]))
+       {
+         int diagonal_move_dir = MovDir[x][y];
+         int stored = Store[x][y];
+         int change_delay = 8;
+         int graphic;
+
+         /* android is moving diagonally */
+
+         CreateField(x, y, EL_DIAGONAL_SHRINKING);
+
+         Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
+         GfxElement[x][y] = EL_EMC_ANDROID;
+         GfxAction[x][y] = ACTION_SHRINKING;
+         GfxDir[x][y] = diagonal_move_dir;
+         ChangeDelay[x][y] = change_delay;
+
+         graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
+                                  GfxDir[x][y]);
+
+         DrawLevelGraphicAnimation(x, y, graphic);
+         PlayLevelSoundAction(x, y, ACTION_SHRINKING);
+
+         if (Feld[newx][newy] == EL_ACID)
+         {
+           SplashAcid(newx, newy);
+
+           return;
+         }
+
+         CreateField(newx, newy, EL_DIAGONAL_GROWING);
+
+         Store[newx][newy] = EL_EMC_ANDROID;
+         GfxElement[newx][newy] = EL_EMC_ANDROID;
+         GfxAction[newx][newy] = ACTION_GROWING;
+         GfxDir[newx][newy] = diagonal_move_dir;
+         ChangeDelay[newx][newy] = change_delay;
+
+         graphic = el_act_dir2img(GfxElement[newx][newy],
+                                  GfxAction[newx][newy], GfxDir[newx][newy]);
+
+         DrawLevelGraphicAnimation(newx, newy, graphic);
+         PlayLevelSoundAction(newx, newy, ACTION_GROWING);
+
+         return;
+       }
+       else
+       {
+         Feld[newx][newy] = EL_EMPTY;
+         DrawLevelField(newx, newy);
+
+         PlayLevelSoundAction(x, y, ACTION_DIGGING);
+       }
+      }
+      else if (!IS_FREE(newx, newy))
+      {
+#if 0
+       if (IS_PLAYER(x, y))
+         DrawPlayerField(x, y);
+       else
+         DrawLevelField(x, y);
+#endif
+
+       return;
+      }
+    }
     else if (IS_CUSTOM_ELEMENT(element) &&
             CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
     {
@@ -5974,16 +6678,28 @@ void ContinueMoving(int x, int y)
   Pushed[x][y] = Pushed[newx][newy] = FALSE;
 
   /* some elements can leave other elements behind after moving */
+#if 1
+  if (ei->move_leave_element != EL_EMPTY &&
+      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
+      (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
+#else
   if (IS_CUSTOM_ELEMENT(element) && ei->move_leave_element != EL_EMPTY &&
       (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
       (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
+#endif
   {
     int move_leave_element = ei->move_leave_element;
 
+#if 1
 #if 1
+    /* this makes it possible to leave the removed element again */
+    if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
+      move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
+#else
     /* this makes it possible to leave the removed element again */
     if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
       move_leave_element = stored;
+#endif
 #else
     /* this makes it possible to leave the removed element again */
     if (ei->move_leave_type == LEAVE_TYPE_LIMITED &&
@@ -6036,13 +6752,13 @@ void ContinueMoving(int x, int y)
     int nextx = newx + dx, nexty = newy + dy;
     boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
 
-    WasJustMoving[newx][newy] = 3;
+    WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
 
     if (CAN_FALL(element) && direction == MV_DOWN)
-      WasJustFalling[newx][newy] = 3;
+      WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
 
     if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
-      CheckCollision[newx][newy] = 2;
+      CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
   }
 
   if (DONT_TOUCH(element))     /* object may be nasty to player or others */
@@ -6074,13 +6790,38 @@ void ContinueMoving(int x, int y)
                                        player->index_bit, push_side);
   }
 
+  if (element == EL_EMC_ANDROID && pushed_by_player)   /* make another move */
+    MovDelay[newx][newy] = 1;
+
   CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
 
   TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
 
-  TestIfElementHitsCustomElement(newx, newy, direction);
-  TestIfPlayerTouchesCustomElement(newx, newy);
-  TestIfElementTouchesCustomElement(newx, newy);
+#if 0
+  if (ChangePage[newx][newy] != -1)            /* delayed change */
+  {
+    int page = ChangePage[newx][newy];
+    struct ElementChangeInfo *change = &ei->change_page[page];
+
+    ChangePage[newx][newy] = -1;
+
+    if (change->can_change)
+    {
+      if (ChangeElement(newx, newy, element, page))
+      {
+        if (change->post_change_function)
+          change->post_change_function(newx, newy);
+      }
+    }
+
+    if (change->has_action)
+      ExecuteCustomElementAction(newx, newy, element, page);
+  }
+#endif
+
+  TestIfElementHitsCustomElement(newx, newy, direction);
+  TestIfPlayerTouchesCustomElement(newx, newy);
+  TestIfElementTouchesCustomElement(newx, newy);
 
 #if 1
   if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
@@ -6351,6 +7092,7 @@ void AmoebeAbleger(int ax, int ay)
   int element = Feld[ax][ay];
   int graphic = el2img(element);
   int newax = ax, neway = ay;
+  boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
   static int xy[4][2] =
   {
     { 0, -1 },
@@ -6359,7 +7101,7 @@ void AmoebeAbleger(int ax, int ay)
     { 0, +1 }
   };
 
-  if (!level.amoeba_speed)
+  if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
   {
     Feld[ax][ay] = EL_AMOEBA_DEAD;
     DrawLevelField(ax, ay);
@@ -6379,7 +7121,7 @@ void AmoebeAbleger(int ax, int ay)
       return;
   }
 
-  if (element == EL_AMOEBA_WET)        /* object is an acid / amoeba drop */
+  if (can_drop)                        /* EL_AMOEBA_WET or EL_EMC_DRIPPER */
   {
     int start = RND(4);
     int x = ax + xy[start][0];
@@ -6473,13 +7215,13 @@ void AmoebeAbleger(int ax, int ay)
     }
   }
 
-  if (element != EL_AMOEBA_WET || neway < ay || !IS_FREE(newax, neway) ||
+  if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
       (neway == lev_fieldy - 1 && newax != ax))
   {
     Feld[newax][neway] = EL_AMOEBA_GROWING;    /* creation of new amoeba */
     Store[newax][neway] = element;
   }
-  else if (neway == ay)
+  else if (neway == ay || element == EL_EMC_DRIPPER)
   {
     Feld[newax][neway] = EL_AMOEBA_DROP;       /* drop left/right of amoeba */
 
@@ -6647,6 +7389,13 @@ static void ActivateMagicBall(int bx, int by)
   game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
 }
 
+static void InitDiagonalMovingElement(int x, int y)
+{
+#if 0
+  MovDelay[x][y] = level.android_move_time;
+#endif
+}
+
 void CheckExit(int x, int y)
 {
   if (local_player->gems_still_needed > 0 ||
@@ -6987,9 +7736,10 @@ static void WarnBuggyBase(int x, int y)
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int xx = x + xy[i][0], yy = y + xy[i][1];
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
 
-    if (IS_PLAYER(xx, yy))
+    if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
     {
       PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
 
@@ -7074,11 +7824,11 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      MV_NONE);
 
   int action_arg_number_min =
-    (action_type == CA_SET_PLAYER_SPEED ? MOVE_STEPSIZE_MIN :
+    (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
      CA_ARG_MIN);
 
   int action_arg_number_max =
-    (action_type == CA_SET_PLAYER_SPEED ? MOVE_STEPSIZE_MAX :
+    (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
      action_type == CA_SET_LEVEL_GEMS ? 999 :
      action_type == CA_SET_LEVEL_TIME ? 9999 :
      action_type == CA_SET_LEVEL_SCORE ? 99999 :
@@ -7087,7 +7837,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      CA_ARG_MAX);
 
   int action_arg_number_reset =
-    (action_type == CA_SET_PLAYER_SPEED ? TILEX/game.initial_move_delay_value :
+    (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize :
      action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
      action_type == CA_SET_LEVEL_TIME ? level.time :
      action_type == CA_SET_LEVEL_SCORE ? 0 :
@@ -7117,8 +7867,10 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
      action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
      action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
-     action_arg == CA_ARG_ELEMENT_TARGET ? GET_NEW_CUSTOM_VALUE(change->target_element) :
-     action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_ce_value :
+     action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CUSTOM_VALUE(change->target_element) :
+     action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
+     action_arg == CA_ARG_ELEMENT_NR_TARGET  ? change->target_element :
+     action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
      -1);
 
   int action_arg_number_old =
@@ -7287,8 +8039,13 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
        {
          int move_stepsize = TILEX / stored_player[i].move_delay_value;
 
-         if (action_arg == CA_ARG_SPEED_SLOWER ||
-             action_arg == CA_ARG_SPEED_FASTER)
+         if (action_arg == CA_ARG_SPEED_FASTER &&
+             stored_player[i].cannot_move)
+         {
+           action_arg_number = STEPSIZE_VERY_SLOW;
+         }
+         else if (action_arg == CA_ARG_SPEED_SLOWER ||
+                  action_arg == CA_ARG_SPEED_FASTER)
          {
            action_arg_number = 2;
            action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
@@ -7302,6 +8059,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
                                    action_arg_number_min,
                                    action_arg_number_max);
 
+#if 1
+         SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
+#else
          /* make sure that value is power of 2 */
          move_stepsize = (1 << log_2(move_stepsize));
 
@@ -7310,6 +8070,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
          stored_player[i].cannot_move =
            (action_arg == CA_ARG_SPEED_NOT_MOVING ? TRUE : FALSE);
+#endif
        }
       }
 
@@ -7400,6 +8161,10 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
        CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
        CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
+
+#if 0
+       printf("::: RESULT: %d, %d\n", Feld[x][y], ChangePage[x][y]);
+#endif
       }
 #endif
 
@@ -7486,6 +8251,21 @@ static void CreateField(int x, int y, int element)
 
 static void CreateElementFromChange(int x, int y, int element)
 {
+  element = GET_VALID_RUNTIME_ELEMENT(element);
+
+#if USE_STOP_CHANGED_ELEMENTS
+  if (game.engine_version >= VERSION_IDENT(3,2,0,7))
+  {
+    int old_element = Feld[x][y];
+
+    /* prevent changed element from moving in same engine frame
+       unless both old and new element can either fall or move */
+    if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
+       (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
+      Stop[x][y] = TRUE;
+  }
+#endif
+
   CreateFieldExt(x, y, element, TRUE);
 }
 
@@ -7633,6 +8413,14 @@ static boolean ChangeElement(int x, int y, int element, int page)
   {
     target_element = GET_TARGET_ELEMENT(change->target_element, change);
 
+    if (element == EL_DIAGONAL_GROWING ||
+       element == EL_DIAGONAL_SHRINKING)
+    {
+      target_element = Store[x][y];
+
+      Store[x][y] = EL_EMPTY;
+    }
+
     CreateElementFromChange(x, y, target_element);
 
     PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
@@ -7926,8 +8714,20 @@ static boolean CheckElementChangeExt(int x, int y,
     element = Feld[x][y];
   }
 
-  if (Feld[x][y] != element)   /* check if element has already changed */
+#if 0
+  /* check if element has already changed */
+  if (Feld[x][y] != element)
     return FALSE;
+#else
+  /* check if element has already changed or is about to change after moving */
+  if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
+       Feld[x][y] != element) ||
+
+      (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
+       (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
+       ChangePage[x][y] != -1)))
+    return FALSE;
+#endif
 
   for (p = 0; p < element_info[element].num_change_pages; p++)
   {
@@ -8039,6 +8839,7 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
   boolean last_waiting = player->is_waiting;
   int move_dir = player->MovDir;
 
+  player->dir_waiting = move_dir;
   player->last_action_waiting = player->action_waiting;
 
   if (is_waiting)
@@ -8056,7 +8857,11 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
        game.player_sleeping_delay_fixed +
        SimpleRND(game.player_sleeping_delay_random);
 
+#if 1
+      InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
+#else
       InitPlayerGfxAnimation(player, ACTION_WAITING, player->MovDir);
+#endif
     }
 
     if (game.player_sleeping_delay_fixed +
@@ -8074,6 +8879,26 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
                              player->is_bored ? ACTION_BORING :
                              ACTION_WAITING);
 
+#if 1
+    if (player->is_sleeping && player->use_murphy)
+    {
+      /* special case for sleeping Murphy when leaning against non-free tile */
+
+      if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
+         (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
+          !IS_MOVING(player->jx - 1, player->jy)))
+       move_dir = MV_LEFT;
+      else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
+              (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
+               !IS_MOVING(player->jx + 1, player->jy)))
+       move_dir = MV_RIGHT;
+      else
+       player->is_sleeping = FALSE;
+
+      player->dir_waiting = move_dir;
+    }
+#endif
+
     if (player->is_sleeping)
     {
       if (player->num_special_action_sleeping > 0)
@@ -8156,6 +8981,7 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
     player->anim_delay_counter = 0;
     player->post_delay_counter = 0;
 
+    player->dir_waiting = player->MovDir;
     player->action_waiting = ACTION_DEFAULT;
 
     player->special_action_bored = ACTION_DEFAULT;
@@ -8172,8 +8998,8 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
   int down     = player_action & JOY_DOWN;
   int button1  = player_action & JOY_BUTTON_1;
   int button2  = player_action & JOY_BUTTON_2;
-  int dx       = (left ? -1    : right ? 1     : 0);
-  int dy       = (up   ? -1    : down  ? 1     : 0);
+  int dx       = (left ? -1 : right ? 1 : 0);
+  int dy       = (up   ? -1 : down  ? 1 : 0);
 
   if (!player->active || tape.pausing)
     return 0;
@@ -8218,11 +9044,86 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
       player->is_moving = FALSE;
 
     player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
+    player->drop_pressed_delay = 0;
 
     return 0;
   }
 }
 
+static void CheckLevelTime()
+{
+  int i;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    if (level.native_em_level->lev->home == 0) /* all players at home */
+    {
+      local_player->LevelSolved = TRUE;
+      AllPlayersGone = TRUE;
+
+      level.native_em_level->lev->home = -1;
+    }
+
+    if (level.native_em_level->ply[0]->alive == 0 &&
+       level.native_em_level->ply[1]->alive == 0 &&
+       level.native_em_level->ply[2]->alive == 0 &&
+       level.native_em_level->ply[3]->alive == 0)      /* all dead */
+      AllPlayersGone = TRUE;
+  }
+
+  if (TimeFrames >= FRAMES_PER_SECOND)
+  {
+    TimeFrames = 0;
+    TapeTime++;
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
+
+      if (SHIELD_ON(player))
+      {
+       player->shield_normal_time_left--;
+
+       if (player->shield_deadly_time_left > 0)
+         player->shield_deadly_time_left--;
+      }
+    }
+
+    if (!level.use_step_counter)
+    {
+      TimePlayed++;
+
+      if (TimeLeft > 0)
+      {
+       TimeLeft--;
+
+       if (TimeLeft <= 10 && setup.time_limit)
+         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+
+       DrawGameValue_Time(TimeLeft);
+
+       if (!TimeLeft && setup.time_limit)
+       {
+         if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+           level.native_em_level->lev->killed_out_of_time = TRUE;
+         else
+           for (i = 0; i < MAX_PLAYERS; i++)
+             KillPlayer(&stored_player[i]);
+       }
+      }
+      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+       DrawGameValue_Time(TimePlayed);
+
+      level.native_em_level->lev->time =
+       (level.time == 0 ? TimePlayed : TimeLeft);
+    }
+
+    if (tape.recording || tape.playing)
+      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+  }
+}
+
 void AdvanceFrameAndPlayerCounters(int player_nr)
 {
   int i;
@@ -8268,20 +9169,81 @@ void AdvanceFrameAndPlayerCounters(int player_nr)
 
     if (stored_player[i].drop_delay > 0)
       stored_player[i].drop_delay--;
+
+    if (stored_player[i].is_dropping_pressed)
+      stored_player[i].drop_pressed_delay++;
   }
 }
 
+void StartGameActions(boolean init_network_game, boolean record_tape,
+                     long random_seed)
+{
+#if 1
+  unsigned long new_random_seed = InitRND(random_seed);
+
+  if (record_tape)
+    TapeStartRecording(new_random_seed);
+#else
+  if (record_tape)
+    TapeStartRecording(random_seed);
+#endif
+
+#if defined(NETWORK_AVALIABLE)
+  if (init_network_game)
+  {
+    SendToServer_StartPlaying();
+
+    return;
+  }
+#endif
+
+  StopAnimation();
+
+  game_status = GAME_MODE_PLAYING;
+
+#if 0
+  InitRND(random_seed);
+#endif
+
+  InitGame();
+}
+
 void GameActions()
 {
   static unsigned long game_frame_delay = 0;
   unsigned long game_frame_delay_value;
-  int magic_wall_x = 0, magic_wall_y = 0;
-  int i, x, y, element, graphic;
   byte *recorded_player_action;
   byte summarized_player_action = 0;
   byte tape_action[MAX_PLAYERS];
+  int i;
 
-  if (game_status != GAME_MODE_PLAYING)
+  if (game.restart_level)
+    StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    if (level.native_em_level->lev->home == 0) /* all players at home */
+    {
+      local_player->LevelSolved = TRUE;
+      AllPlayersGone = TRUE;
+
+      level.native_em_level->lev->home = -1;
+    }
+
+    if (level.native_em_level->ply[0]->alive == 0 &&
+       level.native_em_level->ply[1]->alive == 0 &&
+       level.native_em_level->ply[2]->alive == 0 &&
+       level.native_em_level->ply[3]->alive == 0)      /* all dead */
+      AllPlayersGone = TRUE;
+  }
+
+  if (local_player->LevelSolved)
+    GameWon();
+
+  if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
+    TapeStop();
+
+  if (game_status != GAME_MODE_PLAYING)                /* status might have changed */
     return;
 
   game_frame_delay_value =
@@ -8292,8 +9254,6 @@ void GameActions()
 
   /* ---------- main game synchronization point ---------- */
 
-  InitPlayfieldScanModeVars();
-
   WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
 
   if (network_playing && !network_player_action_received)
@@ -8318,11 +9278,11 @@ void GameActions()
 
   recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
 
-#if 1
-  /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
-  if (recorded_player_action == NULL && tape.pausing)
-    return;
-#endif
+  if (tape.set_centered_player)
+  {
+    game.centered_player_nr_next = tape.centered_player_nr_next;
+    game.set_centered_player = TRUE;
+  }
 
   for (i = 0; i < MAX_PLAYERS; i++)
   {
@@ -8340,6 +9300,13 @@ void GameActions()
   if (!options.network && !setup.team_mode)
     local_player->effective_action = summarized_player_action;
 
+  if (setup.team_mode && setup.input_on_focus && game.centered_player_nr != -1)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action =
+       (i == game.centered_player_nr ? summarized_player_action : 0);
+  }
+
   if (recorded_player_action != NULL)
     for (i = 0; i < MAX_PLAYERS; i++)
       stored_player[i].effective_action = recorded_player_action[i];
@@ -8348,14 +9315,102 @@ void GameActions()
   {
     tape_action[i] = stored_player[i].effective_action;
 
+    /* (this can only happen in the R'n'D game engine) */
     if (tape.recording && tape_action[i] && !tape.player_participates[i])
       tape.player_participates[i] = TRUE;    /* player just appeared from CE */
   }
 
-  /* only save actions from input devices, but not programmed actions */
+  /* only record actions from input devices, but not programmed actions */
   if (tape.recording)
     TapeRecordAction(tape_action);
 
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    GameActions_EM_Main();
+  }
+  else
+  {
+    GameActions_RND();
+  }
+}
+
+void GameActions_EM_Main()
+{
+  byte effective_action[MAX_PLAYERS];
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+  int i;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
+
+  GameActions_EM(effective_action, warp_mode);
+
+  CheckLevelTime();
+
+  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+}
+
+void GameActions_RND()
+{
+  int magic_wall_x = 0, magic_wall_y = 0;
+  int i, x, y, element, graphic;
+
+  InitPlayfieldScanModeVars();
+
+#if USE_ONE_MORE_CHANGE_PER_FRAME
+  if (game.engine_version >= VERSION_IDENT(3,2,0,7))
+  {
+    SCAN_PLAYFIELD(x, y)
+    {
+      ChangeCount[x][y] = 0;
+      ChangeEvent[x][y] = -1;
+    }
+  }
+#endif
+
+#if 1
+  if (game.set_centered_player)
+  {
+    boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
+
+    /* switching to "all players" only possible if all players fit to screen */
+    if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
+    {
+      game.centered_player_nr_next = game.centered_player_nr;
+      game.set_centered_player = FALSE;
+    }
+
+    /* do not switch focus to non-existing (or non-active) player */
+    if (game.centered_player_nr_next >= 0 &&
+       !stored_player[game.centered_player_nr_next].active)
+    {
+      game.centered_player_nr_next = game.centered_player_nr;
+      game.set_centered_player = FALSE;
+    }
+  }
+
+  if (game.set_centered_player &&
+      ScreenMovPos == 0)       /* screen currently aligned at tile position */
+  {
+    int sx, sy;
+
+    if (game.centered_player_nr_next == -1)
+    {
+      setScreenCenteredToAllPlayers(&sx, &sy);
+    }
+    else
+    {
+      sx = stored_player[game.centered_player_nr_next].jx;
+      sy = stored_player[game.centered_player_nr_next].jy;
+    }
+
+    DrawRelocateScreen(sx, sy, MV_NONE, TRUE, setup.quick_switch);
+
+    game.centered_player_nr = game.centered_player_nr_next;
+    game.set_centered_player = FALSE;
+  }
+#endif
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     int actual_player_action = stored_player[i].effective_action;
@@ -8475,7 +9530,7 @@ void GameActions()
     GfxFrame[x][y]++;
 
     /* reset finished pushing action (not done in ContinueMoving() to allow
-       continous pushing animation for elements with zero push delay) */
+       continuous pushing animation for elements with zero push delay) */
     if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
     {
       ResetGfxAnimation(x, y);
@@ -8646,7 +9701,9 @@ void GameActions()
       CheckForDragon(x, y);
     else if (element == EL_EXPLOSION)
       ;        /* drawing of correct explosion animation is handled separately */
-    else if (element == EL_ELEMENT_SNAPPING)
+    else if (element == EL_ELEMENT_SNAPPING ||
+            element == EL_DIAGONAL_SHRINKING ||
+            element == EL_DIAGONAL_GROWING)
     {
 #if 1
       graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
@@ -8832,48 +9889,7 @@ void GameActions()
     }
   }
 
-  if (TimeFrames >= FRAMES_PER_SECOND)
-  {
-    TimeFrames = 0;
-    TapeTime++;
-
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      struct PlayerInfo *player = &stored_player[i];
-
-      if (SHIELD_ON(player))
-      {
-       player->shield_normal_time_left--;
-
-       if (player->shield_deadly_time_left > 0)
-         player->shield_deadly_time_left--;
-      }
-    }
-
-    if (!level.use_step_counter)
-    {
-      TimePlayed++;
-
-      if (TimeLeft > 0)
-      {
-       TimeLeft--;
-
-       if (TimeLeft <= 10 && setup.time_limit)
-         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
-
-       DrawGameValue_Time(TimeLeft);
-
-       if (!TimeLeft && setup.time_limit)
-         for (i = 0; i < MAX_PLAYERS; i++)
-           KillPlayer(&stored_player[i]);
-      }
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
-       DrawGameValue_Time(TimePlayed);
-    }
-
-    if (tape.recording || tape.playing)
-      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
-  }
+  CheckLevelTime();
 
   DrawAllPlayers();
   PlayAllPlayersSound();
@@ -9073,11 +10089,14 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 {
   int jx = player->jx, jy = player->jy;
   int new_jx = jx + dx, new_jy = jy + dy;
+#if !USE_FIXED_DONT_RUN_INTO
   int element;
+#endif
   int can_move;
+  boolean player_can_move = !player->cannot_move;
 
   if (!player->active || (!dx && !dy))
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
 
   player->MovDir = (dx < 0 ? MV_LEFT :
                    dx > 0 ? MV_RIGHT :
@@ -9085,9 +10104,9 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
                    dy > 0 ? MV_DOWN :  MV_NONE);
 
   if (!IN_LEV_FIELD(new_jx, new_jy))
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
 
-  if (player->cannot_move)
+  if (!player_can_move)
   {
 #if 1
     if (player->MovPos == 0)
@@ -9103,15 +10122,25 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
     SnapField(player, 0, 0);
 #endif
 
-    return MF_NO_ACTION;
+#if 0
+    return MP_NO_ACTION;
+#endif
   }
 
+#if 1
+  if (!options.network && game.centered_player_nr == -1 &&
+      !AllPlayersInSight(player, new_jx, new_jy))
+    return MP_NO_ACTION;
+#else
   if (!options.network && !AllPlayersInSight(player, new_jx, new_jy))
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
+#endif
 
+#if !USE_FIXED_DONT_RUN_INTO
   element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
 
-  if (DONT_RUN_INTO(element))
+  /* (moved to DigField()) */
+  if (player_can_move && DONT_RUN_INTO(element))
   {
     if (element == EL_ACID && dx == 0 && dy == 1)
     {
@@ -9125,16 +10154,26 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
     else
       TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
 
-    return MF_MOVING;
+    return MP_MOVING;
   }
+#endif
 
   can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
-  if (can_move != MF_MOVING)
+#if 0
+#if USE_FIXED_DONT_RUN_INTO
+  if (can_move == MP_DONT_RUN_INTO)
+    return MP_MOVING;
+#endif
+#endif
+  if (can_move != MP_MOVING)
     return can_move;
 
+#if USE_FIXED_DONT_RUN_INTO
+#endif
+
   /* check if DigField() has caused relocation of the player */
   if (player->jx != jx || player->jy != jy)
-    return MF_NO_ACTION;       /* <-- !!! CHECK THIS [-> MF_ACTION ?] !!! */
+    return MP_NO_ACTION;       /* <-- !!! CHECK THIS [-> MP_ACTION ?] !!! */
 
   StorePlayer[jx][jy] = 0;
   player->last_jx = jx;
@@ -9158,14 +10197,14 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 
   ScrollPlayer(player, SCROLL_INIT);
 
-  return MF_MOVING;
+  return MP_MOVING;
 }
 
 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 {
   int jx = player->jx, jy = player->jy;
   int old_jx = jx, old_jy = jy;
-  int moved = MF_NO_ACTION;
+  int moved = MP_NO_ACTION;
 
   if (!player->active)
     return FALSE;
@@ -9238,8 +10277,14 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
   jx = player->jx;
   jy = player->jy;
 
-  if (moved & MF_MOVING && !ScreenMovPos &&
+#if 1
+  if (moved & MP_MOVING && !ScreenMovPos &&
+      (player->index_nr == game.centered_player_nr ||
+       game.centered_player_nr == -1))
+#else
+  if (moved & MP_MOVING && !ScreenMovPos &&
       (player == local_player || !options.network))
+#endif
   {
     int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
     int offset = (setup.scroll_delay ? 3 : 0);
@@ -9294,12 +10339,22 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
     if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
     {
+#if 1
+      if (!options.network && game.centered_player_nr == -1 &&
+         !AllPlayersInVisibleScreen())
+      {
+       scroll_x = old_scroll_x;
+       scroll_y = old_scroll_y;
+      }
+      else
+#else
       if (!options.network && !AllPlayersInVisibleScreen())
       {
        scroll_x = old_scroll_x;
        scroll_y = old_scroll_y;
       }
       else
+#endif
       {
        ScrollScreen(player, SCROLL_INIT);
        ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
@@ -9309,7 +10364,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
   player->StepFrame = 0;
 
-  if (moved & MF_MOVING)
+  if (moved & MP_MOVING)
   {
     if (old_jx != jx && old_jy == jy)
       player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
@@ -9323,6 +10378,8 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     player->is_snapping = FALSE;
     player->is_switching = FALSE;
     player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
+    player->drop_pressed_delay = 0;
   }
   else
   {
@@ -9648,7 +10705,94 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
   }
 }
 
+#if USE_ELEMENT_TOUCHING_BUGFIX
+
 void TestIfElementTouchesCustomElement(int x, int y)
+{
+  static int xy[4][2] =
+  {
+    { 0, -1 },
+    { -1, 0 },
+    { +1, 0 },
+    { 0, +1 }
+  };
+  static int trigger_sides[4][2] =
+  {
+    /* center side     border side */
+    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      /* check top    */
+    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* check left   */
+    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* check right  */
+    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     }       /* check bottom */
+  };
+  static int touch_dir[4] =
+  {
+    MV_LEFT | MV_RIGHT,
+    MV_UP   | MV_DOWN,
+    MV_UP   | MV_DOWN,
+    MV_LEFT | MV_RIGHT
+  };
+  boolean change_center_element = FALSE;
+  int center_element = Feld[x][y];     /* should always be non-moving! */
+  int border_element_old[NUM_DIRECTIONS];
+  int i;
+
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
+    int border_element;
+
+    border_element_old[i] = -1;
+
+    if (!IN_LEV_FIELD(xx, yy))
+      continue;
+
+    if (game.engine_version < VERSION_IDENT(3,0,7,0))
+      border_element = Feld[xx][yy];   /* may be moving! */
+    else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
+      border_element = Feld[xx][yy];
+    else if (MovDir[xx][yy] & touch_dir[i])    /* elements are touching */
+      border_element = MovingOrBlocked2Element(xx, yy);
+    else
+      continue;                        /* center and border element do not touch */
+
+    border_element_old[i] = border_element;
+  }
+
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
+    int center_side = trigger_sides[i][0];
+    int border_element = border_element_old[i];
+
+    if (border_element == -1)
+      continue;
+
+    /* check for change of border element */
+    CheckElementChangeBySide(xx, yy, border_element, center_element,
+                            CE_TOUCHING_X, center_side);
+  }
+
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    int border_side = trigger_sides[i][1];
+    int border_element = border_element_old[i];
+
+    if (border_element == -1)
+      continue;
+
+    /* check for change of center element (but change it only once) */
+    if (!change_center_element)
+      change_center_element =
+       CheckElementChangeBySide(x, y, center_element, border_element,
+                                CE_TOUCHING_X, border_side);
+  }
+}
+
+#else
+
+void TestIfElementTouchesCustomElement_OLD(int x, int y)
 {
   static int xy[4][2] =
   {
@@ -9708,6 +10852,8 @@ void TestIfElementTouchesCustomElement(int x, int y)
   }
 }
 
+#endif
+
 void TestIfElementHitsCustomElement(int x, int y, int direction)
 {
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
@@ -9817,6 +10963,7 @@ void TestIfElementSmashesCustomElement(int x, int y, int direction)
 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] =
   {
@@ -10109,7 +11256,7 @@ void RemovePlayer(struct PlayerInfo *player)
 static void setFieldForSnapping(int x, int y, int element, int direction)
 {
   struct ElementInfo *ei = &element_info[element];
-  int direction_bit = MV_DIR_BIT(direction);
+  int direction_bit = MV_DIR_TO_BIT(direction);
   int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
   int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
                IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
@@ -10169,6 +11316,8 @@ int DigField(struct PlayerInfo *player,
 {
   boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
   boolean player_was_pushing = player->is_pushing;
+  boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
+  boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
   int jx = oldx, jy = oldy;
   int dx = x - jx, dy = y - jy;
   int nextx = x + dx, nexty = y + dy;
@@ -10179,7 +11328,11 @@ int DigField(struct PlayerInfo *player,
   int opposite_direction = MV_DIR_OPPOSITE(move_direction);
   int dig_side = MV_DIR_OPPOSITE(move_direction);
   int old_element = Feld[jx][jy];
+#if USE_FIXED_DONT_RUN_INTO
+  int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
+#else
   int element;
+#endif
   int collect_count;
 
   if (is_player)               /* function can also be called by EL_PENGUIN */
@@ -10198,12 +11351,14 @@ int DigField(struct PlayerInfo *player,
       player->is_switching = FALSE;
       player->push_delay = -1;
 
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
     }
   }
 
+#if !USE_FIXED_DONT_RUN_INTO
   if (IS_MOVING(x, y) || IS_PLAYER(x, y))
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
+#endif
 
   if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
@@ -10213,36 +11368,97 @@ int DigField(struct PlayerInfo *player,
           game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
 
+#if 0
+#if USE_FIXED_DONT_RUN_INTO
+  if (player_can_move && DONT_RUN_INTO(element))
+  {
+    if (element == EL_ACID && dx == 0 && dy == 1)
+    {
+      SplashAcid(x, y);
+      Feld[jx][jy] = EL_PLAYER_1;
+      InitMovingField(jx, jy, MV_DOWN);
+      Store[jx][jy] = EL_ACID;
+      ContinueMoving(jx, jy);
+      BuryPlayer(player);
+    }
+    else
+      TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
+
+    return MP_DONT_RUN_INTO;
+  }
+#endif
+#endif
+
+#if 1
+#if USE_FIXED_DONT_RUN_INTO
+  if (player_can_move && DONT_RUN_INTO(element))
+  {
+    TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
+
+    return MP_DONT_RUN_INTO;
+  }
+#endif
+#endif
+
   if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
-    return MF_NO_ACTION;       /* field has no opening in this direction */
+    return MP_NO_ACTION;       /* field has no opening in this direction */
 
   if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
-    return MF_NO_ACTION;       /* field has no opening in this direction */
-
-  element = Feld[x][y];
-#if USE_NEW_CUSTOM_VALUE
+    return MP_NO_ACTION;       /* field has no opening in this direction */
 
 #if 1
-  collect_count = element_info[element].collect_count_initial;
-#else
-  collect_count = CustomValue[x][y];
-#endif
+#if USE_FIXED_DONT_RUN_INTO
+  if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
+  {
+    SplashAcid(x, y);
+    Feld[jx][jy] = EL_PLAYER_1;
+    InitMovingField(jx, jy, MV_DOWN);
+    Store[jx][jy] = EL_ACID;
+    ContinueMoving(jx, jy);
+    BuryPlayer(player);
 
-#else
-  collect_count = element_info[element].collect_count_initial;
+    return MP_DONT_RUN_INTO;
+  }
+#endif
 #endif
 
 #if 0
-  if (element != EL_BLOCKED &&
-      CustomValue[x][y] != element_info[element].collect_count_initial)
-    printf("::: %d: %d != %d\n",
-          element,
-          CustomValue[x][y],
-          element_info[element].collect_count_initial);
+#if USE_FIXED_DONT_RUN_INTO
+  if (player_can_move && DONT_RUN_INTO(element))
+  {
+    if (element == EL_ACID && dx == 0 && dy == 1)
+    {
+      SplashAcid(x, y);
+      Feld[jx][jy] = EL_PLAYER_1;
+      InitMovingField(jx, jy, MV_DOWN);
+      Store[jx][jy] = EL_ACID;
+      ContinueMoving(jx, jy);
+      BuryPlayer(player);
+    }
+    else
+      TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
+
+    return MP_DONT_RUN_INTO;
+  }
+#endif
+#endif
+
+#if USE_FIXED_DONT_RUN_INTO
+  if (IS_MOVING(x, y) || IS_PLAYER(x, y))
+    return MP_NO_ACTION;
 #endif
 
+#if !USE_FIXED_DONT_RUN_INTO
+  element = Feld[x][y];
+#endif
+
+  collect_count = element_info[element].collect_count_initial;
+
   if (!is_player && !IS_COLLECTIBLE(element))  /* penguin cannot collect it */
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
+
+  if (game.engine_version < VERSION_IDENT(2,2,0,0))
+    player_can_move = player_can_move_or_snap;
 
   if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
       game.engine_version >= VERSION_IDENT(2,2,0,0))
@@ -10253,17 +11469,18 @@ int DigField(struct PlayerInfo *player,
                                        player->index_bit, dig_side);
 
     if (Feld[x][y] != element)         /* field changed by snapping */
-      return MF_ACTION;
+      return MP_ACTION;
 
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
   }
 
   if (game.gravity && is_player && !player->is_auto_moving &&
       canFallDown(player) && move_direction != MV_DOWN &&
       !canMoveToValidFieldWithGravity(jx, jy, move_direction))
-    return MF_NO_ACTION;       /* player cannot walk here due to gravity */
+    return MP_NO_ACTION;       /* player cannot walk here due to gravity */
 
-  if (IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
+  if (player_can_move &&
+      IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
   {
     int sound_element = SND_ELEMENT(element);
     int sound_action = ACTION_WALKING;
@@ -10271,17 +11488,17 @@ int DigField(struct PlayerInfo *player,
     if (IS_RND_GATE(element))
     {
       if (!player->key[RND_GATE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_RND_GATE_GRAY(element))
     {
       if (!player->key[RND_GATE_GRAY_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_RND_GATE_GRAY_ACTIVE(element))
     {
       if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (element == EL_EXIT_OPEN ||
             element == EL_SP_EXIT_OPEN ||
@@ -10300,28 +11517,29 @@ int DigField(struct PlayerInfo *player,
     else
       PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
   }
-  else if (IS_PASSABLE(element) && canPassField(x, y, move_direction))
+  else if (player_can_move &&
+          IS_PASSABLE(element) && canPassField(x, y, move_direction))
   {
     if (!ACCESS_FROM(element, opposite_direction))
-      return MF_NO_ACTION;     /* field not accessible from this direction */
+      return MP_NO_ACTION;     /* field not accessible from this direction */
 
     if (CAN_MOVE(element))     /* only fixed elements can be passed! */
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (IS_EM_GATE(element))
     {
       if (!player->key[EM_GATE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_EM_GATE_GRAY(element))
     {
       if (!player->key[EM_GATE_GRAY_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_EM_GATE_GRAY_ACTIVE(element))
     {
       if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
-       return MF_NO_ACTION;
+       return MP_NO_ACTION;
     }
     else if (IS_SP_PORT(element))
     {
@@ -10354,7 +11572,7 @@ int DigField(struct PlayerInfo *player,
 
     PlayLevelSoundAction(x, y, ACTION_PASSING);
   }
-  else if (IS_DIGGABLE(element))
+  else if (player_can_move_or_snap && IS_DIGGABLE(element))
   {
     RemoveField(x, y);
 
@@ -10384,7 +11602,7 @@ int DigField(struct PlayerInfo *player,
                                          player->index_bit, dig_side);
     }
   }
-  else if (IS_COLLECTIBLE(element))
+  else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
   {
     RemoveField(x, y);
 
@@ -10409,7 +11627,9 @@ int DigField(struct PlayerInfo *player,
       if (element == EL_SHIELD_DEADLY)
        player->shield_deadly_time_left += level.shield_deadly_time;
     }
-    else if (element == EL_DYNAMITE || element == EL_SP_DISK_RED)
+    else if (element == EL_DYNAMITE ||
+            element == EL_EM_DYNAMITE ||
+            element == EL_SP_DISK_RED)
     {
       if (player->inventory_size < MAX_INVENTORY_SIZE)
        player->inventory_element[player->inventory_size++] = element;
@@ -10498,17 +11718,17 @@ int DigField(struct PlayerInfo *player,
                                          player->index_bit, dig_side);
     }
   }
-  else if (IS_PUSHABLE(element))
+  else if (player_can_move_or_snap && IS_PUSHABLE(element))
   {
     if (mode == DF_SNAP && element != EL_BD_ROCK)
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (CAN_FALL(element) && dy)
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
        !(element == EL_SPRING && level.use_spring_bug))
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
        ((move_direction & MV_VERTICAL &&
@@ -10521,12 +11741,12 @@ int DigField(struct PlayerInfo *player,
            IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
           (element_info[element].move_pattern & MV_DOWN &&
            IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     /* do not push elements already moving away faster than player */
     if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
        ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (game.engine_version >= VERSION_IDENT(3,1,0,0))
     {
@@ -10550,10 +11770,10 @@ int DigField(struct PlayerInfo *player,
          (IS_FREE(nextx, nexty) ||
           (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY &&
            IS_SB_ELEMENT(element)))))
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
 
     if (player->push_delay == -1)      /* new pushing; restart delay */
       player->push_delay = 0;
@@ -10566,7 +11786,7 @@ int DigField(struct PlayerInfo *player,
       if (game.engine_version >= VERSION_IDENT(3,0,7,1))
        player->move_delay = 0;
 
-      return MF_NO_ACTION;
+      return MP_NO_ACTION;
     }
 
     if (IS_SB_ELEMENT(element))
@@ -10636,7 +11856,7 @@ int DigField(struct PlayerInfo *player,
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                          player->index_bit, dig_side);
 
-      return MF_ACTION;
+      return MP_ACTION;
     }
 
     player->is_switching = TRUE;
@@ -10763,7 +11983,7 @@ int DigField(struct PlayerInfo *player,
     CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                        player->index_bit, dig_side);
 
-    return MF_ACTION;
+    return MP_ACTION;
   }
   else
   {
@@ -10789,7 +12009,7 @@ int DigField(struct PlayerInfo *player,
     CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                        player->index_bit, dig_side);
 
-    return MF_NO_ACTION;
+    return MP_NO_ACTION;
   }
 
   player->push_delay = -1;
@@ -10800,7 +12020,7 @@ int DigField(struct PlayerInfo *player,
       player->is_collecting = !player->is_digging;
   }
 
-  return MF_MOVING;
+  return MP_MOVING;
 }
 
 boolean SnapField(struct PlayerInfo *player, int dx, int dy)
@@ -10811,6 +12031,8 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
                        dx == +1 ? MV_RIGHT :
                        dy == -1 ? MV_UP    :
                        dy == +1 ? MV_DOWN  : MV_NONE);
+  boolean can_continue_snapping = (level.continuous_snapping &&
+                                  WasJustFalling[x][y] < CHECK_DELAY_FALLING);
 
   if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
     return FALSE;
@@ -10838,8 +12060,14 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
     return FALSE;
   }
 
+#if USE_NEW_CONTINUOUS_SNAPPING
+  /* prevent snapping with already pressed snap key when not allowed */
+  if (player->is_snapping && !can_continue_snapping)
+    return FALSE;
+#else
   if (player->is_snapping)
     return FALSE;
+#endif
 
   player->MovDir = snap_direction;
 
@@ -10851,8 +12079,10 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
   }
 
   player->is_dropping = FALSE;
+  player->is_dropping_pressed = FALSE;
+  player->drop_pressed_delay = 0;
 
-  if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MF_NO_ACTION)
+  if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
     return FALSE;
 
   player->is_snapping = TRUE;
@@ -10886,12 +12116,14 @@ boolean DropElement(struct PlayerInfo *player)
                      EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
                      EL_UNDEFINED);
 
+  player->is_dropping_pressed = TRUE;
+
   /* do not drop an element on top of another element; when holding drop key
      pressed without moving, dropped element must move away before the next
      element can be dropped (this is especially important if the next element
      is dynamite, which can be placed on background for historical reasons) */
   if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
-    return MF_ACTION;
+    return MP_ACTION;
 
   if (IS_THROWABLE(drop_element))
   {
@@ -10913,6 +12145,10 @@ boolean DropElement(struct PlayerInfo *player)
   if (new_element == EL_UNDEFINED)
     return FALSE;
 
+  /* check if drop key was pressed long enough for EM style dynamite */
+  if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
+    return FALSE;
+
   /* check if anything can be dropped at the current position */
   if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
     return FALSE;
@@ -10938,6 +12174,8 @@ boolean DropElement(struct PlayerInfo *player)
 
       if (new_element == EL_DYNAMITE)
        new_element = EL_DYNAMITE_ACTIVE;
+      else if (new_element == EL_EM_DYNAMITE)
+       new_element = EL_EM_DYNAMITE_ACTIVE;
       else if (new_element == EL_SP_DISK_RED)
        new_element = EL_SP_DISK_RED_ACTIVE;
     }
@@ -10992,12 +12230,15 @@ boolean DropElement(struct PlayerInfo *player)
     nexty = dropy + GET_DY_FROM_DIR(move_direction);
 
     ChangeCount[dropx][dropy] = 0;     /* allow at least one more change */
-    CheckCollision[dropx][dropy] = 2;
+    CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
   }
 
   player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
   player->is_dropping = TRUE;
 
+  player->drop_pressed_delay = 0;
+  player->is_dropping_pressed = FALSE;
+
   player->drop_x = dropx;
   player->drop_y = dropy;
 
@@ -11169,7 +12410,7 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
       break;
 
     case SAMPLE_slurp:
-      PlayLevelSoundElementAction(x, y, element, ACTION_SLURPED_BY_SPRING);
+      PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
       break;
 
     case SAMPLE_eater:
@@ -11331,6 +12572,7 @@ void RaiseScoreElement(int element)
       RaiseScore(level.score[SC_NUT]);
       break;
     case EL_DYNAMITE:
+    case EL_EM_DYNAMITE:
     case EL_SP_DISK_RED:
     case EL_DYNABOMB_INCREASE_NUMBER:
     case EL_DYNABOMB_INCREASE_SIZE: