rnd-20060816-2-src
[rocksndiamonds.git] / src / game.c
index 9c0dcf992585522a1a3ea9a5597aa1f1d98d0788..4e4c901c990286248b043614b2716d243a3d822e 100644 (file)
 #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_GFX_RESET_GFX_ANIMATION    (USE_NEW_STUFF          * 1)
+#define USE_BOTH_SWITCHGATE_SWITCHES   (USE_NEW_STUFF          * 1)
+#define USE_PLAYER_GRAVITY             (USE_NEW_STUFF          * 1)
+#define USE_FIXED_BORDER_RUNNING_GFX   (USE_NEW_STUFF          * 1)
+#define USE_QUICKSAND_BD_ROCK_BUGFIX   (USE_NEW_STUFF          * 0)
 
 #define USE_QUICKSAND_IMPACT_BUGFIX    (USE_NEW_STUFF          * 0)
 
+#define USE_CODE_THAT_BREAKS_SNAKE_BITE        (USE_NEW_STUFF          * 1)
+
+#define USE_UFAST_PLAYER_EXIT_BUGFIX   (USE_NEW_STUFF          * 1)
+#define USE_NEW_GAME_WON               (USE_NEW_STUFF          * 1)
+
+
 /* for DigField() */
 #define DF_NO_PUSH             0
 #define DF_DIG                 1
 #define EX_TYPE_DYNA           (1 << 4)
 #define EX_TYPE_SINGLE_TILE    (EX_TYPE_CENTER | EX_TYPE_BORDER)
 
+#if 1
+
+#define        PANEL_DEACTIVATED(p)    ((p).x < 0 || (p).y < 0)
+
+/* special positions in the game control window (relative to control window) */
+#define XX_LEVEL1              (game.panel.level.x)
+#define XX_LEVEL2              (game.panel.level.x - 1)
+#define YY_LEVEL               (game.panel.level.y)
+#define XX_EMERALDS            (game.panel.gems.x)
+#define YY_EMERALDS            (game.panel.gems.y)
+#define XX_DYNAMITE            (game.panel.inventory.x)
+#define YY_DYNAMITE            (game.panel.inventory.y)
+#define XX_KEYS                        (game.panel.keys.x)
+#define YY_KEYS                        (game.panel.keys.y)
+#define XX_SCORE               (game.panel.score.x)
+#define YY_SCORE               (game.panel.score.y)
+#define XX_TIME1               (game.panel.time.x)
+#define XX_TIME2               (game.panel.time.x + 1)
+#define YY_TIME                        (game.panel.time.y)
+
+#else
+
 /* special positions in the game control window (relative to control window) */
 #define XX_LEVEL               37
 #define YY_LEVEL               20
 #define XX_TIME2               30
 #define YY_TIME                        194
 
+#endif
+
 /* special positions in the game control window (relative to main window) */
-#define DX_LEVEL               (DX + XX_LEVEL)
+#define DX_LEVEL1              (DX + XX_LEVEL1)
+#define DX_LEVEL2              (DX + XX_LEVEL2)
 #define DY_LEVEL               (DY + YY_LEVEL)
 #define DX_EMERALDS            (DX + XX_EMERALDS)
 #define DY_EMERALDS            (DY + YY_EMERALDS)
 #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
                                 RND(element_info[e].move_delay_random))
 #define GET_MAX_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
                                    (element_info[e].move_delay_random))
-#define GET_NEW_CUSTOM_VALUE(e)        (   (element_info[e].ce_value_fixed_initial) +\
+#define GET_NEW_CE_VALUE(e)    (   (element_info[e].ce_value_fixed_initial) +\
                                 RND(element_info[e].ce_value_random_initial))
+#define GET_CE_SCORE(e)                (   (element_info[e].collect_score))
 #define GET_CHANGE_DELAY(c)    (   ((c)->delay_fixed  * (c)->delay_frames) + \
                                 RND((c)->delay_random * (c)->delay_frames))
 #define GET_CE_DELAY_VALUE(c)  (   ((c)->delay_fixed) + \
                                 RND((c)->delay_random))
 
+
 #if 1
 #define GET_VALID_RUNTIME_ELEMENT(e)                                   \
         ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
        ((e) >= NUM_FILE_ELEMENTS ? EL_UNKNOWN : (e))
 #endif
 
-#define GET_TARGET_ELEMENT(e, ch)                                      \
-       ((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 RESOLVED_REFERENCE_ELEMENT(be, e)                              \
+       ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START :     \
+        (be) + (e) - EL_SELF > EL_CUSTOM_END   ? EL_CUSTOM_END :       \
+        (be) + (e) - EL_SELF)
+
+#define GET_TARGET_ELEMENT(be, e, ch, cv, cs)                          \
+       ((e) == EL_TRIGGER_PLAYER   ? (ch)->actual_trigger_player    :  \
+        (e) == EL_TRIGGER_ELEMENT  ? (ch)->actual_trigger_element   :  \
+        (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value  :  \
+        (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score  :  \
+        (e) == EL_CURRENT_CE_VALUE ? (cv) :                            \
+        (e) == EL_CURRENT_CE_SCORE ? (cs) :                            \
+        (e) >= EL_LAST_CE_8 && (e) <= EL_NEXT_CE_8 ?                   \
+        RESOLVED_REFERENCE_ELEMENT(be, e) :                            \
+        (e))
 
 #define CAN_GROW_INTO(e)                                               \
        ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
@@ -326,6 +382,34 @@ 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 int getInvisibleActiveFromInvisibleElement(int);
+static int getInvisibleFromInvisibleActiveElement(int);
+
 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 
 
@@ -683,6 +767,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)
@@ -725,6 +821,7 @@ static int get_move_delay_from_stepsize(int move_stepsize)
 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
                               boolean init_game)
 {
+  int player_nr = player->index_nr;
   int move_delay = get_move_delay_from_stepsize(move_stepsize);
   boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
 
@@ -736,8 +833,8 @@ static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
 
   if (init_game)
   {
-    player->move_delay       = game.initial_move_delay;
-    player->move_delay_value = game.initial_move_delay_value;
+    player->move_delay       = game.initial_move_delay[player_nr];
+    player->move_delay_value = game.initial_move_delay_value[player_nr];
 
     player->move_delay_value_next = -1;
 
@@ -816,6 +913,32 @@ static int getBeltDirFromBeltSwitchElement(int element)
   return belt_move_dir[belt_dir_nr];
 }
 
+static int get_element_from_group_element(int element)
+{
+  if (IS_GROUP_ELEMENT(element))
+  {
+    struct ElementGroupInfo *group = element_info[element].group;
+    int last_anim_random_frame = gfx.anim_random_frame;
+    int element_pos;
+
+    if (group->choice_mode == ANIM_RANDOM)
+      gfx.anim_random_frame = RND(group->num_elements_resolved);
+
+    element_pos = getAnimationFrame(group->num_elements_resolved, 1,
+                                   group->choice_mode, 0,
+                                   group->choice_pos);
+
+    if (group->choice_mode == ANIM_RANDOM)
+      gfx.anim_random_frame = last_anim_random_frame;
+
+    group->choice_pos++;
+
+    element = group->element_resolved[element_pos];
+  }
+
+  return element;
+}
+
 static void InitPlayerField(int x, int y, int element, boolean init_game)
 {
   if (element == EL_SP_MURPHY)
@@ -831,6 +954,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;
@@ -926,41 +1052,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;
 
@@ -986,6 +1116,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;
@@ -1029,16 +1163,26 @@ static void InitField(int x, int y, boolean init_game)
       }
       break;
 
+#if !USE_BOTH_SWITCHGATE_SWITCHES
     case EL_SWITCHGATE_SWITCH_DOWN:    /* always start with same switch pos */
       if (init_game)
        Feld[x][y] = EL_SWITCHGATE_SWITCH_UP;
       break;
+#endif
 
     case EL_LIGHT_SWITCH_ACTIVE:
       if (init_game)
        game.light_time_left = level.time_light * FRAMES_PER_SECOND;
       break;
 
+    case EL_INVISIBLE_STEELWALL:
+    case EL_INVISIBLE_WALL:
+    case EL_INVISIBLE_SAND:
+      if (game.light_time_left > 0 ||
+         game.lenses_time_left > 0)
+        Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+      break;
+
     case EL_EMC_MAGIC_BALL:
       if (game.ball_state)
        Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
@@ -1058,7 +1202,7 @@ static void InitField(int x, int y, boolean init_game)
 
 #if USE_NEW_CUSTOM_VALUE
        if (!element_info[element].use_last_ce_value || init_game)
-         CustomValue[x][y] = GET_NEW_CUSTOM_VALUE(Feld[x][y]);
+         CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
 #endif
       }
 #else
@@ -1067,6 +1211,11 @@ static void InitField(int x, int y, boolean init_game)
 #endif
       else if (IS_GROUP_ELEMENT(element))
       {
+#if 1
+       Feld[x][y] = get_element_from_group_element(element);
+
+       InitField(x, y, init_game);
+#else
        struct ElementGroupInfo *group = element_info[element].group;
        int last_anim_random_frame = gfx.anim_random_frame;
        int element_pos;
@@ -1086,7 +1235,9 @@ static void InitField(int x, int y, boolean init_game)
        Feld[x][y] = group->element_resolved[element_pos];
 
        InitField(x, y, init_game);
+#endif
       }
+
       break;
   }
 
@@ -1100,7 +1251,7 @@ static void InitField(int x, int y, boolean init_game)
 #if USE_NEW_CUSTOM_VALUE
 
 #if 1
-  CustomValue[x][y] = GET_NEW_CUSTOM_VALUE(Feld[x][y]);
+  CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
 #else
   CustomValue[x][y] = element_info[Feld[x][y]].custom_value_initial;
 #endif
@@ -1143,49 +1294,88 @@ static inline void InitField_WithBug2(int x, int y, boolean init_game)
 
 inline void DrawGameValue_Emeralds(int value)
 {
-  DrawText(DX_EMERALDS, DY_EMERALDS, int2str(value, 3), FONT_TEXT_2);
+  int xpos = (3 * 14 - 3 * getFontWidth(FONT_TEXT_2)) / 2;
+
+  if (PANEL_DEACTIVATED(game.panel.gems))
+    return;
+
+  DrawText(DX_EMERALDS + xpos, DY_EMERALDS, int2str(value, 3), FONT_TEXT_2);
 }
 
 inline void DrawGameValue_Dynamite(int value)
 {
-  DrawText(DX_DYNAMITE, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2);
+  int xpos = (3 * 14 - 3 * getFontWidth(FONT_TEXT_2)) / 2;
+
+  if (PANEL_DEACTIVATED(game.panel.inventory))
+    return;
+
+  DrawText(DX_DYNAMITE + xpos, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2);
 }
 
 inline void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
 {
+  int base_key_graphic = EL_KEY_1;
   int i;
 
+  if (PANEL_DEACTIVATED(game.panel.keys))
+    return;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    base_key_graphic = EL_EM_KEY_1;
+
   /* currently only 4 of 8 possible keys are displayed */
   for (i = 0; i < STD_NUM_KEYS; i++)
   {
+    int x = XX_KEYS + i * MINI_TILEX;
+    int y = YY_KEYS;
+
     if (key[i])
-      DrawMiniGraphicExt(drawto, DX_KEYS + i * MINI_TILEX, DY_KEYS,
-                        el2edimg(EL_KEY_1 + i));
+      DrawMiniGraphicExt(drawto, DX + x,DY + y, el2edimg(base_key_graphic + i));
     else
       BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
-                DOOR_GFX_PAGEX5 + XX_KEYS + i * MINI_TILEX, YY_KEYS,
-                MINI_TILEX, MINI_TILEY, DX_KEYS + i * MINI_TILEX, DY_KEYS);
+                DOOR_GFX_PAGEX5 + x, y, MINI_TILEX, MINI_TILEY, DX + x,DY + y);
   }
 }
 
 inline void DrawGameValue_Score(int value)
 {
-  DrawText(DX_SCORE, DY_SCORE, int2str(value, 5), FONT_TEXT_2);
+  int xpos = (5 * 14 - 5 * getFontWidth(FONT_TEXT_2)) / 2;
+
+  if (PANEL_DEACTIVATED(game.panel.score))
+    return;
+
+  DrawText(DX_SCORE + xpos, DY_SCORE, int2str(value, 5), FONT_TEXT_2);
 }
 
 inline void DrawGameValue_Time(int value)
 {
+  int xpos3 = (3 * 14 - 3 * getFontWidth(FONT_TEXT_2)) / 2;
+  int xpos4 = (4 * 10 - 4 * getFontWidth(FONT_LEVEL_NUMBER)) / 2;
+
+  if (PANEL_DEACTIVATED(game.panel.time))
+    return;
+
+  /* clear background if value just changed its size */
+  if (value == 999 || value == 1000)
+    ClearRectangleOnBackground(drawto, DX_TIME1, DY_TIME, 14 * 3, 14);
+
   if (value < 1000)
-    DrawText(DX_TIME1, DY_TIME, int2str(value, 3), FONT_TEXT_2);
+    DrawText(DX_TIME1 + xpos3, DY_TIME, int2str(value, 3), FONT_TEXT_2);
   else
-    DrawText(DX_TIME2, DY_TIME, int2str(value, 4), FONT_LEVEL_NUMBER);
+    DrawText(DX_TIME2 + xpos4, DY_TIME, int2str(value, 4), FONT_LEVEL_NUMBER);
 }
 
 inline void DrawGameValue_Level(int value)
 {
+  if (PANEL_DEACTIVATED(game.panel.level))
+    return;
+
   if (level_nr < 100)
-    DrawText(DX_LEVEL, DY_LEVEL, int2str(value, 2), FONT_TEXT_2);
+    DrawText(DX_LEVEL1, DY_LEVEL, int2str(value, 2), FONT_TEXT_2);
   else
+#if 1
+    DrawText(DX_LEVEL2, DY_LEVEL, int2str(value, 3), FONT_LEVEL_NUMBER);
+#else
   {
     /* misuse area for displaying emeralds to draw bigger level number */
     DrawTextExt(drawto, DX_EMERALDS, DY_EMERALDS,
@@ -1203,6 +1393,7 @@ inline void DrawGameValue_Level(int value)
 
     /* yes, this is all really ugly :-) */
   }
+#endif
 }
 
 void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
@@ -1226,7 +1417,10 @@ void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
 
 void DrawGameDoorValues()
 {
-  int i;
+  int time_value = (level.time == 0 ? TimePlayed : TimeLeft);
+  int dynamite_state = 0;
+  int key_bits = 0;
+  int i, j;
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
@@ -1235,6 +1429,7 @@ void DrawGameDoorValues()
     return;
   }
 
+#if 0
   DrawGameValue_Level(level_nr);
 
   DrawGameValue_Emeralds(local_player->gems_still_needed);
@@ -1242,8 +1437,37 @@ void DrawGameDoorValues()
   DrawGameValue_Score(local_player->score);
   DrawGameValue_Time(TimeLeft);
 
-  for (i = 0; i < MAX_PLAYERS; i++)
+#else
+
+  if (game.centered_player_nr == -1)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      for (j = 0; j < MAX_NUM_KEYS; j++)
+       if (stored_player[i].key[j])
+         key_bits |= (1 << j);
+
+      dynamite_state += stored_player[i].inventory_size;
+    }
+
+#if 0
     DrawGameValue_Keys(stored_player[i].key);
+#endif
+  }
+  else
+  {
+    int player_nr = game.centered_player_nr;
+
+    for (i = 0; i < MAX_NUM_KEYS; i++)
+      if (stored_player[player_nr].key[i])
+       key_bits |= (1 << i);
+
+    dynamite_state = stored_player[player_nr].inventory_size;
+  }
+
+  DrawAllGameValues(local_player->gems_still_needed, dynamite_state,
+                   local_player->score, time_value, key_bits);
+#endif
 }
 
 #if 0
@@ -1292,6 +1516,18 @@ static void resolve_group_element(int group_element, int recursion_depth)
 }
 #endif
 
+#if 0
+static void replace_reference_element(int base_element, int *element)
+{
+  if (*element >= EL_LAST_CE_8 && *element <= EL_NEXT_CE_8)
+  {
+    *element = base_element + *element - EL_SELF;
+    *element = (*element < EL_CUSTOM_START ? EL_CUSTOM_START :
+               *element > EL_CUSTOM_END   ? EL_CUSTOM_END   : *element);
+  }
+}
+#endif
+
 /*
   =============================================================================
   InitGameEngine()
@@ -1437,8 +1673,9 @@ static void InitGameEngine()
 
 #if 1
   /* dynamically adjust player properties according to level information */
-  game.initial_move_delay_value =
-    get_move_delay_from_stepsize(level.initial_player_stepsize);
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.initial_move_delay_value[i] =
+      get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
 #else
   /* dynamically adjust player properties according to level information */
   game.initial_move_delay_value =
@@ -1446,8 +1683,10 @@ static void InitGameEngine()
 #endif
 
   /* dynamically adjust player properties according to game engine version */
-  game.initial_move_delay = (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
-                            game.initial_move_delay_value : 0);
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.initial_move_delay[i] =
+      (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
+       game.initial_move_delay_value[i] : 0);
 
   /* ---------- initialize player's initial push delay --------------------- */
 
@@ -1554,6 +1793,7 @@ static void InitGameEngine()
       ei->change_page[j].actual_trigger_player = EL_PLAYER_1;
       ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
       ei->change_page[j].actual_trigger_ce_value = 0;
+      ei->change_page[j].actual_trigger_ce_score = 0;
     }
   }
 
@@ -1590,6 +1830,11 @@ static void InitGameEngine()
              for (l = 0; l < group->num_elements_resolved; l++)
                trigger_events[group->element_resolved[l]][k] = TRUE;
            }
+#if 1
+           else if (trigger_element == EL_ANY_ELEMENT)
+             for (l = 0; l < MAX_NUM_ELEMENTS; l++)
+               trigger_events[l][k] = TRUE;
+#endif
            else
              trigger_events[trigger_element][k] = TRUE;
          }
@@ -1605,8 +1850,22 @@ static void InitGameEngine()
   {
     if (!IS_CUSTOM_ELEMENT(i))
     {
+#if 1
+      /* set default push delay values (corrected since version 3.0.7-1) */
+      if (game.engine_version < VERSION_IDENT(3,0,7,1))
+      {
+       element_info[i].push_delay_fixed = 2;
+       element_info[i].push_delay_random = 8;
+      }
+      else
+      {
+       element_info[i].push_delay_fixed = 8;
+       element_info[i].push_delay_random = 8;
+      }
+#else
       element_info[i].push_delay_fixed  = game.default_push_delay_fixed;
       element_info[i].push_delay_random = game.default_push_delay_random;
+#endif
     }
   }
 
@@ -1713,6 +1972,29 @@ static void InitGameEngine()
         EL_EMPTY);
     }
   }
+
+#if 0
+  /* ---------- initialize reference elements ------------------------------- */
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+    struct ElementInfo *ei = &element_info[element];
+
+    for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
+      replace_reference_element(element, &ei->content.e[x][y]);
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      struct ElementChangeInfo *change = &ei->change_page[j];
+
+      replace_reference_element(element, &change->target_element);
+      replace_reference_element(element, &change->trigger_element);
+
+      for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
+       replace_reference_element(element, &change->target_content.e[x][y]);
+    }
+  }
+#endif
 }
 
 int get_num_special_action(int element, int action_first, int action_last)
@@ -1735,6 +2017,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;
 }
 
@@ -1751,8 +2037,11 @@ void InitGame()
   boolean emulate_bd = TRUE;   /* unless non-BOULDERDASH elements found */
   boolean emulate_sb = TRUE;   /* unless non-SOKOBAN     elements found */
   boolean emulate_sp = TRUE;   /* unless non-SUPAPLEX    elements found */
+  boolean do_fading = (game_status == GAME_MODE_MAIN);
   int i, j, x, y;
 
+  game_status = GAME_MODE_PLAYING;
+
   InitGameEngine();
 
   /* don't play tapes over network */
@@ -1803,6 +2092,8 @@ void InitGame()
     player->block_last_field = FALSE;  /* initialized in InitPlayerField() */
     player->block_delay_adjustment = 0;        /* initialized in InitPlayerField() */
 
+    player->gravity = level.initial_player_gravity[i];
+
     player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
 
     player->actual_frame_counter = 0;
@@ -1811,6 +2102,8 @@ void InitGame()
 
     player->last_move_dir = MV_NONE;
 
+    player->is_active = FALSE;
+
     player->is_waiting = FALSE;
     player->is_moving = FALSE;
     player->is_auto_moving = FALSE;
@@ -1820,6 +2113,7 @@ 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;
@@ -1830,11 +2124,15 @@ void InitGame()
     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,
@@ -1842,6 +2140,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;
@@ -1852,7 +2151,7 @@ void InitGame()
     player->show_envelope = 0;
 
 #if 1
-    SetPlayerMoveSpeed(player, level.initial_player_stepsize, TRUE);
+    SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
 #else
     player->move_delay       = game.initial_move_delay;
     player->move_delay_value = game.initial_move_delay_value;
@@ -1868,6 +2167,7 @@ void InitGame()
     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;
@@ -1883,6 +2183,10 @@ void InitGame()
 
     player->LevelSolved = FALSE;
     player->GameOver = FALSE;
+
+    player->LevelSolved_GameEnd = FALSE;
+    player->LevelSolved_SaveTape = FALSE;
+    player->LevelSolved_SaveScore = FALSE;
   }
 
   network_player_action_received = FALSE;
@@ -1917,8 +2221,15 @@ void InitGame()
   game.timegate_time_left = 0;
   game.switchgate_pos = 0;
   game.wind_direction = level.wind_direction_initial;
+
+#if !USE_PLAYER_GRAVITY
+#if 1
+  game.gravity = FALSE;
+#else
   game.gravity = level.initial_gravity;
+#endif
   game.explosions_delayed = TRUE;
+#endif
 
   game.lenses_time_left = 0;
   game.magnify_time_left = 0;
@@ -1928,6 +2239,23 @@ void InitGame()
 
   game.envelope_active = FALSE;
 
+  /* set focus to local player for network games, else to all players */
+  game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
+  game.centered_player_nr_next = game.centered_player_nr;
+  game.set_centered_player = FALSE;
+
+  if (network_playing && tape.recording)
+  {
+    /* store client dependent player focus when recording network games */
+    tape.centered_player_nr_next = game.centered_player_nr_next;
+    tape.set_centered_player = TRUE;
+  }
+
+#if 0
+  printf("::: focus set to player %d [%d]\n",
+        game.centered_player_nr, local_player->index_nr);
+#endif
+
   for (i = 0; i < NUM_BELTS; i++)
   {
     game.belt_dir[i] = MV_NONE;
@@ -1993,6 +2321,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);
@@ -2092,7 +2436,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++)
     {
@@ -2288,9 +2632,14 @@ void InitGame()
                local_player->jy - MIDPOSY);
   }
 
+  StopAnimation();
+
   if (!game.restart_level)
     CloseDoor(DOOR_CLOSE_1);
 
+  if (do_fading)
+    FadeOut(REDRAW_FIELD);
+
   /* !!! FIX THIS (START) !!! */
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
@@ -2309,10 +2658,16 @@ void InitGame()
       BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
 
     redraw_mask |= REDRAW_FROM_BACKBUFFER;
+
+#if 0
     FadeToFront();
+#endif
   }
   /* !!! FIX THIS (END) !!! */
 
+  if (do_fading)
+    FadeIn(REDRAW_FIELD);
+
   if (!game.restart_level)
   {
     /* copy default game door content to main double buffer */
@@ -2320,6 +2675,11 @@ void InitGame()
               DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
   }
 
+#if 1
+  SetPanelBackground();
+  SetDrawBackgroundMask(REDRAW_DOOR_1);
+#endif
+
   DrawGameDoorValues();
 
   if (!game.restart_level)
@@ -2424,6 +2784,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;
@@ -2555,6 +2923,194 @@ void InitAmoebaNr(int x, int y)
   AmoebaCnt2[group_nr]++;
 }
 
+#if USE_NEW_GAME_WON
+
+void GameWon()
+{
+  static boolean score_done = FALSE;
+  static boolean player_done = FALSE;
+  static int game_over_delay = 0;
+  int game_over_delay_value = 50;
+
+  /* do not start end game actions before the player stops moving (to exit) */
+  if (local_player->MovPos)
+    return;
+
+  if (tape.auto_play)          /* tape might already be stopped here */
+    tape.auto_play_level_solved = TRUE;
+
+  if (!local_player->LevelSolved_GameEnd)
+  {
+    local_player->LevelSolved_GameEnd = TRUE;
+    local_player->LevelSolved_SaveTape = tape.recording;
+    local_player->LevelSolved_SaveScore = !tape.playing;
+
+    score_done = FALSE;
+    player_done = FALSE;
+    game_over_delay = 0;
+  }
+
+  PlaySoundStereo(SND_GAME_WINNING, SOUND_MIDDLE);
+
+  if (TimeLeft > 0)
+  {
+    if (!tape.playing)
+    {
+      if (setup.sound_loops)
+       PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
+                    SND_CTRL_PLAY_LOOP);
+      else
+       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
+    }
+
+    if (TimeLeft > 100 && TimeLeft % 10 == 0)
+    {
+      TimeLeft -= 10;
+      RaiseScore(level.score[SC_TIME_BONUS] * 10);
+    }
+    else
+    {
+      TimeLeft--;
+      RaiseScore(level.score[SC_TIME_BONUS]);
+    }
+
+    DrawGameValue_Time(TimeLeft);
+
+#if 0
+    if (!tape.playing)
+      Delay(10);
+#endif
+
+    if (TimeLeft <= 0 && !tape.playing && setup.sound_loops)
+      StopSound(SND_GAME_LEVELTIME_BONUS);
+  }
+  else if (level.time == 0 && TimePlayed < 999)        /* level without time limit */
+  {
+    if (!tape.playing)
+    {
+      if (setup.sound_loops)
+       PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
+                    SND_CTRL_PLAY_LOOP);
+      else
+       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
+    }
+
+    if (TimePlayed < 900 && TimePlayed % 10 == 0)
+    {
+      TimePlayed += 10;
+      RaiseScore(level.score[SC_TIME_BONUS] * 10);
+    }
+    else
+    {
+      TimePlayed++;
+      RaiseScore(level.score[SC_TIME_BONUS]);
+    }
+
+    DrawGameValue_Time(TimePlayed);
+
+    if (TimePlayed >= 999 && !tape.playing && setup.sound_loops)
+      StopSound(SND_GAME_LEVELTIME_BONUS);
+  }
+  else
+  {
+    score_done = TRUE;
+  }
+
+  /* close exit door after last player */
+  if (AllPlayersGone && ExitX >= 0 && ExitY >= 0 &&
+      (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
+       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN))
+  {
+    int element = Feld[ExitX][ExitY];
+
+    Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
+                         EL_SP_EXIT_CLOSING);
+
+    PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
+  }
+
+  /* player disappears */
+  if (ExitX >= 0 && ExitY >= 0 && !player_done)
+  {
+    DrawLevelField(ExitX, ExitY);
+
+    player_done = TRUE;
+  }
+
+  game_over_delay++;
+
+  if (game_over_delay < game_over_delay_value || !score_done)
+    return;
+}
+
+void GameEnd()
+{
+  int hi_pos;
+  boolean raise_level = FALSE;
+
+  CloseDoor(DOOR_CLOSE_1);
+
+  if (local_player->LevelSolved_SaveTape)
+  {
+    TapeStop();
+
+    SaveTape(tape.level_nr);           /* Ask to save tape */
+  }
+
+  if (!local_player->LevelSolved_SaveScore)
+  {
+    FadeOut(REDRAW_FIELD);
+
+    game_status = GAME_MODE_MAIN;
+
+    DrawAndFadeInMainMenu(REDRAW_FIELD);
+
+    return;
+  }
+
+  if (level_nr == leveldir_current->handicap_level)
+  {
+    leveldir_current->handicap_level++;
+    SaveLevelSetup_SeriesInfo();
+  }
+
+  if (level_editor_test_game)
+    local_player->score = -1;  /* no highscore when playing from editor */
+  else if (level_nr < leveldir_current->last_level)
+    raise_level = TRUE;                /* advance to next level */
+
+  if ((hi_pos = NewHiScore()) >= 0) 
+  {
+    game_status = GAME_MODE_SCORES;
+
+    DrawHallOfFame(hi_pos);
+
+    if (raise_level)
+    {
+      level_nr++;
+      TapeErase();
+    }
+  }
+  else
+  {
+    FadeOut(REDRAW_FIELD);
+
+    game_status = GAME_MODE_MAIN;
+
+    if (raise_level)
+    {
+      level_nr++;
+      TapeErase();
+    }
+
+    DrawAndFadeInMainMenu(REDRAW_FIELD);
+  }
+
+  local_player->LevelSolved_SaveScore = FALSE;
+}
+
+#else
+
 void GameWon()
 {
   int hi_pos;
@@ -2656,6 +3212,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;
 
@@ -2702,6 +3265,8 @@ void GameWon()
   BackToFront();
 }
 
+#endif
+
 int NewHiScore()
 {
   int k, l;
@@ -2709,7 +3274,7 @@ int NewHiScore()
 
   LoadScore(level_nr);
 
-  if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
+  if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
       local_player->score < highscore[MAX_SCORE_ENTRIES - 1].Score) 
     return -1;
 
@@ -2725,7 +3290,7 @@ int NewHiScore()
 
 #ifdef ONE_PER_NAME
        for (l = k; l < MAX_SCORE_ENTRIES; l++)
-         if (!strcmp(setup.player_name, highscore[l].Name))
+         if (strEqual(setup.player_name, highscore[l].Name))
            m = l;
        if (m == k)     /* player's new highscore overwrites his old one */
          goto put_into_list;
@@ -2809,10 +3374,26 @@ void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
   }
 }
 
-static void ResetRandomAnimationValue(int x, int y)
+#if USE_GFX_RESET_GFX_ANIMATION
+static void ResetGfxFrame(int x, int y, boolean redraw)
 {
-  GfxRandom[x][y] = INIT_GFX_RANDOM();
+  int element = Feld[x][y];
+  int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+  int last_gfx_frame = GfxFrame[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;
+  else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+    GfxFrame[x][y] = ChangeDelay[x][y];
+
+  if (redraw && GfxFrame[x][y] != last_gfx_frame)
+    DrawLevelGraphicAnimation(x, y, graphic);
 }
+#endif
 
 static void ResetGfxAnimation(int x, int y)
 {
@@ -2820,9 +3401,9 @@ static void ResetGfxAnimation(int x, int y)
   int element, graphic;
 #endif
 
-  GfxFrame[x][y] = 0;
   GfxAction[x][y] = ACTION_DEFAULT;
   GfxDir[x][y] = MovDir[x][y];
+  GfxFrame[x][y] = 0;
 
 #if 0
   element = Feld[x][y];
@@ -2834,9 +3415,20 @@ static void ResetGfxAnimation(int x, int y)
     GfxFrame[x][y] = CustomValue[x][y];
   else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
     GfxFrame[x][y] = element_info[element].collect_score;
+  else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+    GfxFrame[x][y] = ChangeDelay[x][y];
+#endif
+
+#if USE_GFX_RESET_GFX_ANIMATION
+  ResetGfxFrame(x, y, FALSE);
 #endif
 }
 
+static void ResetRandomAnimationValue(int x, int y)
+{
+  GfxRandom[x][y] = INIT_GFX_RANDOM();
+}
+
 void InitMovingField(int x, int y, int direction)
 {
   int element = Feld[x][y];
@@ -2865,6 +3457,8 @@ void InitMovingField(int x, int y, int direction)
     GfxFrame[x][y] = CustomValue[x][y];
   else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
     GfxFrame[x][y] = element_info[element].collect_score;
+  else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+    GfxFrame[x][y] = ChangeDelay[x][y];
 #endif
 
   /* this is needed for CEs with property "can move" / "not moving" */
@@ -3069,21 +3663,198 @@ void CheckDynamite(int x, int y)
   {
     MovDelay[x][y]--;
 
-    if (MovDelay[x][y] != 0)
-    {
-      DrawDynamite(x, y);
-      PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+    if (MovDelay[x][y] != 0)
+    {
+      DrawDynamite(x, y);
+      PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+
+      return;
+    }
+  }
+
+  StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+
+  Bang(x, y);
+}
+
+#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);
+
+  if (quick_relocation)
+  {
+    int offset = (setup.scroll_delay ? 3 : 0);
+
+#if 0
+    if (center_screen)
+      offset = 0;
+#endif
+
+    if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
+    {
+      scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+                 x > SBX_Right + MIDPOSX ? SBX_Right :
+                 x - MIDPOSX);
+
+      scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                 y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                 y - MIDPOSY);
+    }
+    else
+    {
+      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 ((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)
+       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 = (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_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);
 
-      return;
+      /* scroll second step to align at full tile size */
+      BackToFront();
+      Delay(wait_delay_value);
     }
-  }
-
-  StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
 
-  Bang(x, y);
+    DrawAllPlayers();
+    BackToFront();
+    Delay(wait_delay_value);
+  }
 }
 
-void DrawRelocatePlayer(struct PlayerInfo *player)
+#else
+
+void DrawRelocatePlayer(struct PlayerInfo *player, boolean quick_relocation)
 {
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
@@ -3092,19 +3863,19 @@ void DrawRelocatePlayer(struct PlayerInfo *player)
   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)))
     {
-      scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-                 local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-                 local_player->jx - MIDPOSX);
+      scroll_x = (player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
+                 player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+                 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);
+      scroll_y = (player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+                 player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+                 player->jy - MIDPOSY);
     }
     else
     {
@@ -3129,23 +3900,21 @@ void DrawRelocatePlayer(struct PlayerInfo *player)
   }
   else
   {
-    int scroll_xx = -999, scroll_yy = -999;
+    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_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);
 
@@ -3177,6 +3946,8 @@ void DrawRelocatePlayer(struct PlayerInfo *player)
   }
 }
 
+#endif
+
 void RelocatePlayer(int jx, int jy, int el_player_raw)
 {
   int el_player = GET_PLAYER_ELEMENT(el_player_raw);
@@ -3252,8 +4023,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);
@@ -3510,6 +4292,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;
 
@@ -3544,6 +4331,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");
@@ -3553,6 +4345,8 @@ void Explode(int ex, int ey, int phase, int mode)
 
     GfxElement[x][y] = EL_EMPTY;
   }
+#endif
+
 #endif
 
   border_element = Store2[x][y];
@@ -3983,12 +4777,25 @@ static void ToggleSwitchgateSwitch(int x, int y)
   {
     int element = Feld[xx][yy];
 
+#if !USE_BOTH_SWITCHGATE_SWITCHES
     if (element == EL_SWITCHGATE_SWITCH_UP ||
        element == EL_SWITCHGATE_SWITCH_DOWN)
     {
       Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
       DrawLevelField(xx, yy);
     }
+#else
+    if (element == EL_SWITCHGATE_SWITCH_UP)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
+      DrawLevelField(xx, yy);
+    }
+    else if (element == EL_SWITCHGATE_SWITCH_DOWN)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
+      DrawLevelField(xx, yy);
+    }
+#endif
     else if (element == EL_SWITCHGATE_OPEN ||
             element == EL_SWITCHGATE_OPENING)
     {
@@ -5294,7 +6101,7 @@ inline static void TurnRoundExt(int x, int y)
 static void TurnRound(int x, int y)
 {
   int direction = MovDir[x][y];
-#if 1
+#if 0
   int element, graphic;
 #endif
 
@@ -5309,6 +6116,8 @@ static void TurnRound(int x, int y)
     GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
 
 #if 1
+  ResetGfxFrame(x, y, FALSE);
+#else
   element = Feld[x][y];
   graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
 
@@ -5318,6 +6127,8 @@ static void TurnRound(int x, int y)
     GfxFrame[x][y] = CustomValue[x][y];
   else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
     GfxFrame[x][y] = element_info[element].collect_score;
+  else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+    GfxFrame[x][y] = ChangeDelay[x][y];
 #endif
 }
 
@@ -5368,7 +6179,12 @@ void StartMoving(int x, int y)
        started_moving = TRUE;
 
        Feld[x][y] = EL_QUICKSAND_EMPTYING;
+#if USE_QUICKSAND_BD_ROCK_BUGFIX
+       if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
+         Store[x][y] = EL_ROCK;
+#else
        Store[x][y] = EL_ROCK;
+#endif
 
        PlayLevelSoundAction(x, y, ACTION_EMPTYING);
       }
@@ -6451,13 +7267,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 */
@@ -7311,7 +8127,8 @@ void MauerAbleger(int ax, int ay)
 
   if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
       element == EL_EXPANDABLE_WALL_ANY ||
-      element == EL_EXPANDABLE_WALL)
+      element == EL_EXPANDABLE_WALL ||
+      element == EL_BD_EXPANDABLE_WALL)
   {
     if (links_frei)
     {
@@ -7435,9 +8252,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);
 
@@ -7492,6 +8310,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 {
   struct ElementInfo *ei = &element_info[element];
   struct ElementChangeInfo *change = &ei->change_page[page];
+  int target_element = change->target_element;
   int action_type = change->action_type;
   int action_mode = change->action_mode;
   int action_arg = change->action_arg;
@@ -7530,21 +8349,21 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_type == CA_SET_LEVEL_GEMS ? 999 :
      action_type == CA_SET_LEVEL_TIME ? 9999 :
      action_type == CA_SET_LEVEL_SCORE ? 99999 :
-     action_type == CA_SET_CE_SCORE ? 9999 :
      action_type == CA_SET_CE_VALUE ? 9999 :
+     action_type == CA_SET_CE_SCORE ? 9999 :
      CA_ARG_MAX);
 
   int action_arg_number_reset =
-    (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize :
+    (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
      action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
      action_type == CA_SET_LEVEL_TIME ? level.time :
      action_type == CA_SET_LEVEL_SCORE ? 0 :
-     action_type == CA_SET_CE_SCORE ? 0 :
 #if 1
-     action_type == CA_SET_CE_VALUE ? GET_NEW_CUSTOM_VALUE(element) :
+     action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
 #else
      action_type == CA_SET_CE_VALUE ? ei->custom_value_initial :
 #endif
+     action_type == CA_SET_CE_SCORE ? 0 :
      0);
 
   int action_arg_number =
@@ -7555,18 +8374,20 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
      action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
      action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
-     action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
 #if USE_NEW_CUSTOM_VALUE
      action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
 #else
      action_arg == CA_ARG_NUMBER_CE_VALUE ? ei->custom_value_initial :
 #endif
+     action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
      action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
      action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
      action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
      action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
-     action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CUSTOM_VALUE(change->target_element) :
+     action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
      action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
+     action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
+     action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
      action_arg == CA_ARG_ELEMENT_NR_TARGET  ? change->target_element :
      action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
      -1);
@@ -7575,8 +8396,8 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
      action_type == CA_SET_LEVEL_TIME ? TimeLeft :
      action_type == CA_SET_LEVEL_SCORE ? local_player->score :
-     action_type == CA_SET_CE_SCORE ? ei->collect_score :
      action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
+     action_type == CA_SET_CE_SCORE ? ei->collect_score :
      0);
 
   int action_arg_number_new =
@@ -7659,6 +8480,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
       break;
     }
 
+#if !USE_PLAYER_GRAVITY
     case CA_SET_LEVEL_GRAVITY:
     {
       game.gravity = (action_arg == CA_ARG_GRAVITY_OFF    ? FALSE         :
@@ -7667,6 +8489,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
                      game.gravity);
       break;
     }
+#endif
 
     case CA_SET_LEVEL_WIND:
     {
@@ -7719,7 +8542,11 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
          {
            stored_player[i].key[KEY_NR(element)] = key_state;
 
+#if 1
+           DrawGameDoorValues();
+#else
            DrawGameValue_Keys(stored_player[i].key);
+#endif
 
            redraw_mask |= REDRAW_DOOR_1;
          }
@@ -7749,6 +8576,10 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
            action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
                           CA_MODE_MULTIPLY);
          }
+         else if (action_arg == CA_ARG_NUMBER_RESET)
+         {
+           action_arg_number = level.initial_player_stepsize[i];
+         }
 
          move_stepsize =
            getModifiedActionNumber(move_stepsize,
@@ -7801,6 +8632,25 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
       break;
     }
 
+#if USE_PLAYER_GRAVITY
+    case CA_SET_PLAYER_GRAVITY:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (trigger_player_bits & (1 << i))
+       {
+         stored_player[i].gravity =
+           (action_arg == CA_ARG_GRAVITY_OFF    ? FALSE                     :
+            action_arg == CA_ARG_GRAVITY_ON     ? TRUE                      :
+            action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
+            stored_player[i].gravity);
+       }
+      }
+
+      break;
+    }
+#endif
+
     case CA_SET_PLAYER_ARTWORK:
     {
       for (i = 0; i < MAX_PLAYERS; i++)
@@ -7833,37 +8683,95 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
     /* ---------- CE actions  ---------------------------------------------- */
 
-    case CA_SET_CE_SCORE:
+    case CA_SET_CE_VALUE:
     {
-      ei->collect_score = action_arg_number_new;
+#if USE_NEW_CUSTOM_VALUE
+      int last_ce_value = CustomValue[x][y];
+
+      CustomValue[x][y] = action_arg_number_new;
+
+#if 0
+      printf("::: CE value == %d\n", CustomValue[x][y]);
+#endif
+
+      if (CustomValue[x][y] != last_ce_value)
+      {
+       CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
+       CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
+
+       if (CustomValue[x][y] == 0)
+       {
+#if 0
+         printf("::: CE_VALUE_GETS_ZERO\n");
+#endif
+
+         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
 
       break;
     }
 
-    case CA_SET_CE_VALUE:
+    case CA_SET_CE_SCORE:
     {
 #if USE_NEW_CUSTOM_VALUE
-      int last_custom_value = CustomValue[x][y];
+      int last_ce_score = ei->collect_score;
 
-      CustomValue[x][y] = action_arg_number_new;
+      ei->collect_score = action_arg_number_new;
 
 #if 0
-      printf("::: Count == %d\n", CustomValue[x][y]);
+      printf("::: CE score == %d\n", ei->collect_score);
 #endif
 
-      if (CustomValue[x][y] == 0 && last_custom_value > 0)
+      if (ei->collect_score != last_ce_score)
       {
+       CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
+       CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
+
+       if (ei->collect_score == 0)
+       {
+         int xx, yy;
+
 #if 0
-       printf("::: CE_VALUE_GETS_ZERO\n");
+         printf("::: CE_SCORE_GETS_ZERO\n");
 #endif
 
-       CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
-       CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
+         CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
+         CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
 
 #if 0
-       printf("::: RESULT: %d, %d\n", Feld[x][y], ChangePage[x][y]);
+         printf("::: RESULT: %d, %d\n", Feld[x][y], ChangePage[x][y]);
+#endif
+
+#if 1
+         /*
+           This is a very special case that seems to be a mixture between
+           CheckElementChange() and CheckTriggeredElementChange(): while
+           the first one only affects single elements that are triggered
+           directly, the second one affects multiple elements in the playfield
+           that are triggered indirectly by another element. This is a third
+           case: Changing the CE score always affects multiple identical CEs,
+           so every affected CE must be checked, not only the single CE for
+           which the CE score was changed in the first place (as every instance
+           of that CE shares the same CE score, and therefore also can change)!
+         */
+         SCAN_PLAYFIELD(xx, yy)
+         {
+           if (Feld[xx][yy] == element)
+             CheckElementChange(xx, yy, element, EL_UNDEFINED,
+                                CE_SCORE_GETS_ZERO);
+         }
 #endif
+       }
       }
+
 #endif
 
       break;
@@ -7885,54 +8793,87 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
 static void CreateFieldExt(int x, int y, int element, boolean is_change)
 {
+  int old_element = Feld[x][y];
+  int new_element = get_element_from_group_element(element);
   int previous_move_direction = MovDir[x][y];
 #if USE_NEW_CUSTOM_VALUE
   int last_ce_value = CustomValue[x][y];
 #endif
-  boolean add_player = (ELEM_IS_PLAYER(element) &&
-                       IS_WALKABLE(Feld[x][y]));
+  boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
+  boolean add_player_onto_element = (new_element_is_player &&
+#if USE_CODE_THAT_BREAKS_SNAKE_BITE
+                                    /* this breaks SnakeBite when a snake is
+                                       halfway through a door that closes */
+                                    /* NOW FIXED AT LEVEL INIT IN files.c */
+                                    new_element != EL_SOKOBAN_FIELD_PLAYER &&
+#endif
+                                    IS_WALKABLE(old_element));
 
-  /* check if element under player changes from accessible to unaccessible
+#if 0
+  /* check if element under the player changes from accessible to unaccessible
      (needed for special case of dropping element which then changes) */
   if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
-      IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(element))
+      IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
   {
     Bang(x, y);
 
     return;
   }
+#endif
 
-  if (!add_player)
+  if (!add_player_onto_element)
   {
     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
       RemoveMovingField(x, y);
     else
       RemoveField(x, y);
 
-    Feld[x][y] = element;
+    Feld[x][y] = new_element;
 
+#if !USE_GFX_RESET_GFX_ANIMATION
     ResetGfxAnimation(x, y);
     ResetRandomAnimationValue(x, y);
+#endif
 
-    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+    if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
       MovDir[x][y] = previous_move_direction;
 
 #if USE_NEW_CUSTOM_VALUE
-    if (element_info[Feld[x][y]].use_last_ce_value)
+    if (element_info[new_element].use_last_ce_value)
       CustomValue[x][y] = last_ce_value;
 #endif
 
     InitField_WithBug1(x, y, FALSE);
 
+    new_element = Feld[x][y];  /* element may have changed */
+
+#if USE_GFX_RESET_GFX_ANIMATION
+    ResetGfxAnimation(x, y);
+    ResetRandomAnimationValue(x, y);
+#endif
+
     DrawLevelField(x, y);
 
-    if (GFX_CRUMBLED(Feld[x][y]))
+    if (GFX_CRUMBLED(new_element))
       DrawLevelFieldCrumbledSandNeighbours(x, y);
   }
 
+#if 1
+  /* check if element under the player changes from accessible to unaccessible
+     (needed for special case of dropping element which then changes) */
+  /* (must be checked after creating new element for walkable group elements) */
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
+      IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
+  {
+    Bang(x, y);
+
+    return;
+  }
+#endif
+
   /* "ChangeCount" not set yet to allow "entered by player" change one time */
-  if (ELEM_IS_PLAYER(element))
-    RelocatePlayer(x, y, element);
+  if (new_element_is_player)
+    RelocatePlayer(x, y, new_element);
 
   if (is_change)
     ChangeCount[x][y]++;       /* count number of changes in the same frame */
@@ -7969,7 +8910,10 @@ static void CreateElementFromChange(int x, int y, int element)
 
 static boolean ChangeElement(int x, int y, int element, int page)
 {
-  struct ElementChangeInfo *change = &element_info[element].change_page[page];
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  int ce_value = CustomValue[x][y];
+  int ce_score = ei->collect_score;
   int target_element;
   int old_element = Feld[x][y];
 
@@ -7984,6 +8928,7 @@ static boolean ChangeElement(int x, int y, int element, int page)
     change->actual_trigger_player = EL_PLAYER_1;
     change->actual_trigger_side = CH_SIDE_NONE;
     change->actual_trigger_ce_value = 0;
+    change->actual_trigger_ce_score = 0;
   }
 
   /* do not change elements more than a specified maximum number of changes */
@@ -8088,7 +9033,8 @@ static boolean ChangeElement(int x, int y, int element, int page)
          ChangeEvent[ex][ey] = ChangeEvent[x][y];
 
          content_element = change->target_content.e[xx][yy];
-         target_element = GET_TARGET_ELEMENT(content_element, change);
+         target_element = GET_TARGET_ELEMENT(element, content_element, change,
+                                             ce_value, ce_score);
 
          CreateElementFromChange(ex, ey, target_element);
 
@@ -8109,7 +9055,8 @@ static boolean ChangeElement(int x, int y, int element, int page)
   }
   else
   {
-    target_element = GET_TARGET_ELEMENT(change->target_element, change);
+    target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
+                                       ce_value, ce_score);
 
     if (element == EL_DIAGONAL_GROWING ||
        element == EL_DIAGONAL_SHRINKING)
@@ -8343,6 +9290,7 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
        change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
        change->actual_trigger_side = trigger_side;
        change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
+       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
 
        if ((change->can_change && !change_done) || change->has_action)
        {
@@ -8447,6 +9395,7 @@ static boolean CheckElementChangeExt(int x, int y,
       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
       change->actual_trigger_side = trigger_side;
       change->actual_trigger_ce_value = CustomValue[x][y];
+      change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
 
       /* special case: trigger element not at (x,y) position for some events */
       if (check_trigger_element)
@@ -8469,6 +9418,7 @@ static boolean CheckElementChangeExt(int x, int y,
        int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
 
        change->actual_trigger_ce_value = CustomValue[xx][yy];
+       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
       }
 
       if (change->can_change && !change_done)
@@ -8537,6 +9487,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)
@@ -8554,7 +9505,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 +
@@ -8572,6 +9527,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)
@@ -8654,6 +9629,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;
@@ -8670,8 +9646,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;
@@ -8716,15 +9692,95 @@ 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;
 
+#if 0
+  Error(ERR_NETWORK_CLIENT, "advancing frame counter from %d to %d",
+       FrameCounter, FrameCounter + 1);
+#endif
+
   /* advance frame counters (global frame counter and time frame counter) */
   FrameCounter++;
   TimeFrames++;
@@ -8766,20 +9822,68 @@ 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)
+{
+  unsigned long new_random_seed = InitRND(random_seed);
+
+  if (record_tape)
+    TapeStartRecording(new_random_seed);
+
+#if defined(NETWORK_AVALIABLE)
+  if (init_network_game)
+  {
+    SendToServer_StartPlaying();
+
+    return;
   }
+#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 =
@@ -8790,20 +9894,7 @@ void GameActions()
 
   /* ---------- main game synchronization point ---------- */
 
-  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
-
-  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
+  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
 
   if (network_playing && !network_player_action_received)
   {
@@ -8820,19 +9911,27 @@ void GameActions()
 
     if (!network_player_action_received)
       return;          /* failed to get network player actions in time */
+
+    /* do not yet reset "network_player_action_received" (for tape.pausing) */
   }
 
   if (tape.pausing)
     return;
 
-  recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+  /* at this point we know that we really continue executing the game */
 
 #if 1
-  /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
-  if (recorded_player_action == NULL && tape.pausing)
-    return;
+  network_player_action_received = FALSE;
 #endif
 
+  recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+
+  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++)
   {
     summarized_player_action |= stored_player[i].action;
@@ -8849,6 +9948,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];
@@ -8857,14 +9963,107 @@ 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;
+
+#if 0
+  printf("::: %04d: %08x\n", FrameCounter, effective_action[0]);
+#endif
+
+  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;
+    }
+
+    game.centered_player_nr = game.centered_player_nr_next;
+    game.set_centered_player = FALSE;
+
+    DrawRelocateScreen(sx, sy, MV_NONE, TRUE, setup.quick_switch);
+    DrawGameDoorValues();
+  }
+#endif
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     int actual_player_action = stored_player[i].effective_action;
@@ -8896,7 +10095,9 @@ void GameActions()
     ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
   }
 
+#if 0
   network_player_action_received = FALSE;
+#endif
 
   ScrollScreen(NULL, SCROLL_GO_ON);
 
@@ -9024,6 +10225,9 @@ void GameActions()
       printf("::: Yo man! Rocks can fall!\n");
 #endif
 
+#if 1
+    ResetGfxFrame(x, y, TRUE);
+#else
     if (graphic_info[graphic].anim_global_sync)
       GfxFrame[x][y] = FrameCounter;
     else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
@@ -9048,6 +10252,18 @@ void GameActions()
 #endif
        DrawLevelGraphicAnimation(x, y, graphic);
     }
+    else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+    {
+      int old_gfx_frame = GfxFrame[x][y];
+
+      GfxFrame[x][y] = ChangeDelay[x][y];
+
+#if 1
+      if (GfxFrame[x][y] != old_gfx_frame)
+#endif
+       DrawLevelGraphicAnimation(x, y, graphic);
+    }
+#endif
 
     if (ANIM_MODE(graphic) == ANIM_RANDOM &&
        IS_NEXT_FRAME(GfxFrame[x][y], graphic))
@@ -9149,7 +10365,8 @@ void GameActions()
     else if (element == EL_EXPANDABLE_WALL ||
             element == EL_EXPANDABLE_WALL_HORIZONTAL ||
             element == EL_EXPANDABLE_WALL_VERTICAL ||
-            element == EL_EXPANDABLE_WALL_ANY)
+            element == EL_EXPANDABLE_WALL_ANY ||
+            element == EL_BD_EXPANDABLE_WALL)
       MauerAbleger(x, y);
     else if (element == EL_FLAMES)
       CheckForDragon(x, y);
@@ -9343,48 +10560,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();
@@ -9535,11 +10711,15 @@ static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
 
 static void CheckGravityMovement(struct PlayerInfo *player)
 {
+#if USE_PLAYER_GRAVITY
+  if (player->gravity && !player->programmed_action)
+#else
   if (game.gravity && !player->programmed_action)
+#endif
   {
     int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
     int move_dir_vertical   = player->effective_action & MV_VERTICAL;
-    boolean player_is_snapping = player->effective_action & JOY_BUTTON_1;
+    boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
     int jx = player->jx, jy = player->jy;
     boolean player_is_moving_to_valid_field =
       (!player_is_snapping &&
@@ -9557,7 +10737,11 @@ static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
 {
   return CheckGravityMovement(player);
 
+#if USE_PLAYER_GRAVITY
+  if (player->gravity && !player->programmed_action)
+#else
   if (game.gravity && !player->programmed_action)
+#endif
   {
     int jx = player->jx, jy = player->jy;
     boolean field_under_player_is_free =
@@ -9622,8 +10806,14 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 #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 MP_NO_ACTION;
+#endif
 
 #if !USE_FIXED_DONT_RUN_INTO
   element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
@@ -9684,7 +10874,14 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 
   PlayerVisit[jx][jy] = FrameCounter;
 
+#if USE_UFAST_PLAYER_EXIT_BUGFIX
+  player->is_moving = TRUE;
+#endif
+
+#if 1
+  /* should better be called in MovePlayer(), but this breaks some tapes */
   ScrollPlayer(player, SCROLL_INIT);
+#endif
 
   return MP_MOVING;
 }
@@ -9752,6 +10949,8 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     player->move_delay_value = original_move_delay_value;
   }
 
+  player->is_active = FALSE;
+
   if (player->last_move_dir & MV_HORIZONTAL)
   {
     if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
@@ -9763,11 +10962,28 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
   }
 
+#if USE_FIXED_BORDER_RUNNING_GFX
+  if (!moved && !player->is_active)
+  {
+    player->is_moving = FALSE;
+    player->is_digging = FALSE;
+    player->is_collecting = FALSE;
+    player->is_snapping = FALSE;
+    player->is_pushing = FALSE;
+  }
+#endif
+
   jx = player->jx;
   jy = player->jy;
 
+#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);
@@ -9822,12 +11038,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);
@@ -9851,6 +11077,13 @@ 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;
+
+#if 0
+    /* should better be called here than above, but this breaks some tapes */
+    ScrollPlayer(player, SCROLL_INIT);
+#endif
   }
   else
   {
@@ -9916,8 +11149,13 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        last_field_block_delay += player->move_delay_value;
 
        /* when blocking enabled, prevent moving up despite gravity */
+#if USE_PLAYER_GRAVITY
+       if (player->gravity && player->MovDir == MV_UP)
+         block_delay_adjustment = -1;
+#else
        if (game.gravity && player->MovDir == MV_UP)
          block_delay_adjustment = -1;
+#endif
       }
 
       /* add block delay adjustment (also possible when not blocking) */
@@ -10176,7 +11414,7 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
   }
 }
 
-#if 1
+#if USE_ELEMENT_TOUCHING_BUGFIX
 
 void TestIfElementTouchesCustomElement(int x, int y)
 {
@@ -10839,6 +12077,8 @@ int DigField(struct PlayerInfo *player,
           game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
 
+  /* checking here causes player to move into acid even if the current field
+     cannot be left to that direction */
 #if 0
 #if USE_FIXED_DONT_RUN_INTO
   if (player_can_move && DONT_RUN_INTO(element))
@@ -10860,6 +12100,43 @@ int DigField(struct PlayerInfo *player,
 #endif
 #endif
 
+#if 1  /* ------------------------------ NEW ------------------------------ */
+
+  if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
+    return MP_NO_ACTION;       /* field has no opening in this direction */
+
+  if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
+    return MP_NO_ACTION;       /* field has no opening in this direction */
+
+#if USE_FIXED_DONT_RUN_INTO
+  if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
+  {
+    SplashAcid(x, y);
+#if 1
+    Feld[jx][jy] = player->artwork_element;
+#else
+    Feld[jx][jy] = EL_PLAYER_1;
+#endif
+    InitMovingField(jx, jy, MV_DOWN);
+    Store[jx][jy] = EL_ACID;
+    ContinueMoving(jx, jy);
+    BuryPlayer(player);
+
+    return MP_DONT_RUN_INTO;
+  }
+#endif
+
+#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
+
+#else  /* ------------------------------ OLD ------------------------------ */
+
 #if 1
 #if USE_FIXED_DONT_RUN_INTO
   if (player_can_move && DONT_RUN_INTO(element))
@@ -10877,6 +12154,7 @@ int DigField(struct PlayerInfo *player,
   if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
     return MP_NO_ACTION;       /* field has no opening in this direction */
 
+  /* checking here causes player to explode when moving into acid */
 #if 1
 #if USE_FIXED_DONT_RUN_INTO
   if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
@@ -10893,6 +12171,8 @@ int DigField(struct PlayerInfo *player,
 #endif
 #endif
 
+#endif /* ------------------------------ END ------------------------------ */
+
 #if 0
 #if USE_FIXED_DONT_RUN_INTO
   if (player_can_move && DONT_RUN_INTO(element))
@@ -10945,10 +12225,17 @@ int DigField(struct PlayerInfo *player,
     return MP_NO_ACTION;
   }
 
+#if USE_PLAYER_GRAVITY
+  if (player->gravity && is_player && !player->is_auto_moving &&
+      canFallDown(player) && move_direction != MV_DOWN &&
+      !canMoveToValidFieldWithGravity(jx, jy, move_direction))
+    return MP_NO_ACTION;       /* player cannot walk here due to gravity */
+#else
   if (game.gravity && is_player && !player->is_auto_moving &&
       canFallDown(player) && move_direction != MV_DOWN &&
       !canMoveToValidFieldWithGravity(jx, jy, move_direction))
     return MP_NO_ACTION;       /* player cannot walk here due to gravity */
+#endif
 
   if (player_can_move &&
       IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
@@ -11012,23 +12299,50 @@ int DigField(struct PlayerInfo *player,
       if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
        return MP_NO_ACTION;
     }
+    else if (IS_EMC_GATE(element))
+    {
+      if (!player->key[EMC_GATE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE_GRAY(element))
+    {
+      if (!player->key[EMC_GATE_GRAY_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE_GRAY_ACTIVE(element))
+    {
+      if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
+       return MP_NO_ACTION;
+    }
     else if (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)
+#if USE_PLAYER_GRAVITY
+       player->gravity = !player->gravity;
+#else
        game.gravity = !game.gravity;
+#endif
       else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
               element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
               element == EL_SP_GRAVITY_ON_PORT_UP ||
               element == EL_SP_GRAVITY_ON_PORT_DOWN)
+#if USE_PLAYER_GRAVITY
+       player->gravity = TRUE;
+#else
        game.gravity = TRUE;
+#endif
       else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
               element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
               element == EL_SP_GRAVITY_OFF_PORT_UP ||
               element == EL_SP_GRAVITY_OFF_PORT_DOWN)
+#if USE_PLAYER_GRAVITY
+       player->gravity = FALSE;
+#else
        game.gravity = FALSE;
+#endif
     }
 
     /* automatically move to the next field with double speed */
@@ -11098,12 +12412,18 @@ 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;
 
+#if 1
+      DrawGameDoorValues();
+#else
       DrawGameValue_Dynamite(local_player->inventory_size);
+#endif
     }
     else if (element == EL_DYNABOMB_INCREASE_NUMBER)
     {
@@ -11122,7 +12442,11 @@ int DigField(struct PlayerInfo *player,
     {
       player->key[KEY_NR(element)] = TRUE;
 
+#if 1
+      DrawGameDoorValues();
+#else
       DrawGameValue_Keys(player->key);
+#endif
 
       redraw_mask |= REDRAW_DOOR_1;
     }
@@ -11154,7 +12478,11 @@ int DigField(struct PlayerInfo *player,
          if (player->inventory_size < MAX_INVENTORY_SIZE)
            player->inventory_element[player->inventory_size++] = element;
 
+#if 1
+      DrawGameDoorValues();
+#else
       DrawGameValue_Dynamite(local_player->inventory_size);
+#endif
     }
     else if (collect_count > 0)
     {
@@ -11234,6 +12562,7 @@ int DigField(struct PlayerInfo *player,
     }
 
     player->is_pushing = TRUE;
+    player->is_active = TRUE;
 
     if (!(IN_LEV_FIELD(nextx, nexty) &&
          (IS_FREE(nextx, nexty) ||
@@ -11486,7 +12815,10 @@ int DigField(struct PlayerInfo *player,
   if (is_player)               /* function can also be called by EL_PENGUIN */
   {
     if (Feld[x][y] != element)         /* really digged/collected something */
+    {
       player->is_collecting = !player->is_digging;
+      player->is_active = TRUE;
+    }
   }
 
   return MP_MOVING;
@@ -11500,6 +12832,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;
@@ -11527,8 +12861,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;
 
@@ -11540,11 +12880,14 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
   }
 
   player->is_dropping = FALSE;
+  player->is_dropping_pressed = FALSE;
+  player->drop_pressed_delay = 0;
 
   if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
     return FALSE;
 
   player->is_snapping = TRUE;
+  player->is_active = TRUE;
 
   if (player->MovPos == 0)
   {
@@ -11575,6 +12918,8 @@ 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
@@ -11602,6 +12947,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;
@@ -11623,10 +12972,16 @@ boolean DropElement(struct PlayerInfo *player)
     {
       player->inventory_size--;
 
+#if 1
+      DrawGameDoorValues();
+#else
       DrawGameValue_Dynamite(local_player->inventory_size);
+#endif
 
       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;
     }
@@ -11681,12 +13036,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;
 
@@ -11858,7 +13216,7 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
       break;
 
     case SAMPLE_slurp:
-      PlayLevelSoundElementAction(x, y, element, ACTION_SLURPING);
+      PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
       break;
 
     case SAMPLE_eater:
@@ -12020,6 +13378,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:
@@ -12063,12 +13422,24 @@ void RequestQuitGame(boolean ask_if_really_quit)
   {
 #if defined(NETWORK_AVALIABLE)
     if (options.network)
-      SendToServer_StopPlaying();
+      SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
     else
 #endif
     {
-      game_status = GAME_MODE_MAIN;
-      DrawMainMenu();
+      if (!ask_if_really_quit || level_editor_test_game)
+      {
+       game_status = GAME_MODE_MAIN;
+
+       DrawMainMenu();
+      }
+      else
+      {
+       FadeOut(REDRAW_FIELD);
+
+       game_status = GAME_MODE_MAIN;
+
+       DrawAndFadeInMainMenu(REDRAW_FIELD);
+      }
     }
   }
   else
@@ -12240,7 +13611,10 @@ static void HandleGameButtons(struct GadgetInfo *gi)
   switch (id)
   {
     case GAME_CTRL_ID_STOP:
-      RequestQuitGame(TRUE);
+      if (tape.playing)
+       TapeStop();
+      else
+       RequestQuitGame(TRUE);
       break;
 
     case GAME_CTRL_ID_PAUSE:
@@ -12267,7 +13641,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
 #endif
        {
          tape.pausing = FALSE;
-         DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF,0);
+         DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF, 0);
        }
       }
       break;