added functions to get current player position for all game engines
[rocksndiamonds.git] / src / game.c
index 2a9eabb016a0ca6c1ac2e69424e736a30cdb9044..3d2f055f27c88415dccb499d83d65932ad8a5f1e 100644 (file)
@@ -1,15 +1,13 @@
-/***********************************************************
-* Rocks'n'Diamonds -- McDuffin Strikes Back!               *
-*----------------------------------------------------------*
-* (c) 1995-2006 Artsoft Entertainment                      *
-*               Holger Schemel                             *
-*               Detmolder Strasse 189                      *
-*               33604 Bielefeld                            *
-*               Germany                                    *
-*               e-mail: info@artsoft.org                   *
-*----------------------------------------------------------*
-* game.c                                                   *
-***********************************************************/
+// ============================================================================
+// Rocks'n'Diamonds - McDuffin Strikes Back!
+// ----------------------------------------------------------------------------
+// (c) 1995-2014 by Artsoft Entertainment
+//                         Holger Schemel
+//                 info@artsoft.org
+//                 http://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// game.c
+// ============================================================================
 
 #include "libgame/libgame.h"
 
 #include "init.h"
 #include "tools.h"
 #include "screens.h"
+#include "events.h"
 #include "files.h"
 #include "tape.h"
 #include "network.h"
+#include "anim.h"
+
+
+/* DEBUG SETTINGS */
+#define DEBUG_INIT_PLAYER      1
+#define DEBUG_PLAYER_ACTIONS   0
 
 /* EXPERIMENTAL STUFF */
 #define USE_NEW_AMOEBA_CODE    FALSE
 
 /* EXPERIMENTAL STUFF */
-#define USE_NEW_STUFF                  (                         1)
-
-#define USE_NEW_SP_SLIPPERY            (USE_NEW_STUFF          * 1)
-#define USE_NEW_CUSTOM_VALUE           (USE_NEW_STUFF          * 1)
-#define USE_NEW_PLAYER_ANIM            (USE_NEW_STUFF          * 1)
-#define USE_NEW_ALL_SLIPPERY           (USE_NEW_STUFF          * 1)
-#define USE_NEW_PLAYER_SPEED           (USE_NEW_STUFF          * 1)
-#define USE_NEW_DELAYED_ACTION         (USE_NEW_STUFF          * 1)
-#define USE_NEW_SNAP_DELAY             (USE_NEW_STUFF          * 1)
-#define USE_ONLY_ONE_CHANGE_PER_FRAME  (USE_NEW_STUFF          * 1)
-#define USE_ONE_MORE_CHANGE_PER_FRAME  (USE_NEW_STUFF          * 1)
-#define USE_FIXED_DONT_RUN_INTO                (USE_NEW_STUFF          * 1)
-#define USE_NEW_SPRING_BUMPER          (USE_NEW_STUFF          * 1)
-#define USE_STOP_CHANGED_ELEMENTS      (USE_NEW_STUFF          * 1)
-#define USE_ELEMENT_TOUCHING_BUGFIX    (USE_NEW_STUFF          * 1)
-#define USE_NEW_CONTINUOUS_SNAPPING    (USE_NEW_STUFF          * 1)
-#define USE_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_GFX_RESET_ONLY_WHEN_MOVING (USE_NEW_STUFF          * 1)
-#define USE_GFX_RESET_PLAYER_ARTWORK   (USE_NEW_STUFF          * 1)
-
-#define USE_FIX_KILLED_BY_NON_WALKABLE (USE_NEW_STUFF          * 1)
-#define USE_FIX_IMPACT_COLLISION       (USE_NEW_STUFF          * 1)
-
-#define USE_GFX_RESET_WHEN_NOT_MOVING  (USE_NEW_STUFF          * 1)
+#define USE_QUICKSAND_BD_ROCK_BUGFIX   0
+#define USE_QUICKSAND_IMPACT_BUGFIX    0
+#define USE_DELAYED_GFX_REDRAW         0
+#define USE_NEW_PLAYER_ASSIGNMENTS     1
+
+#if USE_DELAYED_GFX_REDRAW
+#define TEST_DrawLevelField(x, y)                              \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE
+#define TEST_DrawLevelFieldCrumbled(x, y)                      \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
+#define TEST_DrawLevelFieldCrumbledNeighbours(x, y)            \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
+#define TEST_DrawTwinkleOnField(x, y)                          \
+       GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
+#else
+#define TEST_DrawLevelField(x, y)                              \
+            DrawLevelField(x, y)
+#define TEST_DrawLevelFieldCrumbled(x, y)                      \
+            DrawLevelFieldCrumbled(x, y)
+#define TEST_DrawLevelFieldCrumbledNeighbours(x, y)            \
+            DrawLevelFieldCrumbledNeighbours(x, y)
+#define TEST_DrawTwinkleOnField(x, y)                          \
+            DrawTwinkleOnField(x, y)
+#endif
 
 
 /* for DigField() */
 #define PANEL_XPOS(p)          (DX + ALIGNED_TEXT_XPOS(p))
 #define PANEL_YPOS(p)          (DY + ALIGNED_TEXT_YPOS(p))
 
-/* special positions in the game control window (relative to control window) */
-#define XX_LEVEL1              (PANEL_XPOS(game.panel.level))
-#define XX_LEVEL2              (PANEL_XPOS(game.panel.level) - 1)
-#define XX_LEVEL               (PANEL_XPOS(game.panel.level))
-#define YY_LEVEL               (PANEL_YPOS(game.panel.level))
-#define XX_EMERALDS            (PANEL_XPOS(game.panel.gems))
-#define YY_EMERALDS            (PANEL_YPOS(game.panel.gems))
-#define XX_DYNAMITE            (PANEL_XPOS(game.panel.inventory))
-#define YY_DYNAMITE            (PANEL_YPOS(game.panel.inventory))
-#define XX_KEYS                        (PANEL_XPOS(game.panel.keys))
-#define YY_KEYS                        (PANEL_YPOS(game.panel.keys))
-#define XX_SCORE               (PANEL_XPOS(game.panel.score))
-#define YY_SCORE               (PANEL_YPOS(game.panel.score))
-#define XX_TIME1               (PANEL_XPOS(game.panel.time))
-#define XX_TIME2               (PANEL_XPOS(game.panel.time) + 1)
-#define XX_TIME                        (PANEL_XPOS(game.panel.time))
-#define YY_TIME                        (PANEL_YPOS(game.panel.time))
-
-/* special positions in the game control window (relative to main window) */
-#define DX_LEVEL1              (DX + XX_LEVEL1)
-#define DX_LEVEL2              (DX + XX_LEVEL2)
-#define DX_LEVEL               (DX + XX_LEVEL)
-#define DY_LEVEL               (DY + YY_LEVEL)
-#define DX_EMERALDS            (DX + XX_EMERALDS)
-#define DY_EMERALDS            (DY + YY_EMERALDS)
-#define DX_DYNAMITE            (DX + XX_DYNAMITE)
-#define DY_DYNAMITE            (DY + YY_DYNAMITE)
-#define DX_KEYS                        (DX + XX_KEYS)
-#define DY_KEYS                        (DY + YY_KEYS)
-#define DX_SCORE               (DX + XX_SCORE)
-#define DY_SCORE               (DY + YY_SCORE)
-#define DX_TIME1               (DX + XX_TIME1)
-#define DX_TIME2               (DX + XX_TIME2)
-#define DX_TIME                        (DX + XX_TIME)
-#define DY_TIME                        (DY + YY_TIME)
-
-#if 1
 /* game panel display and control definitions */
-
-#define GAME_PANEL_LEVEL_NUMBER                0
-#define GAME_PANEL_GEMS                        1
+#define GAME_PANEL_LEVEL_NUMBER                        0
+#define GAME_PANEL_GEMS                                1
 #define GAME_PANEL_INVENTORY_COUNT             2
 #define GAME_PANEL_INVENTORY_FIRST_1           3
 #define GAME_PANEL_INVENTORY_FIRST_2           4
 #define GAME_PANEL_KEY_WHITE                   27
 #define GAME_PANEL_KEY_WHITE_COUNT             28
 #define GAME_PANEL_SCORE                       29
-#define GAME_PANEL_TIME                        30
-#define GAME_PANEL_TIME_HH                     31
-#define GAME_PANEL_TIME_MM                     32
-#define GAME_PANEL_TIME_SS                     33
-#define GAME_PANEL_SHIELD_NORMAL               34
-#define GAME_PANEL_SHIELD_NORMAL_TIME          35
-#define GAME_PANEL_SHIELD_DEADLY               36
-#define GAME_PANEL_SHIELD_DEADLY_TIME          37
-#define GAME_PANEL_EXIT                        38
-#define GAME_PANEL_EMC_MAGIC_BALL              39
-#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH       40
-#define GAME_PANEL_LIGHT_SWITCH                41
-#define GAME_PANEL_LIGHT_SWITCH_TIME           42
-#define GAME_PANEL_TIMEGATE_SWITCH             43
-#define GAME_PANEL_TIMEGATE_SWITCH_TIME        44
-#define GAME_PANEL_SWITCHGATE_SWITCH           45
-#define GAME_PANEL_EMC_LENSES                  46
-#define GAME_PANEL_EMC_LENSES_TIME             47
-#define GAME_PANEL_EMC_MAGNIFIER               48
-#define GAME_PANEL_EMC_MAGNIFIER_TIME          49
-#define GAME_PANEL_BALLOON_SWITCH              50
-#define GAME_PANEL_DYNABOMB_NUMBER             51
-#define GAME_PANEL_DYNABOMB_SIZE               52
-#define GAME_PANEL_DYNABOMB_POWER              53
-#define GAME_PANEL_PENGUINS                    54
-#define GAME_PANEL_SOKOBAN_OBJECTS             55
-#define GAME_PANEL_SOKOBAN_FIELDS              56
-#define GAME_PANEL_ROBOT_WHEEL         57
-#define GAME_PANEL_CONVEYOR_BELT_1             58
-#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH      59
-#define GAME_PANEL_CONVEYOR_BELT_2             60
-#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH      61
+#define GAME_PANEL_HIGHSCORE                   30
+#define GAME_PANEL_TIME                                31
+#define GAME_PANEL_TIME_HH                     32
+#define GAME_PANEL_TIME_MM                     33
+#define GAME_PANEL_TIME_SS                     34
+#define GAME_PANEL_FRAME                       35
+#define GAME_PANEL_SHIELD_NORMAL               36
+#define GAME_PANEL_SHIELD_NORMAL_TIME          37
+#define GAME_PANEL_SHIELD_DEADLY               38
+#define GAME_PANEL_SHIELD_DEADLY_TIME          39
+#define GAME_PANEL_EXIT                                40
+#define GAME_PANEL_EMC_MAGIC_BALL              41
+#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH       42
+#define GAME_PANEL_LIGHT_SWITCH                        43
+#define GAME_PANEL_LIGHT_SWITCH_TIME           44
+#define GAME_PANEL_TIMEGATE_SWITCH             45
+#define GAME_PANEL_TIMEGATE_SWITCH_TIME                46
+#define GAME_PANEL_SWITCHGATE_SWITCH           47
+#define GAME_PANEL_EMC_LENSES                  48
+#define GAME_PANEL_EMC_LENSES_TIME             49
+#define GAME_PANEL_EMC_MAGNIFIER               50
+#define GAME_PANEL_EMC_MAGNIFIER_TIME          51
+#define GAME_PANEL_BALLOON_SWITCH              52
+#define GAME_PANEL_DYNABOMB_NUMBER             53
+#define GAME_PANEL_DYNABOMB_SIZE               54
+#define GAME_PANEL_DYNABOMB_POWER              55
+#define GAME_PANEL_PENGUINS                    56
+#define GAME_PANEL_SOKOBAN_OBJECTS             57
+#define GAME_PANEL_SOKOBAN_FIELDS              58
+#define GAME_PANEL_ROBOT_WHEEL                 59
+#define GAME_PANEL_CONVEYOR_BELT_1             60
+#define GAME_PANEL_CONVEYOR_BELT_2             61
 #define GAME_PANEL_CONVEYOR_BELT_3             62
-#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH      63
-#define GAME_PANEL_CONVEYOR_BELT_4             64
-#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH      65
-#define GAME_PANEL_MAGIC_WALL                  66
-#define GAME_PANEL_MAGIC_WALL_TIME             67
-#define GAME_PANEL_GRAVITY_STATE               68
-#define GAME_PANEL_PLAYER_NAME         69
-#define GAME_PANEL_LEVEL_NAME                  70
-#define GAME_PANEL_LEVEL_AUTHOR                71
-
-#define NUM_GAME_PANEL_CONTROLS                        72
+#define GAME_PANEL_CONVEYOR_BELT_4             63
+#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH      64
+#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH      65
+#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH      66
+#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH      67
+#define GAME_PANEL_MAGIC_WALL                  68
+#define GAME_PANEL_MAGIC_WALL_TIME             69
+#define GAME_PANEL_GRAVITY_STATE               70
+#define GAME_PANEL_GRAPHIC_1                   71
+#define GAME_PANEL_GRAPHIC_2                   72
+#define GAME_PANEL_GRAPHIC_3                   73
+#define GAME_PANEL_GRAPHIC_4                   74
+#define GAME_PANEL_GRAPHIC_5                   75
+#define GAME_PANEL_GRAPHIC_6                   76
+#define GAME_PANEL_GRAPHIC_7                   77
+#define GAME_PANEL_GRAPHIC_8                   78
+#define GAME_PANEL_ELEMENT_1                   79
+#define GAME_PANEL_ELEMENT_2                   80
+#define GAME_PANEL_ELEMENT_3                   81
+#define GAME_PANEL_ELEMENT_4                   82
+#define GAME_PANEL_ELEMENT_5                   83
+#define GAME_PANEL_ELEMENT_6                   84
+#define GAME_PANEL_ELEMENT_7                   85
+#define GAME_PANEL_ELEMENT_8                   86
+#define GAME_PANEL_ELEMENT_COUNT_1             87
+#define GAME_PANEL_ELEMENT_COUNT_2             88
+#define GAME_PANEL_ELEMENT_COUNT_3             89
+#define GAME_PANEL_ELEMENT_COUNT_4             90
+#define GAME_PANEL_ELEMENT_COUNT_5             91
+#define GAME_PANEL_ELEMENT_COUNT_6             92
+#define GAME_PANEL_ELEMENT_COUNT_7             93
+#define GAME_PANEL_ELEMENT_COUNT_8             94
+#define GAME_PANEL_CE_SCORE_1                  95
+#define GAME_PANEL_CE_SCORE_2                  96
+#define GAME_PANEL_CE_SCORE_3                  97
+#define GAME_PANEL_CE_SCORE_4                  98
+#define GAME_PANEL_CE_SCORE_5                  99
+#define GAME_PANEL_CE_SCORE_6                  100
+#define GAME_PANEL_CE_SCORE_7                  101
+#define GAME_PANEL_CE_SCORE_8                  102
+#define GAME_PANEL_CE_SCORE_1_ELEMENT          103
+#define GAME_PANEL_CE_SCORE_2_ELEMENT          104
+#define GAME_PANEL_CE_SCORE_3_ELEMENT          105
+#define GAME_PANEL_CE_SCORE_4_ELEMENT          106
+#define GAME_PANEL_CE_SCORE_5_ELEMENT          107
+#define GAME_PANEL_CE_SCORE_6_ELEMENT          108
+#define GAME_PANEL_CE_SCORE_7_ELEMENT          109
+#define GAME_PANEL_CE_SCORE_8_ELEMENT          110
+#define GAME_PANEL_PLAYER_NAME                 111
+#define GAME_PANEL_LEVEL_NAME                  112
+#define GAME_PANEL_LEVEL_AUTHOR                        113
+
+#define NUM_GAME_PANEL_CONTROLS                        114
+
+struct GamePanelOrderInfo
+{
+  int nr;
+  int sort_priority;
+};
+
+static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
 
 struct GamePanelControlInfo
 {
@@ -216,6 +222,7 @@ struct GamePanelControlInfo
   int value, last_value;
   int frame, last_frame;
   int gfx_frame;
+  int gfx_random;
 };
 
 static struct GamePanelControlInfo game_panel_controls[] =
@@ -237,82 +244,82 @@ static struct GamePanelControlInfo game_panel_controls[] =
   },
   {
     GAME_PANEL_INVENTORY_FIRST_1,
-    &game.panel.inventory_first_1,
+    &game.panel.inventory_first[0],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_FIRST_2,
-    &game.panel.inventory_first_2,
+    &game.panel.inventory_first[1],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_FIRST_3,
-    &game.panel.inventory_first_3,
+    &game.panel.inventory_first[2],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_FIRST_4,
-    &game.panel.inventory_first_4,
+    &game.panel.inventory_first[3],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_FIRST_5,
-    &game.panel.inventory_first_5,
+    &game.panel.inventory_first[4],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_FIRST_6,
-    &game.panel.inventory_first_6,
+    &game.panel.inventory_first[5],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_FIRST_7,
-    &game.panel.inventory_first_7,
+    &game.panel.inventory_first[6],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_FIRST_8,
-    &game.panel.inventory_first_8,
+    &game.panel.inventory_first[7],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_1,
-    &game.panel.inventory_last_1,
+    &game.panel.inventory_last[0],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_2,
-    &game.panel.inventory_last_2,
+    &game.panel.inventory_last[1],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_3,
-    &game.panel.inventory_last_3,
+    &game.panel.inventory_last[2],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_4,
-    &game.panel.inventory_last_4,
+    &game.panel.inventory_last[3],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_5,
-    &game.panel.inventory_last_5,
+    &game.panel.inventory_last[4],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_6,
-    &game.panel.inventory_last_6,
+    &game.panel.inventory_last[5],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_7,
-    &game.panel.inventory_last_7,
+    &game.panel.inventory_last[6],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_INVENTORY_LAST_8,
-    &game.panel.inventory_last_8,
+    &game.panel.inventory_last[7],
     TYPE_ELEMENT,
   },
   {
@@ -370,6 +377,11 @@ static struct GamePanelControlInfo game_panel_controls[] =
     &game.panel.score,
     TYPE_INTEGER,
   },
+  {
+    GAME_PANEL_HIGHSCORE,
+    &game.panel.highscore,
+    TYPE_INTEGER,
+  },
   {
     GAME_PANEL_TIME,
     &game.panel.time,
@@ -390,6 +402,11 @@ static struct GamePanelControlInfo game_panel_controls[] =
     &game.panel.time_ss,
     TYPE_INTEGER,
   },
+  {
+    GAME_PANEL_FRAME,
+    &game.panel.frame,
+    TYPE_INTEGER,
+  },
   {
     GAME_PANEL_SHIELD_NORMAL,
     &game.panel.shield_normal,
@@ -512,42 +529,42 @@ static struct GamePanelControlInfo game_panel_controls[] =
   },
   {
     GAME_PANEL_CONVEYOR_BELT_1,
-    &game.panel.conveyor_belt_1,
+    &game.panel.conveyor_belt[0],
     TYPE_ELEMENT,
   },
   {
-    GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
-    &game.panel.conveyor_belt_1_switch,
+    GAME_PANEL_CONVEYOR_BELT_2,
+    &game.panel.conveyor_belt[1],
     TYPE_ELEMENT,
   },
   {
-    GAME_PANEL_CONVEYOR_BELT_2,
-    &game.panel.conveyor_belt_2,
+    GAME_PANEL_CONVEYOR_BELT_3,
+    &game.panel.conveyor_belt[2],
     TYPE_ELEMENT,
   },
   {
-    GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
-    &game.panel.conveyor_belt_2_switch,
+    GAME_PANEL_CONVEYOR_BELT_4,
+    &game.panel.conveyor_belt[3],
     TYPE_ELEMENT,
   },
   {
-    GAME_PANEL_CONVEYOR_BELT_3,
-    &game.panel.conveyor_belt_3,
+    GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
+    &game.panel.conveyor_belt_switch[0],
     TYPE_ELEMENT,
   },
   {
-    GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
-    &game.panel.conveyor_belt_3_switch,
+    GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
+    &game.panel.conveyor_belt_switch[1],
     TYPE_ELEMENT,
   },
   {
-    GAME_PANEL_CONVEYOR_BELT_4,
-    &game.panel.conveyor_belt_4,
+    GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
+    &game.panel.conveyor_belt_switch[2],
     TYPE_ELEMENT,
   },
   {
     GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
-    &game.panel.conveyor_belt_4_switch,
+    &game.panel.conveyor_belt_switch[3],
     TYPE_ELEMENT,
   },
   {
@@ -565,6 +582,206 @@ static struct GamePanelControlInfo game_panel_controls[] =
     &game.panel.gravity_state,
     TYPE_STRING,
   },
+  {
+    GAME_PANEL_GRAPHIC_1,
+    &game.panel.graphic[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_2,
+    &game.panel.graphic[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_3,
+    &game.panel.graphic[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_4,
+    &game.panel.graphic[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_5,
+    &game.panel.graphic[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_6,
+    &game.panel.graphic[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_7,
+    &game.panel.graphic[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_GRAPHIC_8,
+    &game.panel.graphic[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_1,
+    &game.panel.element[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_2,
+    &game.panel.element[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_3,
+    &game.panel.element[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_4,
+    &game.panel.element[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_5,
+    &game.panel.element[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_6,
+    &game.panel.element[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_7,
+    &game.panel.element[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_8,
+    &game.panel.element[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_1,
+    &game.panel.element_count[0],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_2,
+    &game.panel.element_count[1],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_3,
+    &game.panel.element_count[2],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_4,
+    &game.panel.element_count[3],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_5,
+    &game.panel.element_count[4],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_6,
+    &game.panel.element_count[5],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_7,
+    &game.panel.element_count[6],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ELEMENT_COUNT_8,
+    &game.panel.element_count[7],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_1,
+    &game.panel.ce_score[0],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_2,
+    &game.panel.ce_score[1],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_3,
+    &game.panel.ce_score[2],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_4,
+    &game.panel.ce_score[3],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_5,
+    &game.panel.ce_score[4],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_6,
+    &game.panel.ce_score[5],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_7,
+    &game.panel.ce_score[6],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_8,
+    &game.panel.ce_score[7],
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_CE_SCORE_1_ELEMENT,
+    &game.panel.ce_score_element[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_2_ELEMENT,
+    &game.panel.ce_score_element[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_3_ELEMENT,
+    &game.panel.ce_score_element[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_4_ELEMENT,
+    &game.panel.ce_score_element[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_5_ELEMENT,
+    &game.panel.ce_score_element[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_6_ELEMENT,
+    &game.panel.ce_score_element[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_7_ELEMENT,
+    &game.panel.ce_score_element[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CE_SCORE_8_ELEMENT,
+    &game.panel.ce_score_element[7],
+    TYPE_ELEMENT,
+  },
   {
     GAME_PANEL_PLAYER_NAME,
     &game.panel.player_name,
@@ -587,8 +804,6 @@ static struct GamePanelControlInfo game_panel_controls[] =
     -1,
   }
 };
-#endif
-
 
 /* values for delayed check of falling and moving elements and for collision */
 #define CHECK_DELAY_MOVING     3
@@ -647,6 +862,9 @@ static struct GamePanelControlInfo game_panel_controls[] =
         (be) + (e) - EL_SELF > EL_CUSTOM_END   ? EL_CUSTOM_END :       \
         (be) + (e) - EL_SELF)
 
+#define GET_PLAYER_FROM_BITS(p)                                                \
+       (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
+
 #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   :  \
@@ -754,17 +972,24 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define GAME_CTRL_ID_STOP              0
 #define GAME_CTRL_ID_PAUSE             1
 #define GAME_CTRL_ID_PLAY              2
-#define SOUND_CTRL_ID_MUSIC            3
-#define SOUND_CTRL_ID_LOOPS            4
-#define SOUND_CTRL_ID_SIMPLE           5
+#define GAME_CTRL_ID_UNDO              3
+#define GAME_CTRL_ID_REDO              4
+#define GAME_CTRL_ID_SAVE              5
+#define GAME_CTRL_ID_PAUSE2            6
+#define GAME_CTRL_ID_LOAD              7
+#define SOUND_CTRL_ID_MUSIC            8
+#define SOUND_CTRL_ID_LOOPS            9
+#define SOUND_CTRL_ID_SIMPLE           10
 
-#define NUM_GAME_BUTTONS               6
+#define NUM_GAME_BUTTONS               11
 
 
 /* forward declaration for internal use */
 
 static void CreateField(int, int, int);
 
+static void ResetGfxAnimation(int, int);
+
 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
 static void AdvanceFrameAndPlayerCounters(int);
 
@@ -773,7 +998,10 @@ static boolean MovePlayer(struct PlayerInfo *, int, int);
 static void ScrollPlayer(struct PlayerInfo *, int);
 static void ScrollScreen(struct PlayerInfo *, int);
 
-int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
+static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
+static boolean DigFieldByCE(int, int, int);
+static boolean SnapField(struct PlayerInfo *, int, int);
+static boolean DropElement(struct PlayerInfo *);
 
 static void InitBeltMovement(void);
 static void CloseAllOpenTimegates(void);
@@ -785,9 +1013,6 @@ static void KillPlayerUnlessExplosionProtected(int, int);
 static void TestIfPlayerTouchesCustomElement(int, int);
 static void TestIfElementTouchesCustomElement(int, int);
 static void TestIfElementHitsCustomElement(int, int, int);
-#if 0
-static void TestIfElementSmashesCustomElement(int, int, int);
-#endif
 
 static void HandleElementChange(int, int, int);
 static void ExecuteCustomElementAction(int, int, int, int);
@@ -820,7 +1045,6 @@ static void PlayLevelSoundActionIfLoop(int, int, int);
 static void StopLevelSoundActionIfLoop(int, int, int);
 static void PlayLevelMusic();
 
-static void MapGameButtons();
 static void HandleGameButtons(struct GadgetInfo *);
 
 int AmoebeNachbarNr(int, int);
@@ -840,14 +1064,12 @@ void TestIfBadThingRunsIntoPlayer(int, int, int);
 void TestIfFriendTouchesBadThing(int, int);
 void TestIfBadThingTouchesFriend(int, int);
 void TestIfBadThingTouchesOtherBadThing(int, int);
+void TestIfGoodThingGetsHitByBadThing(int, int, int);
 
 void KillPlayer(struct PlayerInfo *);
 void BuryPlayer(struct PlayerInfo *);
 void RemovePlayer(struct PlayerInfo *);
 
-boolean SnapField(struct PlayerInfo *, int, int);
-boolean DropElement(struct PlayerInfo *);
-
 static int getInvisibleActiveFromInvisibleElement(int);
 static int getInvisibleFromInvisibleActiveElement(int);
 
@@ -880,6 +1102,8 @@ static int recursion_loop_depth;
 static boolean recursion_loop_detected;
 static boolean recursion_loop_element;
 
+static int map_player_action[MAX_PLAYERS];
+
 
 /* ------------------------------------------------------------------------- */
 /* definition of elements that automatically change to other elements after  */
@@ -974,11 +1198,7 @@ static struct ChangingElementInfo change_delay_list[] =
   },
   {
     EL_EM_EXIT_CLOSING,
-#if 1
     EL_EMPTY,
-#else
-    EL_EM_EXIT_CLOSED,
-#endif
     29,
     NULL,
     NULL,
@@ -994,11 +1214,7 @@ static struct ChangingElementInfo change_delay_list[] =
   },
   {
     EL_EM_STEEL_EXIT_CLOSING,
-#if 1
     EL_STEELWALL,
-#else
-    EL_EM_STEEL_EXIT_CLOSED,
-#endif
     29,
     NULL,
     NULL,
@@ -1438,6 +1654,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
       }
       else
       {
+       stored_player[0].initial_element = element;
        stored_player[0].use_murphy = TRUE;
 
        if (!level.use_artwork_element[0])
@@ -1482,20 +1699,31 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
 
       StorePlayer[x][y] = Feld[x][y];
 
+#if DEBUG_INIT_PLAYER
       if (options.debug)
       {
-       printf("Player %d activated.\n", player->element_nr);
-       printf("[Local player is %d and currently %s.]\n",
+       printf("- player element %d activated", player->element_nr);
+       printf(" (local player is %d and currently %s)\n",
               local_player->element_nr,
               local_player->active ? "active" : "not active");
       }
     }
+#endif
 
     Feld[x][y] = EL_EMPTY;
 
     player->jx = player->last_jx = x;
     player->jy = player->last_jy = y;
   }
+
+  if (!init_game)
+  {
+    int player_nr = GET_PLAYER_NR(element);
+    struct PlayerInfo *player = &stored_player[player_nr];
+
+    if (player->active && player->killed)
+      player->reanimated = TRUE; /* if player was just killed, reanimate him */
+  }
 }
 
 static void InitField(int x, int y, boolean init_game)
@@ -1648,18 +1876,6 @@ 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;
-
-    case EL_DC_SWITCHGATE_SWITCH_DOWN: /* always start with same switch pos */
-      if (init_game)
-       Feld[x][y] = EL_DC_SWITCHGATE_SWITCH_UP;
-      break;
-#endif
-
     case EL_LIGHT_SWITCH_ACTIVE:
       if (init_game)
        game.light_time_left = level.time_light * FRAMES_PER_SECOND;
@@ -1683,16 +1899,42 @@ static void InitField(int x, int y, boolean init_game)
        Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
       break;
 
+    case EL_TRIGGER_PLAYER:
+    case EL_TRIGGER_ELEMENT:
+    case EL_TRIGGER_CE_VALUE:
+    case EL_TRIGGER_CE_SCORE:
+    case EL_SELF:
+    case EL_ANY_ELEMENT:
+    case EL_CURRENT_CE_VALUE:
+    case EL_CURRENT_CE_SCORE:
+    case EL_PREV_CE_1:
+    case EL_PREV_CE_2:
+    case EL_PREV_CE_3:
+    case EL_PREV_CE_4:
+    case EL_PREV_CE_5:
+    case EL_PREV_CE_6:
+    case EL_PREV_CE_7:
+    case EL_PREV_CE_8:
+    case EL_NEXT_CE_1:
+    case EL_NEXT_CE_2:
+    case EL_NEXT_CE_3:
+    case EL_NEXT_CE_4:
+    case EL_NEXT_CE_5:
+    case EL_NEXT_CE_6:
+    case EL_NEXT_CE_7:
+    case EL_NEXT_CE_8:
+      /* reference elements should not be used on the playfield */
+      Feld[x][y] = EL_EMPTY;
+      break;
+
     default:
       if (IS_CUSTOM_ELEMENT(element))
       {
        if (CAN_MOVE(element))
          InitMovDir(x, y);
 
-#if USE_NEW_CUSTOM_VALUE
        if (!element_info[element].use_last_ce_value || init_game)
          CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
-#endif
       }
       else if (IS_GROUP_ELEMENT(element))
       {
@@ -1708,7 +1950,7 @@ static void InitField(int x, int y, boolean init_game)
     CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
 }
 
-static inline void InitField_WithBug1(int x, int y, boolean init_game)
+inline static void InitField_WithBug1(int x, int y, boolean init_game)
 {
   InitField(x, y, init_game);
 
@@ -1718,7 +1960,7 @@ static inline void InitField_WithBug1(int x, int y, boolean init_game)
     InitMovDir(x, y);
 }
 
-static inline void InitField_WithBug2(int x, int y, boolean init_game)
+inline static void InitField_WithBug2(int x, int y, boolean init_game)
 {
   int old_element = Feld[x][y];
 
@@ -1739,8 +1981,6 @@ static inline void InitField_WithBug2(int x, int y, boolean init_game)
   */
 }
 
-#if 1
-
 static int get_key_element_from_nr(int key_nr)
 {
   int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
@@ -1796,6 +2036,20 @@ static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
   }
 }
 
+static int compareGamePanelOrderInfo(const void *object1, const void *object2)
+{
+  const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
+  const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
+  int compare_result;
+
+  if (gpo1->sort_priority != gpo2->sort_priority)
+    compare_result = gpo1->sort_priority - gpo2->sort_priority;
+  else
+    compare_result = gpo1->nr - gpo2->nr;
+
+  return compare_result;
+}
+
 void InitGameControlValues()
 {
   int i;
@@ -1803,13 +2057,14 @@ void InitGameControlValues()
   for (i = 0; game_panel_controls[i].nr != -1; i++)
   {
     struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+    struct GamePanelOrderInfo *gpo = &game_panel_order[i];
+    struct TextPosInfo *pos = gpc->pos;
     int nr = gpc->nr;
     int type = gpc->type;
-    struct TextPosInfo *pos = gpc->pos;
 
     if (nr != i)
     {
-      Error(ERR_INFO, "'game_panel_controls' structure corrupted");
+      Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
       Error(ERR_EXIT, "this should not happen -- please debug");
     }
 
@@ -1829,22 +2084,81 @@ void InitGameControlValues()
       pos->width = pos->size;
       pos->height = pos->size;
     }
+
+    /* fill structure for game panel draw order */
+    gpo->nr = gpc->nr;
+    gpo->sort_priority = pos->sort_priority;
+  }
+
+  /* sort game panel controls according to sort_priority and control number */
+  qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
+       sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
+}
+
+void UpdatePlayfieldElementCount()
+{
+  boolean use_element_count = FALSE;
+  int i, j, x, y;
+
+  /* first check if it is needed at all to calculate playfield element count */
+  for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
+    if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
+      use_element_count = TRUE;
+
+  if (!use_element_count)
+    return;
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    element_info[i].element_count = 0;
+
+  SCAN_PLAYFIELD(x, y)
+  {
+    element_info[Feld[x][y]].element_count++;
   }
+
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+    for (j = 0; j < MAX_NUM_ELEMENTS; j++)
+      if (IS_IN_GROUP(j, i))
+       element_info[EL_GROUP_START + i].element_count +=
+         element_info[j].element_count;
 }
 
 void UpdateGameControlValues()
 {
   int i, k;
-  int time = (level.time == 0 ? TimePlayed : TimeLeft);
-  int score = (local_player->LevelSolved ? local_player->score_final :
+  int time = (local_player->LevelSolved ?
+             local_player->LevelSolved_CountingTime :
+             level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+             level.native_em_level->lev->time :
+             level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+             level.native_sp_level->game_sp->time_played :
+             game.no_time_limit ? TimePlayed : TimeLeft);
+  int score = (local_player->LevelSolved ?
+              local_player->LevelSolved_CountingScore :
+              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+              level.native_em_level->lev->score :
+              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+              level.native_sp_level->game_sp->score :
               local_player->score);
-  int exit_closed = (local_player->gems_still_needed > 0 ||
+  int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+             level.native_em_level->lev->required :
+             level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+             level.native_sp_level->game_sp->infotrons_still_needed :
+             local_player->gems_still_needed);
+  int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                    level.native_em_level->lev->required > 0 :
+                    level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+                    level.native_sp_level->game_sp->infotrons_still_needed > 0 :
+                    local_player->gems_still_needed > 0 ||
                     local_player->sokobanfields_still_needed > 0 ||
                     local_player->lights_still_needed > 0);
 
+  UpdatePlayfieldElementCount();
+
+  /* update game panel control values */
+
   game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = level_nr;
-  game_panel_controls[GAME_PANEL_GEMS].value =
-    local_player->gems_still_needed;
+  game_panel_controls[GAME_PANEL_GEMS].value = gems;
 
   game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
   for (i = 0; i < MAX_NUM_KEYS; i++)
@@ -1856,13 +2170,32 @@ void UpdateGameControlValues()
   {
     for (i = 0; i < MAX_PLAYERS; i++)
     {
+      /* only one player in Supaplex game engine */
+      if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
+       break;
+
       for (k = 0; k < MAX_NUM_KEYS; k++)
-       if (stored_player[i].key[k])
+      {
+       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+       {
+         if (level.native_em_level->ply[i]->keys & (1 << k))
+           game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+             get_key_element_from_nr(k);
+       }
+       else if (stored_player[i].key[k])
          game_panel_controls[GAME_PANEL_KEY_1 + k].value =
            get_key_element_from_nr(k);
+      }
 
-      game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
-       stored_player[i].inventory_size;
+      if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+       game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+         level.native_em_level->ply[i]->dynamite;
+      else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+       game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+         level.native_sp_level->game_sp->red_disk_count;
+      else
+       game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+         stored_player[i].inventory_size;
 
       if (stored_player[i].num_white_keys > 0)
        game_panel_controls[GAME_PANEL_KEY_WHITE].value =
@@ -1877,12 +2210,27 @@ void UpdateGameControlValues()
     int player_nr = game.centered_player_nr;
 
     for (k = 0; k < MAX_NUM_KEYS; k++)
-      if (stored_player[player_nr].key[k])
+    {
+      if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+      {
+       if (level.native_em_level->ply[player_nr]->keys & (1 << k))
+         game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+           get_key_element_from_nr(k);
+      }
+      else if (stored_player[player_nr].key[k])
        game_panel_controls[GAME_PANEL_KEY_1 + k].value =
          get_key_element_from_nr(k);
+    }
 
-    game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
-      stored_player[player_nr].inventory_size;
+    if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+      game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+       level.native_em_level->ply[player_nr]->dynamite;
+    else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+      game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+       level.native_sp_level->game_sp->red_disk_count;
+    else
+      game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+       stored_player[player_nr].inventory_size;
 
     if (stored_player[player_nr].num_white_keys > 0)
       game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
@@ -1891,7 +2239,7 @@ void UpdateGameControlValues()
       stored_player[player_nr].num_white_keys;
   }
 
-  for (i = 0; i < 8; i++)
+  for (i = 0; i < NUM_PANEL_INVENTORY; i++)
   {
     game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
       get_inventory_element_from_pos(local_player, i);
@@ -1900,6 +2248,7 @@ void UpdateGameControlValues()
   }
 
   game_panel_controls[GAME_PANEL_SCORE].value = score;
+  game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
 
   game_panel_controls[GAME_PANEL_TIME].value = time;
 
@@ -1907,6 +2256,8 @@ void UpdateGameControlValues()
   game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
   game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
 
+  game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
+
   game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
     (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
      EL_EMPTY);
@@ -1975,92 +2326,148 @@ void UpdateGameControlValues()
   game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
     (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
 
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1].value =
-    (game.belt_dir[0] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
-     EL_CONVEYOR_BELT_1_MIDDLE);
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH].value =
-    getBeltSwitchElementFromBeltNrAndBeltDir(0, game.belt_dir[0]);
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_2].value =
-    (game.belt_dir[1] != MV_NONE ? EL_CONVEYOR_BELT_2_MIDDLE_ACTIVE :
-     EL_CONVEYOR_BELT_2_MIDDLE);
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_2_SWITCH].value =
-    getBeltSwitchElementFromBeltNrAndBeltDir(1, game.belt_dir[1]);
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_3].value =
-    (game.belt_dir[2] != MV_NONE ? EL_CONVEYOR_BELT_3_MIDDLE_ACTIVE :
-     EL_CONVEYOR_BELT_3_MIDDLE);
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_3_SWITCH].value =
-    getBeltSwitchElementFromBeltNrAndBeltDir(2, game.belt_dir[2]);
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_4].value =
-    (game.belt_dir[3] != MV_NONE ? EL_CONVEYOR_BELT_4_MIDDLE_ACTIVE :
-     EL_CONVEYOR_BELT_4_MIDDLE);
-  game_panel_controls[GAME_PANEL_CONVEYOR_BELT_4_SWITCH].value =
-    getBeltSwitchElementFromBeltNrAndBeltDir(3, game.belt_dir[3]);
+  for (i = 0; i < NUM_BELTS; i++)
+  {
+    game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
+      (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
+       EL_CONVEYOR_BELT_1_MIDDLE) + i;
+    game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
+      getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
+  }
 
   game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
     (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
   game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
     game.magic_wall_time_left;
 
-#if USE_PLAYER_GRAVITY
   game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
     local_player->gravity;
-#else
-  game_panel_controls[GAME_PANEL_GRAVITY_STATE].value = game.gravity;
-#endif
+
+  for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
+    game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
+
+  for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+    game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
+      (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
+       game.panel.element[i].id : EL_UNDEFINED);
+
+  for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+    game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
+      (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
+       element_info[game.panel.element_count[i].id].element_count : 0);
+
+  for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+    game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
+      (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
+       element_info[game.panel.ce_score[i].id].collect_score : 0);
+
+  for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+    game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
+      (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
+       element_info[game.panel.ce_score_element[i].id].collect_score :
+       EL_UNDEFINED);
 
   game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
   game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
   game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
 
+  /* update game panel control frames */
+
   for (i = 0; game_panel_controls[i].nr != -1; i++)
   {
     struct GamePanelControlInfo *gpc = &game_panel_controls[i];
 
     if (gpc->type == TYPE_ELEMENT)
     {
-      if (gpc->value != gpc->last_value)
-       gpc->gfx_frame = 0;
-      else
-       gpc->gfx_frame++;
+      if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
+      {
+       int last_anim_random_frame = gfx.anim_random_frame;
+       int element = gpc->value;
+       int graphic = el2panelimg(element);
 
-      gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
-                                           gpc->gfx_frame);
-    }
-  }
-}
+       if (gpc->value != gpc->last_value)
+       {
+         gpc->gfx_frame = 0;
+         gpc->gfx_random = INIT_GFX_RANDOM();
+       }
+       else
+       {
+         gpc->gfx_frame++;
 
-void DisplayGameControlValues()
+         if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+             IS_NEXT_FRAME(gpc->gfx_frame, graphic))
+           gpc->gfx_random = INIT_GFX_RANDOM();
+       }
+
+       if (ANIM_MODE(graphic) == ANIM_RANDOM)
+         gfx.anim_random_frame = gpc->gfx_random;
+
+       if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+         gpc->gfx_frame = element_info[element].collect_score;
+
+       gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
+                                             gpc->gfx_frame);
+
+       if (ANIM_MODE(graphic) == ANIM_RANDOM)
+         gfx.anim_random_frame = last_anim_random_frame;
+      }
+    }
+  }
+}
+
+void DisplayGameControlValues()
 {
+  boolean redraw_panel = FALSE;
   int i;
 
-  game_status = GAME_MODE_PSEUDO_PANEL;
-
   for (i = 0; game_panel_controls[i].nr != -1; i++)
   {
     struct GamePanelControlInfo *gpc = &game_panel_controls[i];
-    int nr = gpc->nr;
-    int type = gpc->type;
+
+    if (PANEL_DEACTIVATED(gpc->pos))
+      continue;
+
+    if (gpc->value == gpc->last_value &&
+       gpc->frame == gpc->last_frame)
+      continue;
+
+    redraw_panel = TRUE;
+  }
+
+  if (!redraw_panel)
+    return;
+
+  /* copy default game door content to main double buffer */
+
+  /* !!! CHECK AGAIN !!! */
+  SetPanelBackground();
+  // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+  DrawBackground(DX, DY, DXSIZE, DYSIZE);
+
+  /* redraw game control buttons */
+  RedrawGameButtons();
+
+  SetGameStatus(GAME_MODE_PSEUDO_PANEL);
+
+  for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
+  {
+    int nr = game_panel_order[i].nr;
+    struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
     struct TextPosInfo *pos = gpc->pos;
+    int type = gpc->type;
     int value = gpc->value;
     int frame = gpc->frame;
-    int last_value = gpc->last_value;
-    int last_frame = gpc->last_frame;
     int size = pos->size;
     int font = pos->font;
+    boolean draw_masked = pos->draw_masked;
+    int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
 
-    if (value == last_value && frame == last_frame)
+    if (PANEL_DEACTIVATED(pos))
       continue;
 
     gpc->last_value = value;
     gpc->last_frame = frame;
 
-#if 0
-    printf("::: value %d changed from %d to %d\n", nr, last_value, value);
-#endif
-
-    if (PANEL_DEACTIVATED(pos))
-      continue;
-
     if (type == TYPE_INTEGER)
     {
       if (nr == GAME_PANEL_LEVEL_NUMBER ||
@@ -2078,45 +2485,50 @@ void DisplayGameControlValues()
 
          size = (value < value_change ? size1 : size2);
          font = (value < value_change ? font1 : font2);
-
-         /* clear background if value just changed its size (dynamic digits) */
-         if ((last_value < value_change) != (value < value_change))
-         {
-           int width1 = size1 * getFontWidth(font1);
-           int width2 = size2 * getFontWidth(font2);
-           int max_width = MAX(width1, width2);
-           int max_height = MAX(getFontHeight(font1), getFontHeight(font2));
-
-           pos->width = max_width;
-
-           ClearRectangleOnBackground(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
-                                      max_width, max_height);
-         }
        }
-
-       pos->width = size * getFontWidth(font);
       }
 
-      DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, size), font);
+      /* correct text size if "digits" is zero or less */
+      if (size <= 0)
+       size = strlen(int2str(value, size));
+
+      /* dynamically correct text alignment */
+      pos->width = size * getFontWidth(font);
+
+      DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+                 int2str(value, size), font, mask_mode);
     }
     else if (type == TYPE_ELEMENT)
     {
+      int element, graphic;
+      Bitmap *src_bitmap;
+      int src_x, src_y;
+      int width, height;
       int dst_x = PANEL_XPOS(pos);
       int dst_y = PANEL_YPOS(pos);
 
-      if (value == EL_UNDEFINED || value == EL_EMPTY)
+      if (value != EL_UNDEFINED && value != EL_EMPTY)
       {
-       int src_x = DOOR_GFX_PAGEX5 + ALIGNED_TEXT_XPOS(pos);
-       int src_y = DOOR_GFX_PAGEY1 + ALIGNED_TEXT_YPOS(pos);
+       element = value;
+       graphic = el2panelimg(value);
 
-       BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y,
-                  size, size, dst_x, dst_y);
-      }
-      else
-      {
-       int graphic = el2panelimg(value);
+       // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
 
-       DrawSizedGraphicExt(drawto, dst_x, dst_y, graphic, frame, size);
+       if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
+         size = TILESIZE;
+
+       getSizedGraphicSource(graphic, frame, size, &src_bitmap,
+                             &src_x, &src_y);
+
+       width  = graphic_info[graphic].width  * size / TILESIZE;
+       height = graphic_info[graphic].height * size / TILESIZE;
+
+       if (draw_masked)
+         BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+                          dst_x, dst_y);
+       else
+         BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+                    dst_x, dst_y);
       }
     }
     else if (type == TYPE_STRING)
@@ -2134,39 +2546,27 @@ void DisplayGameControlValues()
       {
        int font1 = pos->font;          /* (used for normal state) */
        int font2 = pos->font_alt;      /* (used for active state) */
-       int size1 = strlen(state_normal);
-       int size2 = strlen(state_active);
-       int width1 = size1 * getFontWidth(font1);
-       int width2 = size2 * getFontWidth(font2);
-       int max_width = MAX(width1, width2);
-       int max_height = MAX(getFontHeight(font1), getFontHeight(font2));
-
-       pos->width = max_width;
-
-       /* clear background for values that may have changed its size */
-       ClearRectangleOnBackground(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
-                                  max_width, max_height);
 
        font = (active ? font2 : font1);
       }
 
-#if 1
-      /* as with numbers, don't truncate output if "chars" is zero or less */
-      size = (size > 0 ? size : strlen(s));
-#endif
-
       if (s != NULL)
       {
-       char *s_cut = getStringCopyN(s, size);
+       char *s_cut;
 
-#if 0
-       /* (not needed anymore with above correction of "size" value) */
-       size = strlen(s_cut);   /* string size may be smaller than "chars" */
-#endif
+       if (size <= 0)
+       {
+         /* don't truncate output if "chars" is zero or less */
+         size = strlen(s);
 
-       pos->width = size * getFontWidth(font);
+         /* dynamically correct text alignment */
+         pos->width = size * getFontWidth(font);
+       }
 
-       DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), s_cut, font);
+       s_cut = getStringCopyN(s, size);
+
+       DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+                   s_cut, font, mask_mode);
 
        free(s_cut);
       }
@@ -2175,390 +2575,16 @@ void DisplayGameControlValues()
     redraw_mask |= REDRAW_DOOR_1;
   }
 
-  game_status = GAME_MODE_PLAYING;
-}
-
-void DrawGameValue_Emeralds(int value)
-{
-  struct TextPosInfo *pos = &game.panel.gems;
-#if 1
-  int font_nr = pos->font;
-#else
-  int font_nr = FONT_TEXT_2;
-#endif
-  int font_width = getFontWidth(font_nr);
-  int chars = pos->size;
-
-#if 1
-  return;      /* !!! USE NEW STUFF !!! */
-#endif
-
-  if (PANEL_DEACTIVATED(pos))
-    return;
-
-  pos->width = chars * font_width;
-
-  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr);
-}
-
-void DrawGameValue_Dynamite(int value)
-{
-  struct TextPosInfo *pos = &game.panel.inventory_count;
-#if 1
-  int font_nr = pos->font;
-#else
-  int font_nr = FONT_TEXT_2;
-#endif
-  int font_width = getFontWidth(font_nr);
-  int chars = pos->size;
-
-#if 1
-  return;      /* !!! USE NEW STUFF !!! */
-#endif
-
-  if (PANEL_DEACTIVATED(pos))
-    return;
-
-  pos->width = chars * font_width;
-
-  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr);
-}
-
-void DrawGameValue_Score(int value)
-{
-  struct TextPosInfo *pos = &game.panel.score;
-#if 1
-  int font_nr = pos->font;
-#else
-  int font_nr = FONT_TEXT_2;
-#endif
-  int font_width = getFontWidth(font_nr);
-  int chars = pos->size;
-
-#if 1
-  return;      /* !!! USE NEW STUFF !!! */
-#endif
-
-  if (PANEL_DEACTIVATED(pos))
-    return;
-
-  pos->width = chars * font_width;
-
-  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr);
-}
-
-void DrawGameValue_Time(int value)
-{
-  struct TextPosInfo *pos = &game.panel.time;
-  static int last_value = -1;
-  int chars1 = 3;
-  int chars2 = 4;
-  int chars = pos->size;
-#if 1
-  int font1_nr = pos->font;
-  int font2_nr = pos->font_alt;
-#else
-  int font1_nr = FONT_TEXT_2;
-  int font2_nr = FONT_TEXT_1;
-#endif
-  int font_nr = font1_nr;
-  boolean use_dynamic_chars = (chars == -1 ? TRUE : FALSE);
-
-#if 1
-  return;      /* !!! USE NEW STUFF !!! */
-#endif
-
-  if (PANEL_DEACTIVATED(pos))
-    return;
-
-  if (use_dynamic_chars)               /* use dynamic number of chars */
-  {
-    chars   = (value < 1000 ? chars1   : chars2);
-    font_nr = (value < 1000 ? font1_nr : font2_nr);
-  }
-
-  /* clear background if value just changed its size (dynamic chars only) */
-  if (use_dynamic_chars && (last_value < 1000) != (value < 1000))
-  {
-    int width1 = chars1 * getFontWidth(font1_nr);
-    int width2 = chars2 * getFontWidth(font2_nr);
-    int max_width = MAX(width1, width2);
-    int max_height = MAX(getFontHeight(font1_nr), getFontHeight(font2_nr));
-
-    pos->width = max_width;
-
-    ClearRectangleOnBackground(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
-                              max_width, max_height);
-  }
-
-  pos->width = chars * getFontWidth(font_nr);
-
-  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr);
-
-  last_value = value;
-}
-
-void DrawGameValue_Level(int value)
-{
-  struct TextPosInfo *pos = &game.panel.level_number;
-  int chars1 = 2;
-  int chars2 = 3;
-  int chars = pos->size;
-#if 1
-  int font1_nr = pos->font;
-  int font2_nr = pos->font_alt;
-#else
-  int font1_nr = FONT_TEXT_2;
-  int font2_nr = FONT_TEXT_1;
-#endif
-  int font_nr = font1_nr;
-  boolean use_dynamic_chars = (chars == -1 ? TRUE : FALSE);
-
-#if 1
-  return;      /* !!! USE NEW STUFF !!! */
-#endif
-
-  if (PANEL_DEACTIVATED(pos))
-    return;
-
-  if (use_dynamic_chars)               /* use dynamic number of chars */
-  {
-    chars   = (level_nr < 100 ? chars1   : chars2);
-    font_nr = (level_nr < 100 ? font1_nr : font2_nr);
-  }
-
-  pos->width = chars * getFontWidth(font_nr);
-
-  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr);
-}
-
-void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
-{
-#if 0
-  struct TextPosInfo *pos = &game.panel.keys;
-#endif
-#if 0
-  int base_key_graphic = EL_KEY_1;
-#endif
-  int i;
-
-#if 1
-  return;      /* !!! USE NEW STUFF !!! */
-#endif
-
-#if 0
-  if (PANEL_DEACTIVATED(pos))
-    return;
-#endif
-
-#if 0
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-    base_key_graphic = EL_EM_KEY_1;
-#endif
-
-#if 0
-  pos->width = 4 * MINI_TILEX;
-#endif
-
-#if 1
-  for (i = 0; i < MAX_NUM_KEYS; i++)
-#else
-  /* currently only 4 of 8 possible keys are displayed */
-  for (i = 0; i < STD_NUM_KEYS; i++)
-#endif
-  {
-#if 1
-    struct TextPosInfo *pos = &game.panel.key[i];
-#endif
-    int src_x = DOOR_GFX_PAGEX5 + 18 + (i % 4) * MINI_TILEX;
-    int src_y = DOOR_GFX_PAGEY1 + 123;
-#if 1
-    int dst_x = PANEL_XPOS(pos);
-    int dst_y = PANEL_YPOS(pos);
-#else
-    int dst_x = PANEL_XPOS(pos) + i * MINI_TILEX;
-    int dst_y = PANEL_YPOS(pos);
-#endif
-
-#if 1
-    int element = (i >= STD_NUM_KEYS ? EL_EMC_KEY_5 - 4 :
-                  level.game_engine_type == GAME_ENGINE_TYPE_EM ? EL_EM_KEY_1 :
-                  EL_KEY_1) + i;
-    int graphic = el2edimg(element);
-#endif
-
-#if 1
-    if (PANEL_DEACTIVATED(pos))
-      continue;
-#endif
-
-#if 0
-    /* masked blit with tiles from half-size scaled bitmap does not work yet
-       (no mask bitmap created for these sizes after loading and scaling) --
-       solution: load without creating mask, scale, then create final mask */
-
-    BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y,
-              MINI_TILEX, MINI_TILEY, dst_x, dst_y);
-
-    if (key[i])
-    {
-#if 0
-      int graphic = el2edimg(base_key_graphic + i);
-#endif
-      Bitmap *src_bitmap;
-      int src_x, src_y;
-
-      getMiniGraphicSource(graphic, &src_bitmap, &src_x, &src_y);
-
-      SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc,
-                   dst_x - src_x, dst_y - src_y);
-      BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, MINI_TILEX, MINI_TILEY,
-                      dst_x, dst_y);
-    }
-#else
-#if 1
-    if (key[i])
-      DrawMiniGraphicExt(drawto, dst_x, dst_y, graphic);
-    else
-      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y,
-                MINI_TILEX, MINI_TILEY, dst_x, dst_y);
-#else
-    if (key[i])
-      DrawMiniGraphicExt(drawto, dst_x, dst_y, el2edimg(base_key_graphic + i));
-    else
-      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y,
-                MINI_TILEX, MINI_TILEY, dst_x, dst_y);
-#endif
-#endif
-  }
-}
-
-#else
-
-void DrawGameValue_Emeralds(int value)
-{
-  int font_nr = FONT_TEXT_2;
-  int xpos = (3 * 14 - 3 * getFontWidth(font_nr)) / 2;
-
-  if (PANEL_DEACTIVATED(game.panel.gems))
-    return;
-
-  DrawText(DX_EMERALDS + xpos, DY_EMERALDS, int2str(value, 3), font_nr);
-}
-
-void DrawGameValue_Dynamite(int value)
-{
-  int font_nr = FONT_TEXT_2;
-  int xpos = (3 * 14 - 3 * getFontWidth(font_nr)) / 2;
-
-  if (PANEL_DEACTIVATED(game.panel.inventory_count))
-    return;
-
-  DrawText(DX_DYNAMITE + xpos, DY_DYNAMITE, int2str(value, 3), font_nr);
-}
-
-void DrawGameValue_Score(int value)
-{
-  int font_nr = FONT_TEXT_2;
-  int xpos = (5 * 14 - 5 * getFontWidth(font_nr)) / 2;
-
-  if (PANEL_DEACTIVATED(game.panel.score))
-    return;
-
-  DrawText(DX_SCORE + xpos, DY_SCORE, int2str(value, 5), font_nr);
-}
-
-void DrawGameValue_Time(int value)
-{
-  int font1_nr = FONT_TEXT_2;
-#if 1
-  int font2_nr = FONT_TEXT_1;
-#else
-  int font2_nr = FONT_LEVEL_NUMBER;
-#endif
-  int xpos3 = (3 * 14 - 3 * getFontWidth(font1_nr)) / 2;
-  int xpos4 = (4 * 10 - 4 * getFontWidth(font2_nr)) / 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 + xpos3, DY_TIME, int2str(value, 3), font1_nr);
-  else
-    DrawText(DX_TIME2 + xpos4, DY_TIME, int2str(value, 4), font2_nr);
-}
-
-void DrawGameValue_Level(int value)
-{
-  int font1_nr = FONT_TEXT_2;
-#if 1
-  int font2_nr = FONT_TEXT_1;
-#else
-  int font2_nr = FONT_LEVEL_NUMBER;
-#endif
-
-  if (PANEL_DEACTIVATED(game.panel.level))
-    return;
-
-  if (level_nr < 100)
-    DrawText(DX_LEVEL1, DY_LEVEL, int2str(value, 2), font1_nr);
-  else
-    DrawText(DX_LEVEL2, DY_LEVEL, int2str(value, 3), font2_nr);
-}
-
-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 + x,DY + y, el2edimg(base_key_graphic + i));
-    else
-      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
-                DOOR_GFX_PAGEX5 + x, y, MINI_TILEX, MINI_TILEY, DX + x,DY + y);
-  }
+  SetGameStatus(GAME_MODE_PLAYING);
 }
 
-#endif
-
-void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
-                      int key_bits)
+void UpdateAndDisplayGameControlValues()
 {
-  int key[MAX_NUM_KEYS];
-  int i;
-
-  /* prevent EM engine from updating time/score values parallel to GameWon() */
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM &&
-      local_player->LevelSolved)
+  if (tape.deactivate_display)
     return;
 
-  for (i = 0; i < MAX_NUM_KEYS; i++)
-    key[i] = key_bits & (1 << i);
-
-  DrawGameValue_Level(level_nr);
-
-  DrawGameValue_Emeralds(emeralds);
-  DrawGameValue_Dynamite(dynamite);
-  DrawGameValue_Score(score);
-  DrawGameValue_Time(time);
-
-  DrawGameValue_Keys(key);
+  UpdateGameControlValues();
+  DisplayGameControlValues();
 }
 
 void UpdateGameDoorValues()
@@ -2571,49 +2597,6 @@ void DrawGameDoorValues()
   DisplayGameControlValues();
 }
 
-void DrawGameDoorValues_OLD()
-{
-  int time_value = (level.time == 0 ? TimePlayed : TimeLeft);
-  int dynamite_value = 0;
-  int score_value = (local_player->LevelSolved ? local_player->score_final :
-                    local_player->score);
-  int gems_value = local_player->gems_still_needed;
-  int key_bits = 0;
-  int i, j;
-
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-  {
-    DrawGameDoorValues_EM();
-
-    return;
-  }
-
-  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_value += stored_player[i].inventory_size;
-    }
-  }
-  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_value = stored_player[player_nr].inventory_size;
-  }
-
-  DrawAllGameValues(gems_value, dynamite_value, score_value, time_value,
-                   key_bits);
-}
-
 
 /*
   =============================================================================
@@ -2631,6 +2614,21 @@ static void InitGameEngine()
   game.engine_version = (tape.playing ? tape.engine_version :
                         level.game_version);
 
+  /* set single or multi-player game mode (needed for re-playing tapes) */
+  game.team_mode = setup.team_mode;
+
+  if (tape.playing)
+  {
+    int num_players = 0;
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (tape.player_participates[i])
+       num_players++;
+
+    /* multi-player tapes contain input data for more than one player */
+    game.team_mode = (num_players > 1);
+  }
+
   /* ---------------------------------------------------------------------- */
   /* set flags for bugs and changes according to active game engine version */
   /* ---------------------------------------------------------------------- */
@@ -2686,51 +2684,22 @@ static void InitGameEngine()
     Since 3.1.1, due to changes in player movement handling, the last field
     is not blocked at all when blocking is disabled. When blocking is enabled,
     the last field is blocked for exactly the number of frames of one player
-    move. Additionally, if the player is Murphy, the hero of Supaplex, the
-    last field is blocked for exactly one more than the number of frames of
-    one player move.
-
-    Affected levels/tapes:
-    (!!! yet to be determined -- probably many !!!)
-  */
-
-  game.use_block_last_field_bug =
-    (game.engine_version < VERSION_IDENT(3,1,1,0));
-
-  /*
-    Summary of bugfix/change:
-    Changed behaviour of CE changes with multiple changes per single frame.
-
-    Fixed/changed in version:
-    3.2.0-6
-
-    Description:
-    Before 3.2.0-6, only one single CE change was allowed in each engine frame.
-    This resulted in race conditions where CEs seem to behave strange in some
-    situations (where triggered CE changes were just skipped because there was
-    already a CE change on that tile in the playfield in that engine frame).
-    Since 3.2.0-6, this was changed to allow up to MAX_NUM_CHANGES_PER_FRAME.
-    (The number of changes per frame must be limited in any case, because else
-    it is easily possible to define CE changes that would result in an infinite
-    loop, causing the whole game to freeze. The MAX_NUM_CHANGES_PER_FRAME value
-    should be set large enough so that it would only be reached in cases where
-    the corresponding CE change conditions run into a loop. Therefore, it seems
-    to be reasonable to set MAX_NUM_CHANGES_PER_FRAME to the same value as the
-    maximal number of change pages for custom elements.)
+    move. Additionally, if the player is Murphy, the hero of Supaplex, the
+    last field is blocked for exactly one more than the number of frames of
+    one player move.
 
     Affected levels/tapes:
-    Probably many.
+    (!!! yet to be determined -- probably many !!!)
   */
 
-#if USE_ONLY_ONE_CHANGE_PER_FRAME
-  game.max_num_changes_per_frame = 1;
-#else
-  game.max_num_changes_per_frame =
-    (game.engine_version < VERSION_IDENT(3,2,0,6) ? 1 : 32);
-#endif
+  game.use_block_last_field_bug =
+    (game.engine_version < VERSION_IDENT(3,1,1,0));
 
   /* ---------------------------------------------------------------------- */
 
+  /* set maximal allowed number of custom element changes per game frame */
+  game.max_num_changes_per_frame = 1;
+
   /* default scan direction: scan playfield from top/left to bottom/right */
   InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
 
@@ -2813,7 +2782,7 @@ static void InitGameEngine()
     SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
   }
 
-  /* ---------- initialize internal run-time variables ------------- */
+  /* ---------- initialize internal run-time variables --------------------- */
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
@@ -2851,6 +2820,25 @@ static void InitGameEngine()
     }
   }
 
+  /* ---------- initialize reference elements in change conditions --------- */
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+    struct ElementInfo *ei = &element_info[element];
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      int trigger_element = ei->change_page[j].initial_trigger_element;
+
+      if (trigger_element >= EL_PREV_CE_8 &&
+         trigger_element <= EL_NEXT_CE_8)
+       trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
+
+      ei->change_page[j].trigger_element = trigger_element;
+    }
+  }
+
   /* ---------- initialize run-time trigger player and element ------------- */
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
@@ -2860,7 +2848,8 @@ static void InitGameEngine()
     for (j = 0; j < ei->num_change_pages; j++)
     {
       ei->change_page[j].actual_trigger_element = EL_EMPTY;
-      ei->change_page[j].actual_trigger_player = EL_PLAYER_1;
+      ei->change_page[j].actual_trigger_player = EL_EMPTY;
+      ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
       ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
       ei->change_page[j].actual_trigger_ce_value = 0;
       ei->change_page[j].actual_trigger_ce_score = 0;
@@ -3047,6 +3036,20 @@ static void InitGameEngine()
      setup.scroll_delay                   ? setup.scroll_delay_value       : 0);
   game.scroll_delay_value =
     MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
+
+  /* ---------- initialize game engine snapshots ---------------------------- */
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.snapshot.last_action[i] = 0;
+  game.snapshot.changed_action = FALSE;
+  game.snapshot.collected_item = FALSE;
+  game.snapshot.mode =
+    (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
+     SNAPSHOT_MODE_EVERY_STEP :
+     strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
+     SNAPSHOT_MODE_EVERY_MOVE :
+     strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
+     SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
+  game.snapshot.save_snapshot = FALSE;
 }
 
 int get_num_special_action(int element, int action_first, int action_last)
@@ -3083,15 +3086,46 @@ int get_num_special_action(int element, int action_first, int action_last)
 
 void InitGame()
 {
+  int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
+  int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
+  int fade_mask = REDRAW_FIELD;
+
   boolean emulate_bd = TRUE;   /* unless non-BOULDERDASH elements found */
   boolean emulate_sb = TRUE;   /* unless non-SOKOBAN     elements found */
   boolean emulate_sp = TRUE;   /* unless non-SUPAPLEX    elements found */
-#if 0
-  boolean do_fading = (game_status == GAME_MODE_MAIN);
-#endif
+  int initial_move_dir = MV_DOWN;
   int i, j, x, y;
 
-  game_status = GAME_MODE_PLAYING;
+  // required here to update video display before fading (FIX THIS)
+  DrawMaskedBorder(REDRAW_DOOR_2);
+
+  if (!game.restart_level)
+    CloseDoor(DOOR_CLOSE_1);
+
+  SetGameStatus(GAME_MODE_PLAYING);
+
+  if (level_editor_test_game)
+    FadeSkipNextFadeIn();
+  else
+    FadeSetEnterScreen();
+
+  if (CheckIfGlobalBorderHasChanged())
+    fade_mask = REDRAW_ALL;
+
+  FadeSoundsAndMusic();
+
+  ExpireSoundLoops(TRUE);
+
+  FadeOut(fade_mask);
+
+  /* needed if different viewport properties defined for playing */
+  ChangeViewportPropertiesIfNeeded();
+
+  ClearField();
+
+  OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
+
+  DrawCompleteVideoDisplay();
 
   InitGameEngine();
   InitGameControlValues();
@@ -3109,7 +3143,10 @@ void InitGame()
 
     player->present = FALSE;
     player->active = FALSE;
+    player->mapped = FALSE;
+
     player->killed = FALSE;
+    player->reanimated = FALSE;
 
     player->action = 0;
     player->effective_action = 0;
@@ -3133,18 +3170,19 @@ void InitGame()
     player->dynabombs_left = 0;
     player->dynabomb_xl = FALSE;
 
-    player->MovDir = MV_NONE;
+    player->MovDir = initial_move_dir;
     player->MovPos = 0;
     player->GfxPos = 0;
-    player->GfxDir = MV_NONE;
+    player->GfxDir = initial_move_dir;
     player->GfxAction = ACTION_DEFAULT;
     player->Frame = 0;
     player->StepFrame = 0;
 
-    player->use_murphy = FALSE;
+    player->initial_element = player->element_nr;
     player->artwork_element =
       (level.use_artwork_element[i] ? level.artwork_element[i] :
        player->element_nr);
+    player->use_murphy = FALSE;
 
     player->block_last_field = FALSE;  /* initialized in InitPlayerField() */
     player->block_delay_adjustment = 0;        /* initialized in InitPlayerField() */
@@ -3157,7 +3195,7 @@ void InitGame()
 
     player->step_counter = 0;
 
-    player->last_move_dir = MV_NONE;
+    player->last_move_dir = initial_move_dir;
 
     player->is_active = FALSE;
 
@@ -3175,13 +3213,18 @@ void InitGame()
     player->is_bored = FALSE;
     player->is_sleeping = FALSE;
 
+    player->was_waiting = TRUE;
+    player->was_moving = FALSE;
+    player->was_snapping = FALSE;
+    player->was_dropping = FALSE;
+
     player->frame_counter_bored = -1;
     player->frame_counter_sleeping = -1;
 
     player->anim_delay_counter = 0;
     player->post_delay_counter = 0;
 
-    player->dir_waiting = MV_NONE;
+    player->dir_waiting = initial_move_dir;
     player->action_waiting = ACTION_DEFAULT;
     player->last_action_waiting = ACTION_DEFAULT;
     player->special_action_bored = ACTION_DEFAULT;
@@ -3214,6 +3257,26 @@ void InitGame()
     player->inventory_infinite_element = EL_UNDEFINED;
     player->inventory_size = 0;
 
+    if (level.use_initial_inventory[i])
+    {
+      for (j = 0; j < level.initial_inventory_size[i]; j++)
+      {
+       int element = level.initial_inventory_content[i][j];
+       int collect_count = element_info[element].collect_count_initial;
+       int k;
+
+       if (!IS_CUSTOM_ELEMENT(element))
+         collect_count = 1;
+
+       if (collect_count == 0)
+         player->inventory_infinite_element = element;
+       else
+         for (k = 0; k < collect_count; k++)
+           if (player->inventory_size < MAX_INVENTORY_SIZE)
+             player->inventory_element[player->inventory_size++] = element;
+      }
+    }
+
     DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
     SnapField(player, 0, 0);
 
@@ -3225,6 +3288,10 @@ void InitGame()
     player->LevelSolved_PanelOff = FALSE;
     player->LevelSolved_SaveTape = FALSE;
     player->LevelSolved_SaveScore = FALSE;
+    player->LevelSolved_CountingTime = 0;
+    player->LevelSolved_CountingScore = 0;
+
+    map_player_action[i] = i;
   }
 
   network_player_action_received = FALSE;
@@ -3252,6 +3319,8 @@ void InitGame()
 
   AllPlayersGone = FALSE;
 
+  game.no_time_limit = (level.time == 0);
+
   game.yamyam_content_nr = 0;
   game.robot_wheel_active = FALSE;
   game.magic_wall_active = FALSE;
@@ -3261,11 +3330,6 @@ void InitGame()
   game.switchgate_pos = 0;
   game.wind_direction = level.wind_direction_initial;
 
-#if !USE_PLAYER_GRAVITY
-  game.gravity = FALSE;
-  game.explosions_delayed = TRUE;
-#endif
-
   game.lenses_time_left = 0;
   game.magnify_time_left = 0;
 
@@ -3295,15 +3359,20 @@ void InitGame()
   for (i = 0; i < MAX_NUM_AMOEBA; i++)
     AmoebaCnt[i] = AmoebaCnt2[i] = 0;
 
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+  {
+    printf("Player status at level initialization:\n");
+  }
+#endif
+
   SCAN_PLAYFIELD(x, y)
   {
     Feld[x][y] = level.field[x][y];
     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
     ChangeDelay[x][y] = 0;
     ChangePage[x][y] = -1;
-#if USE_NEW_CUSTOM_VALUE
     CustomValue[x][y] = 0;             /* initialized in InitField() */
-#endif
     Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
     AmoebaNr[x][y] = 0;
     WasJustMoving[x][y] = 0;
@@ -3328,6 +3397,7 @@ void InitGame()
     GfxElement[x][y] = EL_UNDEFINED;
     GfxAction[x][y] = ACTION_DEFAULT;
     GfxDir[x][y] = MV_NONE;
+    GfxRedraw[x][y] = GFX_REDRAW_NONE;
   }
 
   SCAN_PLAYFIELD(x, y)
@@ -3340,6 +3410,8 @@ void InitGame()
       emulate_sp = FALSE;
 
     InitField(x, y, TRUE);
+
+    ResetGfxAnimation(x, y);
   }
 
   InitBeltMovement();
@@ -3361,7 +3433,6 @@ void InitGame()
                    emulate_sb ? EMU_SOKOBAN :
                    emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
 
-#if USE_NEW_ALL_SLIPPERY
   /* initialize type of slippery elements */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
   {
@@ -3379,7 +3450,6 @@ void InitGame()
        element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
     }
   }
-#endif
 
   /* initialize explosion and ignition delay */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
@@ -3399,14 +3469,6 @@ void InitGame()
       if (i == EL_BLACK_ORB)
        element_info[i].ignition_delay = 1;
     }
-
-#if 0
-    if (element_info[i].explosion_delay < 1)   /* !!! check again !!! */
-      element_info[i].explosion_delay = 1;
-
-    if (element_info[i].ignition_delay < 1)    /* !!! check again !!! */
-      element_info[i].ignition_delay = 1;
-#endif
   }
 
   /* correct non-moving belts to start moving left */
@@ -3414,6 +3476,164 @@ void InitGame()
     if (game.belt_dir[i] == MV_NONE)
       game.belt_dir_nr[i] = 3;         /* not moving, next moving left */
 
+#if USE_NEW_PLAYER_ASSIGNMENTS
+  /* !!! SAME AS init.c:InitPlayerInfo() -- FIX THIS !!! */
+  /* choose default local player */
+  local_player = &stored_player[0];
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    stored_player[i].connected = FALSE;
+
+  local_player->connected = TRUE;
+  /* !!! SAME AS init.c:InitPlayerInfo() -- FIX THIS !!! */
+
+  if (tape.playing)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].connected = tape.player_participates[i];
+  }
+  else if (game.team_mode && !options.network)
+  {
+    /* try to guess locally connected team mode players (needed for correct
+       assignment of player figures from level to locally playing players) */
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (setup.input[i].use_joystick ||
+         setup.input[i].key.left != KSYM_UNDEFINED)
+       stored_player[i].connected = TRUE;
+  }
+
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+  {
+    printf("Player status after level initialization:\n");
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
+
+      printf("- player %d: present == %d, connected == %d, active == %d",
+            i + 1,
+            player->present,
+            player->connected,
+            player->active);
+
+      if (local_player == player)
+       printf(" (local player)");
+
+      printf("\n");
+    }
+  }
+#endif
+
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+    printf("Reassigning players ...\n");
+#endif
+
+  /* check if any connected player was not found in playfield */
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    if (player->connected && !player->present)
+    {
+      struct PlayerInfo *field_player = NULL;
+
+#if DEBUG_INIT_PLAYER
+      if (options.debug)
+       printf("- looking for field player for player %d ...\n", i + 1);
+#endif
+
+      /* assign first free player found that is present in the playfield */
+
+      /* first try: look for unmapped playfield player that is not connected */
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (field_player == NULL &&
+           stored_player[j].present &&
+           !stored_player[j].mapped &&
+           !stored_player[j].connected)
+         field_player = &stored_player[j];
+
+      /* second try: look for *any* unmapped playfield player */
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (field_player == NULL &&
+           stored_player[j].present &&
+           !stored_player[j].mapped)
+         field_player = &stored_player[j];
+
+      if (field_player != NULL)
+      {
+       int jx = field_player->jx, jy = field_player->jy;
+
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("- found player %d\n", field_player->index_nr + 1);
+#endif
+
+       player->present = FALSE;
+       player->active = FALSE;
+
+       field_player->present = TRUE;
+       field_player->active = TRUE;
+
+       /*
+       player->initial_element = field_player->initial_element;
+       player->artwork_element = field_player->artwork_element;
+
+       player->block_last_field       = field_player->block_last_field;
+       player->block_delay_adjustment = field_player->block_delay_adjustment;
+       */
+
+       StorePlayer[jx][jy] = field_player->element_nr;
+
+       field_player->jx = field_player->last_jx = jx;
+       field_player->jy = field_player->last_jy = jy;
+
+       if (local_player == player)
+         local_player = field_player;
+
+       map_player_action[field_player->index_nr] = i;
+
+       field_player->mapped = TRUE;
+
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("- map_player_action[%d] == %d\n",
+                field_player->index_nr + 1, i + 1);
+#endif
+      }
+    }
+
+    if (player->connected && player->present)
+      player->mapped = TRUE;
+  }
+
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+  {
+    printf("Player status after player assignment (first stage):\n");
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
+
+      printf("- player %d: present == %d, connected == %d, active == %d",
+            i + 1,
+            player->present,
+            player->connected,
+            player->active);
+
+      if (local_player == player)
+       printf(" (local player)");
+
+      printf("\n");
+    }
+  }
+#endif
+
+#else
+
   /* check if any connected player was not found in playfield */
   for (i = 0; i < MAX_PLAYERS; i++)
   {
@@ -3423,24 +3643,26 @@ void InitGame()
     {
       for (j = 0; j < MAX_PLAYERS; j++)
       {
-       struct PlayerInfo *some_player = &stored_player[j];
-       int jx = some_player->jx, jy = some_player->jy;
+       struct PlayerInfo *field_player = &stored_player[j];
+       int jx = field_player->jx, jy = field_player->jy;
 
        /* assign first free player found that is present in the playfield */
-       if (some_player->present && !some_player->connected)
+       if (field_player->present && !field_player->connected)
        {
          player->present = TRUE;
          player->active = TRUE;
 
-         some_player->present = FALSE;
-         some_player->active = FALSE;
+         field_player->present = FALSE;
+         field_player->active = FALSE;
 
-         player->artwork_element = some_player->artwork_element;
+         player->initial_element = field_player->initial_element;
+         player->artwork_element = field_player->artwork_element;
 
-         player->block_last_field       = some_player->block_last_field;
-         player->block_delay_adjustment = some_player->block_delay_adjustment;
+         player->block_last_field       = field_player->block_last_field;
+         player->block_delay_adjustment = field_player->block_delay_adjustment;
 
          StorePlayer[jx][jy] = player->element_nr;
+
          player->jx = player->last_jx = jx;
          player->jy = player->last_jy = jy;
 
@@ -3449,14 +3671,46 @@ void InitGame()
       }
     }
   }
+#endif
+
+#if 0
+  printf("::: local_player->present == %d\n", local_player->present);
+#endif
 
   if (tape.playing)
   {
     /* when playing a tape, eliminate all players who do not participate */
 
+#if USE_NEW_PLAYER_ASSIGNMENTS
+
+    if (!game.team_mode)
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (stored_player[i].active &&
+           !tape.player_participates[map_player_action[i]])
+       {
+         struct PlayerInfo *player = &stored_player[i];
+         int jx = player->jx, jy = player->jy;
+
+#if DEBUG_INIT_PLAYER
+         if (options.debug)
+           printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
+#endif
+
+         player->active = FALSE;
+         StorePlayer[jx][jy] = 0;
+         Feld[jx][jy] = EL_EMPTY;
+       }
+      }
+    }
+
+#else
+
     for (i = 0; i < MAX_PLAYERS; i++)
     {
-      if (stored_player[i].active && !tape.player_participates[i])
+      if (stored_player[i].active &&
+         !tape.player_participates[i])
       {
        struct PlayerInfo *player = &stored_player[i];
        int jx = player->jx, jy = player->jy;
@@ -3466,8 +3720,9 @@ void InitGame()
        Feld[jx][jy] = EL_EMPTY;
       }
     }
+#endif
   }
-  else if (!options.network && !setup.team_mode)       /* && !tape.playing */
+  else if (!options.network && !game.team_mode)                /* && !tape.playing */
   {
     /* when in single player mode, eliminate all but the first active player */
 
@@ -3496,26 +3751,39 @@ void InitGame()
   /* when recording the game, store which players take part in the game */
   if (tape.recording)
   {
+#if USE_NEW_PLAYER_ASSIGNMENTS
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (stored_player[i].connected)
+       tape.player_participates[i] = TRUE;
+#else
     for (i = 0; i < MAX_PLAYERS; i++)
       if (stored_player[i].active)
        tape.player_participates[i] = TRUE;
+#endif
   }
 
+#if DEBUG_INIT_PLAYER
   if (options.debug)
   {
+    printf("Player status after player assignment (final stage):\n");
+
     for (i = 0; i < MAX_PLAYERS; i++)
     {
       struct PlayerInfo *player = &stored_player[i];
 
-      printf("Player %d: present == %d, connected == %d, active == %d.\n",
-            i+1,
+      printf("- player %d: present == %d, connected == %d, active == %d",
+            i + 1,
             player->present,
             player->connected,
             player->active);
+
       if (local_player == player)
-       printf("Player  %d is local player.\n", i+1);
+       printf(" (local player)");
+
+      printf("\n");
     }
   }
+#endif
 
   if (BorderElement == EL_EMPTY)
   {
@@ -3532,12 +3800,16 @@ void InitGame()
     SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
   }
 
-  if (lev_fieldx + (SBX_Left == -1 ? 2 : 0) <= SCR_FIELDX)
+  if (full_lev_fieldx <= SCR_FIELDX)
     SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
-
-  if (lev_fieldy + (SBY_Upper == -1 ? 2 : 0) <= SCR_FIELDY)
+  if (full_lev_fieldy <= SCR_FIELDY)
     SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
 
+  if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
+    SBX_Left--;
+  if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
+    SBY_Upper--;
+
   /* if local player not found, look for custom element that might create
      the player (make some assumptions about the right custom element) */
   if (!local_player->present)
@@ -3647,95 +3919,73 @@ void InitGame()
                local_player->jy - MIDPOSY);
   }
 
-  /* do not use PLAYING mask for fading out from main screen */
-  game_status = GAME_MODE_MAIN;
-
-  StopAnimation();
-
-  if (!game.restart_level)
-    CloseDoor(DOOR_CLOSE_1);
-
-#if 1
-  if (level_editor_test_game)
-    FadeSkipNextFadeIn();
-  else
-    FadeSetEnterScreen();
-#else
-  if (level_editor_test_game)
-    fading = fading_none;
-  else
-    fading = menu.destination;
-#endif
-
-#if 1
-  FadeOut(REDRAW_FIELD);
-#else
-  if (do_fading)
-    FadeOut(REDRAW_FIELD);
-#endif
-
-  game_status = GAME_MODE_PLAYING;
-
   /* !!! FIX THIS (START) !!! */
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     InitGameEngine_EM();
-
-    /* blit playfield from scroll buffer to normal back buffer for fading in */
-    BlitScreenToBitmap_EM(backbuffer);
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    InitGameEngine_SP();
   }
   else
   {
-    DrawLevel();
+    DrawLevel(REDRAW_FIELD);
     DrawAllPlayers();
 
     /* after drawing the level, correct some elements */
     if (game.timegate_time_left == 0)
       CloseAllOpenTimegates();
-
-    /* blit playfield from scroll buffer to normal back buffer for fading in */
-    if (setup.soft_scrolling)
-      BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
-
-    redraw_mask |= REDRAW_FROM_BACKBUFFER;
   }
+
+  /* blit playfield from scroll buffer to normal back buffer for fading in */
+  BlitScreenToBitmap(backbuffer);
   /* !!! FIX THIS (END) !!! */
 
-#if 1
-  FadeIn(REDRAW_FIELD);
-#else
-  if (do_fading)
-    FadeIn(REDRAW_FIELD);
+  DrawMaskedBorder(fade_mask);
+
+  FadeIn(fade_mask);
 
+#if 1
+  // full screen redraw is required at this point in the following cases:
+  // - special editor door undrawn when game was started from level editor
+  // - drawing area (playfield) was changed and has to be removed completely
+  redraw_mask = REDRAW_ALL;
   BackToFront();
 #endif
 
   if (!game.restart_level)
   {
     /* copy default game door content to main double buffer */
-    BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
-              DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
+
+    /* !!! CHECK AGAIN !!! */
+    SetPanelBackground();
+    // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+    DrawBackground(DX, DY, DXSIZE, DYSIZE);
   }
 
   SetPanelBackground();
   SetDrawBackgroundMask(REDRAW_DOOR_1);
 
-  UpdateGameDoorValues();
-  DrawGameDoorValues();
+  UpdateAndDisplayGameControlValues();
 
   if (!game.restart_level)
   {
     UnmapGameButtons();
     UnmapTapeButtons();
+
+    FreeGameButtons();
+    CreateGameButtons();
+
     game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
     game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
     game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
+
     MapGameButtons();
     MapTapeButtons();
 
     /* copy actual game door content to door double buffer for OpenDoor() */
-    BlitBitmap(drawto, bitmap_db_door,
-              DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
+    BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
 
     OpenDoor(DOOR_OPEN_ALL);
 
@@ -3746,31 +3996,65 @@ void InitGame()
 
     KeyboardAutoRepeatOffUnlessAutoplay();
 
+#if DEBUG_INIT_PLAYER
     if (options.debug)
     {
+      printf("Player status (final):\n");
+
       for (i = 0; i < MAX_PLAYERS; i++)
-       printf("Player %d %sactive.\n",
-              i + 1, (stored_player[i].active ? "" : "not "));
+      {
+       struct PlayerInfo *player = &stored_player[i];
+
+       printf("- player %d: present == %d, connected == %d, active == %d",
+              i + 1,
+              player->present,
+              player->connected,
+              player->active);
+
+       if (local_player == player)
+         printf(" (local player)");
+
+       printf("\n");
+      }
     }
+#endif
   }
 
-#if 1
   UnmapAllGadgets();
 
   MapGameButtons();
   MapTapeButtons();
-#endif
+
+  if (!game.restart_level && !tape.playing)
+  {
+    LevelStats_incPlayed(level_nr);
+
+    SaveLevelSetup_SeriesInfo();
+  }
 
   game.restart_level = FALSE;
+
+  SaveEngineSnapshotToListInitial();
 }
 
-void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y)
+void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
+                       int actual_player_x, int actual_player_y)
 {
   /* this is used for non-R'n'D game engines to update certain engine values */
 
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    actual_player_x = correctLevelPosX_EM(actual_player_x);
+    actual_player_y = correctLevelPosY_EM(actual_player_y);
+  }
+
   /* needed to determine if sounds are played within the visible screen area */
   scroll_x = actual_scroll_x;
   scroll_y = actual_scroll_y;
+
+  /* needed to get player position for "follow finger" playing input method */
+  local_player->jx = actual_player_x;
+  local_player->jy = actual_player_y;
 }
 
 void InitMovDir(int x, int y)
@@ -3978,6 +4262,10 @@ static void PlayerWins(struct PlayerInfo *player)
 
   player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
                         level.native_em_level->lev->score : player->score);
+
+  player->LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed :
+                                     TimeLeft);
+  player->LevelSolved_CountingScore = player->score_final;
 }
 
 void GameWon()
@@ -4001,17 +4289,22 @@ void GameWon()
     local_player->LevelSolved_SaveTape = tape.recording;
     local_player->LevelSolved_SaveScore = !tape.playing;
 
+    if (!tape.playing)
+    {
+      LevelStats_incSolved(level_nr);
+
+      SaveLevelSetup_SeriesInfo();
+    }
+
     if (tape.auto_play)                /* tape might already be stopped here */
       tape.auto_play_level_solved = TRUE;
 
-#if 1
     TapeStop();
-#endif
 
     game_over_delay_1 = game_over_delay_value_1;
     game_over_delay_2 = game_over_delay_value_2;
 
-    time = time_final = (level.time == 0 ? TimePlayed : TimeLeft);
+    time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
     score = score_final = local_player->score_final;
 
     if (TimeLeft > 0)
@@ -4019,7 +4312,7 @@ void GameWon()
       time_final = 0;
       score_final += TimeLeft * level.score[SC_TIME_BONUS];
     }
-    else if (level.time == 0 && TimePlayed < 999)
+    else if (game.no_time_limit && TimePlayed < 999)
     {
       time_final = 999;
       score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
@@ -4032,15 +4325,13 @@ void GameWon()
       time = time_final;
       score = score_final;
 
-#if 1
+      local_player->LevelSolved_CountingTime = time;
+      local_player->LevelSolved_CountingScore = score;
+
       game_panel_controls[GAME_PANEL_TIME].value = time;
       game_panel_controls[GAME_PANEL_SCORE].value = score;
 
       DisplayGameControlValues();
-#else
-      DrawGameValue_Time(time);
-      DrawGameValue_Score(score);
-#endif
     }
 
     if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
@@ -4057,24 +4348,14 @@ void GameWon()
        {
          int element = Feld[ExitX][ExitY];
 
-#if 0
-         if (element == EL_EM_EXIT_OPEN ||
-             element == EL_EM_STEEL_EXIT_OPEN)
-         {
-           Bang(ExitX, ExitY);
-         }
-         else
-#endif
-         {
-           Feld[ExitX][ExitY] =
-             (element == EL_EXIT_OPEN          ? EL_EXIT_CLOSING :
-              element == EL_EM_EXIT_OPEN       ? EL_EM_EXIT_CLOSING :
-              element == EL_SP_EXIT_OPEN       ? EL_SP_EXIT_CLOSING:
-              element == EL_STEEL_EXIT_OPEN    ? EL_STEEL_EXIT_CLOSING:
-              EL_EM_STEEL_EXIT_CLOSING);
-
-           PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
-         }
+         Feld[ExitX][ExitY] =
+           (element == EL_EXIT_OPEN            ? EL_EXIT_CLOSING :
+            element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
+            element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
+            element == EL_STEEL_EXIT_OPEN      ? EL_STEEL_EXIT_CLOSING:
+            EL_EM_STEEL_EXIT_CLOSING);
+
+         PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
        }
 
        /* player disappears */
@@ -4114,15 +4395,13 @@ void GameWon()
     time  += time_count_steps * time_count_dir;
     score += time_count_steps * level.score[SC_TIME_BONUS];
 
-#if 1
+    local_player->LevelSolved_CountingTime = time;
+    local_player->LevelSolved_CountingScore = score;
+
     game_panel_controls[GAME_PANEL_TIME].value = time;
     game_panel_controls[GAME_PANEL_SCORE].value = score;
 
     DisplayGameControlValues();
-#else
-    DrawGameValue_Time(time);
-    DrawGameValue_Score(score);
-#endif
 
     if (time == time_final)
       StopSound(SND_GAME_LEVELTIME_BONUS);
@@ -4143,9 +4422,7 @@ void GameWon()
     return;
   }
 
-#if 1
   GameEnd();
-#endif
 }
 
 void GameEnd()
@@ -4155,43 +4432,30 @@ void GameEnd()
 
   local_player->LevelSolved_GameEnd = TRUE;
 
-  CloseDoor(DOOR_CLOSE_1);
+  if (!global.use_envelope_request)
+    CloseDoor(DOOR_CLOSE_1);
 
   if (local_player->LevelSolved_SaveTape)
   {
-#if 0
-    TapeStop();
-#endif
-
-#if 1
     SaveTapeChecked(tape.level_nr);    /* ask to save tape */
-#else
-    SaveTape(tape.level_nr);           /* ask to save tape */
-#endif
   }
 
+  CloseDoor(DOOR_CLOSE_ALL);
+
   if (level_editor_test_game)
   {
-    game_status = GAME_MODE_MAIN;
+    SetGameStatus(GAME_MODE_MAIN);
 
-#if 1
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
-#else
     DrawMainMenu();
-#endif
 
     return;
   }
 
   if (!local_player->LevelSolved_SaveScore)
   {
-#if 1
-    FadeOut(REDRAW_FIELD);
-#endif
-
-    game_status = GAME_MODE_MAIN;
+    SetGameStatus(GAME_MODE_MAIN);
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
 
     return;
   }
@@ -4199,6 +4463,7 @@ void GameEnd()
   if (level_nr == leveldir_current->handicap_level)
   {
     leveldir_current->handicap_level++;
+
     SaveLevelSetup_SeriesInfo();
   }
 
@@ -4207,7 +4472,7 @@ void GameEnd()
 
   if ((hi_pos = NewHiScore()) >= 0) 
   {
-    game_status = GAME_MODE_SCORES;
+    SetGameStatus(GAME_MODE_SCORES);
 
     DrawHallOfFame(hi_pos);
 
@@ -4219,11 +4484,7 @@ void GameEnd()
   }
   else
   {
-#if 1
-    FadeOut(REDRAW_FIELD);
-#endif
-
-    game_status = GAME_MODE_MAIN;
+    SetGameStatus(GAME_MODE_MAIN);
 
     if (raise_level)
     {
@@ -4231,7 +4492,7 @@ void GameEnd()
       TapeErase();
     }
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
   }
 }
 
@@ -4326,11 +4587,6 @@ void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
 {
   if (player->GfxAction != action || player->GfxDir != dir)
   {
-#if 0
-    printf("Player frame reset! (%d => %d, %d => %d)\n",
-          player->GfxAction, action, player->GfxDir, dir);
-#endif
-
     player->GfxAction = action;
     player->GfxDir = dir;
     player->Frame = 0;
@@ -4338,7 +4594,6 @@ void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
   }
 }
 
-#if USE_GFX_RESET_GFX_ANIMATION
 static void ResetGfxFrame(int x, int y, boolean redraw)
 {
   int element = Feld[x][y];
@@ -4357,7 +4612,6 @@ static void ResetGfxFrame(int x, int y, boolean redraw)
   if (redraw && GfxFrame[x][y] != last_gfx_frame)
     DrawLevelGraphicAnimation(x, y, graphic);
 }
-#endif
 
 static void ResetGfxAnimation(int x, int y)
 {
@@ -4365,9 +4619,7 @@ static void ResetGfxAnimation(int x, int y)
   GfxDir[x][y] = MovDir[x][y];
   GfxFrame[x][y] = 0;
 
-#if USE_GFX_RESET_GFX_ANIMATION
   ResetGfxFrame(x, y, FALSE);
-#endif
 }
 
 static void ResetRandomAnimationValue(int x, int y)
@@ -4383,51 +4635,24 @@ void InitMovingField(int x, int y, int direction)
   int newx = x + dx;
   int newy = y + dy;
   boolean is_moving_before, is_moving_after;
-#if 0
-  boolean continues_moving = (WasJustMoving[x][y] && direction == MovDir[x][y]);
-#endif
 
   /* check if element was/is moving or being moved before/after mode change */
-#if 1
-#if 1
   is_moving_before = (WasJustMoving[x][y] != 0);
-#else
-  /* (!!! this does not work -- WasJustMoving is NOT a boolean value !!!) */
-  is_moving_before = WasJustMoving[x][y];
-#endif
-#else
-  is_moving_before = (getElementMoveStepsizeExt(x, y, MovDir[x][y]) != 0);
-#endif
   is_moving_after  = (getElementMoveStepsizeExt(x, y, direction)    != 0);
 
   /* reset animation only for moving elements which change direction of moving
      or which just started or stopped moving
      (else CEs with property "can move" / "not moving" are reset each frame) */
-#if USE_GFX_RESET_ONLY_WHEN_MOVING
-#if 1
   if (is_moving_before != is_moving_after ||
       direction != MovDir[x][y])
     ResetGfxAnimation(x, y);
-#else
-  if ((is_moving_before || is_moving_after) && !continues_moving)
-    ResetGfxAnimation(x, y);
-#endif
-#else
-  if (!continues_moving)
-    ResetGfxAnimation(x, y);
-#endif
 
   MovDir[x][y] = direction;
   GfxDir[x][y] = direction;
 
-#if USE_GFX_RESET_ONLY_WHEN_MOVING
   GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
                     direction == MV_DOWN && CAN_FALL(element) ?
                     ACTION_FALLING : ACTION_MOVING);
-#else
-  GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
-                    ACTION_FALLING : ACTION_MOVING);
-#endif
 
   /* this is needed for CEs with property "can move" / "not moving" */
 
@@ -4438,9 +4663,7 @@ void InitMovingField(int x, int y, int direction)
 
     MovDir[newx][newy] = MovDir[x][y];
 
-#if USE_NEW_CUSTOM_VALUE
     CustomValue[newx][newy] = CustomValue[x][y];
-#endif
 
     GfxFrame[newx][newy] = GfxFrame[x][y];
     GfxRandom[newx][newy] = GfxRandom[x][y];
@@ -4523,19 +4746,13 @@ static void RemoveField(int x, int y)
   MovDir[x][y] = 0;
   MovDelay[x][y] = 0;
 
-#if USE_NEW_CUSTOM_VALUE
   CustomValue[x][y] = 0;
-#endif
 
   AmoebaNr[x][y] = 0;
   ChangeDelay[x][y] = 0;
   ChangePage[x][y] = -1;
   Pushed[x][y] = FALSE;
 
-#if 0
-  ExplodeField[x][y] = EX_TYPE_NONE;
-#endif
-
   GfxElement[x][y] = EL_UNDEFINED;
   GfxAction[x][y] = ACTION_DEFAULT;
   GfxDir[x][y] = MV_NONE;
@@ -4564,7 +4781,7 @@ void RemoveMovingField(int x, int y)
 
       Store[oldx][oldy] = Store2[oldx][oldy] = 0;
 
-      DrawLevelField(oldx, oldy);
+      TEST_DrawLevelField(oldx, oldy);
 
       return;
     }
@@ -4593,8 +4810,8 @@ void RemoveMovingField(int x, int y)
   if (next_element != EL_UNDEFINED)
     Feld[oldx][oldy] = next_element;
 
-  DrawLevelField(oldx, oldy);
-  DrawLevelField(newx, newy);
+  TEST_DrawLevelField(oldx, oldy);
+  TEST_DrawLevelField(newx, newy);
 }
 
 void DrawDynamite(int x, int y)
@@ -4695,166 +4912,107 @@ static void setScreenCenteredToAllPlayers(int *sx, int *sy)
 void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
                        boolean center_screen, boolean quick_relocation)
 {
+  unsigned int frame_delay_value_old = GetVideoFrameDelay();
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+  int new_scroll_x, new_scroll_y;
 
-  if (quick_relocation)
+  if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
   {
-    int offset = game.scroll_delay_value;
+    /* case 1: quick relocation inside visible screen (without scrolling) */
 
-    if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
-    {
-      if (!level.shifted_relocation || center_screen)
-      {
-       /* quick relocation (without scrolling), with centering of screen */
+    RedrawPlayfield();
+
+    return;
+  }
+
+  if (!level.shifted_relocation || center_screen)
+  {
+    /* relocation _with_ centering of screen */
 
-       scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+    new_scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
                    x > SBX_Right + MIDPOSX ? SBX_Right :
                    x - MIDPOSX);
 
-       scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+    new_scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
                    y > SBY_Lower + MIDPOSY ? SBY_Lower :
                    y - MIDPOSY);
-      }
-      else
-      {
-       /* quick relocation (without scrolling), but do not center screen */
+  }
+  else
+  {
+    /* relocation _without_ centering of screen */
 
-       int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                              old_x > SBX_Right + MIDPOSX ? SBX_Right :
-                              old_x - MIDPOSX);
+    int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                          old_x > SBX_Right + MIDPOSX ? SBX_Right :
+                          old_x - MIDPOSX);
 
-       int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                              old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                              old_y - MIDPOSY);
+    int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                          old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                          old_y - MIDPOSY);
 
-       int offset_x = x + (scroll_x - center_scroll_x);
-       int offset_y = y + (scroll_y - center_scroll_y);
+    int offset_x = x + (scroll_x - center_scroll_x);
+    int offset_y = y + (scroll_y - center_scroll_y);
 
-       scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
+    new_scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
                    offset_x > SBX_Right + MIDPOSX ? SBX_Right :
                    offset_x - MIDPOSX);
 
-       scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+    new_scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
                    offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
                    offset_y - MIDPOSY);
-      }
-    }
-    else
-    {
-      /* quick relocation (without scrolling), inside visible screen area */
-
-      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
-  {
-#if 1
-    int scroll_xx, scroll_yy;
-
-    if (!level.shifted_relocation || center_screen)
-    {
-      /* visible relocation (with scrolling), with centering of screen */
 
-      scroll_xx = (x < SBX_Left  + MIDPOSX ? SBX_Left :
-                  x > SBX_Right + MIDPOSX ? SBX_Right :
-                  x - MIDPOSX);
-
-      scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                  y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                  y - MIDPOSY);
-    }
-    else
-    {
-      /* visible relocation (with scrolling), but do not center screen */
-
-      int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                            old_x > SBX_Right + MIDPOSX ? SBX_Right :
-                            old_x - MIDPOSX);
-
-      int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                            old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                            old_y - MIDPOSY);
-
-      int offset_x = x + (scroll_x - center_scroll_x);
-      int offset_y = y + (scroll_y - center_scroll_y);
+  if (quick_relocation)
+  {
+    /* case 2: quick relocation (redraw without visible scrolling) */
 
-      scroll_xx = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                  offset_x > SBX_Right + MIDPOSX ? SBX_Right :
-                  offset_x - MIDPOSX);
+    scroll_x = new_scroll_x;
+    scroll_y = new_scroll_y;
 
-      scroll_yy = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                  offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                  offset_y - MIDPOSY);
-    }
+    RedrawPlayfield();
 
-#else
+    return;
+  }
 
-    /* visible relocation (with scrolling), with centering of screen */
+  /* case 3: visible relocation (with scrolling to new position) */
 
-    int scroll_xx = (x < SBX_Left  + MIDPOSX ? SBX_Left :
-                    x > SBX_Right + MIDPOSX ? SBX_Right :
-                    x - MIDPOSX);
+  ScrollScreen(NULL, SCROLL_GO_ON);    /* scroll last frame to full tile */
 
-    int scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                    y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                    y - MIDPOSY);
-#endif
+  SetVideoFrameDelay(wait_delay_value);
 
-    ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
+  while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
+  {
+    int dx = 0, dy = 0;
+    int fx = FX, fy = FY;
 
-    while (scroll_x != scroll_xx || scroll_y != scroll_yy)
-    {
-      int dx = 0, dy = 0;
-      int fx = FX, fy = FY;
+    dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
+    dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
 
-      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;
 
-      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
-       break;
+    scroll_x -= dx;
+    scroll_y -= dy;
 
-      scroll_x -= dx;
-      scroll_y -= dy;
+    fx += dx * TILEX / 2;
+    fy += dy * TILEY / 2;
 
-      fx += dx * TILEX / 2;
-      fy += dy * TILEY / 2;
+    ScrollLevel(dx, dy);
+    DrawAllPlayers();
 
-      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);
 
-      /* scroll in two steps of half tile size to make things smoother */
-      BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
-      FlushDisplay();
-      Delay(wait_delay_value);
+    /* scroll second step to align at full tile size */
+    BlitScreenToBitmap(window);
+  }
 
-      /* scroll second step to align at full tile size */
-      BackToFront();
-      Delay(wait_delay_value);
-    }
+  DrawAllPlayers();
+  BackToFront();
 
-    DrawAllPlayers();
-    BackToFront();
-    Delay(wait_delay_value);
-  }
+  SetVideoFrameDelay(frame_delay_value_old);
 }
 
 void RelocatePlayer(int jx, int jy, int el_player_raw)
@@ -4904,8 +5062,7 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
 
       DrawPlayer(player);
 
-      BackToFront();
-      Delay(wait_delay_value);
+      BackToFront_WithFrameDelay(wait_delay_value);
     }
 
     DrawPlayer(player);                /* needed here only to cleanup last field */
@@ -4926,10 +5083,13 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
   Feld[jx][jy] = el_player;
   InitPlayerField(jx, jy, el_player, TRUE);
 
+  /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
+     possible that the relocation target field did not contain a player element,
+     but a walkable element, to which the new player was relocated -- in this
+     case, restore that (already initialized!) element on the player field */
   if (!ELEM_IS_PLAYER(element))        /* player may be set on walkable element */
   {
-    Feld[jx][jy] = element;
-    InitField(jx, jy, FALSE);
+    Feld[jx][jy] = element;    /* restore previously existing element */
   }
 
   /* only visually relocate centered player */
@@ -4945,6 +5105,19 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
 
   CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
                                      player->index_bit, enter_side);
+
+  if (player->is_switching)
+  {
+    /* ensure that relocation while still switching an element does not cause
+       a new element to be treated as also switched directly after relocation
+       (this is important for teleporter switches that teleport the player to
+       a place where another teleporter switch is in the same direction, which
+       would then incorrectly be treated as immediately switched before the
+       direction key that caused the switch was released) */
+
+    player->switch_x += jx - old_jx;
+    player->switch_y += jy - old_jy;
+  }
 }
 
 void Explode(int ex, int ey, int phase, int mode)
@@ -4967,23 +5140,6 @@ void Explode(int ex, int ey, int phase, int mode)
     int center_element = Feld[ex][ey];
     int artwork_element, explosion_element;    /* set these values later */
 
-#if 0
-    /* --- This is only really needed (and now handled) in "Impact()". --- */
-    /* do not explode moving elements that left the explode field in time */
-    if (game.engine_version >= VERSION_IDENT(2,2,0,7) &&
-       center_element == EL_EMPTY &&
-       (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER))
-      return;
-#endif
-
-#if 0
-    /* !!! at this place, the center element may be EL_BLOCKED !!! */
-    if (mode == EX_TYPE_NORMAL ||
-       mode == EX_TYPE_CENTER ||
-       mode == EX_TYPE_CROSS)
-      PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
-#endif
-
     /* remove things displayed in background while burning dynamite */
     if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
       Back[ex][ey] = 0;
@@ -5013,12 +5169,10 @@ void Explode(int ex, int ey, int phase, int mode)
       }
     }
 
-#if 1
     if (mode == EX_TYPE_NORMAL ||
        mode == EX_TYPE_CENTER ||
        mode == EX_TYPE_CROSS)
       PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
-#endif
 
     last_phase = element_info[explosion_element].explosion_delay + 1;
 
@@ -5155,31 +5309,9 @@ void Explode(int ex, int ey, int phase, int mode)
 
   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
 
-#ifdef DEBUG
-
-  /* activate this even in non-DEBUG version until cause for crash in
-     getGraphicAnimationFrame() (see below) is found and eliminated */
-
-#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");
-    printf("Explode(): x = %d, y = %d: GfxElement == EL_UNDEFINED\n", x, y);
-    printf("Explode(): This should never happen!\n");
-    printf("\n\n");
-
-    GfxElement[x][y] = EL_EMPTY;
-  }
-#endif
-
-#endif
 
   border_element = Store2[x][y];
   if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
@@ -5251,18 +5383,16 @@ void Explode(int ex, int ey, int phase, int mode)
     ChangeDelay[x][y] = 0;
     ChangePage[x][y] = -1;
 
-#if USE_NEW_CUSTOM_VALUE
     CustomValue[x][y] = 0;
-#endif
 
     InitField_WithBug2(x, y, FALSE);
 
-    DrawLevelField(x, y);
+    TEST_DrawLevelField(x, y);
 
     TestIfElementTouchesCustomElement(x, y);
 
     if (GFX_CRUMBLED(element))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
     if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
       StorePlayer[x][y] = 0;
@@ -5276,7 +5406,7 @@ void Explode(int ex, int ey, int phase, int mode)
     int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
     if (phase == delay)
-      DrawLevelFieldCrumbledSand(x, y);
+      TEST_DrawLevelFieldCrumbled(x, y);
 
     if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
     {
@@ -5353,8 +5483,7 @@ void Bang(int x, int y)
   {
     struct PlayerInfo *player = PLAYERINFO(x, y);
 
-    element = Feld[x][y] = (player->use_murphy ? EL_SP_MURPHY :
-                           player->element_nr);
+    element = Feld[x][y] = player->initial_element;
 
     if (level.use_explosion_element[player->index_nr])
     {
@@ -5392,10 +5521,6 @@ void Bang(int x, int y)
       break;
 
     case EL_DC_LANDMINE:
-#if 0
-    case EL_EM_EXIT_OPEN:
-    case EL_EM_STEEL_EXIT_OPEN:
-#endif
       explosion_type = EX_TYPE_CENTER;
       break;
 
@@ -5579,7 +5704,7 @@ static void ToggleBeltSwitch(int x, int y)
       if (e_belt_nr == belt_nr)
       {
        Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
-       DrawLevelField(xx, yy);
+       TEST_DrawLevelField(xx, yy);
       }
     }
     else if (IS_BELT(element) && belt_dir != MV_NONE)
@@ -5591,7 +5716,7 @@ static void ToggleBeltSwitch(int x, int y)
        int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
 
        Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
-       DrawLevelField(xx, yy);
+       TEST_DrawLevelField(xx, yy);
       }
     }
     else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
@@ -5603,7 +5728,7 @@ static void ToggleBeltSwitch(int x, int y)
        int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
 
        Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
-       DrawLevelField(xx, yy);
+       TEST_DrawLevelField(xx, yy);
       }
     }
   }
@@ -5619,41 +5744,26 @@ 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_DC_SWITCHGATE_SWITCH_UP ||
-            element == EL_DC_SWITCHGATE_SWITCH_DOWN)
-    {
-      Feld[xx][yy] = EL_DC_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);
+      TEST_DrawLevelField(xx, yy);
     }
     else if (element == EL_SWITCHGATE_SWITCH_DOWN)
     {
       Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
-      DrawLevelField(xx, yy);
+      TEST_DrawLevelField(xx, yy);
     }
     else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
     {
       Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
-      DrawLevelField(xx, yy);
+      TEST_DrawLevelField(xx, yy);
     }
     else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
     {
       Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
-      DrawLevelField(xx, yy);
+      TEST_DrawLevelField(xx, yy);
     }
-#endif
     else if (element == EL_SWITCHGATE_OPEN ||
             element == EL_SWITCHGATE_OPENING)
     {
@@ -5699,25 +5809,25 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
        game.light_time_left > 0)
     {
       Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_LIGHT_SWITCH_ACTIVE &&
             game.light_time_left == 0)
     {
       Feld[x][y] = EL_LIGHT_SWITCH;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_EMC_DRIPPER &&
             game.light_time_left > 0)
     {
       Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_EMC_DRIPPER_ACTIVE &&
             game.light_time_left == 0)
     {
       Feld[x][y] = EL_EMC_DRIPPER;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_INVISIBLE_STEELWALL ||
             element == EL_INVISIBLE_WALL ||
@@ -5726,11 +5836,11 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
       if (game.light_time_left > 0)
        Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
 
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
 
       /* uncrumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
     else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
             element == EL_INVISIBLE_WALL_ACTIVE ||
@@ -5739,11 +5849,11 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
       if (game.light_time_left == 0)
        Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
 
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
 
       /* re-crumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
   }
 }
@@ -5760,13 +5870,13 @@ static void RedrawAllInvisibleElementsForLenses()
        game.lenses_time_left > 0)
     {
       Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_EMC_DRIPPER_ACTIVE &&
             game.lenses_time_left == 0)
     {
       Feld[x][y] = EL_EMC_DRIPPER;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_INVISIBLE_STEELWALL ||
             element == EL_INVISIBLE_WALL ||
@@ -5775,11 +5885,11 @@ static void RedrawAllInvisibleElementsForLenses()
       if (game.lenses_time_left > 0)
        Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
 
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
 
       /* uncrumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
     else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
             element == EL_INVISIBLE_WALL_ACTIVE ||
@@ -5788,11 +5898,11 @@ static void RedrawAllInvisibleElementsForLenses()
       if (game.lenses_time_left == 0)
        Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
 
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
 
       /* re-crumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
   }
 }
@@ -5809,13 +5919,13 @@ static void RedrawAllInvisibleElementsForMagnifier()
        game.magnify_time_left > 0)
     {
       Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
             game.magnify_time_left == 0)
     {
       Feld[x][y] = EL_EMC_FAKE_GRASS;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (IS_GATE_GRAY(element) &&
             game.magnify_time_left > 0)
@@ -5826,8 +5936,10 @@ static void RedrawAllInvisibleElementsForMagnifier()
                    element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
                    IS_EMC_GATE_GRAY(element) ?
                    element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
+                   IS_DC_GATE_GRAY(element) ?
+                   EL_DC_GATE_WHITE_GRAY_ACTIVE :
                    element);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (IS_GATE_GRAY_ACTIVE(element) &&
             game.magnify_time_left == 0)
@@ -5838,8 +5950,10 @@ static void RedrawAllInvisibleElementsForMagnifier()
                    element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
                    IS_EMC_GATE_GRAY_ACTIVE(element) ?
                    element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
+                   IS_DC_GATE_GRAY_ACTIVE(element) ?
+                   EL_DC_GATE_WHITE_GRAY :
                    element);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
   }
 }
@@ -5876,18 +5990,14 @@ static void ActivateTimegateSwitch(int x, int y)
     else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
     {
       Feld[xx][yy] = EL_TIMEGATE_SWITCH;
-      DrawLevelField(xx, yy);
+      TEST_DrawLevelField(xx, yy);
     }
     */
 
   }
 
-#if 1
   Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
                EL_DC_TIMEGATE_SWITCH_ACTIVE);
-#else
-  Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE;
-#endif
 }
 
 void Impact(int x, int y)
@@ -5918,7 +6028,7 @@ void Impact(int x, int y)
       RemoveMovingField(x, y + 1);
       Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
       Feld[x][y + 2] = EL_ROCK;
-      DrawLevelField(x, y + 2);
+      TEST_DrawLevelField(x, y + 2);
 
       object_hit = TRUE;
     }
@@ -5928,7 +6038,7 @@ void Impact(int x, int y)
       RemoveMovingField(x, y + 1);
       Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
       Feld[x][y + 2] = EL_ROCK;
-      DrawLevelField(x, y + 2);
+      TEST_DrawLevelField(x, y + 2);
 
       object_hit = TRUE;
     }
@@ -5952,7 +6062,7 @@ void Impact(int x, int y)
       el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
   {
     ResetGfxAnimation(x, y);
-    DrawLevelField(x, y);
+    TEST_DrawLevelField(x, y);
   }
 
   if (impact && CAN_EXPLODE_IMPACT(element))
@@ -6113,10 +6223,6 @@ void Impact(int x, int y)
        }
        else
        {
-#if 0
-         TestIfElementSmashesCustomElement(x, y, MV_DOWN);
-#endif
-
          CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
 
          CheckElementChangeBySide(x, y + 1, smashed, element,
@@ -6418,7 +6524,6 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (element == EL_SPRING)
   {
-#if USE_NEW_SPRING_BUMPER
     if (MovDir[x][y] & MV_HORIZONTAL)
     {
       if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
@@ -6426,7 +6531,7 @@ inline static void TurnRoundExt(int x, int y)
       {
        Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
        ResetGfxAnimation(move_x, move_y);
-       DrawLevelField(move_x, move_y);
+       TEST_DrawLevelField(move_x, move_y);
 
        MovDir[x][y] = back_dir;
       }
@@ -6434,12 +6539,6 @@ inline static void TurnRoundExt(int x, int y)
               SPRING_CAN_ENTER_FIELD(element, x, y + 1))
        MovDir[x][y] = MV_NONE;
     }
-#else
-    if (MovDir[x][y] & MV_HORIZONTAL &&
-       (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
-        SPRING_CAN_ENTER_FIELD(element, x, y + 1)))
-      MovDir[x][y] = MV_NONE;
-#endif
 
     MovDelay[x][y] = 0;
   }
@@ -7043,10 +7142,18 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
       {
        if (!MovDelay[x][y])
+       {
          MovDelay[x][y] = TILEY + 1;
 
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
+
        if (MovDelay[x][y])
        {
+         DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
+
          MovDelay[x][y]--;
          if (MovDelay[x][y])
            return;
@@ -7057,6 +7164,33 @@ void StartMoving(int x, int y)
        Store[x][y + 1] = Store[x][y];
        Store[x][y] = 0;
 
+       PlayLevelSoundAction(x, y, ACTION_FILLING);
+      }
+      else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
+      {
+       if (!MovDelay[x][y])
+       {
+         MovDelay[x][y] = TILEY + 1;
+
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
+
+       if (MovDelay[x][y])
+       {
+         DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
+
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
+
+       Feld[x][y] = EL_QUICKSAND_EMPTY;
+       Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
+       Store[x][y + 1] = Store[x][y];
+       Store[x][y] = 0;
+
        PlayLevelSoundAction(x, y, ACTION_FILLING);
       }
     }
@@ -7080,10 +7214,18 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
       {
        if (!MovDelay[x][y])
+       {
          MovDelay[x][y] = TILEY + 1;
 
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
+
        if (MovDelay[x][y])
        {
+         DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
+
          MovDelay[x][y]--;
          if (MovDelay[x][y])
            return;
@@ -7094,6 +7236,33 @@ void StartMoving(int x, int y)
        Store[x][y + 1] = Store[x][y];
        Store[x][y] = 0;
 
+       PlayLevelSoundAction(x, y, ACTION_FILLING);
+      }
+      else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
+      {
+       if (!MovDelay[x][y])
+       {
+         MovDelay[x][y] = TILEY + 1;
+
+         ResetGfxAnimation(x, y);
+         ResetGfxAnimation(x, y + 1);
+       }
+
+       if (MovDelay[x][y])
+       {
+         DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
+         DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
+
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
+
+       Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
+       Feld[x][y + 1] = EL_QUICKSAND_FULL;
+       Store[x][y + 1] = Store[x][y];
+       Store[x][y] = 0;
+
        PlayLevelSoundAction(x, y, ACTION_FILLING);
       }
     }
@@ -7132,7 +7301,7 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+         MovDelay[x][y] = TILEY / 4 + 1;
 
        if (MovDelay[x][y])
        {
@@ -7160,7 +7329,7 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+         MovDelay[x][y] = TILEY / 4 + 1;
 
        if (MovDelay[x][y])
        {
@@ -7188,7 +7357,7 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+         MovDelay[x][y] = TILEY / 4 + 1;
 
        if (MovDelay[x][y])
        {
@@ -7229,13 +7398,8 @@ void StartMoving(int x, int y)
       Store[x][y] = EL_ACID;
     }
     else if (
-#if USE_FIX_IMPACT_COLLISION
             (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
              CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
-#else
-            (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
-             CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
-#endif
             (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
              CAN_FALL(element) && WasJustFalling[x][y] &&
              (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
@@ -7295,7 +7459,6 @@ void StartMoving(int x, int y)
       boolean can_fall_both = (can_fall_left && can_fall_right);
       int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
 
-#if USE_NEW_ALL_SLIPPERY
       if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
       {
        if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
@@ -7306,41 +7469,11 @@ void StartMoving(int x, int y)
          can_fall_right = FALSE;
        else if (slippery_type == SLIPPERY_ONLY_RIGHT)
          can_fall_left = FALSE;
-
-       can_fall_any  = (can_fall_left || can_fall_right);
-       can_fall_both = FALSE;
-      }
-#else
-      if (can_fall_any && IS_CUSTOM_ELEMENT(Feld[x][y + 1]))
-      {
-       if (slippery_type == SLIPPERY_ONLY_LEFT)
-         can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ONLY_RIGHT)
-         can_fall_left = FALSE;
-       else if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
-         can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
-         can_fall_left = FALSE;
-
-       can_fall_any  = (can_fall_left || can_fall_right);
-       can_fall_both = (can_fall_left && can_fall_right);
-      }
-#endif
-
-#if USE_NEW_ALL_SLIPPERY
-#else
-#if USE_NEW_SP_SLIPPERY
-      /* !!! better use the same properties as for custom elements here !!! */
-      else if (game.engine_version >= VERSION_IDENT(3,1,1,0) &&
-              can_fall_both && IS_SP_ELEMENT(Feld[x][y + 1]))
-      {
-       can_fall_right = FALSE;         /* slip down on left side */
+
+       can_fall_any  = (can_fall_left || can_fall_right);
        can_fall_both = FALSE;
       }
-#endif
-#endif
 
-#if USE_NEW_ALL_SLIPPERY
       if (can_fall_both)
       {
        if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
@@ -7350,18 +7483,6 @@ void StartMoving(int x, int y)
 
        can_fall_both = FALSE;
       }
-#else
-      if (can_fall_both)
-      {
-       if (game.emulation == EMU_BOULDERDASH ||
-           element == EL_BD_ROCK || element == EL_BD_DIAMOND)
-         can_fall_right = FALSE;       /* slip down on left side */
-       else
-         can_fall_left = !(can_fall_right = RND(2));
-
-       can_fall_both = FALSE;
-      }
-#endif
 
       if (can_fall_any)
       {
@@ -7370,11 +7491,7 @@ void StartMoving(int x, int y)
        started_moving = TRUE;
       }
     }
-#if 0
-    else if (IS_BELT_ACTIVE(Feld[x][y + 1]) && !CAN_MOVE(element))
-#else
     else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
-#endif
     {
       boolean left_is_free  = (x > 0 && IS_FREE(x - 1, y));
       boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
@@ -7402,26 +7519,11 @@ void StartMoving(int x, int y)
   }
 
   /* not "else if" because of elements that can fall and move (EL_SPRING) */
-#if 0
-  if (CAN_MOVE(element) && !started_moving && MovDir[x][y] != MV_NONE)
-#else
   if (CAN_MOVE(element) && !started_moving)
-#endif
   {
     int move_pattern = element_info[element].move_pattern;
     int newx, newy;
 
-#if 0
-#if DEBUG
-    if (MovDir[x][y] == MV_NONE)
-    {
-      printf("StartMoving(): %d,%d: element %d ['%s'] not moving\n",
-            x, y, element, element_info[element].token_name);
-      printf("StartMoving(): This should never happen!\n");
-    }
-#endif
-#endif
-
     Moving2Blocked(x, y, &newx, &newy);
 
     if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
@@ -7461,7 +7563,7 @@ void StartMoving(int x, int y)
                               element == EL_SP_SNIKSNAK ||
                               element == EL_SP_ELECTRON ||
                               element == EL_MOLE))
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
       }
     }
 
@@ -7495,7 +7597,7 @@ void StartMoving(int x, int y)
        if (IS_PLAYER(x, y))
          DrawPlayerField(x, y);
        else
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
 
        PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
 
@@ -7514,20 +7616,10 @@ void StartMoving(int x, int y)
          {
            int flamed = MovingOrBlocked2Element(xx, yy);
 
-           /* !!! */
-#if 0
-           if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
-             Bang(xx, yy);
-           else if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
-             RemoveMovingField(xx, yy);
-           else
-             RemoveField(xx, yy);
-#else
            if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
              Bang(xx, yy);
            else
              RemoveMovingField(xx, yy);
-#endif
 
            ChangeDelay[xx][yy] = 0;
 
@@ -7535,7 +7627,7 @@ void StartMoving(int x, int y)
 
            if (IN_SCR_FIELD(sx, sy))
            {
-             DrawLevelFieldCrumbledSand(xx, yy);
+             TEST_DrawLevelFieldCrumbled(xx, yy);
              DrawGraphic(sx, sy, flame_graphic, frame);
            }
          }
@@ -7543,7 +7635,7 @@ void StartMoving(int x, int y)
          {
            if (Feld[xx][yy] == EL_FLAMES)
              Feld[xx][yy] = EL_EMPTY;
-           DrawLevelField(xx, yy);
+           TEST_DrawLevelField(xx, yy);
          }
        }
       }
@@ -7586,7 +7678,7 @@ void StartMoving(int x, int y)
          Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
       {
        RemoveField(x, y);
-       DrawLevelField(x, y);
+       TEST_DrawLevelField(x, y);
 
        PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
        if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
@@ -7602,7 +7694,7 @@ void StartMoving(int x, int y)
       else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
       {
        if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
-         DrawLevelField(newx, newy);
+         TEST_DrawLevelField(newx, newy);
        else
          GfxDir[x][y] = MovDir[x][y] = MV_NONE;
       }
@@ -7613,7 +7705,7 @@ void StartMoving(int x, int y)
        if (IS_PLAYER(x, y))
          DrawPlayerField(x, y);
        else
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
 
        return;
       }
@@ -7627,7 +7719,7 @@ void StartMoving(int x, int y)
        else
        {
          Feld[newx][newy] = EL_EMPTY;
-         DrawLevelField(newx, newy);
+         TEST_DrawLevelField(newx, newy);
        }
 
        PlayLevelSound(x, y, SND_PIG_DIGGING);
@@ -7637,7 +7729,7 @@ void StartMoving(int x, int y)
        if (IS_PLAYER(x, y))
          DrawPlayerField(x, y);
        else
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
 
        return;
       }
@@ -7716,78 +7808,21 @@ void StartMoving(int x, int y)
        else
        {
          Feld[newx][newy] = EL_EMPTY;
-         DrawLevelField(newx, newy);
+         TEST_DrawLevelField(newx, newy);
 
          PlayLevelSoundAction(x, y, ACTION_DIGGING);
        }
       }
       else if (!IS_FREE(newx, newy))
       {
-#if 0
-       if (IS_PLAYER(x, y))
-         DrawPlayerField(x, y);
-       else
-         DrawLevelField(x, y);
-#endif
-
        return;
       }
     }
     else if (IS_CUSTOM_ELEMENT(element) &&
             CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
     {
-      int new_element = Feld[newx][newy];
-
-      if (!IS_FREE(newx, newy))
-      {
-       int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING :
-                     IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING :
-                     ACTION_BREAKING);
-
-       /* no element can dig solid indestructible elements */
-       if (IS_INDESTRUCTIBLE(new_element) &&
-           !IS_DIGGABLE(new_element) &&
-           !IS_COLLECTIBLE(new_element))
-         return;
-
-       if (AmoebaNr[newx][newy] &&
-           (new_element == EL_AMOEBA_FULL ||
-            new_element == EL_BD_AMOEBA ||
-            new_element == EL_AMOEBA_GROWING))
-       {
-         AmoebaCnt[AmoebaNr[newx][newy]]--;
-         AmoebaCnt2[AmoebaNr[newx][newy]]--;
-       }
-
-       if (IS_MOVING(newx, newy))
-         RemoveMovingField(newx, newy);
-       else
-       {
-         RemoveField(newx, newy);
-         DrawLevelField(newx, newy);
-       }
-
-       /* if digged element was about to explode, prevent the explosion */
-       ExplodeField[newx][newy] = EX_TYPE_NONE;
-
-       PlayLevelSoundAction(x, y, action);
-      }
-
-      Store[newx][newy] = EL_EMPTY;
-#if 1
-      /* this makes it possible to leave the removed element again */
-      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
-       Store[newx][newy] = new_element;
-#else
-      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
-      {
-       int move_leave_element = element_info[element].move_leave_element;
-
-       /* this makes it possible to leave the removed element again */
-       Store[newx][newy] = (move_leave_element == EL_TRIGGER_ELEMENT ?
-                            new_element : move_leave_element);
-      }
-#endif
+      if (!DigFieldByCE(newx, newy, element))
+       return;
 
       if (move_pattern & MV_MAZE_RUNNER_STYLE)
       {
@@ -7802,7 +7837,7 @@ void StartMoving(int x, int y)
        if (IS_PLAYER(x, y))
          DrawPlayerField(x, y);
        else
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
 
        return;
       }
@@ -7829,31 +7864,17 @@ void StartMoving(int x, int y)
          if (IS_PLAYER(x, y))
            DrawPlayerField(x, y);
          else
-           DrawLevelField(x, y);
+           TEST_DrawLevelField(x, y);
 
          PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
 
          MovDelay[x][y] = 50;
 
-         /* !!! */
-#if 0
-         RemoveField(newx, newy);
-#endif
          Feld[newx][newy] = EL_FLAMES;
          if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
-         {
-#if 0
-           RemoveField(newx1, newy1);
-#endif
            Feld[newx1][newy1] = EL_FLAMES;
-         }
          if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
-         {
-#if 0
-           RemoveField(newx2, newy2);
-#endif
            Feld[newx2][newy2] = EL_FLAMES;
-         }
 
          return;
        }
@@ -7867,7 +7888,7 @@ void StartMoving(int x, int y)
       else
       {
        Feld[newx][newy] = EL_EMPTY;
-       DrawLevelField(newx, newy);
+       TEST_DrawLevelField(newx, newy);
       }
 
       PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
@@ -7883,22 +7904,14 @@ void StartMoving(int x, int y)
          AmoebaCnt[AmoebaNr[newx][newy]]--;
       }
 
-#if 0
-      /* !!! test !!! */
-      if (IS_MOVING(newx, newy) || IS_BLOCKED(newx, newy))
-      {
-       RemoveMovingField(newx, newy);
-      }
-#else
       if (IS_MOVING(newx, newy))
       {
        RemoveMovingField(newx, newy);
       }
-#endif
       else
       {
        Feld[newx][newy] = EL_EMPTY;
-       DrawLevelField(newx, newy);
+       TEST_DrawLevelField(newx, newy);
       }
 
       PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
@@ -7921,7 +7934,7 @@ void StartMoving(int x, int y)
 
        ResetGfxAnimation(x, y);
        GfxAction[x][y] = ACTION_DIGGING;
-       DrawLevelField(x, y);
+       TEST_DrawLevelField(x, y);
 
        MovDelay[newx][newy] = 0;       /* start amoeba shrinking delay */
 
@@ -7930,7 +7943,7 @@ void StartMoving(int x, int y)
       else     /* element == EL_PACMAN */
       {
        Feld[newx][newy] = EL_EMPTY;
-       DrawLevelField(newx, newy);
+       TEST_DrawLevelField(newx, newy);
        PlayLevelSound(x, y, SND_PACMAN_DIGGING);
       }
     }
@@ -7947,21 +7960,6 @@ void StartMoving(int x, int y)
 
       TurnRound(x, y);
 
-#if 0
-      /* !!! NEW "CE_BLOCKED" STUFF !!! -- DOES NOT WORK YET... !!! */
-      if (move_pattern & MV_ANY_DIRECTION &&
-         move_pattern == MovDir[x][y])
-      {
-       int blocking_element =
-         (IN_LEV_FIELD(newx, newy) ? Feld[newx][newy] : BorderElement);
-
-       CheckElementChangeBySide(x, y, element, blocking_element, CE_BLOCKED,
-                                MovDir[x][y]);
-
-       element = Feld[x][y];   /* element might have changed */
-      }
-#endif
-
       if (GFX_ELEMENT(element) != EL_SAND)     /* !!! FIX THIS (crumble) !!! */
        DrawLevelElementAnimation(x, y, element);
 
@@ -8001,18 +7999,7 @@ void ContinueMoving(int x, int y)
 
   if (ABS(MovPos[x][y]) < TILEX)
   {
-#if 0
-    int ee = Feld[x][y];
-    int gg = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-    int ff = getGraphicAnimationFrame(gg, GfxFrame[x][y]);
-
-    printf("::: %d.%d: moving %d ... [%d, %d, %d] [%d, %d, %d]\n",
-          x, y, ABS(MovPos[x][y]),
-          ee, gg, ff,
-          GfxAction[x][y], GfxDir[x][y], GfxFrame[x][y]);
-#endif
-
-    DrawLevelField(x, y);
+    TEST_DrawLevelField(x, y);
 
     return;    /* element is still moving */
   }
@@ -8031,7 +8018,7 @@ void ContinueMoving(int x, int y)
   {
     Feld[x][y] = EL_SAND;
 
-    DrawLevelFieldCrumbledSandNeighbours(x, y);
+    TEST_DrawLevelFieldCrumbledNeighbours(x, y);
   }
   else if (element == EL_QUICKSAND_FILLING)
   {
@@ -8067,9 +8054,7 @@ void ContinueMoving(int x, int y)
       Feld[x][y] = EL_MAGIC_WALL_DEAD;
     element = Feld[newx][newy] = Store[x][y];
 
-#if USE_NEW_CUSTOM_VALUE
     InitField(newx, newy, FALSE);
-#endif
   }
   else if (element == EL_BD_MAGIC_WALL_FILLING)
   {
@@ -8085,9 +8070,7 @@ void ContinueMoving(int x, int y)
       Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
     element = Feld[newx][newy] = Store[x][y];
 
-#if USE_NEW_CUSTOM_VALUE
     InitField(newx, newy, FALSE);
-#endif
   }
   else if (element == EL_DC_MAGIC_WALL_FILLING)
   {
@@ -8103,9 +8086,7 @@ void ContinueMoving(int x, int y)
       Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
     element = Feld[newx][newy] = Store[x][y];
 
-#if USE_NEW_CUSTOM_VALUE
     InitField(newx, newy, FALSE);
-#endif
   }
   else if (element == EL_AMOEBA_DROPPING)
   {
@@ -8139,18 +8120,14 @@ void ContinueMoving(int x, int y)
     ChangeEvent[newx][newy] = ChangeEvent[x][y];
   }
 
-#if USE_NEW_CUSTOM_VALUE
-    CustomValue[newx][newy] = CustomValue[x][y];
-#endif
+  CustomValue[newx][newy] = CustomValue[x][y];
 
   ChangeDelay[x][y] = 0;
   ChangePage[x][y] = -1;
   ChangeCount[x][y] = 0;
   ChangeEvent[x][y] = -1;
 
-#if USE_NEW_CUSTOM_VALUE
   CustomValue[x][y] = 0;
-#endif
 
   /* copy animation control values to new field */
   GfxFrame[newx][newy]  = GfxFrame[x][y];
@@ -8161,34 +8138,15 @@ void ContinueMoving(int x, int y)
   Pushed[x][y] = Pushed[newx][newy] = FALSE;
 
   /* some elements can leave other elements behind after moving */
-#if 1
   if (ei->move_leave_element != EL_EMPTY &&
       (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
       (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
-#else
-  if (IS_CUSTOM_ELEMENT(element) && ei->move_leave_element != EL_EMPTY &&
-      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
-      (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
-#endif
   {
     int move_leave_element = ei->move_leave_element;
 
-#if 1
-#if 1
     /* this makes it possible to leave the removed element again */
     if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
       move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
-#else
-    /* this makes it possible to leave the removed element again */
-    if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
-      move_leave_element = stored;
-#endif
-#else
-    /* this makes it possible to leave the removed element again */
-    if (ei->move_leave_type == LEAVE_TYPE_LIMITED &&
-        ei->move_leave_element == EL_TRIGGER_ELEMENT)
-      move_leave_element = stored;
-#endif
 
     Feld[x][y] = move_leave_element;
 
@@ -8198,7 +8156,7 @@ void ContinueMoving(int x, int y)
     InitField(x, y, FALSE);
 
     if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
     if (ELEM_IS_PLAYER(move_leave_element))
       RelocatePlayer(x, y, move_leave_element);
@@ -8214,8 +8172,8 @@ void ContinueMoving(int x, int y)
        element_info[element].move_pattern == MV_WHEN_DROPPED)))
     GfxDir[x][y] = MovDir[newx][newy] = 0;
 
-  DrawLevelField(x, y);
-  DrawLevelField(newx, newy);
+  TEST_DrawLevelField(x, y);
+  TEST_DrawLevelField(newx, newy);
 
   Stop[newx][newy] = TRUE;     /* ignore this element until the next frame */
 
@@ -8243,10 +8201,8 @@ void ContinueMoving(int x, int y)
     if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
       CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
 
-#if USE_FIX_IMPACT_COLLISION
     if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
       CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
-#endif
   }
 
   if (DONT_TOUCH(element))     /* object may be nasty to player or others */
@@ -8260,6 +8216,11 @@ void ContinueMoving(int x, int y)
   else if (element == EL_PENGUIN)
     TestIfFriendTouchesBadThing(newx, newy);
 
+  if (DONT_GET_HIT_BY(element))
+  {
+    TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
+  }
+
   /* give the player one last chance (one more frame) to move away */
   if (CAN_FALL(element) && direction == MV_DOWN &&
       (last_line || (!IS_FREE(x, newy + 1) &&
@@ -8284,29 +8245,6 @@ void ContinueMoving(int x, int y)
   CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
 
   TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
-
-#if 0
-  if (ChangePage[newx][newy] != -1)            /* delayed change */
-  {
-    int page = ChangePage[newx][newy];
-    struct ElementChangeInfo *change = &ei->change_page[page];
-
-    ChangePage[newx][newy] = -1;
-
-    if (change->can_change)
-    {
-      if (ChangeElement(newx, newy, element, page))
-      {
-        if (change->post_change_function)
-          change->post_change_function(newx, newy);
-      }
-    }
-
-    if (change->has_action)
-      ExecuteCustomElementAction(newx, newy, element, page);
-  }
-#endif
-
   TestIfElementHitsCustomElement(newx, newy, direction);
   TestIfPlayerTouchesCustomElement(newx, newy);
   TestIfElementTouchesCustomElement(newx, newy);
@@ -8477,7 +8415,7 @@ void AmoebeUmwandelnBD(int ax, int ay, int new_element)
       AmoebaNr[x][y] = 0;
       Feld[x][y] = new_element;
       InitField(x, y, FALSE);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
       done = TRUE;
     }
   }
@@ -8490,8 +8428,8 @@ void AmoebeUmwandelnBD(int ax, int ay, int new_element)
 
 void AmoebeWaechst(int x, int y)
 {
-  static unsigned long sound_delay = 0;
-  static unsigned long sound_delay_value = 0;
+  static unsigned int sound_delay = 0;
+  static unsigned int sound_delay_value = 0;
 
   if (!MovDelay[x][y])         /* start new growing cycle */
   {
@@ -8519,15 +8457,15 @@ void AmoebeWaechst(int x, int y)
     {
       Feld[x][y] = Store[x][y];
       Store[x][y] = 0;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
   }
 }
 
 void AmoebaDisappearing(int x, int y)
 {
-  static unsigned long sound_delay = 0;
-  static unsigned long sound_delay_value = 0;
+  static unsigned int sound_delay = 0;
+  static unsigned int sound_delay_value = 0;
 
   if (!MovDelay[x][y])         /* start new shrinking cycle */
   {
@@ -8551,7 +8489,7 @@ void AmoebaDisappearing(int x, int y)
     if (!MovDelay[x][y])
     {
       Feld[x][y] = EL_EMPTY;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
 
       /* don't let mole enter this field in this cycle;
         (give priority to objects falling to this field from above) */
@@ -8578,7 +8516,7 @@ void AmoebeAbleger(int ax, int ay)
   if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
   {
     Feld[ax][ay] = EL_AMOEBA_DEAD;
-    DrawLevelField(ax, ay);
+    TEST_DrawLevelField(ax, ay);
     return;
   }
 
@@ -8648,7 +8586,7 @@ void AmoebeAbleger(int ax, int ay)
       if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
       {
        Feld[ax][ay] = EL_AMOEBA_DEAD;
-       DrawLevelField(ax, ay);
+       TEST_DrawLevelField(ax, ay);
        AmoebaCnt[AmoebaNr[ax][ay]]--;
 
        if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0)   /* amoeba is completely dead */
@@ -8712,7 +8650,7 @@ void AmoebeAbleger(int ax, int ay)
     return;
   }
 
-  DrawLevelField(newax, neway);
+  TEST_DrawLevelField(newax, neway);
 }
 
 void Life(int ax, int ay)
@@ -8770,7 +8708,7 @@ void Life(int ax, int ay)
       {
        Feld[xx][yy] = EL_EMPTY;
        if (!Stop[xx][yy])
-         DrawLevelField(xx, yy);
+         TEST_DrawLevelField(xx, yy);
        Stop[xx][yy] = TRUE;
        changed = TRUE;
       }
@@ -8783,7 +8721,7 @@ void Life(int ax, int ay)
        Feld[xx][yy] = element;
        MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
        if (!Stop[xx][yy])
-         DrawLevelField(xx, yy);
+         TEST_DrawLevelField(xx, yy);
        Stop[xx][yy] = TRUE;
        changed = TRUE;
       }
@@ -8827,11 +8765,7 @@ static void RunTimegateWheel(int x, int y)
 
 static void InitMagicBallDelay(int x, int y)
 {
-#if 1
   ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
-#else
-  ChangeDelay[x][y] = level.ball_time * FRAMES_PER_SECOND + 1;
-#endif
 }
 
 static void ActivateMagicBall(int bx, int by)
@@ -9011,9 +8945,6 @@ void DrawTwinkleOnField(int x, int y)
   {
     MovDelay[x][y]--;
 
-    if (setup.direct_draw && MovDelay[x][y])
-      SetDrawtoField(DRAW_BUFFERED);
-
     DrawLevelElementAnimation(x, y, Feld[x][y]);
 
     if (MovDelay[x][y] != 0)
@@ -9022,18 +8953,6 @@ void DrawTwinkleOnField(int x, int y)
                                           10 - MovDelay[x][y]);
 
       DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
-
-      if (setup.direct_draw)
-      {
-       int dest_x, dest_y;
-
-       dest_x = FX + SCREENX(x) * TILEX;
-       dest_y = FY + SCREENY(y) * TILEY;
-
-       BlitBitmap(drawto_field, window,
-                  dest_x, dest_y, TILEX, TILEY, dest_x, dest_y);
-       SetDrawtoField(DRAW_DIRECT);
-      }
     }
   }
 }
@@ -9062,28 +8981,28 @@ void MauerWaechst(int x, int y)
       if (MovDir[x][y] == MV_LEFT)
       {
        if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
-         DrawLevelField(x - 1, y);
+         TEST_DrawLevelField(x - 1, y);
       }
       else if (MovDir[x][y] == MV_RIGHT)
       {
        if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
-         DrawLevelField(x + 1, y);
+         TEST_DrawLevelField(x + 1, y);
       }
       else if (MovDir[x][y] == MV_UP)
       {
        if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
-         DrawLevelField(x, y - 1);
+         TEST_DrawLevelField(x, y - 1);
       }
       else
       {
        if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
-         DrawLevelField(x, y + 1);
+         TEST_DrawLevelField(x, y + 1);
       }
 
       Feld[x][y] = Store[x][y];
       Store[x][y] = 0;
       GfxDir[x][y] = MovDir[x][y] = MV_NONE;
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
   }
 }
@@ -9174,7 +9093,7 @@ void MauerAbleger(int ax, int ay)
   }
 
   if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
-    DrawLevelField(ax, ay);
+    TEST_DrawLevelField(ax, ay);
 
   if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
     oben_massiv = TRUE;
@@ -9292,7 +9211,7 @@ void MauerAblegerStahl(int ax, int ay)
        element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
       ((links_massiv && rechts_massiv) ||
        element == EL_EXPANDABLE_STEELWALL_VERTICAL))
-    Feld[ax][ay] = EL_WALL;
+    Feld[ax][ay] = EL_STEELWALL;
 
   if (new_wall)
     PlayLevelSoundAction(ax, ay, ACTION_GROWING);
@@ -9338,7 +9257,7 @@ void CheckForDragon(int x, int y)
        if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
        {
          Feld[xx][yy] = EL_EMPTY;
-         DrawLevelField(xx, yy);
+         TEST_DrawLevelField(xx, yy);
        }
        else
          break;
@@ -9402,7 +9321,7 @@ static void ChangeActiveTrap(int x, int y)
 
   /* if new animation frame was drawn, correct crumbled sand border */
   if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
-    DrawLevelFieldCrumbledSand(x, y);
+    TEST_DrawLevelFieldCrumbled(x, y);
 }
 
 static int getSpecialActionElement(int element, int number, int base_element)
@@ -9436,6 +9355,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
   int action_type = change->action_type;
   int action_mode = change->action_mode;
   int action_arg = change->action_arg;
+  int action_element = change->action_element;
   int i;
 
   if (!change->has_action)
@@ -9447,11 +9367,16 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     (level.time > 0 ? TimeLeft :
      TimePlayed);
 
-  int action_arg_element =
+  int action_arg_element_raw =
     (action_arg == CA_ARG_PLAYER_TRIGGER  ? change->actual_trigger_player :
      action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
      action_arg == CA_ARG_ELEMENT_TARGET  ? change->target_element :
+     action_arg == CA_ARG_ELEMENT_ACTION  ? change->action_element :
+     action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
+     action_arg == CA_ARG_INVENTORY_RM_TARGET  ? change->target_element :
+     action_arg == CA_ARG_INVENTORY_RM_ACTION  ? change->action_element :
      EL_EMPTY);
+  int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
 
   int action_arg_direction =
     (action_arg >= CA_ARG_DIRECTION_LEFT &&
@@ -9480,11 +9405,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
      action_type == CA_SET_LEVEL_TIME ? level.time :
      action_type == CA_SET_LEVEL_SCORE ? 0 :
-#if USE_NEW_CUSTOM_VALUE
      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);
 
@@ -9496,11 +9417,7 @@ 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 :
-#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 :
@@ -9508,10 +9425,13 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
      action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
      action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
+     action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
      action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
      action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
+     action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
      action_arg == CA_ARG_ELEMENT_NR_TARGET  ? change->target_element :
      action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
+     action_arg == CA_ARG_ELEMENT_NR_ACTION  ? change->action_element :
      -1);
 
   int action_arg_number_old =
@@ -9528,15 +9448,14 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
                            action_arg_number_min, action_arg_number_max);
 
   int trigger_player_bits =
-    (change->actual_trigger_player >= EL_PLAYER_1 &&
-     change->actual_trigger_player <= EL_PLAYER_4 ?
-     (1 << (change->actual_trigger_player - EL_PLAYER_1)) :
-     PLAYER_BITS_ANY);
+    (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
+     change->actual_trigger_player_bits : change->trigger_player);
 
   int action_arg_player_bits =
     (action_arg >= CA_ARG_PLAYER_1 &&
      action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
      action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
+     action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
      PLAYER_BITS_ANY);
 
   /* ---------- execute action  -------------------------------------------- */
@@ -9574,13 +9493,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
       {
        TimeLeft = action_arg_number_new;
 
-#if 1
        game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
        DisplayGameControlValues();
-#else
-       DrawGameValue_Time(TimeLeft);
-#endif
 
        if (!TimeLeft && setup.time_limit)
          for (i = 0; i < MAX_PLAYERS; i++)
@@ -9594,13 +9509,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     {
       local_player->score = action_arg_number_new;
 
-#if 1
       game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
 
       DisplayGameControlValues();
-#else
-      DrawGameValue_Score(local_player->score);
-#endif
 
       break;
     }
@@ -9609,31 +9520,27 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     {
       local_player->gems_still_needed = action_arg_number_new;
 
-#if 1
-      game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
+      game.snapshot.collected_item = TRUE;
+
+      game_panel_controls[GAME_PANEL_GEMS].value =
+       local_player->gems_still_needed;
 
       DisplayGameControlValues();
-#else
-      DrawGameValue_Emeralds(local_player->gems_still_needed);
-#endif
 
       break;
     }
 
-#if !USE_PLAYER_GRAVITY
-    case CA_SET_LEVEL_GRAVITY:
+    case CA_SET_LEVEL_WIND:
     {
-      game.gravity = (action_arg == CA_ARG_GRAVITY_OFF    ? FALSE         :
-                     action_arg == CA_ARG_GRAVITY_ON     ? TRUE          :
-                     action_arg == CA_ARG_GRAVITY_TOGGLE ? !game.gravity :
-                     game.gravity);
+      game.wind_direction = action_arg_direction;
+
       break;
     }
-#endif
 
-    case CA_SET_LEVEL_WIND:
+    case CA_SET_LEVEL_RANDOM_SEED:
     {
-      game.wind_direction = action_arg_direction;
+      /* ensure that setting a new random seed while playing is predictable */
+      InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
 
       break;
     }
@@ -9755,7 +9662,6 @@ 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++)
@@ -9772,7 +9678,6 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
       break;
     }
-#endif
 
     case CA_SET_PLAYER_ARTWORK:
     {
@@ -9787,10 +9692,8 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
              (level.use_artwork_element[i] ? level.artwork_element[i] :
               stored_player[i].element_nr);
 
-#if USE_GFX_RESET_PLAYER_ARTWORK
          if (stored_player[i].artwork_element != artwork_element)
            stored_player[i].Frame = 0;
-#endif
 
          stored_player[i].artwork_element = artwork_element;
 
@@ -9809,11 +9712,108 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
       break;
     }
 
+    case CA_SET_PLAYER_INVENTORY:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int j, k;
+
+       if (trigger_player_bits & (1 << i))
+       {
+         int inventory_element = action_arg_element;
+
+         if (action_arg == CA_ARG_ELEMENT_TARGET ||
+             action_arg == CA_ARG_ELEMENT_TRIGGER ||
+             action_arg == CA_ARG_ELEMENT_ACTION)
+         {
+           int element = inventory_element;
+           int collect_count = element_info[element].collect_count_initial;
+
+           if (!IS_CUSTOM_ELEMENT(element))
+             collect_count = 1;
+
+           if (collect_count == 0)
+             player->inventory_infinite_element = element;
+           else
+             for (k = 0; k < collect_count; k++)
+               if (player->inventory_size < MAX_INVENTORY_SIZE)
+                 player->inventory_element[player->inventory_size++] =
+                   element;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
+                  action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
+                  action_arg == CA_ARG_INVENTORY_RM_ACTION)
+         {
+           if (player->inventory_infinite_element != EL_UNDEFINED &&
+               IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
+                                    action_arg_element_raw))
+             player->inventory_infinite_element = EL_UNDEFINED;
+
+           for (k = 0, j = 0; j < player->inventory_size; j++)
+           {
+             if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
+                                       action_arg_element_raw))
+               player->inventory_element[k++] = player->inventory_element[j];
+           }
+
+           player->inventory_size = k;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
+         {
+           if (player->inventory_size > 0)
+           {
+             for (j = 0; j < player->inventory_size - 1; j++)
+               player->inventory_element[j] = player->inventory_element[j + 1];
+
+             player->inventory_size--;
+           }
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
+         {
+           if (player->inventory_size > 0)
+             player->inventory_size--;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
+         {
+           player->inventory_infinite_element = EL_UNDEFINED;
+           player->inventory_size = 0;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RESET)
+         {
+           player->inventory_infinite_element = EL_UNDEFINED;
+           player->inventory_size = 0;
+
+           if (level.use_initial_inventory[i])
+           {
+             for (j = 0; j < level.initial_inventory_size[i]; j++)
+             {
+               int element = level.initial_inventory_content[i][j];
+               int collect_count = element_info[element].collect_count_initial;
+
+               if (!IS_CUSTOM_ELEMENT(element))
+                 collect_count = 1;
+
+               if (collect_count == 0)
+                 player->inventory_infinite_element = element;
+               else
+                 for (k = 0; k < collect_count; k++)
+                   if (player->inventory_size < MAX_INVENTORY_SIZE)
+                     player->inventory_element[player->inventory_size++] =
+                       element;
+             }
+           }
+         }
+       }
+      }
+
+      break;
+    }
+
     /* ---------- CE actions  ---------------------------------------------- */
 
     case CA_SET_CE_VALUE:
     {
-#if USE_NEW_CUSTOM_VALUE
       int last_ce_value = CustomValue[x][y];
 
       CustomValue[x][y] = action_arg_number_new;
@@ -9829,14 +9829,12 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
          CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
        }
       }
-#endif
 
       break;
     }
 
     case CA_SET_CE_SCORE:
     {
-#if USE_NEW_CUSTOM_VALUE
       int last_ce_score = ei->collect_score;
 
       ei->collect_score = action_arg_number_new;
@@ -9872,7 +9870,38 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
          }
        }
       }
-#endif
+
+      break;
+    }
+
+    case CA_SET_CE_ARTWORK:
+    {
+      int artwork_element = action_arg_element;
+      boolean reset_frame = FALSE;
+      int xx, yy;
+
+      if (action_arg == CA_ARG_ELEMENT_RESET)
+       artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
+                          element);
+
+      if (ei->gfx_element != artwork_element)
+       reset_frame = TRUE;
+
+      ei->gfx_element = artwork_element;
+
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       if (Feld[xx][yy] == element)
+       {
+         if (reset_frame)
+         {
+           ResetGfxAnimation(xx, yy);
+           ResetRandomAnimationValue(xx, yy);
+         }
+
+         TEST_DrawLevelField(xx, yy);
+       }
+      }
 
       break;
     }
@@ -9896,32 +9925,13 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
   int old_element = Feld[x][y];
   int new_element = GetElementFromGroupElement(element);
   int previous_move_direction = MovDir[x][y];
-#if USE_NEW_CUSTOM_VALUE
   int last_ce_value = CustomValue[x][y];
-#endif
   boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
   boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
   boolean add_player_onto_element = (new_element_is_player &&
-#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));
 
-#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(old_element) && !IS_ACCESSIBLE(new_element))
-  {
-    Bang(x, y);
-
-    return;
-  }
-#endif
-
   if (!add_player_onto_element)
   {
     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
@@ -9931,56 +9941,35 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
 
     Feld[x][y] = new_element;
 
-#if !USE_GFX_RESET_GFX_ANIMATION
-    ResetGfxAnimation(x, y);
-    ResetRandomAnimationValue(x, y);
-#endif
-
     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[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);
+    TEST_DrawLevelField(x, y);
 
     if (GFX_CRUMBLED(new_element))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
+      TEST_DrawLevelFieldCrumbledNeighbours(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 USE_FIX_KILLED_BY_NON_WALKABLE
-  if (IS_PLAYER(x, y) && !player_explosion_protected &&
-      IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
-  {
-    Bang(x, y);
-
-    return;
-  }
-#else
-  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
+  /* (must be checked after creating new element for walkable group elements) */
+  if (IS_PLAYER(x, y) && !player_explosion_protected &&
       IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
   {
     Bang(x, y);
 
     return;
   }
-#endif
-#endif
 
   /* "ChangeCount" not set yet to allow "entered by player" change one time */
   if (new_element_is_player)
@@ -10003,7 +9992,6 @@ static void CreateElementFromChange(int x, int y, int element)
 {
   element = GET_VALID_RUNTIME_ELEMENT(element);
 
-#if USE_STOP_CHANGED_ELEMENTS
   if (game.engine_version >= VERSION_IDENT(3,2,0,7))
   {
     int old_element = Feld[x][y];
@@ -10014,7 +10002,6 @@ static void CreateElementFromChange(int x, int y, int element)
        (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
       Stop[x][y] = TRUE;
   }
-#endif
 
   CreateFieldExt(x, y, element, TRUE);
 }
@@ -10036,7 +10023,8 @@ static boolean ChangeElement(int x, int y, int element, int page)
   {
     /* reset actual trigger element, trigger player and action element */
     change->actual_trigger_element = EL_EMPTY;
-    change->actual_trigger_player = EL_PLAYER_1;
+    change->actual_trigger_player = EL_EMPTY;
+    change->actual_trigger_player_bits = CH_PLAYER_NONE;
     change->actual_trigger_side = CH_SIDE_NONE;
     change->actual_trigger_ce_value = 0;
     change->actual_trigger_ce_score = 0;
@@ -10189,13 +10177,12 @@ static boolean ChangeElement(int x, int y, int element, int page)
   return TRUE;
 }
 
-#if USE_NEW_DELAYED_ACTION
-
 static void HandleElementChange(int x, int y, int page)
 {
   int element = MovingOrBlocked2Element(x, y);
   struct ElementInfo *ei = &element_info[element];
   struct ElementChangeInfo *change = &ei->change_page[page];
+  boolean handle_action_before_change = FALSE;
 
 #ifdef DEBUG
   if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
@@ -10212,11 +10199,6 @@ static void HandleElementChange(int x, int y, int page)
   /* this can happen with classic bombs on walkable, changing elements */
   if (!CAN_CHANGE_OR_HAS_ACTION(element))
   {
-#if 0
-    if (!CAN_CHANGE(Back[x][y]))       /* prevent permanent repetition */
-      ChangeDelay[x][y] = 0;
-#endif
-
     return;
   }
 
@@ -10226,19 +10208,56 @@ static void HandleElementChange(int x, int y, int page)
 
     if (change->can_change)
     {
-#if 1
       /* !!! not clear why graphic animation should be reset at all here !!! */
       /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
-#if USE_GFX_RESET_WHEN_NOT_MOVING
+      /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */
+
+      /*
+       GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
+
+       When using an animation frame delay of 1 (this only happens with
+       "sp_zonk.moving.left/right" in the classic graphics), the default
+       (non-moving) animation shows wrong animation frames (while the
+       moving animation, like "sp_zonk.moving.left/right", is correct,
+       so this graphical bug never shows up with the classic graphics).
+       For an animation with 4 frames, this causes wrong frames 0,0,1,2
+       be drawn instead of the correct frames 0,1,2,3. This is caused by
+       "GfxFrame[][]" being reset *twice* (in two successive frames) after
+       an element change: First when the change delay ("ChangeDelay[][]")
+       counter has reached zero after decrementing, then a second time in
+       the next frame (after "GfxFrame[][]" was already incremented) when
+       "ChangeDelay[][]" is reset to the initial delay value again.
+
+       This causes frame 0 to be drawn twice, while the last frame won't
+       be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
+
+       As some animations may already be cleverly designed around this bug
+       (at least the "Snake Bite" snake tail animation does this), it cannot
+       simply be fixed here without breaking such existing animations.
+       Unfortunately, it cannot easily be detected if a graphics set was
+       designed "before" or "after" the bug was fixed. As a workaround,
+       a new graphics set option "game.graphics_engine_version" was added
+       to be able to specify the game's major release version for which the
+       graphics set was designed, which can then be used to decide if the
+       bugfix should be used (version 4 and above) or not (version 3 or
+       below, or if no version was specified at all, as with old sets).
+
+       (The wrong/fixed animation frames can be tested with the test level set
+       "test_gfxframe" and level "000", which contains a specially prepared
+       custom element at level position (x/y) == (11/9) which uses the zonk
+       animation mentioned above. Using "game.graphics_engine_version: 4"
+       fixes the wrong animation frames, showing the correct frames 0,1,2,3.
+       This can also be seen from the debug output for this test element.)
+      */
+
       /* when a custom element is about to change (for example by change delay),
         do not reset graphic animation when the custom element is moving */
-      if (!IS_MOVING(x, y))
-#endif
+      if (game.graphics_engine_version < 4 &&
+         !IS_MOVING(x, y))
       {
        ResetGfxAnimation(x, y);
        ResetRandomAnimationValue(x, y);
       }
-#endif
 
       if (change->pre_change_function)
        change->pre_change_function(x, y);
@@ -10278,6 +10297,13 @@ static void HandleElementChange(int x, int y, int page)
       return;
     }
 
+    /* special case: set new level random seed before changing element */
+    if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
+      handle_action_before_change = TRUE;
+
+    if (change->has_action && handle_action_before_change)
+      ExecuteCustomElementAction(x, y, element, page);
+
     if (change->can_change)
     {
       if (ChangeElement(x, y, element, page))
@@ -10287,92 +10313,11 @@ static void HandleElementChange(int x, int y, int page)
       }
     }
 
-    if (change->has_action)
+    if (change->has_action && !handle_action_before_change)
       ExecuteCustomElementAction(x, y, element, page);
   }
 }
 
-#else
-
-static void HandleElementChange(int x, int y, int page)
-{
-  int element = MovingOrBlocked2Element(x, y);
-  struct ElementInfo *ei = &element_info[element];
-  struct ElementChangeInfo *change = &ei->change_page[page];
-
-#ifdef DEBUG
-  if (!CAN_CHANGE(element) && !CAN_CHANGE(Back[x][y]))
-  {
-    printf("\n\n");
-    printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
-          x, y, element, element_info[element].token_name);
-    printf("HandleElementChange(): This should never happen!\n");
-    printf("\n\n");
-  }
-#endif
-
-  /* this can happen with classic bombs on walkable, changing elements */
-  if (!CAN_CHANGE(element))
-  {
-#if 0
-    if (!CAN_CHANGE(Back[x][y]))       /* prevent permanent repetition */
-      ChangeDelay[x][y] = 0;
-#endif
-
-    return;
-  }
-
-  if (ChangeDelay[x][y] == 0)          /* initialize element change */
-  {
-    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
-
-    ResetGfxAnimation(x, y);
-    ResetRandomAnimationValue(x, y);
-
-    if (change->pre_change_function)
-      change->pre_change_function(x, y);
-  }
-
-  ChangeDelay[x][y]--;
-
-  if (ChangeDelay[x][y] != 0)          /* continue element change */
-  {
-    int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-
-    if (IS_ANIMATED(graphic))
-      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
-
-    if (change->change_function)
-      change->change_function(x, y);
-  }
-  else                                 /* finish element change */
-  {
-    if (ChangePage[x][y] != -1)                /* remember page from delayed change */
-    {
-      page = ChangePage[x][y];
-      ChangePage[x][y] = -1;
-
-      change = &ei->change_page[page];
-    }
-
-    if (IS_MOVING(x, y))               /* never change a running system ;-) */
-    {
-      ChangeDelay[x][y] = 1;           /* try change after next move step */
-      ChangePage[x][y] = page;         /* remember page to use for change */
-
-      return;
-    }
-
-    if (ChangeElement(x, y, element, page))
-    {
-      if (change->post_change_function)
-       change->post_change_function(x, y);
-    }
-  }
-}
-
-#endif
-
 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
                                              int trigger_element,
                                              int trigger_event,
@@ -10387,12 +10332,6 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
   if (!(trigger_events[trigger_element][trigger_event]))
     return FALSE;
 
-#if 0
-  printf("::: CheckTriggeredElementChangeExt %d ... [%d, %d, %d, '%s']\n",
-        trigger_event, recursion_loop_depth, recursion_loop_detected,
-        recursion_loop_element, EL_NAME(recursion_loop_element));
-#endif
-
   RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
@@ -10417,7 +10356,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
          IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
       {
        change->actual_trigger_element = trigger_element;
-       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+       change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
+       change->actual_trigger_player_bits = trigger_player;
        change->actual_trigger_side = trigger_side;
        change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
        change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
@@ -10432,24 +10372,32 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
            {
              if (change->can_change && !change_done)
              {
+               /* if element already changed in this frame, not only prevent
+                  another element change (checked in ChangeElement()), but
+                  also prevent additional element actions for this element */
+
+               if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+                   !level.use_action_after_change_bug)
+                 continue;
+
                ChangeDelay[x][y] = 1;
                ChangeEvent[x][y] = trigger_event;
 
                HandleElementChange(x, y, p);
              }
-#if USE_NEW_DELAYED_ACTION
              else if (change->has_action)
              {
+               /* if element already changed in this frame, not only prevent
+                  another element change (checked in ChangeElement()), but
+                  also prevent additional element actions for this element */
+
+               if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+                   !level.use_action_after_change_bug)
+                 continue;
+
                ExecuteCustomElementAction(x, y, element, p);
                PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
              }
-#else
-             if (change->has_action)
-             {
-               ExecuteCustomElementAction(x, y, element, p);
-               PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
-             }
-#endif
            }
          }
 
@@ -10488,11 +10436,6 @@ static boolean CheckElementChangeExt(int x, int y,
     element = Feld[x][y];
   }
 
-#if 0
-  /* check if element has already changed */
-  if (Feld[x][y] != element)
-    return FALSE;
-#else
   /* check if element has already changed or is about to change after moving */
   if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
        Feld[x][y] != element) ||
@@ -10501,13 +10444,6 @@ static boolean CheckElementChangeExt(int x, int y,
        (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
        ChangePage[x][y] != -1)))
     return FALSE;
-#endif
-
-#if 0
-  printf("::: CheckElementChangeExt %d ... [%d, %d, %d, '%s']\n",
-        trigger_event, recursion_loop_depth, recursion_loop_detected,
-        recursion_loop_element, EL_NAME(recursion_loop_element));
-#endif
 
   RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
 
@@ -10523,10 +10459,7 @@ static boolean CheckElementChangeExt(int x, int y,
       (trigger_event == CE_TOUCHING_X ||
        trigger_event == CE_HITTING_X ||
        trigger_event == CE_HIT_BY_X ||
-#if 1
-       /* this one was forgotten until 3.2.3 */
-       trigger_event == CE_DIGGING_X);
-#endif
+       trigger_event == CE_DIGGING_X); /* this one was forgotten until 3.2.3 */
 
     if (change->can_change_or_has_action &&
        change->has_event[trigger_event] &&
@@ -10536,7 +10469,8 @@ static boolean CheckElementChangeExt(int x, int y,
         IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
     {
       change->actual_trigger_element = trigger_element;
-      change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+      change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
+      change->actual_trigger_player_bits = trigger_player;
       change->actual_trigger_side = trigger_side;
       change->actual_trigger_ce_value = CustomValue[x][y];
       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
@@ -10574,19 +10508,11 @@ static boolean CheckElementChangeExt(int x, int y,
 
        change_done = TRUE;
       }
-#if USE_NEW_DELAYED_ACTION
       else if (change->has_action)
       {
        ExecuteCustomElementAction(x, y, element, p);
        PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
       }
-#else
-      if (change->has_action)
-      {
-       ExecuteCustomElementAction(x, y, element, p);
-       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
-      }
-#endif
     }
   }
 
@@ -10777,9 +10703,51 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
   }
 }
 
+static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
+{
+  if ((!player->is_moving  && player->was_moving) ||
+      (player->MovPos == 0 && player->was_moving) ||
+      (player->is_snapping && !player->was_snapping) ||
+      (player->is_dropping && !player->was_dropping))
+  {
+    if (!CheckSaveEngineSnapshotToList())
+      return;
+
+    player->was_moving = FALSE;
+    player->was_snapping = TRUE;
+    player->was_dropping = TRUE;
+  }
+  else
+  {
+    if (player->is_moving)
+      player->was_moving = TRUE;
+
+    if (!player->is_snapping)
+      player->was_snapping = FALSE;
+
+    if (!player->is_dropping)
+      player->was_dropping = FALSE;
+  }
+}
+
+static void CheckSingleStepMode(struct PlayerInfo *player)
+{
+  if (tape.single_step && tape.recording && !tape.pausing)
+  {
+    /* as it is called "single step mode", just return to pause mode when the
+       player stopped moving after one tile (or never starts moving at all) */
+    if (!player->is_moving && !player->is_pushing)
+    {
+      TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+      SnapField(player, 0, 0);                 /* stop snapping */
+    }
+  }
+
+  CheckSaveEngineSnapshot(player);
+}
+
 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 {
-  boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
   int left     = player_action & JOY_LEFT;
   int right    = player_action & JOY_RIGHT;
   int up       = player_action & JOY_UP;
@@ -10795,23 +10763,16 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
   if (player_action)
   {
     if (button1)
-      snapped = SnapField(player, dx, dy);
+      SnapField(player, dx, dy);
     else
     {
       if (button2)
-       dropped = DropElement(player);
+       DropElement(player);
 
-      moved = MovePlayer(player, dx, dy);
+      MovePlayer(player, dx, dy);
     }
 
-    if (tape.single_step && tape.recording && !tape.pausing)
-    {
-      if (button1 || (dropped && !moved))
-      {
-       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
-       SnapField(player, 0, 0);                /* stop snapping */
-      }
-    }
+    CheckSingleStepMode(player);
 
     SetPlayerWaiting(player, FALSE);
 
@@ -10835,6 +10796,8 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
     player->is_dropping_pressed = FALSE;
     player->drop_pressed_delay = 0;
 
+    CheckSingleStepMode(player);
+
     return 0;
   }
 }
@@ -10843,6 +10806,7 @@ static void CheckLevelTime()
 {
   int i;
 
+  /* !!! SAME CODE AS IN "GameActions()" -- FIX THIS !!! */
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     if (level.native_em_level->lev->home == 0) /* all players at home */
@@ -10860,6 +10824,21 @@ static void CheckLevelTime()
        level.native_em_level->ply[3]->alive == 0)      /* all dead */
       AllPlayersGone = TRUE;
   }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    if (game_sp.LevelSolved &&
+       !game_sp.GameOver)                              /* game won */
+    {
+      PlayerWins(local_player);
+
+      game_sp.GameOver = TRUE;
+
+      AllPlayersGone = TRUE;
+    }
+
+    if (game_sp.GameOver)                              /* game lost */
+      AllPlayersGone = TRUE;
+  }
 
   if (TimeFrames >= FRAMES_PER_SECOND)
   {
@@ -10890,13 +10869,10 @@ static void CheckLevelTime()
        if (TimeLeft <= 10 && setup.time_limit)
          PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
 
-#if 1
-       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+       /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
+          is reset from other values in UpdateGameDoorValues() -- FIX THIS */
 
-       DisplayGameControlValues();
-#else
-       DrawGameValue_Time(TimeLeft);
-#endif
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
        if (!TimeLeft && setup.time_limit)
        {
@@ -10907,28 +10883,23 @@ static void CheckLevelTime()
              KillPlayer(&stored_player[i]);
        }
       }
-#if 1
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+      else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
       {
        game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
-
-       DisplayGameControlValues();
       }
-#else
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
-       DrawGameValue_Time(TimePlayed);
-#endif
 
       level.native_em_level->lev->time =
-       (level.time == 0 ? TimePlayed : TimeLeft);
+       (game.no_time_limit ? TimePlayed : TimeLeft);
     }
 
     if (tape.recording || tape.playing)
       DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
   }
 
-  UpdateGameDoorValues();
-  DrawGameDoorValues();
+  if (tape.recording || tape.playing)
+    DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
+
+  UpdateAndDisplayGameControlValues();
 }
 
 void AdvanceFrameAndPlayerCounters(int player_nr)
@@ -10949,7 +10920,6 @@ void AdvanceFrameAndPlayerCounters(int player_nr)
     if (!advance_player_counters)      /* not all players may be affected */
       continue;
 
-#if USE_NEW_PLAYER_ANIM
     if (move_frames == 0)      /* less than one move per game frame */
     {
       int stepsize = TILEX / move_delay_value;
@@ -10960,7 +10930,6 @@ void AdvanceFrameAndPlayerCounters(int player_nr)
       if (count % delay == 0)
        move_frames = 1;
     }
-#endif
 
     stored_player[i].Frame += move_frames;
 
@@ -10983,9 +10952,9 @@ void AdvanceFrameAndPlayerCounters(int player_nr)
 }
 
 void StartGameActions(boolean init_network_game, boolean record_tape,
-                     long random_seed)
+                     int random_seed)
 {
-  unsigned long new_random_seed = InitRND(random_seed);
+  unsigned int new_random_seed = InitRND(random_seed);
 
   if (record_tape)
     TapeStartRecording(new_random_seed);
@@ -11002,10 +10971,12 @@ void StartGameActions(boolean init_network_game, boolean record_tape,
   InitGame();
 }
 
-void GameActions()
+void GameActionsExt()
 {
-  static unsigned long game_frame_delay = 0;
-  unsigned long game_frame_delay_value;
+#if 0
+  static unsigned int game_frame_delay = 0;
+#endif
+  unsigned int game_frame_delay_value;
   byte *recorded_player_action;
   byte summarized_player_action = 0;
   byte tape_action[MAX_PLAYERS];
@@ -11014,9 +10985,9 @@ void GameActions()
   /* detect endless loops, caused by custom element programming */
   if (recursion_loop_detected && recursion_loop_depth == 0)
   {
-    char *message = getStringCat3("Internal Error ! Element ",
+    char *message = getStringCat3("Internal Error! Element ",
                                  EL_NAME(recursion_loop_element),
-                                 " caused endless loop ! Quit the game ?");
+                                 " caused endless loop! Quit the game?");
 
     Error(ERR_WARN, "element '%s' caused endless loop in game engine",
          EL_NAME(recursion_loop_element));
@@ -11031,8 +11002,9 @@ void GameActions()
   }
 
   if (game.restart_level)
-    StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
+    StartGameActions(options.network, setup.autorecord, level.random_seed);
 
+  /* !!! SAME CODE AS IN "CheckLevelTime()" -- FIX THIS !!! */
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     if (level.native_em_level->lev->home == 0) /* all players at home */
@@ -11050,6 +11022,21 @@ void GameActions()
        level.native_em_level->ply[3]->alive == 0)      /* all dead */
       AllPlayersGone = TRUE;
   }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    if (game_sp.LevelSolved &&
+       !game_sp.GameOver)                              /* game won */
+    {
+      PlayerWins(local_player);
+
+      game_sp.GameOver = TRUE;
+
+      AllPlayersGone = TRUE;
+    }
+
+    if (game_sp.GameOver)                              /* game lost */
+      AllPlayersGone = TRUE;
+  }
 
   if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
     GameWon();
@@ -11066,9 +11053,22 @@ void GameActions()
   if (tape.playing && tape.warp_forward && !tape.pausing)
     game_frame_delay_value = 0;
 
+  SetVideoFrameDelay(game_frame_delay_value);
+
+#if 0
+#if 0
+  /* ---------- main game synchronization point ---------- */
+
+  int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+
+  printf("::: skip == %d\n", skip);
+
+#else
   /* ---------- main game synchronization point ---------- */
 
   WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+#endif
+#endif
 
   if (network_playing && !network_player_action_received)
   {
@@ -11099,11 +11099,9 @@ void GameActions()
   /* when playing tape, read previously recorded player input from tape data */
   recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
 
-#if 1
   /* TapePlayAction() may return NULL when toggling to "pause before death" */
   if (tape.pausing)
     return;
-#endif
 
   if (tape.set_centered_player)
   {
@@ -11115,7 +11113,7 @@ void GameActions()
   {
     summarized_player_action |= stored_player[i].action;
 
-    if (!network_playing)
+    if (!network_playing && (game.team_mode || tape.playing))
       stored_player[i].effective_action = stored_player[i].action;
   }
 
@@ -11124,41 +11122,150 @@ void GameActions()
     SendToServer_MovePlayer(summarized_player_action);
 #endif
 
-  if (!options.network && !setup.team_mode)
-    local_player->effective_action = summarized_player_action;
+  // summarize all actions at local players mapped input device position
+  // (this allows using different input devices in single player mode)
+  if (!options.network && !game.team_mode)
+    stored_player[map_player_action[local_player->index_nr]].effective_action =
+      summarized_player_action;
+
+  if (tape.recording &&
+      setup.team_mode &&
+      setup.input_on_focus &&
+      game.centered_player_nr != -1)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action =
+       (i == game.centered_player_nr ? summarized_player_action : 0);
+  }
+
+  if (recorded_player_action != NULL)
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action = recorded_player_action[i];
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    tape_action[i] = stored_player[i].effective_action;
+
+    /* (this may happen in the RND game engine if a player was not present on
+       the playfield on level start, but appeared later from a custom element */
+    if (setup.team_mode &&
+       tape.recording &&
+       tape_action[i] &&
+       !tape.player_participates[i])
+      tape.player_participates[i] = TRUE;
+  }
+
+  /* only record actions from input devices, but not programmed actions */
+  if (tape.recording)
+    TapeRecordAction(tape_action);
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+  // !!! also map player actions in single player mode !!!
+  // if (game.team_mode)
+  if (1)
+  {
+    byte mapped_action[MAX_PLAYERS];
+
+#if DEBUG_PLAYER_ACTIONS
+    printf(":::");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+#endif
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      mapped_action[i] = stored_player[map_player_action[i]].effective_action;
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action = mapped_action[i];
+
+#if DEBUG_PLAYER_ACTIONS
+    printf(" =>");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+    printf("\n");
+#endif
+  }
+#if DEBUG_PLAYER_ACTIONS
+  else
+  {
+    printf(":::");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+    printf("\n");
+  }
+#endif
+#endif
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    // allow engine snapshot in case of changed movement attempt
+    if ((game.snapshot.last_action[i] & KEY_MOTION) !=
+       (stored_player[i].effective_action & KEY_MOTION))
+      game.snapshot.changed_action = TRUE;
+
+    // allow engine snapshot in case of snapping/dropping attempt
+    if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
+       (stored_player[i].effective_action & KEY_BUTTON) != 0)
+      game.snapshot.changed_action = TRUE;
+
+    game.snapshot.last_action[i] = stored_player[i].effective_action;
+  }
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    GameActions_EM_Main();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    GameActions_SP_Main();
+  }
+  else
+  {
+    GameActions_RND_Main();
+  }
+
+  BlitScreenToBitmap(backbuffer);
+
+  CheckLevelTime();
+
+  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+
+  if (options.debug)                   /* calculate frames per second */
+  {
+    static unsigned int fps_counter = 0;
+    static int fps_frames = 0;
+    unsigned int fps_delay_ms = Counter() - fps_counter;
+
+    fps_frames++;
+
+    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
+    {
+      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
+
+      fps_frames = 0;
+      fps_counter = Counter();
+    }
 
-  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);
+    redraw_mask |= REDRAW_FPS;
   }
+}
 
-  if (recorded_player_action != NULL)
-    for (i = 0; i < MAX_PLAYERS; i++)
-      stored_player[i].effective_action = recorded_player_action[i];
+static void GameActions_CheckSaveEngineSnapshot()
+{
+  if (!game.snapshot.save_snapshot)
+    return;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    tape_action[i] = stored_player[i].effective_action;
+  // clear flag for saving snapshot _before_ saving snapshot
+  game.snapshot.save_snapshot = FALSE;
 
-    /* (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 */
-  }
+  SaveEngineSnapshotToList();
+}
 
-  /* only record actions from input devices, but not programmed actions */
-  if (tape.recording)
-    TapeRecordAction(tape_action);
+void GameActions()
+{
+  GameActionsExt();
 
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-  {
-    GameActions_EM_Main();
-  }
-  else
-  {
-    GameActions_RND();
-  }
+  GameActions_CheckSaveEngineSnapshot();
 }
 
 void GameActions_EM_Main()
@@ -11171,10 +11278,23 @@ void GameActions_EM_Main()
     effective_action[i] = stored_player[i].effective_action;
 
   GameActions_EM(effective_action, warp_mode);
+}
 
-  CheckLevelTime();
+void GameActions_SP_Main()
+{
+  byte effective_action[MAX_PLAYERS];
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+  int i;
 
-  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
+
+  GameActions_SP(effective_action, warp_mode);
+}
+
+void GameActions_RND_Main()
+{
+  GameActions_RND();
 }
 
 void GameActions_RND()
@@ -11184,7 +11304,6 @@ void GameActions_RND()
 
   InitPlayfieldScanModeVars();
 
-#if USE_ONE_MORE_CHANGE_PER_FRAME
   if (game.engine_version >= VERSION_IDENT(3,2,0,7))
   {
     SCAN_PLAYFIELD(x, y)
@@ -11193,7 +11312,6 @@ void GameActions_RND()
       ChangeEvent[x][y] = -1;
     }
   }
-#endif
 
   if (game.set_centered_player)
   {
@@ -11293,10 +11411,6 @@ void GameActions_RND()
     }
   }
 
-#if 0
-  debug_print_timestamp(0, "start main loop profiling");
-#endif
-
   SCAN_PLAYFIELD(x, y)
   {
     ChangeCount[x][y] = 0;
@@ -11310,19 +11424,17 @@ void GameActions_RND()
        RemoveField(x, y);
     }
 
-#if USE_NEW_SNAP_DELAY
     if (Feld[x][y] == EL_ELEMENT_SNAPPING)
     {
       MovDelay[x][y]--;
       if (MovDelay[x][y] <= 0)
       {
        RemoveField(x, y);
-       DrawLevelField(x, y);
+       TEST_DrawLevelField(x, y);
 
        TestIfElementTouchesCustomElement(x, y);        /* for empty space */
       }
     }
-#endif
 
 #if DEBUG
     if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
@@ -11351,7 +11463,7 @@ void GameActions_RND()
     if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
     {
       ResetGfxAnimation(x, y);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
 
 #if DEBUG
@@ -11371,63 +11483,6 @@ void GameActions_RND()
 #endif
   }
 
-#if 0
-  debug_print_timestamp(0, "- time for pre-main loop:");
-#endif
-
-#if 0  // -------------------- !!! TEST ONLY !!! --------------------
-  SCAN_PLAYFIELD(x, y)
-  {
-    element = Feld[x][y];
-    graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-
-#if 1
-    {
-#if 1
-      int element2 = element;
-      int graphic2 = graphic;
-#else
-      int element2 = Feld[x][y];
-      int graphic2 = el_act_dir2img(element2, GfxAction[x][y], GfxDir[x][y]);
-#endif
-      int last_gfx_frame = GfxFrame[x][y];
-
-      if (graphic_info[graphic2].anim_global_sync)
-       GfxFrame[x][y] = FrameCounter;
-      else if (ANIM_MODE(graphic2) == ANIM_CE_VALUE)
-       GfxFrame[x][y] = CustomValue[x][y];
-      else if (ANIM_MODE(graphic2) == ANIM_CE_SCORE)
-       GfxFrame[x][y] = element_info[element2].collect_score;
-      else if (ANIM_MODE(graphic2) == ANIM_CE_DELAY)
-       GfxFrame[x][y] = ChangeDelay[x][y];
-
-      if (redraw && GfxFrame[x][y] != last_gfx_frame)
-       DrawLevelGraphicAnimation(x, y, graphic2);
-    }
-#else
-    ResetGfxFrame(x, y, TRUE);
-#endif
-
-#if 1
-    if (ANIM_MODE(graphic) == ANIM_RANDOM &&
-       IS_NEXT_FRAME(GfxFrame[x][y], graphic))
-      ResetRandomAnimationValue(x, y);
-#endif
-
-#if 1
-    SetRandomAnimationValue(x, y);
-#endif
-
-#if 1
-    PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
-#endif
-  }
-#endif // -------------------- !!! TEST ONLY !!! --------------------
-
-#if 0
-  debug_print_timestamp(0, "- time for TEST loop:     -->");
-#endif
-
   SCAN_PLAYFIELD(x, y)
   {
     element = Feld[x][y];
@@ -11457,157 +11512,12 @@ void GameActions_RND()
     {
       int page = element_info[element].event_page_nr[CE_DELAY];
 
-#if 1
       HandleElementChange(x, y, page);
-#else
-      if (CAN_CHANGE(element))
-       HandleElementChange(x, y, page);
-
-      if (HAS_ACTION(element))
-       ExecuteCustomElementAction(x, y, element, page);
-#endif
-
-      element = Feld[x][y];
-      graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-    }
-
-#if 0  // ---------------------------------------------------------------------
-
-    if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
-    {
-      StartMoving(x, y);
 
       element = Feld[x][y];
       graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-
-      if (IS_ANIMATED(graphic) &&
-         !IS_MOVING(x, y) &&
-         !Stop[x][y])
-       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
-
-      if (IS_GEM(element) || element == EL_SP_INFOTRON)
-       DrawTwinkleOnField(x, y);
-    }
-    else if (IS_MOVING(x, y))
-      ContinueMoving(x, y);
-    else
-    {
-      switch (element)
-      {
-        case EL_ACID:
-        case EL_EXIT_OPEN:
-        case EL_EM_EXIT_OPEN:
-        case EL_SP_EXIT_OPEN:
-        case EL_STEEL_EXIT_OPEN:
-        case EL_EM_STEEL_EXIT_OPEN:
-        case EL_SP_TERMINAL:
-        case EL_SP_TERMINAL_ACTIVE:
-        case EL_EXTRA_TIME:
-        case EL_SHIELD_NORMAL:
-        case EL_SHIELD_DEADLY:
-         if (IS_ANIMATED(graphic))
-           DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
-         break;
-
-        case EL_DYNAMITE_ACTIVE:
-        case EL_EM_DYNAMITE_ACTIVE:
-        case EL_DYNABOMB_PLAYER_1_ACTIVE:
-        case EL_DYNABOMB_PLAYER_2_ACTIVE:
-        case EL_DYNABOMB_PLAYER_3_ACTIVE:
-        case EL_DYNABOMB_PLAYER_4_ACTIVE:
-        case EL_SP_DISK_RED_ACTIVE:
-         CheckDynamite(x, y);
-         break;
-
-        case EL_AMOEBA_GROWING:
-         AmoebeWaechst(x, y);
-         break;
-
-        case EL_AMOEBA_SHRINKING:
-         AmoebaDisappearing(x, y);
-         break;
-
-#if !USE_NEW_AMOEBA_CODE
-        case EL_AMOEBA_WET:
-        case EL_AMOEBA_DRY:
-        case EL_AMOEBA_FULL:
-        case EL_BD_AMOEBA:
-        case EL_EMC_DRIPPER:
-         AmoebeAbleger(x, y);
-         break;
-#endif
-
-        case EL_GAME_OF_LIFE:
-        case EL_BIOMAZE:
-         Life(x, y);
-         break;
-
-        case EL_EXIT_CLOSED:
-         CheckExit(x, y);
-         break;
-
-        case EL_EM_EXIT_CLOSED:
-         CheckExitEM(x, y);
-         break;
-
-        case EL_STEEL_EXIT_CLOSED:
-         CheckExitSteel(x, y);
-         break;
-
-        case EL_EM_STEEL_EXIT_CLOSED:
-         CheckExitSteelEM(x, y);
-         break;
-
-        case EL_SP_EXIT_CLOSED:
-         CheckExitSP(x, y);
-         break;
-
-        case EL_EXPANDABLE_WALL_GROWING:
-        case EL_EXPANDABLE_STEELWALL_GROWING:
-         MauerWaechst(x, y);
-         break;
-
-        case EL_EXPANDABLE_WALL:
-        case EL_EXPANDABLE_WALL_HORIZONTAL:
-        case EL_EXPANDABLE_WALL_VERTICAL:
-        case EL_EXPANDABLE_WALL_ANY:
-        case EL_BD_EXPANDABLE_WALL:
-         MauerAbleger(x, y);
-         break;
-
-        case EL_EXPANDABLE_STEELWALL_HORIZONTAL:
-        case EL_EXPANDABLE_STEELWALL_VERTICAL:
-        case EL_EXPANDABLE_STEELWALL_ANY:
-         MauerAblegerStahl(x, y);
-         break;
-
-        case EL_FLAMES:
-         CheckForDragon(x, y);
-         break;
-
-        case EL_EXPLOSION:
-         break;
-
-        case EL_ELEMENT_SNAPPING:
-        case EL_DIAGONAL_SHRINKING:
-        case EL_DIAGONAL_GROWING:
-       {
-         graphic =
-           el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
-
-         DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
-         break;
-       }
-
-        default:
-         if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
-           DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
-         break;
-      }
     }
 
-#else  // ---------------------------------------------------------------------
-
     if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
     {
       StartMoving(x, y);
@@ -11621,7 +11531,7 @@ void GameActions_RND()
        DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
       if (IS_GEM(element) || element == EL_SP_INFOTRON)
-       DrawTwinkleOnField(x, y);
+       TEST_DrawTwinkleOnField(x, y);
     }
     else if ((element == EL_ACID ||
              element == EL_EXIT_OPEN ||
@@ -11690,8 +11600,6 @@ void GameActions_RND()
     else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-#endif // ---------------------------------------------------------------------
-
     if (IS_BELT_ACTIVE(element))
       PlayLevelSoundAction(x, y, ACTION_ACTIVE);
 
@@ -11717,15 +11625,11 @@ void GameActions_RND()
     }
   }
 
-#if 0
-  debug_print_timestamp(0, "- time for MAIN loop:     -->");
-#endif
-
 #if USE_NEW_AMOEBA_CODE
   /* new experimental amoeba growth stuff */
   if (!(FrameCounter % 8))
   {
-    static unsigned long random = 1684108901;
+    static unsigned int random = 1684108901;
 
     for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
     {
@@ -11753,27 +11657,22 @@ void GameActions_RND()
   }
 #endif
 
-#if 0
-  if (game.explosions_delayed)
-#endif
-  {
-    game.explosions_delayed = FALSE;
-
-    SCAN_PLAYFIELD(x, y)
-    {
-      element = Feld[x][y];
+  game.explosions_delayed = FALSE;
 
-      if (ExplodeField[x][y])
-       Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
-      else if (element == EL_EXPLOSION)
-       Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
+  SCAN_PLAYFIELD(x, y)
+  {
+    element = Feld[x][y];
 
-      ExplodeField[x][y] = EX_TYPE_NONE;
-    }
+    if (ExplodeField[x][y])
+      Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
+    else if (element == EL_EXPLOSION)
+      Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
 
-    game.explosions_delayed = TRUE;
+    ExplodeField[x][y] = EX_TYPE_NONE;
   }
 
+  game.explosions_delayed = TRUE;
+
   if (game.magic_wall_active)
   {
     if (!(game.magic_wall_time_left % 4))
@@ -11806,19 +11705,19 @@ void GameActions_RND()
              element == EL_MAGIC_WALL_FULL)
          {
            Feld[x][y] = EL_MAGIC_WALL_DEAD;
-           DrawLevelField(x, y);
+           TEST_DrawLevelField(x, y);
          }
          else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
                   element == EL_BD_MAGIC_WALL_FULL)
          {
            Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
-           DrawLevelField(x, y);
+           TEST_DrawLevelField(x, y);
          }
          else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
                   element == EL_DC_MAGIC_WALL_FULL)
          {
            Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
-           DrawLevelField(x, y);
+           TEST_DrawLevelField(x, y);
          }
        }
 
@@ -11872,31 +11771,33 @@ void GameActions_RND()
     }
   }
 
-  CheckLevelTime();
-
-  DrawAllPlayers();
-  PlayAllPlayersSound();
-
-  if (options.debug)                   /* calculate frames per second */
+#if USE_DELAYED_GFX_REDRAW
+  SCAN_PLAYFIELD(x, y)
   {
-    static unsigned long fps_counter = 0;
-    static int fps_frames = 0;
-    unsigned long fps_delay_ms = Counter() - fps_counter;
+    if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
+    {
+      /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
+        !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
 
-    fps_frames++;
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
+       DrawLevelField(x, y);
 
-    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
-    {
-      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
+       DrawLevelFieldCrumbled(x, y);
 
-      fps_frames = 0;
-      fps_counter = Counter();
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
+       DrawLevelFieldCrumbledNeighbours(x, y);
+
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
+       DrawTwinkleOnField(x, y);
     }
 
-    redraw_mask |= REDRAW_FPS;
+    GfxRedraw[x][y] = GFX_REDRAW_NONE;
   }
+#endif
 
-  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+  DrawAllPlayers();
+  PlayAllPlayersSound();
 
   if (local_player->show_envelope != 0 && local_player->MovPos == 0)
   {
@@ -11905,11 +11806,6 @@ void GameActions_RND()
     local_player->show_envelope = 0;
   }
 
-#if 0
-  debug_print_timestamp(0, "stop main loop profiling ");
-  printf("----------------------------------------------------------\n");
-#endif
-
   /* use random number generator in every frame to make it less predictable */
   if (game.engine_version >= VERSION_IDENT(3,1,1,0))
     RND(1);
@@ -11956,76 +11852,16 @@ static boolean AllPlayersInVisibleScreen()
 
 void ScrollLevel(int dx, int dy)
 {
-#if 1
-  static Bitmap *bitmap_db_field2 = NULL;
-  int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
+  int scroll_offset = 2 * TILEX_VAR;
   int x, y;
-#else
-  int i, x, y;
-#endif
-
-#if 0
-  /* !!! THIS IS APPARENTLY WRONG FOR PLAYER RELOCATION !!! */
-  /* only horizontal XOR vertical scroll direction allowed */
-  if ((dx == 0 && dy == 0) || (dx != 0 && dy != 0))
-    return;
-#endif
-
-#if 1
-  if (bitmap_db_field2 == NULL)
-    bitmap_db_field2 = CreateBitmap(FXSIZE, FYSIZE, DEFAULT_DEPTH);
-
-  /* needed when blitting directly to same bitmap -- should not be needed with
-     recent SDL libraries, but apparently does not work in 1.2.11 directly */
-  BlitBitmap(drawto_field, bitmap_db_field2,
-            FX + TILEX * (dx == -1) - softscroll_offset,
-            FY + TILEY * (dy == -1) - softscroll_offset,
-            SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset,
-            SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset,
-            FX + TILEX * (dx == 1) - softscroll_offset,
-            FY + TILEY * (dy == 1) - softscroll_offset);
-  BlitBitmap(bitmap_db_field2, drawto_field,
-            FX + TILEX * (dx == 1) - softscroll_offset,
-            FY + TILEY * (dy == 1) - softscroll_offset,
-            SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset,
-            SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset,
-            FX + TILEX * (dx == 1) - softscroll_offset,
-            FY + TILEY * (dy == 1) - softscroll_offset);
-
-#else
-
-#if 1
-  /* !!! DOES NOT WORK FOR DIAGONAL PLAYER RELOCATION !!! */
-  int xsize = (BX2 - BX1 + 1);
-  int ysize = (BY2 - BY1 + 1);
-  int start = (dx != 0 ? (dx == -1 ? BX1 : BX2) : (dy == -1 ? BY1 : BY2));
-  int end   = (dx != 0 ? (dx == -1 ? BX2 : BX1) : (dy == -1 ? BY2 : BY1));
-  int step  = (start < end ? +1 : -1);
-
-  for (i = start; i != end; i += step)
-  {
-    BlitBitmap(drawto_field, drawto_field,
-              FX + TILEX * (dx != 0 ? i + step : 0),
-              FY + TILEY * (dy != 0 ? i + step : 0),
-              TILEX * (dx != 0 ? 1 : xsize),
-              TILEY * (dy != 0 ? 1 : ysize),
-              FX + TILEX * (dx != 0 ? i : 0),
-              FY + TILEY * (dy != 0 ? i : 0));
-  }
-
-#else
-
-  int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
 
   BlitBitmap(drawto_field, drawto_field,
-            FX + TILEX * (dx == -1) - softscroll_offset,
-            FY + TILEY * (dy == -1) - softscroll_offset,
-            SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset,
-            SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset,
-            FX + TILEX * (dx == 1) - softscroll_offset,
-            FY + TILEY * (dy == 1) - softscroll_offset);
-#endif
-#endif
+            FX + TILEX_VAR * (dx == -1) - scroll_offset,
+            FY + TILEY_VAR * (dy == -1) - scroll_offset,
+            SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
+            SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
+            FX + TILEX_VAR * (dx == 1) - scroll_offset,
+            FY + TILEY_VAR * (dy == 1) - scroll_offset);
 
   if (dx != 0)
   {
@@ -12088,11 +11924,7 @@ 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;
@@ -12114,11 +11946,7 @@ 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 =
@@ -12145,9 +11973,6 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 {
   int jx = player->jx, jy = player->jy;
   int new_jx = jx + dx, new_jy = jy + dy;
-#if !USE_FIXED_DONT_RUN_INTO
-  int element;
-#endif
   int can_move;
   boolean player_can_move = !player->cannot_move;
 
@@ -12174,36 +11999,9 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
     }
   }
 
-#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);
-
-  /* (moved to DigField()) */
-  if (player_can_move && DONT_RUN_INTO(element))
-  {
-    if (element == EL_ACID && dx == 0 && dy == 1)
-    {
-      SplashAcid(new_jx, new_jy);
-      Feld[jx][jy] = EL_PLAYER_1;
-      InitMovingField(jx, jy, MV_DOWN);
-      Store[jx][jy] = EL_ACID;
-      ContinueMoving(jx, jy);
-      BuryPlayer(player);
-    }
-    else
-      TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
-
-    return MP_MOVING;
-  }
-#endif
 
   can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
   if (can_move != MP_MOVING)
@@ -12233,9 +12031,7 @@ 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 */
@@ -12287,7 +12083,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     int original_move_delay_value = player->move_delay_value;
 
 #if DEBUG
-    printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%ld]\n",
+    printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
           tape.counter);
 #endif
 
@@ -12302,7 +12098,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       AdvanceFrameAndPlayerCounters(player->index_nr);
 
       DrawAllPlayers();
-      BackToFront();
+      BackToFront_WithFrameDelay(0);
     }
 
     player->move_delay_value = original_move_delay_value;
@@ -12321,7 +12117,6 @@ 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;
@@ -12330,19 +12125,13 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     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
+      (player->index_nr == game.centered_player_nr ||
+       game.centered_player_nr == -1))
   {
     int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
     int offset = game.scroll_delay_value;
@@ -12397,7 +12186,6 @@ 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())
       {
@@ -12405,14 +12193,6 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
        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);
@@ -12429,7 +12209,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     else if (old_jx == jx && old_jy != jy)
       player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
 
-    DrawLevelField(jx, jy);    /* for "crumbled sand" */
+    TEST_DrawLevelField(jx, jy);       /* for "crumbled sand" */
 
     player->last_move_dir = player->MovDir;
     player->is_moving = TRUE;
@@ -12480,16 +12260,11 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   int last_jx = player->last_jx, last_jy = player->last_jy;
   int move_stepsize = TILEX / player->move_delay_value;
 
-#if USE_NEW_PLAYER_SPEED
   if (!player->active)
     return;
 
   if (player->MovPos == 0 && mode == SCROLL_GO_ON)     /* player not moving */
     return;
-#else
-  if (!player->active || player->MovPos == 0)
-    return;
-#endif
 
   if (mode == SCROLL_INIT)
   {
@@ -12508,13 +12283,8 @@ 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) */
@@ -12524,17 +12294,12 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
       MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
     }
 
-#if USE_NEW_PLAYER_SPEED
     if (player->MovPos != 0)   /* player has not yet reached destination */
       return;
-#else
-    return;
-#endif
   }
   else if (!FrameReached(&player->actual_frame_counter, 1))
     return;
 
-#if USE_NEW_PLAYER_SPEED
   if (player->MovPos != 0)
   {
     player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
@@ -12544,14 +12309,6 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     if (player->MovPos == 0)
       CheckGravityMovement(player);
   }
-#else
-  player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
-  player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
-
-  /* before DrawPlayer() to draw correct player graphic for this case */
-  if (player->MovPos == 0)
-    CheckGravityMovement(player);
-#endif
 
   if (player->MovPos == 0)     /* player reached destination field */
   {
@@ -12574,8 +12331,10 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
 
     if (Feld[jx][jy] == EL_EXIT_OPEN ||
        Feld[jx][jy] == EL_EM_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_EXIT_OPENING ||
        Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
        Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
        Feld[jx][jy] == EL_SP_EXIT_OPEN ||
        Feld[jx][jy] == EL_SP_EXIT_OPENING)     /* <-- special case */
     {
@@ -12614,7 +12373,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
                                          CE_PLAYER_ENTERS_X,
                                          player->index_bit, enter_side);
 
-      CheckTriggeredElementChangeBySide(jx, jy, player->element_nr,
+      CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
                                        CE_MOVE_OF_X, move_direction);
     }
 
@@ -12645,40 +12404,34 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        if (TimeLeft <= 10 && setup.time_limit)
          PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
 
-#if 1
        game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
        DisplayGameControlValues();
-#else
-       DrawGameValue_Time(TimeLeft);
-#endif
 
        if (!TimeLeft && setup.time_limit)
          for (i = 0; i < MAX_PLAYERS; i++)
            KillPlayer(&stored_player[i]);
       }
-#if 1
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+      else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
       {
        game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
 
        DisplayGameControlValues();
       }
-#else
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
-       DrawGameValue_Time(TimePlayed);
-#endif
     }
 
     if (tape.single_step && tape.recording && !tape.pausing &&
        !player->programmed_action)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
+    if (!player->programmed_action)
+      CheckSaveEngineSnapshot(player);
   }
 }
 
 void ScrollScreen(struct PlayerInfo *player, int mode)
 {
-  static unsigned long screen_frame_counter = 0;
+  static unsigned int screen_frame_counter = 0;
 
   if (mode == SCROLL_INIT)
   {
@@ -12742,7 +12495,7 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
     if (!IN_LEV_FIELD(xx, yy))
       continue;
 
-    if (IS_PLAYER(x, y))
+    if (IS_PLAYER(x, y))               /* player found at center element */
     {
       struct PlayerInfo *player = PLAYERINFO(x, y);
 
@@ -12760,8 +12513,19 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
       CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
                                          CE_PLAYER_TOUCHES_X,
                                          player->index_bit, border_side);
+
+      {
+       /* use player element that is initially defined in the level playfield,
+          not the player element that corresponds to the runtime player number
+          (example: a level that contains EL_PLAYER_3 as the only player would
+          incorrectly give EL_PLAYER_1 for "player->element_nr") */
+       int player_element = PLAYERINFO(x, y)->initial_element;
+
+       CheckElementChangeBySide(xx, yy, border_element, player_element,
+                                CE_TOUCHING_X, border_side);
+      }
     }
-    else if (IS_PLAYER(xx, yy))
+    else if (IS_PLAYER(xx, yy))                /* player found at border element */
     {
       struct PlayerInfo *player = PLAYERINFO(xx, yy);
 
@@ -12776,13 +12540,23 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
       CheckTriggeredElementChangeByPlayer(x, y, center_element,
                                          CE_PLAYER_TOUCHES_X,
                                          player->index_bit, center_side);
+
+      {
+       /* use player element that is initially defined in the level playfield,
+          not the player element that corresponds to the runtime player number
+          (example: a level that contains EL_PLAYER_3 as the only player would
+          incorrectly give EL_PLAYER_1 for "player->element_nr") */
+       int player_element = PLAYERINFO(xx, yy)->initial_element;
+
+       CheckElementChangeBySide(x, y, center_element, player_element,
+                                CE_TOUCHING_X, center_side);
+      }
+
       break;
     }
   }
 }
 
-#if USE_ELEMENT_TOUCHING_BUGFIX
-
 void TestIfElementTouchesCustomElement(int x, int y)
 {
   static int xy[4][2] =
@@ -12848,88 +12622,40 @@ void TestIfElementTouchesCustomElement(int x, int y)
     /* check for change of border element */
     CheckElementChangeBySide(xx, yy, border_element, center_element,
                             CE_TOUCHING_X, center_side);
-  }
-
-  for (i = 0; i < NUM_DIRECTIONS; i++)
-  {
-    int border_side = trigger_sides[i][1];
-    int border_element = border_element_old[i];
-
-    if (border_element == -1)
-      continue;
 
-    /* check for change of center element (but change it only once) */
-    if (!change_center_element)
-      change_center_element =
-       CheckElementChangeBySide(x, y, center_element, border_element,
-                                CE_TOUCHING_X, border_side);
+    /* (center element cannot be player, so we dont have to check this here) */
   }
-}
-
-#else
-
-void TestIfElementTouchesCustomElement_OLD(int x, int y)
-{
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
-  static int trigger_sides[4][2] =
-  {
-    /* center side     border side */
-    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      /* check top    */
-    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* check left   */
-    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* check right  */
-    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     }       /* check bottom */
-  };
-  static int touch_dir[4] =
-  {
-    MV_LEFT | MV_RIGHT,
-    MV_UP   | MV_DOWN,
-    MV_UP   | MV_DOWN,
-    MV_LEFT | MV_RIGHT
-  };
-  boolean change_center_element = FALSE;
-  int center_element = Feld[x][y];     /* should always be non-moving! */
-  int i;
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int xx = x + xy[i][0];
     int yy = y + xy[i][1];
-    int center_side = trigger_sides[i][0];
     int border_side = trigger_sides[i][1];
-    int border_element;
+    int border_element = border_element_old[i];
 
-    if (!IN_LEV_FIELD(xx, yy))
+    if (border_element == -1)
       continue;
 
-    if (game.engine_version < VERSION_IDENT(3,0,7,0))
-      border_element = Feld[xx][yy];   /* may be moving! */
-    else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
-      border_element = Feld[xx][yy];
-    else if (MovDir[xx][yy] & touch_dir[i])    /* elements are touching */
-      border_element = MovingOrBlocked2Element(xx, yy);
-    else
-      continue;                        /* center and border element do not touch */
-
     /* check for change of center element (but change it only once) */
     if (!change_center_element)
       change_center_element =
        CheckElementChangeBySide(x, y, center_element, border_element,
                                 CE_TOUCHING_X, border_side);
 
-    /* check for change of border element */
-    CheckElementChangeBySide(xx, yy, border_element, center_element,
-                            CE_TOUCHING_X, center_side);
+    if (IS_PLAYER(xx, yy))
+    {
+      /* use player element that is initially defined in the level playfield,
+        not the player element that corresponds to the runtime player number
+        (example: a level that contains EL_PLAYER_3 as the only player would
+        incorrectly give EL_PLAYER_1 for "player->element_nr") */
+      int player_element = PLAYERINFO(xx, yy)->initial_element;
+
+      CheckElementChangeBySide(x, y, center_element, player_element,
+                              CE_TOUCHING_X, border_side);
+    }
   }
 }
 
-#endif
-
 void TestIfElementHitsCustomElement(int x, int y, int direction)
 {
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
@@ -12960,11 +12686,23 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
       CheckElementChangeBySide(x, y, hitting_element, touched_element,
                               CE_HITTING_X, touched_side);
 
-      CheckElementChangeBySide(hitx, hity, touched_element,
-                              hitting_element, CE_HIT_BY_X, hitting_side);
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_HIT_BY_X, hitting_side);
 
       CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
                               CE_HIT_BY_SOMETHING, opposite_direction);
+
+      if (IS_PLAYER(hitx, hity))
+      {
+       /* use player element that is initially defined in the level playfield,
+          not the player element that corresponds to the runtime player number
+          (example: a level that contains EL_PLAYER_3 as the only player would
+          incorrectly give EL_PLAYER_1 for "player->element_nr") */
+       int player_element = PLAYERINFO(hitx, hity)->initial_element;
+
+       CheckElementChangeBySide(x, y, hitting_element, player_element,
+                                CE_HITTING_X, touched_side);
+      }
     }
   }
 
@@ -12973,69 +12711,6 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
                           CE_HITTING_SOMETHING, direction);
 }
 
-#if 0
-void TestIfElementSmashesCustomElement(int x, int y, int direction)
-{
-  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-  int hitx = x + dx, hity = y + dy;
-  int hitting_element = Feld[x][y];
-  int touched_element;
-#if 0
-  boolean object_hit = (IN_LEV_FIELD(hitx, hity) &&
-                       !IS_FREE(hitx, hity) &&
-                       (!IS_MOVING(hitx, hity) ||
-                        MovDir[hitx][hity] != direction ||
-                        ABS(MovPos[hitx][hity]) <= TILEY / 2));
-#endif
-
-  if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
-    return;
-
-#if 0
-  if (IN_LEV_FIELD(hitx, hity) && !object_hit)
-    return;
-#endif
-
-  touched_element = (IN_LEV_FIELD(hitx, hity) ?
-                    MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
-
-  CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                          EP_CAN_SMASH_EVERYTHING, direction);
-
-  if (IN_LEV_FIELD(hitx, hity))
-  {
-    int opposite_direction = MV_DIR_OPPOSITE(direction);
-    int hitting_side = direction;
-    int touched_side = opposite_direction;
-#if 0
-    int touched_element = MovingOrBlocked2Element(hitx, hity);
-#endif
-#if 1
-    boolean object_hit = (!IS_MOVING(hitx, hity) ||
-                         MovDir[hitx][hity] != direction ||
-                         ABS(MovPos[hitx][hity]) <= TILEY / 2);
-
-    object_hit = TRUE;
-#endif
-
-    if (object_hit)
-    {
-      int i;
-
-      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_SMASHED_BY_SOMETHING, opposite_direction);
-
-      CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                              CE_OTHER_IS_SMASHING, touched_side);
-
-      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_OTHER_GETS_SMASHED, hitting_side);
-    }
-  }
-}
-#endif
-
 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
 {
   int i, kill_x = -1, kill_y = -1;
@@ -13137,6 +12812,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
     test_x = bad_x + test_xy[i][0];
     test_y = bad_y + test_xy[i][1];
+
     if (!IN_LEV_FIELD(test_x, test_y))
       continue;
 
@@ -13167,12 +12843,14 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
        kill_x = test_x;
        kill_y = test_y;
+
        break;
       }
       else if (test_element == EL_PENGUIN)
       {
        kill_x = test_x;
        kill_y = test_y;
+
        break;
       }
     }
@@ -13195,6 +12873,63 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
   }
 }
 
+void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
+{
+  int bad_element = Feld[bad_x][bad_y];
+  int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
+  int dy = (bad_move_dir == MV_UP   ? -1 : bad_move_dir == MV_DOWN  ? +1 : 0);
+  int test_x = bad_x + dx, test_y = bad_y + dy;
+  int test_move_dir, test_element;
+  int kill_x = -1, kill_y = -1;
+
+  if (!IN_LEV_FIELD(test_x, test_y))
+    return;
+
+  test_move_dir =
+    (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
+
+  test_element = Feld[test_x][test_y];
+
+  if (test_move_dir != bad_move_dir)
+  {
+    /* good thing can be player or penguin that does not move away */
+    if (IS_PLAYER(test_x, test_y))
+    {
+      struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
+
+      /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
+        player as being hit when he is moving towards the bad thing, because
+        the "get hit by" condition would be lost after the player stops) */
+      if (player->MovPos != 0 && player->MovDir == bad_move_dir)
+       return;         /* player moves away from bad thing */
+
+      kill_x = test_x;
+      kill_y = test_y;
+    }
+    else if (test_element == EL_PENGUIN)
+    {
+      kill_x = test_x;
+      kill_y = test_y;
+    }
+  }
+
+  if (kill_x != -1 || kill_y != -1)
+  {
+    if (IS_PLAYER(kill_x, kill_y))
+    {
+      struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
+
+      if (player->shield_deadly_time_left > 0 &&
+         !IS_INDESTRUCTIBLE(bad_element))
+       Bang(bad_x, bad_y);
+      else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
+       KillPlayer(player);
+    }
+    else
+      Bang(kill_x, kill_y);
+  }
+}
+
 void TestIfPlayerTouchesBadThing(int x, int y)
 {
   TestIfGoodThingHitsBadThing(x, y, MV_NONE);
@@ -13266,6 +13001,11 @@ void KillPlayer(struct PlayerInfo *player)
   if (!player->active)
     return;
 
+#if 0
+  printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
   /* the following code was introduced to prevent an infinite loop when calling
      -> Bang()
      -> CheckTriggeredElementChangeExt()
@@ -13290,8 +13030,22 @@ void KillPlayer(struct PlayerInfo *player)
   player->shield_normal_time_left = 0;
   player->shield_deadly_time_left = 0;
 
+#if 0
+  printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
   Bang(jx, jy);
-  BuryPlayer(player);
+
+#if 0
+  printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
+  if (player->reanimated)      /* killed player may have been reanimated */
+    player->killed = player->reanimated = FALSE;
+  else
+    BuryPlayer(player);
 }
 
 static void KillPlayerUnlessEnemyProtected(int x, int y)
@@ -13332,7 +13086,7 @@ void RemovePlayer(struct PlayerInfo *player)
     StorePlayer[jx][jy] = 0;
 
   if (player->is_moving)
-    DrawLevelField(player->last_jx, player->last_jy);
+    TEST_DrawLevelField(player->last_jx, player->last_jy);
 
   for (i = 0; i < MAX_PLAYERS; i++)
     if (stored_player[i].active)
@@ -13345,7 +13099,6 @@ void RemovePlayer(struct PlayerInfo *player)
   ExitY = ZY = jy;
 }
 
-#if USE_NEW_SNAP_DELAY
 static void setFieldForSnapping(int x, int y, int element, int direction)
 {
   struct ElementInfo *ei = &element_info[element];
@@ -13364,7 +13117,6 @@ static void setFieldForSnapping(int x, int y, int element, int direction)
   GfxDir[x][y] = direction;
   GfxFrame[x][y] = -1;
 }
-#endif
 
 /*
   =============================================================================
@@ -13403,9 +13155,9 @@ static boolean checkDiagonalPushing(struct PlayerInfo *player,
   =============================================================================
 */
 
-int DigField(struct PlayerInfo *player,
-            int oldx, int oldy, int x, int y,
-            int real_dx, int real_dy, int mode)
+static int DigField(struct PlayerInfo *player,
+                   int oldx, int oldy, int x, int y,
+                   int real_dx, int real_dy, int mode)
 {
   boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
   boolean player_was_pushing = player->is_pushing;
@@ -13421,11 +13173,7 @@ int DigField(struct PlayerInfo *player,
   int opposite_direction = MV_DIR_OPPOSITE(move_direction);
   int dig_side = MV_DIR_OPPOSITE(move_direction);
   int old_element = Feld[jx][jy];
-#if USE_FIXED_DONT_RUN_INTO
   int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
-#else
-  int element;
-#endif
   int collect_count;
 
   if (is_player)               /* function can also be called by EL_PENGUIN */
@@ -13448,11 +13196,6 @@ int DigField(struct PlayerInfo *player,
     }
   }
 
-#if !USE_FIXED_DONT_RUN_INTO
-  if (IS_MOVING(x, y) || IS_PLAYER(x, y))
-    return MP_NO_ACTION;
-#endif
-
   if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
 
@@ -13467,7 +13210,6 @@ 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 */
 
-#if USE_FIXED_DONT_RUN_INTO
   if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
   {
     SplashAcid(x, y);
@@ -13480,25 +13222,16 @@ int DigField(struct PlayerInfo *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
 
-#if USE_FIXED_DONT_RUN_INTO
   if (IS_MOVING(x, y) || IS_PLAYER(x, y))
     return MP_NO_ACTION;
-#endif
-
-#if !USE_FIXED_DONT_RUN_INTO
-  element = Feld[x][y];
-#endif
 
   collect_count = element_info[element].collect_count_initial;
 
@@ -13525,17 +13258,10 @@ 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))
@@ -13560,8 +13286,10 @@ int DigField(struct PlayerInfo *player,
     }
     else if (element == EL_EXIT_OPEN ||
             element == EL_EM_EXIT_OPEN ||
+            element == EL_EM_EXIT_OPENING ||
             element == EL_STEEL_EXIT_OPEN ||
             element == EL_EM_STEEL_EXIT_OPEN ||
+            element == EL_EM_STEEL_EXIT_OPENING ||
             element == EL_SP_EXIT_OPEN ||
             element == EL_SP_EXIT_OPENING)
     {
@@ -13632,29 +13360,17 @@ int DigField(struct PlayerInfo *player,
          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 */
@@ -13686,14 +13402,10 @@ int DigField(struct PlayerInfo *player,
 
     if (mode == DF_SNAP)
     {
-#if USE_NEW_SNAP_DELAY
       if (level.block_snap_field)
        setFieldForSnapping(x, y, element, move_direction);
       else
        TestIfElementTouchesCustomElement(x, y);        /* for empty space */
-#else
-      TestIfElementTouchesCustomElement(x, y);         /* for empty space */
-#endif
 
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
                                          player->index_bit, dig_side);
@@ -13717,13 +13429,9 @@ int DigField(struct PlayerInfo *player,
     {
       TimeLeft += level.extra_time;
 
-#if 1
       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
       DisplayGameControlValues();
-#else
-      DrawGameValue_Time(TimeLeft);
-#endif
     }
     else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
     {
@@ -13802,13 +13510,11 @@ int DigField(struct PlayerInfo *player,
       if (local_player->gems_still_needed < 0)
        local_player->gems_still_needed = 0;
 
-#if 1
+      game.snapshot.collected_item = TRUE;
+
       game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
 
       DisplayGameControlValues();
-#else
-      DrawGameValue_Emeralds(local_player->gems_still_needed);
-#endif
     }
 
     RaiseScoreElement(element);
@@ -13820,14 +13526,10 @@ int DigField(struct PlayerInfo *player,
 
     if (mode == DF_SNAP)
     {
-#if USE_NEW_SNAP_DELAY
       if (level.block_snap_field)
        setFieldForSnapping(x, y, element, move_direction);
       else
        TestIfElementTouchesCustomElement(x, y);        /* for empty space */
-#else
-      TestIfElementTouchesCustomElement(x, y);         /* for empty space */
-#endif
 
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
                                          player->index_bit, dig_side);
@@ -13884,8 +13586,10 @@ int DigField(struct PlayerInfo *player,
 
     if (!(IN_LEV_FIELD(nextx, nexty) &&
          (IS_FREE(nextx, nexty) ||
-          (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY &&
-           IS_SB_ELEMENT(element)))))
+          (IS_SB_ELEMENT(element) &&
+           Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
+          (IS_CUSTOM_ELEMENT(element) &&
+           CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
       return MP_NO_ACTION;
 
     if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
@@ -13905,6 +13609,13 @@ int DigField(struct PlayerInfo *player,
       return MP_NO_ACTION;
     }
 
+    if (IS_CUSTOM_ELEMENT(element) &&
+       CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
+    {
+      if (!DigFieldByCE(nextx, nexty, element))
+       return MP_NO_ACTION;
+    }
+
     if (IS_SB_ELEMENT(element))
     {
       if (element == EL_SOKOBAN_FIELD_FULL)
@@ -13931,7 +13642,7 @@ int DigField(struct PlayerInfo *player,
                                    ACTION_FILLING);
 
       if (local_player->sokobanfields_still_needed == 0 &&
-         game.emulation == EMU_SOKOBAN)
+         (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
       {
        PlayerWins(player);
 
@@ -13990,7 +13701,7 @@ int DigField(struct PlayerInfo *player,
 
       game.robot_wheel_active = TRUE;
 
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_SP_TERMINAL)
     {
@@ -13999,9 +13710,16 @@ int DigField(struct PlayerInfo *player,
       SCAN_PLAYFIELD(xx, yy)
       {
        if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
+       {
          Bang(xx, yy);
+       }
        else if (Feld[xx][yy] == EL_SP_TERMINAL)
+       {
          Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
+
+         ResetGfxAnimation(xx, yy);
+         TEST_DrawLevelField(xx, yy);
+       }
       }
     }
     else if (IS_BELT_SWITCH(element))
@@ -14045,7 +13763,7 @@ int DigField(struct PlayerInfo *player,
       local_player->lights_still_needed--;
 
       ResetGfxAnimation(x, y);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_TIME_ORB_FULL)
     {
@@ -14054,18 +13772,15 @@ int DigField(struct PlayerInfo *player,
       if (level.time > 0 || level.use_time_orb_bug)
       {
        TimeLeft += level.time_orb_time;
+       game.no_time_limit = FALSE;
 
-#if 1
        game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
        DisplayGameControlValues();
-#else
-       DrawGameValue_Time(TimeLeft);
-#endif
       }
 
       ResetGfxAnimation(x, y);
-      DrawLevelField(x, y);
+      TEST_DrawLevelField(x, y);
     }
     else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
             element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
@@ -14147,7 +13862,55 @@ int DigField(struct PlayerInfo *player,
   return MP_MOVING;
 }
 
-boolean SnapField(struct PlayerInfo *player, int dx, int dy)
+static boolean DigFieldByCE(int x, int y, int digging_element)
+{
+  int element = Feld[x][y];
+
+  if (!IS_FREE(x, y))
+  {
+    int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
+                 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
+                 ACTION_BREAKING);
+
+    /* no element can dig solid indestructible elements */
+    if (IS_INDESTRUCTIBLE(element) &&
+       !IS_DIGGABLE(element) &&
+       !IS_COLLECTIBLE(element))
+      return FALSE;
+
+    if (AmoebaNr[x][y] &&
+       (element == EL_AMOEBA_FULL ||
+        element == EL_BD_AMOEBA ||
+        element == EL_AMOEBA_GROWING))
+    {
+      AmoebaCnt[AmoebaNr[x][y]]--;
+      AmoebaCnt2[AmoebaNr[x][y]]--;
+    }
+
+    if (IS_MOVING(x, y))
+      RemoveMovingField(x, y);
+    else
+    {
+      RemoveField(x, y);
+      TEST_DrawLevelField(x, y);
+    }
+
+    /* if digged element was about to explode, prevent the explosion */
+    ExplodeField[x][y] = EX_TYPE_NONE;
+
+    PlayLevelSoundAction(x, y, action);
+  }
+
+  Store[x][y] = EL_EMPTY;
+
+  /* this makes it possible to leave the removed element again */
+  if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
+    Store[x][y] = element;
+
+  return TRUE;
+}
+
+static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 {
   int jx = player->jx, jy = player->jy;
   int x = jx + dx, y = jy + dy;
@@ -14184,14 +13947,9 @@ 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;
 
@@ -14220,30 +13978,20 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
   }
 
   if (player->MovPos != 0)     /* prevent graphic bugs in versions < 2.2.0 */
-    DrawLevelField(player->last_jx, player->last_jy);
+    TEST_DrawLevelField(player->last_jx, player->last_jy);
 
-  DrawLevelField(x, y);
+  TEST_DrawLevelField(x, y);
 
   return TRUE;
 }
 
-boolean DropElement(struct PlayerInfo *player)
+static boolean DropElement(struct PlayerInfo *player)
 {
   int old_element, new_element;
   int dropx = player->jx, dropy = player->jy;
   int drop_direction = player->MovDir;
   int drop_side = drop_direction;
-#if 1
   int drop_element = get_next_dropped_element(player);
-#else
-  int drop_element = (player->inventory_size > 0 ?
-                     player->inventory_element[player->inventory_size - 1] :
-                     player->inventory_infinite_element != EL_UNDEFINED ?
-                     player->inventory_infinite_element :
-                     player->dynabombs_left > 0 ?
-                     EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
-                     EL_UNDEFINED);
-#endif
 
   player->is_dropping_pressed = TRUE;
 
@@ -14349,23 +14097,13 @@ boolean DropElement(struct PlayerInfo *player)
   if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
       element_info[new_element].move_pattern == MV_WHEN_DROPPED)
   {
-    int move_direction, nextx, nexty;
-
     if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
       MovDir[dropx][dropy] = drop_direction;
 
-    move_direction = MovDir[dropx][dropy];
-    nextx = dropx + GET_DX_FROM_DIR(move_direction);
-    nexty = dropy + GET_DY_FROM_DIR(move_direction);
-
     ChangeCount[dropx][dropy] = 0;     /* allow at least one more change */
 
-#if USE_FIX_IMPACT_COLLISION
     /* do not cause impact style collision by dropping elements that can fall */
     CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
-#else
-    CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
-#endif
   }
 
   player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
@@ -14658,42 +14396,24 @@ void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
   }
 }
 
-#if 0
-void ChangeTime(int value)
-{
-  int *time = (level.time == 0 ? &TimePlayed : &TimeLeft);
-
-  *time += value;
-
-  /* EMC game engine uses value from time counter of RND game engine */
-  level.native_em_level->lev->time = *time;
-
-  DrawGameValue_Time(*time);
-}
-
-void RaiseScore(int value)
+void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
 {
-  /* EMC game engine and RND game engine have separate score counters */
-  int *score = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-               &level.native_em_level->lev->score : &local_player->score);
-
-  *score += value;
+  int element = map_element_SP_to_RND(element_sp);
+  int action = map_action_SP_to_RND(action_sp);
+  int offset = (setup.sp_show_border_elements ? 0 : 1);
+  int x = xx - offset;
+  int y = yy - offset;
 
-  DrawGameValue_Score(*score);
+  PlayLevelSoundElementAction(x, y, element, action);
 }
-#endif
 
 void RaiseScore(int value)
 {
   local_player->score += value;
 
-#if 1
   game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
 
   DisplayGameControlValues();
-#else
-  DrawGameValue_Score(local_player->score);
-#endif
 }
 
 void RaiseScoreElement(int element)
@@ -14780,6 +14500,10 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 {
   if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
   {
+    /* closing door required in case of envelope style request dialogs */
+    if (!skip_request)
+      CloseDoor(DOOR_CLOSE_1);
+
 #if defined(NETWORK_AVALIABLE)
     if (options.network)
       SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
@@ -14787,37 +14511,11 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 #endif
     {
       if (quick_quit)
-      {
-#if 1
-
-#if 1
        FadeSkipNextFadeIn();
-#else
-       fading = fading_none;
-#endif
-
-#else
-       OpenDoor(DOOR_CLOSE_1);
-#endif
-
-       game_status = GAME_MODE_MAIN;
-
-#if 1
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
-#else
-       DrawMainMenu();
-#endif
-      }
-      else
-      {
-#if 0
-       FadeOut(REDRAW_FIELD);
-#endif
 
-       game_status = GAME_MODE_MAIN;
+      SetGameStatus(GAME_MODE_MAIN);
 
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
-      }
+      DrawMainMenu();
     }
   }
   else         /* continue playing the game */
@@ -14838,7 +14536,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
   boolean skip_request = AllPlayersGone || quick_quit;
 
   RequestQuitGameExt(skip_request, quick_quit,
-                    "Do you really want to quit the game ?");
+                    "Do you really want to quit the game?");
 }
 
 
@@ -14846,22 +14544,11 @@ void RequestQuitGame(boolean ask_if_really_quit)
 /* random generator functions                                                */
 /* ------------------------------------------------------------------------- */
 
-unsigned int InitEngineRandom_RND(long seed)
+unsigned int InitEngineRandom_RND(int seed)
 {
   game.num_random_calls = 0;
 
-#if 0
-  unsigned int rnd_seed = InitEngineRandom(seed);
-
-  printf("::: START RND: %d\n", rnd_seed);
-
-  return rnd_seed;
-#else
-
   return InitEngineRandom(seed);
-
-#endif
-
 }
 
 unsigned int RND(int max)
@@ -14881,8 +14568,6 @@ unsigned int RND(int max)
 /* game engine snapshot handling functions                                   */
 /* ------------------------------------------------------------------------- */
 
-#define ARGS_ADDRESS_AND_SIZEOF(x)             (&(x)), (sizeof(x))
-
 struct EngineSnapshotInfo
 {
   /* runtime values for custom element collect score */
@@ -14892,32 +14577,14 @@ struct EngineSnapshotInfo
   int choice_pos[NUM_GROUP_ELEMENTS];
 
   /* runtime values for belt position animations */
-  int belt_graphic[4 * NUM_BELT_PARTS];
-  int belt_anim_mode[4 * NUM_BELT_PARTS];
-};
-
-struct EngineSnapshotNodeInfo
-{
-  void *buffer_orig;
-  void *buffer_copy;
-  int size;
+  int belt_graphic[4][NUM_BELT_PARTS];
+  int belt_anim_mode[4][NUM_BELT_PARTS];
 };
 
 static struct EngineSnapshotInfo engine_snapshot_rnd;
-static ListNode *engine_snapshot_list = NULL;
 static char *snapshot_level_identifier = NULL;
 static int snapshot_level_nr = -1;
 
-void FreeEngineSnapshot()
-{
-  while (engine_snapshot_list != NULL)
-    deleteNodeFromList(&engine_snapshot_list, engine_snapshot_list->key,
-                      checked_free);
-
-  setString(&snapshot_level_identifier, NULL);
-  snapshot_level_nr = -1;
-}
-
 static void SaveEngineSnapshotValues_RND()
 {
   static int belt_base_active_element[4] =
@@ -14951,15 +14618,15 @@ static void SaveEngineSnapshotValues_RND()
       int graphic = el2img(element);
       int anim_mode = graphic_info[graphic].anim_mode;
 
-      engine_snapshot_rnd.belt_graphic[i * 4 + j] = graphic;
-      engine_snapshot_rnd.belt_anim_mode[i * 4 + j] = anim_mode;
+      engine_snapshot_rnd.belt_graphic[i][j] = graphic;
+      engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
     }
   }
 }
 
 static void LoadEngineSnapshotValues_RND()
 {
-  unsigned long num_random_calls = game.num_random_calls;
+  unsigned int num_random_calls = game.num_random_calls;
   int i, j;
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
@@ -14980,8 +14647,8 @@ static void LoadEngineSnapshotValues_RND()
   {
     for (j = 0; j < NUM_BELT_PARTS; j++)
     {
-      int graphic = engine_snapshot_rnd.belt_graphic[i * 4 + j];
-      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i * 4 + j];
+      int graphic = engine_snapshot_rnd.belt_graphic[i][j];
+      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
 
       graphic_info[graphic].anim_mode = anim_mode;
     }
@@ -15003,110 +14670,109 @@ static void LoadEngineSnapshotValues_RND()
   }
 }
 
-static void SaveEngineSnapshotBuffer(void *buffer, int size)
+void FreeEngineSnapshotSingle()
 {
-  struct EngineSnapshotNodeInfo *bi =
-    checked_calloc(sizeof(struct EngineSnapshotNodeInfo));
-
-  bi->buffer_orig = buffer;
-  bi->buffer_copy = checked_malloc(size);
-  bi->size = size;
+  FreeSnapshotSingle();
 
-  memcpy(bi->buffer_copy, buffer, size);
-
-  addNodeToList(&engine_snapshot_list, NULL, bi);
+  setString(&snapshot_level_identifier, NULL);
+  snapshot_level_nr = -1;
 }
 
-void SaveEngineSnapshot()
+void FreeEngineSnapshotList()
 {
-  FreeEngineSnapshot();                /* free previous snapshot, if needed */
+  FreeSnapshotList();
+}
 
-  if (level_editor_test_game)  /* do not save snapshots from editor */
-    return;
+ListNode *SaveEngineSnapshotBuffers()
+{
+  ListNode *buffers = NULL;
 
   /* copy some special values to a structure better suited for the snapshot */
 
-  SaveEngineSnapshotValues_RND();
-  SaveEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    SaveEngineSnapshotValues_RND();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    SaveEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    SaveEngineSnapshotValues_SP(&buffers);
 
   /* save values stored in special snapshot structure */
 
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
 
   /* save further RND engine values */
 
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(stored_player));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(game));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(tape));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZX));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZY));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitX));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitY));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TapeTime));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Feld));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovPos));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDir));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDelay));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangePage));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CustomValue));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store2));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Back));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Stop));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Pushed));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxElement));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxAction));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxDir));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_x));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_y));
-
-  /* save level identification information */
-
-  setString(&snapshot_level_identifier, leveldir_current->identifier);
-  snapshot_level_nr = level_nr;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
 
 #if 0
-  ListNode *node = engine_snapshot_list;
+  ListNode *node = engine_snapshot_list_rnd;
   int num_bytes = 0;
 
   while (node != NULL)
@@ -15118,134 +14784,156 @@ void SaveEngineSnapshot()
 
   printf("::: size of engine snapshot: %d bytes\n", num_bytes);
 #endif
+
+  return buffers;
 }
 
-static void LoadEngineSnapshotBuffer(struct EngineSnapshotNodeInfo *bi)
+void SaveEngineSnapshotSingle()
 {
-  memcpy(bi->buffer_orig, bi->buffer_copy, bi->size);
+  ListNode *buffers = SaveEngineSnapshotBuffers();
+
+  /* finally save all snapshot buffers to single snapshot */
+  SaveSnapshotSingle(buffers);
+
+  /* save level identification information */
+  setString(&snapshot_level_identifier, leveldir_current->identifier);
+  snapshot_level_nr = level_nr;
 }
 
-void LoadEngineSnapshot()
+boolean CheckSaveEngineSnapshotToList()
 {
-  ListNode *node = engine_snapshot_list;
+  boolean save_snapshot =
+    ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
+     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
+      game.snapshot.changed_action) ||
+     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
+      game.snapshot.collected_item));
+
+  game.snapshot.changed_action = FALSE;
+  game.snapshot.collected_item = FALSE;
+  game.snapshot.save_snapshot = save_snapshot;
+
+  return save_snapshot;
+}
 
-  if (engine_snapshot_list == NULL)
+void SaveEngineSnapshotToList()
+{
+  if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
+      tape.quick_resume)
     return;
 
-  while (node != NULL)
-  {
-    LoadEngineSnapshotBuffer((struct EngineSnapshotNodeInfo *)node->content);
+  ListNode *buffers = SaveEngineSnapshotBuffers();
 
-    node = node->next;
-  }
+  /* finally save all snapshot buffers to snapshot list */
+  SaveSnapshotToList(buffers);
+}
+
+void SaveEngineSnapshotToListInitial()
+{
+  FreeEngineSnapshotList();
 
+  SaveEngineSnapshotToList();
+}
+
+void LoadEngineSnapshotValues()
+{
   /* restore special values from snapshot structure */
 
-  LoadEngineSnapshotValues_RND();
-  LoadEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    LoadEngineSnapshotValues_RND();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    LoadEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    LoadEngineSnapshotValues_SP();
+}
+
+void LoadEngineSnapshotSingle()
+{
+  LoadSnapshotSingle();
+
+  LoadEngineSnapshotValues();
+}
+
+void LoadEngineSnapshot_Undo(int steps)
+{
+  LoadSnapshotFromList_Older(steps);
+
+  LoadEngineSnapshotValues();
+}
+
+void LoadEngineSnapshot_Redo(int steps)
+{
+  LoadSnapshotFromList_Newer(steps);
+
+  LoadEngineSnapshotValues();
 }
 
-boolean CheckEngineSnapshot()
+boolean CheckEngineSnapshotSingle()
 {
   return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
          snapshot_level_nr == level_nr);
 }
 
+boolean CheckEngineSnapshotList()
+{
+  return CheckSnapshotList();
+}
 
-/* ---------- new game button stuff ---------------------------------------- */
 
-/* graphic position values for game buttons */
-#define GAME_BUTTON_XSIZE      30
-#define GAME_BUTTON_YSIZE      30
-#define GAME_BUTTON_XPOS       5
-#define GAME_BUTTON_YPOS       215
-#define SOUND_BUTTON_XPOS      5
-#define SOUND_BUTTON_YPOS      (GAME_BUTTON_YPOS + GAME_BUTTON_YSIZE)
-
-#define GAME_BUTTON_STOP_XPOS  (GAME_BUTTON_XPOS + 0 * GAME_BUTTON_XSIZE)
-#define GAME_BUTTON_PAUSE_XPOS (GAME_BUTTON_XPOS + 1 * GAME_BUTTON_XSIZE)
-#define GAME_BUTTON_PLAY_XPOS  (GAME_BUTTON_XPOS + 2 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_MUSIC_XPOS        (SOUND_BUTTON_XPOS + 0 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_LOOPS_XPOS        (SOUND_BUTTON_XPOS + 1 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_SIMPLE_XPOS (SOUND_BUTTON_XPOS + 2 * GAME_BUTTON_XSIZE)
+/* ---------- new game button stuff ---------------------------------------- */
 
 static struct
 {
-  int *x, *y;
-  int gd_x, gd_y;
+  int graphic;
+  struct XY *pos;
   int gadget_id;
   char *infotext;
 } gamebutton_info[NUM_GAME_BUTTONS] =
 {
-#if 1
   {
-    &game.button.stop.x,       &game.button.stop.y,
-    GAME_BUTTON_STOP_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_STOP,
-    "stop game"
+    IMG_GFX_GAME_BUTTON_STOP,          &game.button.stop,
+    GAME_CTRL_ID_STOP,                 "stop game"
   },
   {
-    &game.button.pause.x,      &game.button.pause.y,
-    GAME_BUTTON_PAUSE_XPOS,    GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PAUSE,
-    "pause game"
+    IMG_GFX_GAME_BUTTON_PAUSE,         &game.button.pause,
+    GAME_CTRL_ID_PAUSE,                        "pause game"
   },
   {
-    &game.button.play.x,       &game.button.play.y,
-    GAME_BUTTON_PLAY_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PLAY,
-    "play game"
+    IMG_GFX_GAME_BUTTON_PLAY,          &game.button.play,
+    GAME_CTRL_ID_PLAY,                 "play game"
   },
   {
-    &game.button.sound_music.x,        &game.button.sound_music.y,
-    SOUND_BUTTON_MUSIC_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_MUSIC,
-    "background music on/off"
+    IMG_GFX_GAME_BUTTON_UNDO,          &game.button.undo,
+    GAME_CTRL_ID_UNDO,                 "undo step"
   },
   {
-    &game.button.sound_loops.x,        &game.button.sound_loops.y,
-    SOUND_BUTTON_LOOPS_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_LOOPS,
-    "sound loops on/off"
+    IMG_GFX_GAME_BUTTON_REDO,          &game.button.redo,
+    GAME_CTRL_ID_REDO,                 "redo step"
   },
   {
-    &game.button.sound_simple.x,&game.button.sound_simple.y,
-    SOUND_BUTTON_SIMPLE_XPOS,  SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_SIMPLE,
-    "normal sounds on/off"
-  }
-#else
-  {
-    GAME_BUTTON_STOP_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_STOP,
-    "stop game"
+    IMG_GFX_GAME_BUTTON_SAVE,          &game.button.save,
+    GAME_CTRL_ID_SAVE,                 "save game"
   },
   {
-    GAME_BUTTON_PAUSE_XPOS,    GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PAUSE,
-    "pause game"
+    IMG_GFX_GAME_BUTTON_PAUSE2,                &game.button.pause2,
+    GAME_CTRL_ID_PAUSE2,               "pause game"
   },
   {
-    GAME_BUTTON_PLAY_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PLAY,
-    "play game"
+    IMG_GFX_GAME_BUTTON_LOAD,          &game.button.load,
+    GAME_CTRL_ID_LOAD,                 "load game"
   },
   {
-    SOUND_BUTTON_MUSIC_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_MUSIC,
-    "background music on/off"
+    IMG_GFX_GAME_BUTTON_SOUND_MUSIC,   &game.button.sound_music,
+    SOUND_CTRL_ID_MUSIC,               "background music on/off"
   },
   {
-    SOUND_BUTTON_LOOPS_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_LOOPS,
-    "sound loops on/off"
+    IMG_GFX_GAME_BUTTON_SOUND_LOOPS,   &game.button.sound_loops,
+    SOUND_CTRL_ID_LOOPS,               "sound loops on/off"
   },
   {
-    SOUND_BUTTON_SIMPLE_XPOS,  SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_SIMPLE,
-    "normal sounds on/off"
+    IMG_GFX_GAME_BUTTON_SOUND_SIMPLE,  &game.button.sound_simple,
+    SOUND_CTRL_ID_SIMPLE,              "normal sounds on/off"
   }
-#endif
 };
 
 void CreateGameButtons()
@@ -15254,32 +14942,46 @@ void CreateGameButtons()
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
   {
-    Bitmap *gd_bitmap = graphic_info[IMG_GLOBAL_DOOR].bitmap;
+    struct GraphicInfo *gfx = &graphic_info[gamebutton_info[i].graphic];
+    struct XY *pos = gamebutton_info[i].pos;
     struct GadgetInfo *gi;
     int button_type;
     boolean checked;
-    unsigned long event_mask;
-    int x, y;
-    int gd_xoffset, gd_yoffset;
-    int gd_x1, gd_x2, gd_y1, gd_y2;
+    unsigned int event_mask;
+    int base_x = (tape.show_game_buttons ? VX : DX);
+    int base_y = (tape.show_game_buttons ? VY : DY);
+    int gd_x   = gfx->src_x;
+    int gd_y   = gfx->src_y;
+    int gd_xp  = gfx->src_x + gfx->pressed_xoffset;
+    int gd_yp  = gfx->src_y + gfx->pressed_yoffset;
+    int gd_xa  = gfx->src_x + gfx->active_xoffset;
+    int gd_ya  = gfx->src_y + gfx->active_yoffset;
+    int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
+    int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
     int id = i;
 
-    x = DX + *gamebutton_info[i].x;
-    y = DY + *gamebutton_info[i].y;
-    gd_xoffset = gamebutton_info[i].gd_x;
-    gd_yoffset = gamebutton_info[i].gd_y;
-    gd_x1 = DOOR_GFX_PAGEX4 + gd_xoffset;
-    gd_x2 = DOOR_GFX_PAGEX3 + gd_xoffset;
+    if (gfx->bitmap == NULL)
+    {
+      game_gadget[id] = NULL;
+
+      continue;
+    }
 
     if (id == GAME_CTRL_ID_STOP ||
-       id == GAME_CTRL_ID_PAUSE ||
-       id == GAME_CTRL_ID_PLAY)
+       id == GAME_CTRL_ID_PLAY ||
+       id == GAME_CTRL_ID_SAVE ||
+       id == GAME_CTRL_ID_LOAD)
     {
       button_type = GD_TYPE_NORMAL_BUTTON;
       checked = FALSE;
       event_mask = GD_EVENT_RELEASED;
-      gd_y1  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
-      gd_y2  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
+    }
+    else if (id == GAME_CTRL_ID_UNDO ||
+            id == GAME_CTRL_ID_REDO)
+    {
+      button_type = GD_TYPE_NORMAL_BUTTON;
+      checked = FALSE;
+      event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
     }
     else
     {
@@ -15289,28 +14991,22 @@ void CreateGameButtons()
         (id == SOUND_CTRL_ID_LOOPS && setup.sound_loops) ||
         (id == SOUND_CTRL_ID_SIMPLE && setup.sound_simple) ? TRUE : FALSE);
       event_mask = GD_EVENT_PRESSED;
-      gd_y1  = DOOR_GFX_PAGEY1 + gd_yoffset;
-      gd_y2  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
     }
 
     gi = CreateGadget(GDI_CUSTOM_ID, id,
                      GDI_INFO_TEXT, gamebutton_info[i].infotext,
-#if 1
-                     GDI_X, x,
-                     GDI_Y, y,
-#else
-                     GDI_X, DX + gd_xoffset,
-                     GDI_Y, DY + gd_yoffset,
-#endif
-                     GDI_WIDTH, GAME_BUTTON_XSIZE,
-                     GDI_HEIGHT, GAME_BUTTON_YSIZE,
+                     GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
+                     GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
+                     GDI_WIDTH, gfx->width,
+                     GDI_HEIGHT, gfx->height,
                      GDI_TYPE, button_type,
                      GDI_STATE, GD_BUTTON_UNPRESSED,
                      GDI_CHECKED, checked,
-                     GDI_DESIGN_UNPRESSED, gd_bitmap, gd_x1, gd_y1,
-                     GDI_DESIGN_PRESSED, gd_bitmap, gd_x2, gd_y1,
-                     GDI_ALT_DESIGN_UNPRESSED, gd_bitmap, gd_x1, gd_y2,
-                     GDI_ALT_DESIGN_PRESSED, gd_bitmap, gd_x2, gd_y2,
+                     GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
+                     GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
+                     GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
+                     GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
+                     GDI_DIRECT_DRAW, FALSE,
                      GDI_EVENT_MASK, event_mask,
                      GDI_CALLBACK_ACTION, HandleGameButtons,
                      GDI_END);
@@ -15330,12 +15026,80 @@ void FreeGameButtons()
     FreeGadget(game_gadget[i]);
 }
 
-static void MapGameButtons()
+static void UnmapGameButtonsAtSamePosition(int id)
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    if (i != id &&
+       gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
+       gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
+      UnmapGadget(game_gadget[i]);
+}
+
+static void UnmapGameButtonsAtSamePosition_All()
+{
+  if (setup.show_snapshot_buttons)
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
+  }
+  else
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
+  }
+}
+
+static void MapGameButtonsAtSamePosition(int id)
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    if (i != id &&
+       gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
+       gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
+      MapGadget(game_gadget[i]);
+
+  UnmapGameButtonsAtSamePosition_All();
+}
+
+void MapUndoRedoButtons()
+{
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+
+  MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
+  MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
+
+  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
+}
+
+void UnmapUndoRedoButtons()
+{
+  UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
+  UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
+
+  MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
+  MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+
+  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
+}
+
+void MapGameButtons()
 {
   int i;
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
-    MapGadget(game_gadget[i]);
+    if (i != GAME_CTRL_ID_UNDO &&
+       i != GAME_CTRL_ID_REDO)
+      MapGadget(game_gadget[i]);
+
+  UnmapGameButtonsAtSamePosition_All();
+
+  RedrawGameButtons();
 }
 
 void UnmapGameButtons()
@@ -15346,24 +15110,81 @@ void UnmapGameButtons()
     UnmapGadget(game_gadget[i]);
 }
 
-static void HandleGameButtons(struct GadgetInfo *gi)
+void RedrawGameButtons()
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    RedrawGadget(game_gadget[i]);
+
+  // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
+  redraw_mask &= ~REDRAW_ALL;
+}
+
+void GameUndoRedoExt()
+{
+  ClearPlayerAction();
+
+  tape.pausing = TRUE;
+
+  RedrawPlayfield();
+  UpdateAndDisplayGameControlValues();
+
+  DrawCompleteVideoDisplay();
+  DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+  DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
+  DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
+
+  BackToFront();
+}
+
+void GameUndo(int steps)
+{
+  if (!CheckEngineSnapshotList())
+    return;
+
+  LoadEngineSnapshot_Undo(steps);
+
+  GameUndoRedoExt();
+}
+
+void GameRedo(int steps)
+{
+  if (!CheckEngineSnapshotList())
+    return;
+
+  LoadEngineSnapshot_Redo(steps);
+
+  GameUndoRedoExt();
+}
+
+static void HandleGameButtonsExt(int id, int button)
 {
-  int id = gi->custom_id;
+  static boolean game_undo_executed = FALSE;
+  int steps = BUTTON_STEPSIZE(button);
+  boolean handle_game_buttons =
+    (game_status == GAME_MODE_PLAYING ||
+     (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
 
-  if (game_status != GAME_MODE_PLAYING)
+  if (!handle_game_buttons)
     return;
 
   switch (id)
   {
     case GAME_CTRL_ID_STOP:
+      if (game_status == GAME_MODE_MAIN)
+       break;
+
       if (tape.playing)
        TapeStop();
       else
        RequestQuitGame(TRUE);
+
       break;
 
     case GAME_CTRL_ID_PAUSE:
-      if (options.network)
+    case GAME_CTRL_ID_PAUSE2:
+      if (options.network && game_status == GAME_MODE_PLAYING)
       {
 #if defined(NETWORK_AVALIABLE)
        if (tape.pausing)
@@ -15374,27 +15195,59 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       }
       else
        TapeTogglePause(TAPE_TOGGLE_MANUAL);
+
+      game_undo_executed = FALSE;
+
       break;
 
     case GAME_CTRL_ID_PLAY:
-      if (tape.pausing)
+      if (game_status == GAME_MODE_MAIN)
+      {
+        StartGameActions(options.network, setup.autorecord, level.random_seed);
+      }
+      else if (tape.pausing)
       {
 #if defined(NETWORK_AVALIABLE)
        if (options.network)
          SendToServer_ContinuePlaying();
        else
 #endif
-       {
-         tape.pausing = FALSE;
-         DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF, 0);
-       }
+         TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
       }
       break;
 
+    case GAME_CTRL_ID_UNDO:
+      // Important: When using "save snapshot when collecting an item" mode,
+      // load last (current) snapshot for first "undo" after pressing "pause"
+      // (else the last-but-one snapshot would be loaded, because the snapshot
+      // pointer already points to the last snapshot when pressing "pause",
+      // which is fine for "every step/move" mode, but not for "every collect")
+      if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
+         !game_undo_executed)
+       steps--;
+
+      game_undo_executed = TRUE;
+
+      GameUndo(steps);
+      break;
+
+    case GAME_CTRL_ID_REDO:
+      GameRedo(steps);
+      break;
+
+    case GAME_CTRL_ID_SAVE:
+      TapeQuickSave();
+      break;
+
+    case GAME_CTRL_ID_LOAD:
+      TapeQuickLoad();
+      break;
+
     case SOUND_CTRL_ID_MUSIC:
       if (setup.sound_music)
       { 
        setup.sound_music = FALSE;
+
        FadeMusic();
       }
       else if (audio.music_available)
@@ -15413,6 +15266,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       else if (audio.loops_available)
       {
        setup.sound = setup.sound_loops = TRUE;
+
        SetAudioMode(setup.sound);
       }
       break;
@@ -15423,6 +15277,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       else if (audio.sound_available)
       {
        setup.sound = setup.sound_simple = TRUE;
+
        SetAudioMode(setup.sound);
       }
       break;
@@ -15431,3 +15286,19 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       break;
   }
 }
+
+static void HandleGameButtons(struct GadgetInfo *gi)
+{
+  HandleGameButtonsExt(gi->custom_id, gi->event.button);
+}
+
+void HandleSoundButtonKeys(Key key)
+{
+
+  if (key == setup.shortcut.sound_simple)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
+  else if (key == setup.shortcut.sound_loops)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
+  else if (key == setup.shortcut.sound_music)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);
+}