added functions to get current player position for all game engines
[rocksndiamonds.git] / src / game.c
index b0d7ec6bfd1a670820bce99d0111fd67b8c7db58..3d2f055f27c88415dccb499d83d65932ad8a5f1e 100644 (file)
@@ -1,15 +1,13 @@
-/***********************************************************
-* Rocks'n'Diamonds -- McDuffin Strikes Back!               *
-*----------------------------------------------------------*
-* (c) 1995-2002 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"
 
-/* this switch controls how rocks move horizontally */
-#define OLD_GAME_BEHAVIOUR     FALSE
+
+/* 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                  (TRUE                           * 1)
-
-#define USE_NEW_MOVE_STYLE             (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_NEW_MOVE_DELAY             (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_NEW_PUSH_DELAY             (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_NEW_BLOCK_STYLE            (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_NEW_SP_SLIPPERY            (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_NEW_RANDOMIZE              (TRUE   * USE_NEW_STUFF         * 1)
-
-#define USE_CAN_MOVE_NOT_MOVING                (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_PREVIOUS_MOVE_DIR          (TRUE   * USE_NEW_STUFF         * 1)
-
-#define USE_PUSH_BUGFIX                        (TRUE   * USE_NEW_STUFF         * 1)
-#if 0
-#define USE_BLOCK_DELAY_BUGFIX         (TRUE   * 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
-#define USE_GRAVITY_BUGFIX_NEW         (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_GRAVITY_BUGFIX_OLD         (TRUE   * USE_NEW_STUFF         * 0)
-
-#define USE_PENGUIN_COLLECT_BUGFIX     (TRUE   * USE_NEW_STUFF         * 1)
-
-#define USE_IMPACT_BUGFIX              (TRUE   * USE_NEW_STUFF         * 1)
-
-#define USE_HITTING_SOMETHING_BUGFIX   (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_HIT_BY_SOMETHING_BUGFIX    (TRUE   * USE_NEW_STUFF         * 1)
-
-#define USE_DROP_BUGFIX                        (TRUE   * USE_NEW_STUFF         * 1)
-
-#define USE_CHANGE_TO_TRIGGERED                (TRUE   * USE_NEW_STUFF         * 1)
 
 
 /* for DigField() */
 #define DF_SNAP                        2
 
 /* for MovePlayer() */
-#define MF_NO_ACTION           0
-#define MF_MOVING              1
-#define MF_ACTION              2
+#define MP_NO_ACTION           0
+#define MP_MOVING              1
+#define MP_ACTION              2
+#define MP_DONT_RUN_INTO       (MP_MOVING | MP_ACTION)
 
 /* for ScrollPlayer() */
 #define SCROLL_INIT            0
 #define SCROLL_GO_ON           1
 
-/* for Explode() */
+/* for Bang()/Explode() */
 #define EX_PHASE_START         0
 #define EX_TYPE_NONE           0
 #define EX_TYPE_NORMAL         (1 << 0)
 #define EX_TYPE_CENTER         (1 << 1)
 #define EX_TYPE_BORDER         (1 << 2)
 #define EX_TYPE_CROSS          (1 << 3)
+#define EX_TYPE_DYNA           (1 << 4)
 #define EX_TYPE_SINGLE_TILE    (EX_TYPE_CENTER | EX_TYPE_BORDER)
 
-/* special positions in the game control window (relative to control window) */
-#define XX_LEVEL               37
-#define YY_LEVEL               20
-#define XX_EMERALDS            29
-#define YY_EMERALDS            54
-#define XX_DYNAMITE            29
-#define YY_DYNAMITE            89
-#define XX_KEYS                        18
-#define YY_KEYS                        123
-#define XX_SCORE               15
-#define YY_SCORE               159
-#define XX_TIME1               29
-#define XX_TIME2               30
-#define YY_TIME                        194
-
-/* special positions in the game control window (relative to main window) */
-#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 DY_TIME                        (DY + YY_TIME)
+#define PANEL_OFF()            (local_player->LevelSolved_PanelOff)
+#define        PANEL_DEACTIVATED(p)    ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
+#define PANEL_XPOS(p)          (DX + ALIGNED_TEXT_XPOS(p))
+#define PANEL_YPOS(p)          (DY + ALIGNED_TEXT_YPOS(p))
+
+/* game panel display and control definitions */
+#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_INVENTORY_FIRST_3           5
+#define GAME_PANEL_INVENTORY_FIRST_4           6
+#define GAME_PANEL_INVENTORY_FIRST_5           7
+#define GAME_PANEL_INVENTORY_FIRST_6           8
+#define GAME_PANEL_INVENTORY_FIRST_7           9
+#define GAME_PANEL_INVENTORY_FIRST_8           10
+#define GAME_PANEL_INVENTORY_LAST_1            11
+#define GAME_PANEL_INVENTORY_LAST_2            12
+#define GAME_PANEL_INVENTORY_LAST_3            13
+#define GAME_PANEL_INVENTORY_LAST_4            14
+#define GAME_PANEL_INVENTORY_LAST_5            15
+#define GAME_PANEL_INVENTORY_LAST_6            16
+#define GAME_PANEL_INVENTORY_LAST_7            17
+#define GAME_PANEL_INVENTORY_LAST_8            18
+#define GAME_PANEL_KEY_1                       19
+#define GAME_PANEL_KEY_2                       20
+#define GAME_PANEL_KEY_3                       21
+#define GAME_PANEL_KEY_4                       22
+#define GAME_PANEL_KEY_5                       23
+#define GAME_PANEL_KEY_6                       24
+#define GAME_PANEL_KEY_7                       25
+#define GAME_PANEL_KEY_8                       26
+#define GAME_PANEL_KEY_WHITE                   27
+#define GAME_PANEL_KEY_WHITE_COUNT             28
+#define GAME_PANEL_SCORE                       29
+#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_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
+{
+  int nr;
+
+  struct TextPosInfo *pos;
+  int type;
+
+  int value, last_value;
+  int frame, last_frame;
+  int gfx_frame;
+  int gfx_random;
+};
+
+static struct GamePanelControlInfo game_panel_controls[] =
+{
+  {
+    GAME_PANEL_LEVEL_NUMBER,
+    &game.panel.level_number,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_GEMS,
+    &game.panel.gems,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_INVENTORY_COUNT,
+    &game.panel.inventory_count,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_1,
+    &game.panel.inventory_first[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_2,
+    &game.panel.inventory_first[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_3,
+    &game.panel.inventory_first[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_4,
+    &game.panel.inventory_first[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_5,
+    &game.panel.inventory_first[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_6,
+    &game.panel.inventory_first[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_7,
+    &game.panel.inventory_first[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_FIRST_8,
+    &game.panel.inventory_first[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_1,
+    &game.panel.inventory_last[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_2,
+    &game.panel.inventory_last[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_3,
+    &game.panel.inventory_last[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_4,
+    &game.panel.inventory_last[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_5,
+    &game.panel.inventory_last[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_6,
+    &game.panel.inventory_last[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_7,
+    &game.panel.inventory_last[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_INVENTORY_LAST_8,
+    &game.panel.inventory_last[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_1,
+    &game.panel.key[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_2,
+    &game.panel.key[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_3,
+    &game.panel.key[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_4,
+    &game.panel.key[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_5,
+    &game.panel.key[4],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_6,
+    &game.panel.key[5],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_7,
+    &game.panel.key[6],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_8,
+    &game.panel.key[7],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_WHITE,
+    &game.panel.key_white,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_KEY_WHITE_COUNT,
+    &game.panel.key_white_count,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SCORE,
+    &game.panel.score,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_HIGHSCORE,
+    &game.panel.highscore,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME,
+    &game.panel.time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME_HH,
+    &game.panel.time_hh,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME_MM,
+    &game.panel.time_mm,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIME_SS,
+    &game.panel.time_ss,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_FRAME,
+    &game.panel.frame,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SHIELD_NORMAL,
+    &game.panel.shield_normal,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_SHIELD_NORMAL_TIME,
+    &game.panel.shield_normal_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SHIELD_DEADLY,
+    &game.panel.shield_deadly,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_SHIELD_DEADLY_TIME,
+    &game.panel.shield_deadly_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_EXIT,
+    &game.panel.exit,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_MAGIC_BALL,
+    &game.panel.emc_magic_ball,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
+    &game.panel.emc_magic_ball_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_LIGHT_SWITCH,
+    &game.panel.light_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_LIGHT_SWITCH_TIME,
+    &game.panel.light_switch_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_TIMEGATE_SWITCH,
+    &game.panel.timegate_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_TIMEGATE_SWITCH_TIME,
+    &game.panel.timegate_switch_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SWITCHGATE_SWITCH,
+    &game.panel.switchgate_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_LENSES,
+    &game.panel.emc_lenses,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_LENSES_TIME,
+    &game.panel.emc_lenses_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_EMC_MAGNIFIER,
+    &game.panel.emc_magnifier,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_EMC_MAGNIFIER_TIME,
+    &game.panel.emc_magnifier_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_BALLOON_SWITCH,
+    &game.panel.balloon_switch,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_DYNABOMB_NUMBER,
+    &game.panel.dynabomb_number,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_DYNABOMB_SIZE,
+    &game.panel.dynabomb_size,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_DYNABOMB_POWER,
+    &game.panel.dynabomb_power,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_PENGUINS,
+    &game.panel.penguins,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SOKOBAN_OBJECTS,
+    &game.panel.sokoban_objects,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_SOKOBAN_FIELDS,
+    &game.panel.sokoban_fields,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_ROBOT_WHEEL,
+    &game.panel.robot_wheel,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_1,
+    &game.panel.conveyor_belt[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_2,
+    &game.panel.conveyor_belt[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_3,
+    &game.panel.conveyor_belt[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_4,
+    &game.panel.conveyor_belt[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
+    &game.panel.conveyor_belt_switch[0],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
+    &game.panel.conveyor_belt_switch[1],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
+    &game.panel.conveyor_belt_switch[2],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
+    &game.panel.conveyor_belt_switch[3],
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_MAGIC_WALL,
+    &game.panel.magic_wall,
+    TYPE_ELEMENT,
+  },
+  {
+    GAME_PANEL_MAGIC_WALL_TIME,
+    &game.panel.magic_wall_time,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_GRAVITY_STATE,
+    &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,
+    TYPE_STRING,
+  },
+  {
+    GAME_PANEL_LEVEL_NAME,
+    &game.panel.level_name,
+    TYPE_STRING,
+  },
+  {
+    GAME_PANEL_LEVEL_AUTHOR,
+    &game.panel.level_author,
+    TYPE_STRING,
+  },
+
+  {
+    -1,
+    NULL,
+    -1,
+  }
+};
+
+/* values for delayed check of falling and moving elements and for collision */
+#define CHECK_DELAY_MOVING     3
+#define CHECK_DELAY_FALLING    CHECK_DELAY_MOVING
+#define CHECK_DELAY_COLLISION  2
+#define CHECK_DELAY_IMPACT     CHECK_DELAY_COLLISION
 
 /* values for initial player move delay (initial delay counter value) */
 #define INITIAL_MOVE_DELAY_OFF -1
 #define INITIAL_MOVE_DELAY_ON  0
 
 /* values for player movement speed (which is in fact a delay value) */
+#define MOVE_DELAY_MIN_SPEED   32
 #define MOVE_DELAY_NORMAL_SPEED        8
 #define MOVE_DELAY_HIGH_SPEED  4
+#define MOVE_DELAY_MAX_SPEED   1
+
+#define DOUBLE_MOVE_DELAY(x)   (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
+#define HALVE_MOVE_DELAY(x)    (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
 
-#define DOUBLE_MOVE_DELAY(x)   (x = (x <= MOVE_DELAY_HIGH_SPEED ? x * 2 : x))
-#define HALVE_MOVE_DELAY(x)    (x = (x >= MOVE_DELAY_HIGH_SPEED ? x / 2 : x))
-#define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY((p)->move_delay_value))
+#define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
 #define HALVE_PLAYER_SPEED(p)  (DOUBLE_MOVE_DELAY((p)->move_delay_value))
 
 /* values for other actions */
 #define MOVE_STEPSIZE_NORMAL   (TILEX / MOVE_DELAY_NORMAL_SPEED)
+#define MOVE_STEPSIZE_MIN      (1)
+#define MOVE_STEPSIZE_MAX      (TILEX)
 
 #define GET_DX_FROM_DIR(d)     ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
 #define GET_DY_FROM_DIR(d)     ((d) == MV_UP   ? -1 : (d) == MV_DOWN  ? 1 : 0)
 
-#define        INIT_GFX_RANDOM()       (SimpleRND(1000000))
+#define        INIT_GFX_RANDOM()       (GetSimpleRandom(1000000))
 
 #define GET_NEW_PUSH_DELAY(e)  (   (element_info[e].push_delay_fixed) + \
                                 RND(element_info[e].push_delay_random))
                                 RND(element_info[e].move_delay_random))
 #define GET_MAX_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
                                    (element_info[e].move_delay_random))
-
-#define GET_TARGET_ELEMENT(e, ch)                                      \
-       ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element :     \
-        (e) == EL_TRIGGER_PLAYER  ? (ch)->actual_trigger_player : (e))
-
-#define GET_VALID_PLAYER_ELEMENT(e)                                    \
-       ((e) >= EL_PLAYER_1 && (e) <= EL_PLAYER_4 ? (e) : EL_PLAYER_1)
+#define GET_NEW_CE_VALUE(e)    (   (element_info[e].ce_value_fixed_initial) +\
+                                RND(element_info[e].ce_value_random_initial))
+#define GET_CE_SCORE(e)                (   (element_info[e].collect_score))
+#define GET_CHANGE_DELAY(c)    (   ((c)->delay_fixed  * (c)->delay_frames) + \
+                                RND((c)->delay_random * (c)->delay_frames))
+#define GET_CE_DELAY_VALUE(c)  (   ((c)->delay_fixed) + \
+                                RND((c)->delay_random))
+
+
+#define GET_VALID_RUNTIME_ELEMENT(e)                                   \
+        ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
+
+#define RESOLVED_REFERENCE_ELEMENT(be, e)                              \
+       ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START :     \
+        (be) + (e) - EL_SELF > EL_CUSTOM_END   ? EL_CUSTOM_END :       \
+        (be) + (e) - EL_SELF)
+
+#define GET_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   :  \
+        (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value  :  \
+        (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score  :  \
+        (e) == EL_CURRENT_CE_VALUE ? (cv) :                            \
+        (e) == EL_CURRENT_CE_SCORE ? (cs) :                            \
+        (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ?                   \
+        RESOLVED_REFERENCE_ELEMENT(be, e) :                            \
+        (e))
 
 #define CAN_GROW_INTO(e)                                               \
        ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
                                         IS_PLAYER(x, y) &&             \
                                         !PLAYER_ENEMY_PROTECTED(x, y))))
 
-#if 0
-#define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (condition) ||                  \
-                                       (DONT_COLLIDE_WITH(e) &&        \
-                                        IS_PLAYER(x, y) &&             \
-                                        !PLAYER_ENEMY_PROTECTED(x, y))))
-#endif
-
 #define ELEMENT_CAN_ENTER_FIELD(e, x, y)                               \
        ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
 
-#if 1
 #define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
        ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
-#else
-#define SATELLITE_CAN_ENTER_FIELD(x, y)                                        \
-       ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, Feld[x][y] == EL_ACID)
-#endif
 
-#if 0
-#define ENEMY_CAN_ENTER_FIELD(e, x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
-#endif
+#define ANDROID_CAN_ENTER_FIELD(e, x, y)                               \
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
+
+#define ANDROID_CAN_CLONE_FIELD(x, y)                                  \
+       (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
+                               CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
 
 #define ENEMY_CAN_ENTER_FIELD(e, x, y)                                 \
        ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
-#if 1
-
 #define YAMYAM_CAN_ENTER_FIELD(e, x, y)                                        \
        ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
 
        ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
 
 #define PENGUIN_CAN_ENTER_FIELD(e, x, y)                               \
-       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN ||\
+       ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
+                                                Feld[x][y] == EL_EM_EXIT_OPEN || \
+                                                Feld[x][y] == EL_STEEL_EXIT_OPEN || \
+                                                Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
                                                 IS_FOOD_PENGUIN(Feld[x][y])))
 #define DRAGON_CAN_ENTER_FIELD(e, x, y)                                        \
        ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 #define SPRING_CAN_ENTER_FIELD(e, x, y)                                        \
        ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
 
-#else
-
-#define YAMYAM_CAN_ENTER_FIELD(e, x, y)                                        \
-               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       Feld[x][y] == EL_DIAMOND))
-
-#define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y)                           \
-               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       IS_FOOD_DARK_YAMYAM(Feld[x][y])))
-
-#define PACMAN_CAN_ENTER_FIELD(e, x, y)                                        \
-               (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       IS_AMOEBOID(Feld[x][y])))
-
-#define PIG_CAN_ENTER_FIELD(e, x, y)                                   \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       IS_FOOD_PIG(Feld[x][y])))
-
-#define PENGUIN_CAN_ENTER_FIELD(e, x, y)                               \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       IS_FOOD_PENGUIN(Feld[x][y]) ||  \
-                                       Feld[x][y] == EL_EXIT_OPEN))
-
-#define DRAGON_CAN_ENTER_FIELD(e, x, y)                                        \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID)))
-
-#define MOLE_CAN_ENTER_FIELD(e, x, y, condition)                       \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID) ||      \
-                                       (condition)))
-
-#define SPRING_CAN_ENTER_FIELD(e, x, y)                                        \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       (CAN_MOVE_INTO_ACID(e) &&       \
-                                        Feld[x][y] == EL_ACID)))
-
-#endif
+#define SPRING_CAN_BUMP_FROM_FIELD(x, y)                               \
+       (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER ||   \
+                               Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
 
-#define GROUP_NR(e)            ((e) - EL_GROUP_START)
 #define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
-#define IS_IN_GROUP(e, nr)     (element_info[e].in_group[nr] == TRUE)
-#define IS_IN_GROUP_EL(e, ge)  (IS_IN_GROUP(e, (ge) - EL_GROUP_START))
-
-#define IS_EQUAL_OR_IN_GROUP(e, ge)                                    \
-       (IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge))
 
-#if 0
-#define CE_ENTER_FIELD_COND(e, x, y)                                   \
-               (!IS_PLAYER(x, y) &&                                    \
-                (Feld[x][y] == EL_ACID ||                              \
-                 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e))))
-#else
 #define CE_ENTER_FIELD_COND(e, x, y)                                   \
                (!IS_PLAYER(x, y) &&                                    \
                 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
-#endif
 
 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y)                                \
        ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
 #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);
 
 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
@@ -331,45 +998,43 @@ 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);
 static void CheckGravityMovement(struct PlayerInfo *);
 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
-static void KillHeroUnlessEnemyProtected(int, int);
-static void KillHeroUnlessExplosionProtected(int, int);
+static void KillPlayerUnlessEnemyProtected(int, int);
+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 ChangeElement(int, int, int);
+static void HandleElementChange(int, int, int);
+static void ExecuteCustomElementAction(int, int, int, int);
+static boolean ChangeElement(int, int, int, int);
 
 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
 #define CheckTriggeredElementChange(x, y, e, ev)                       \
-       CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY,      \
-                                      CH_SIDE_ANY, -1)
+       CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s)         \
        CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s)              \
        CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p)              \
-       CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY,      \
-                                      CH_SIDE_ANY, p)
+       CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
 
-static boolean CheckElementChangeExt(int, int, int, int, int, int, int, int);
+static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
 #define CheckElementChange(x, y, e, te, ev)                            \
-       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
 #define CheckElementChangeByPlayer(x, y, e, ev, p, s)                  \
-       CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s, CH_PAGE_ANY)
+       CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
 #define CheckElementChangeBySide(x, y, e, te, ev, s)                   \
-       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s, CH_PAGE_ANY)
-#define CheckElementChangeByPage(x, y, e, te, ev, p)                   \
-       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
+       CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
 
 static void PlayLevelSound(int, int, int);
 static void PlayLevelSoundNearest(int, int, int);
@@ -380,11 +1045,65 @@ 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);
+void AmoebeUmwandeln(int, int);
+void ContinueMoving(int, int);
+void Bang(int, int);
+void InitMovDir(int, int);
+void InitAmoebaNr(int, int);
+int NewHiScore(void);
+
+void TestIfGoodThingHitsBadThing(int, int, int);
+void TestIfBadThingHitsGoodThing(int, int, int);
+void TestIfPlayerTouchesBadThing(int, int);
+void TestIfPlayerRunsIntoBadThing(int, int, int);
+void TestIfBadThingTouchesPlayer(int, int);
+void TestIfBadThingRunsIntoPlayer(int, int, int);
+void TestIfFriendTouchesBadThing(int, int);
+void TestIfBadThingTouchesFriend(int, int);
+void TestIfBadThingTouchesOtherBadThing(int, int);
+void TestIfGoodThingGetsHitByBadThing(int, int, int);
+
+void KillPlayer(struct PlayerInfo *);
+void BuryPlayer(struct PlayerInfo *);
+void RemovePlayer(struct PlayerInfo *);
+
+static int getInvisibleActiveFromInvisibleElement(int);
+static int getInvisibleFromInvisibleActiveElement(int);
+
 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 
+/* for detection of endless loops, caused by custom element programming */
+/* (using maximal playfield width x 10 is just a rough approximation) */
+#define MAX_ELEMENT_CHANGE_RECURSION_DEPTH     (MAX_PLAYFIELD_WIDTH * 10)
+
+#define RECURSION_LOOP_DETECTION_START(e, rc)                          \
+{                                                                      \
+  if (recursion_loop_detected)                                         \
+    return (rc);                                                       \
+                                                                       \
+  if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH)       \
+  {                                                                    \
+    recursion_loop_detected = TRUE;                                    \
+    recursion_loop_element = (e);                                      \
+  }                                                                    \
+                                                                       \
+  recursion_loop_depth++;                                              \
+}
+
+#define RECURSION_LOOP_DETECTION_END()                                 \
+{                                                                      \
+  recursion_loop_depth--;                                              \
+}
+
+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  */
@@ -392,19 +1111,22 @@ static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 /* ------------------------------------------------------------------------- */
 
 /* forward declaration for changer functions */
-static void InitBuggyBase(int x, int y);
-static void WarnBuggyBase(int x, int y);
+static void InitBuggyBase(int, int);
+static void WarnBuggyBase(int, int);
+
+static void InitTrap(int, int);
+static void ActivateTrap(int, int);
+static void ChangeActiveTrap(int, int);
 
-static void InitTrap(int x, int y);
-static void ActivateTrap(int x, int y);
-static void ChangeActiveTrap(int x, int y);
+static void InitRobotWheel(int, int);
+static void RunRobotWheel(int, int);
+static void StopRobotWheel(int, int);
 
-static void InitRobotWheel(int x, int y);
-static void RunRobotWheel(int x, int y);
-static void StopRobotWheel(int x, int y);
+static void InitTimegateWheel(int, int);
+static void RunTimegateWheel(int, int);
 
-static void InitTimegateWheel(int x, int y);
-static void RunTimegateWheel(int x, int y);
+static void InitMagicBallDelay(int, int);
+static void ActivateMagicBall(int, int);
 
 struct ChangingElementInfo
 {
@@ -450,6 +1172,54 @@ static struct ChangingElementInfo change_delay_list[] =
     NULL,
     NULL
   },
+  {
+    EL_STEEL_EXIT_OPENING,
+    EL_STEEL_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_STEEL_EXIT_CLOSING,
+    EL_STEEL_EXIT_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_EXIT_OPENING,
+    EL_EM_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_EXIT_CLOSING,
+    EL_EMPTY,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_STEEL_EXIT_OPENING,
+    EL_EM_STEEL_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_STEEL_EXIT_CLOSING,
+    EL_STEELWALL,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
   {
     EL_SP_EXIT_OPENING,
     EL_SP_EXIT_OPEN,
@@ -571,6 +1341,46 @@ static struct ChangingElementInfo change_delay_list[] =
     RunTimegateWheel,
     NULL
   },
+  {
+    EL_DC_TIMEGATE_SWITCH_ACTIVE,
+    EL_DC_TIMEGATE_SWITCH,
+    0,
+    InitTimegateWheel,
+    RunTimegateWheel,
+    NULL
+  },
+  {
+    EL_EMC_MAGIC_BALL_ACTIVE,
+    EL_EMC_MAGIC_BALL_ACTIVE,
+    0,
+    InitMagicBallDelay,
+    NULL,
+    ActivateMagicBall
+  },
+  {
+    EL_EMC_SPRING_BUMPER_ACTIVE,
+    EL_EMC_SPRING_BUMPER,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_DIAGONAL_SHRINKING,
+    EL_UNDEFINED,
+    0,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_DIAGONAL_GROWING,
+    EL_UNDEFINED,
+    0,
+    NULL,
+    NULL,
+    NULL,
+  },
 
   {
     EL_UNDEFINED,
@@ -611,10 +1421,14 @@ move_stepsize_list[] =
   { EL_AMOEBA_DROPPING,                2 },
   { EL_QUICKSAND_FILLING,      1 },
   { EL_QUICKSAND_EMPTYING,     1 },
+  { EL_QUICKSAND_FAST_FILLING, 2 },
+  { EL_QUICKSAND_FAST_EMPTYING,        2 },
   { EL_MAGIC_WALL_FILLING,     2 },
-  { EL_BD_MAGIC_WALL_FILLING,  2 },
   { EL_MAGIC_WALL_EMPTYING,    2 },
+  { EL_BD_MAGIC_WALL_FILLING,  2 },
   { EL_BD_MAGIC_WALL_EMPTYING, 2 },
+  { EL_DC_MAGIC_WALL_FILLING,  2 },
+  { EL_DC_MAGIC_WALL_EMPTYING, 2 },
 
   { EL_UNDEFINED,              0 },
 };
@@ -678,7 +1492,7 @@ access_direction_list[] =
   { EL_SP_GRAVITY_OFF_PORT_UP,                                      MV_DOWN },
   { EL_SP_GRAVITY_OFF_PORT_DOWN,                            MV_UP           },
 
-  { EL_UNDEFINED,                      MV_NO_MOVING                         }
+  { EL_UNDEFINED,                      MV_NONE                              }
 };
 
 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
@@ -690,9 +1504,98 @@ static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
 
 #define CE_PAGE(e, ce)         (element_info[e].event_page[ce])
 
+/* static variables for playfield scan mode (scanning forward or backward) */
+static int playfield_scan_start_x = 0;
+static int playfield_scan_start_y = 0;
+static int playfield_scan_delta_x = 1;
+static int playfield_scan_delta_y = 1;
+
+#define SCAN_PLAYFIELD(x, y)   for ((y) = playfield_scan_start_y;      \
+                                    (y) >= 0 && (y) <= lev_fieldy - 1; \
+                                    (y) += playfield_scan_delta_y)     \
+                               for ((x) = playfield_scan_start_x;      \
+                                    (x) >= 0 && (x) <= lev_fieldx - 1; \
+                                    (x) += playfield_scan_delta_x)
+
+#ifdef DEBUG
+void DEBUG_SetMaximumDynamite()
+{
+  int i;
+
+  for (i = 0; i < MAX_INVENTORY_SIZE; i++)
+    if (local_player->inventory_size < MAX_INVENTORY_SIZE)
+      local_player->inventory_element[local_player->inventory_size++] =
+       EL_DYNAMITE;
+}
+#endif
+
+static void InitPlayfieldScanModeVars()
+{
+  if (game.use_reverse_scan_direction)
+  {
+    playfield_scan_start_x = lev_fieldx - 1;
+    playfield_scan_start_y = lev_fieldy - 1;
+
+    playfield_scan_delta_x = -1;
+    playfield_scan_delta_y = -1;
+  }
+  else
+  {
+    playfield_scan_start_x = 0;
+    playfield_scan_start_y = 0;
+
+    playfield_scan_delta_x = 1;
+    playfield_scan_delta_y = 1;
+  }
+}
+
+static void InitPlayfieldScanMode(int mode)
+{
+  game.use_reverse_scan_direction =
+    (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
+
+  InitPlayfieldScanModeVars();
+}
+
+static int get_move_delay_from_stepsize(int move_stepsize)
+{
+  move_stepsize =
+    MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
+
+  /* make sure that stepsize value is always a power of 2 */
+  move_stepsize = (1 << log_2(move_stepsize));
+
+  return TILEX / move_stepsize;
+}
+
+static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
+                              boolean init_game)
+{
+  int player_nr = player->index_nr;
+  int move_delay = get_move_delay_from_stepsize(move_stepsize);
+  boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
+
+  /* do no immediately change move delay -- the player might just be moving */
+  player->move_delay_value_next = move_delay;
+
+  /* information if player can move must be set separately */
+  player->cannot_move = cannot_move;
+
+  if (init_game)
+  {
+    player->move_delay       = game.initial_move_delay[player_nr];
+    player->move_delay_value = game.initial_move_delay_value[player_nr];
+
+    player->move_delay_value_next = -1;
+
+    player->move_delay_reset_counter = 0;
+  }
+}
 
 void GetPlayerConfig()
 {
+  GameFrameDelay = setup.game_frame_delay;
+
   if (!audio.sound_available)
     setup.sound_simple = FALSE;
 
@@ -711,55 +1614,30 @@ void GetPlayerConfig()
   InitJoysticks();
 }
 
-static int getBeltNrFromBeltElement(int element)
-{
-  return (element < EL_CONVEYOR_BELT_2_LEFT ? 0 :
-         element < EL_CONVEYOR_BELT_3_LEFT ? 1 :
-         element < EL_CONVEYOR_BELT_4_LEFT ? 2 : 3);
-}
-
-static int getBeltNrFromBeltActiveElement(int element)
+int GetElementFromGroupElement(int element)
 {
-  return (element < EL_CONVEYOR_BELT_2_LEFT_ACTIVE ? 0 :
-         element < EL_CONVEYOR_BELT_3_LEFT_ACTIVE ? 1 :
-         element < EL_CONVEYOR_BELT_4_LEFT_ACTIVE ? 2 : 3);
-}
-
-static int getBeltNrFromBeltSwitchElement(int element)
-{
-  return (element < EL_CONVEYOR_BELT_2_SWITCH_LEFT ? 0 :
-         element < EL_CONVEYOR_BELT_3_SWITCH_LEFT ? 1 :
-         element < EL_CONVEYOR_BELT_4_SWITCH_LEFT ? 2 : 3);
-}
-
-static int getBeltDirNrFromBeltSwitchElement(int element)
-{
-  static int belt_base_element[4] =
+  if (IS_GROUP_ELEMENT(element))
   {
-    EL_CONVEYOR_BELT_1_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_2_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_3_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_4_SWITCH_LEFT
-  };
+    struct ElementGroupInfo *group = element_info[element].group;
+    int last_anim_random_frame = gfx.anim_random_frame;
+    int element_pos;
 
-  int belt_nr = getBeltNrFromBeltSwitchElement(element);
-  int belt_dir_nr = element - belt_base_element[belt_nr];
+    if (group->choice_mode == ANIM_RANDOM)
+      gfx.anim_random_frame = RND(group->num_elements_resolved);
 
-  return (belt_dir_nr % 3);
-}
+    element_pos = getAnimationFrame(group->num_elements_resolved, 1,
+                                   group->choice_mode, 0,
+                                   group->choice_pos);
 
-static int getBeltDirFromBeltSwitchElement(int element)
-{
-  static int belt_move_dir[3] =
-  {
-    MV_LEFT,
-    MV_NO_MOVING,
-    MV_RIGHT
-  };
+    if (group->choice_mode == ANIM_RANDOM)
+      gfx.anim_random_frame = last_anim_random_frame;
 
-  int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(element);
+    group->choice_pos++;
+
+    element = group->element_resolved[element_pos];
+  }
 
-  return belt_move_dir[belt_dir_nr];
+  return element;
 }
 
 static void InitPlayerField(int x, int y, int element, boolean init_game)
@@ -776,7 +1654,11 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
       }
       else
       {
-       stored_player[0].use_murphy_graphic = TRUE;
+       stored_player[0].initial_element = element;
+       stored_player[0].use_murphy = TRUE;
+
+       if (!level.use_artwork_element[0])
+         stored_player[0].artwork_element = EL_SP_MURPHY;
       }
 
       Feld[x][y] = EL_PLAYER_1;
@@ -794,9 +1676,6 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
                                level.sp_block_last_field :
                                level.block_last_field);
 
-#if USE_NEW_BLOCK_STYLE
-#if 1
-
     /* ---------- initialize player's last field block delay --------------- */
 
     /* always start with reliable default value (no adjustment needed) */
@@ -810,49 +1689,6 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
     if (game.use_block_last_field_bug)
       player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
 
-#if 0
-    /* blocking the last field when moving was corrected in version 3.1.1 */
-    if (game.use_block_last_field_bug)
-    {
-      /* even "not blocking" was blocking the last field for one frame */
-      level.block_delay    = (level.block_last_field    ? 7 : 1);
-      level.sp_block_delay = (level.sp_block_last_field ? 7 : 1);
-
-      level.block_last_field = TRUE;
-      level.sp_block_last_field = TRUE;
-    }
-#endif
-
-#if 0  /* !!! THIS IS NOT A LEVEL SETTING => REMOVED !!! */
-    level.block_delay = 8;             /* when blocking, block 8 frames */
-    level.sp_block_delay = 9;          /* SP indeed blocks 9 frames, not 8 */
-#endif
-
-#if 0
-    printf("::: %d, %d\n", level.block_delay, level.sp_block_delay);
-#endif
-
-#else
-
-#if 1
-    player->block_delay = (player->block_last_field ?
-                          (element == EL_SP_MURPHY ?
-                           level.sp_block_delay :
-                           level.block_delay) : 0);
-#else
-    player->block_delay = (element == EL_SP_MURPHY ?
-                          (player->block_last_field ? 7 : 1) :
-                          (player->block_last_field ? 7 : 1));
-#endif
-
-#endif
-
-#if 0
-    printf("::: block_last_field == %d, block_delay = %d\n",
-          player->block_last_field, player->block_delay);
-#endif
-#endif
-
     if (!options.network || player->connected)
     {
       player->active = TRUE;
@@ -863,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)
@@ -918,41 +1765,45 @@ static void InitField(int x, int y, boolean init_game)
        Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
       break;
 
+    case EL_BUG:
     case EL_BUG_RIGHT:
     case EL_BUG_UP:
     case EL_BUG_LEFT:
     case EL_BUG_DOWN:
-    case EL_BUG:
+    case EL_SPACESHIP:
     case EL_SPACESHIP_RIGHT:
     case EL_SPACESHIP_UP:
     case EL_SPACESHIP_LEFT:
     case EL_SPACESHIP_DOWN:
-    case EL_SPACESHIP:
+    case EL_BD_BUTTERFLY:
     case EL_BD_BUTTERFLY_RIGHT:
     case EL_BD_BUTTERFLY_UP:
     case EL_BD_BUTTERFLY_LEFT:
     case EL_BD_BUTTERFLY_DOWN:
-    case EL_BD_BUTTERFLY:
+    case EL_BD_FIREFLY:
     case EL_BD_FIREFLY_RIGHT:
     case EL_BD_FIREFLY_UP:
     case EL_BD_FIREFLY_LEFT:
     case EL_BD_FIREFLY_DOWN:
-    case EL_BD_FIREFLY:
     case EL_PACMAN_RIGHT:
     case EL_PACMAN_UP:
     case EL_PACMAN_LEFT:
     case EL_PACMAN_DOWN:
     case EL_YAMYAM:
+    case EL_YAMYAM_LEFT:
+    case EL_YAMYAM_RIGHT:
+    case EL_YAMYAM_UP:
+    case EL_YAMYAM_DOWN:
     case EL_DARK_YAMYAM:
     case EL_ROBOT:
     case EL_PACMAN:
     case EL_SP_SNIKSNAK:
     case EL_SP_ELECTRON:
+    case EL_MOLE:
     case EL_MOLE_LEFT:
     case EL_MOLE_RIGHT:
     case EL_MOLE_UP:
     case EL_MOLE_DOWN:
-    case EL_MOLE:
       InitMovDir(x, y);
       break;
 
@@ -978,6 +1829,10 @@ static void InitField(int x, int y, boolean init_game)
       MovDelay[x][y] = 96;
       break;
 
+    case EL_EM_DYNAMITE_ACTIVE:
+      MovDelay[x][y] = 32;
+      break;
+
     case EL_LAMP:
       local_player->lights_still_needed++;
       break;
@@ -991,27 +1846,6 @@ static void InitField(int x, int y, boolean init_game)
       GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
       break;
 
-#if 0
-    case EL_SP_EMPTY:
-      Feld[x][y] = EL_EMPTY;
-      break;
-#endif
-
-#if 0
-    case EL_EM_KEY_1_FILE:
-      Feld[x][y] = EL_EM_KEY_1;
-      break;
-    case EL_EM_KEY_2_FILE:
-      Feld[x][y] = EL_EM_KEY_2;
-      break;
-    case EL_EM_KEY_3_FILE:
-      Feld[x][y] = EL_EM_KEY_3;
-      break;
-    case EL_EM_KEY_4_FILE:
-      Feld[x][y] = EL_EM_KEY_4;
-      break;
-#endif
-
     case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
     case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
     case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
@@ -1042,46 +1876,81 @@ static void InitField(int x, int y, boolean init_game)
       }
       break;
 
-    case EL_SWITCHGATE_SWITCH_DOWN:    /* always start with same switch pos */
-      if (init_game)
-       Feld[x][y] = EL_SWITCHGATE_SWITCH_UP;
-      break;
-
     case EL_LIGHT_SWITCH_ACTIVE:
       if (init_game)
        game.light_time_left = level.time_light * FRAMES_PER_SECOND;
       break;
 
-    default:
-      if (IS_CUSTOM_ELEMENT(element) && CAN_MOVE(element))
-       InitMovDir(x, y);
-      else if (IS_GROUP_ELEMENT(element))
-      {
-       struct ElementGroupInfo *group = element_info[element].group;
-       int last_anim_random_frame = gfx.anim_random_frame;
-       int element_pos;
+    case EL_INVISIBLE_STEELWALL:
+    case EL_INVISIBLE_WALL:
+    case EL_INVISIBLE_SAND:
+      if (game.light_time_left > 0 ||
+         game.lenses_time_left > 0)
+        Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+      break;
 
-       if (group->choice_mode == ANIM_RANDOM)
-         gfx.anim_random_frame = RND(group->num_elements_resolved);
+    case EL_EMC_MAGIC_BALL:
+      if (game.ball_state)
+       Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
+      break;
 
-       element_pos = getAnimationFrame(group->num_elements_resolved, 1,
-                                       group->choice_mode, 0,
-                                       group->choice_pos);
+    case EL_EMC_MAGIC_BALL_SWITCH:
+      if (game.ball_state)
+       Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
+      break;
 
-       if (group->choice_mode == ANIM_RANDOM)
-         gfx.anim_random_frame = last_anim_random_frame;
+    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;
 
-       group->choice_pos++;
+    default:
+      if (IS_CUSTOM_ELEMENT(element))
+      {
+       if (CAN_MOVE(element))
+         InitMovDir(x, y);
 
-       Feld[x][y] = group->element_resolved[element_pos];
+       if (!element_info[element].use_last_ce_value || init_game)
+         CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
+      }
+      else if (IS_GROUP_ELEMENT(element))
+      {
+       Feld[x][y] = GetElementFromGroupElement(element);
 
        InitField(x, y, init_game);
       }
+
       break;
   }
+
+  if (!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);
 
@@ -1091,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];
 
@@ -1112,158 +1981,620 @@ static inline void InitField_WithBug2(int x, int y, boolean init_game)
   */
 }
 
-inline void DrawGameValue_Emeralds(int value)
+static int get_key_element_from_nr(int key_nr)
 {
-  DrawText(DX_EMERALDS, DY_EMERALDS, int2str(value, 3), FONT_TEXT_2);
+  int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
+                         level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                         EL_EM_KEY_1 : EL_KEY_1);
+
+  return key_base_element + key_nr;
 }
 
-inline void DrawGameValue_Dynamite(int value)
+static int get_next_dropped_element(struct PlayerInfo *player)
 {
-  DrawText(DX_DYNAMITE, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2);
+  return (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);
 }
 
-inline void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
+static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
 {
-  int i;
+  /* pos >= 0: get element from bottom of the stack;
+     pos <  0: get element from top of the stack */
 
-  /* currently only 4 of 8 possible keys are displayed */
-  for (i = 0; i < STD_NUM_KEYS; i++)
-    if (key[i])
-      DrawMiniGraphicExt(drawto, DX_KEYS + i * MINI_TILEX, DY_KEYS,
-                        el2edimg(EL_KEY_1 + i));
-}
+  if (pos < 0)
+  {
+    int min_inventory_size = -pos;
+    int inventory_pos = player->inventory_size - min_inventory_size;
+    int min_dynabombs_left = min_inventory_size - player->inventory_size;
 
-inline void DrawGameValue_Score(int value)
-{
-  DrawText(DX_SCORE, DY_SCORE, int2str(value, 5), FONT_TEXT_2);
+    return (player->inventory_size >= min_inventory_size ?
+           player->inventory_element[inventory_pos] :
+           player->inventory_infinite_element != EL_UNDEFINED ?
+           player->inventory_infinite_element :
+           player->dynabombs_left >= min_dynabombs_left ?
+           EL_DYNABOMB_PLAYER_1 + player->index_nr :
+           EL_UNDEFINED);
+  }
+  else
+  {
+    int min_dynabombs_left = pos + 1;
+    int min_inventory_size = pos + 1 - player->dynabombs_left;
+    int inventory_pos = pos - player->dynabombs_left;
+
+    return (player->inventory_infinite_element != EL_UNDEFINED ?
+           player->inventory_infinite_element :
+           player->dynabombs_left >= min_dynabombs_left ?
+           EL_DYNABOMB_PLAYER_1 + player->index_nr :
+           player->inventory_size >= min_inventory_size ?
+           player->inventory_element[inventory_pos] :
+           EL_UNDEFINED);
+  }
 }
 
-inline void DrawGameValue_Time(int value)
+static int compareGamePanelOrderInfo(const void *object1, const void *object2)
 {
-  if (value < 1000)
-    DrawText(DX_TIME1, DY_TIME, int2str(value, 3), FONT_TEXT_2);
+  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
-    DrawText(DX_TIME2, DY_TIME, int2str(value, 4), FONT_LEVEL_NUMBER);
+    compare_result = gpo1->nr - gpo2->nr;
+
+  return compare_result;
 }
 
-inline void DrawGameValue_Level(int value)
+void InitGameControlValues()
 {
-  if (level_nr < 100)
-    DrawText(DX_LEVEL, DY_LEVEL, int2str(value, 2), FONT_TEXT_2);
-  else
+  int i;
+
+  for (i = 0; game_panel_controls[i].nr != -1; i++)
   {
-    /* misuse area for displaying emeralds to draw bigger level number */
-    DrawTextExt(drawto, DX_EMERALDS, DY_EMERALDS,
-               int2str(value, 3), FONT_LEVEL_NUMBER, BLIT_OPAQUE);
+    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;
 
-    /* now copy it to the area for displaying level number */
-    BlitBitmap(drawto, drawto,
-              DX_EMERALDS, DY_EMERALDS + 1,
-              getFontWidth(FONT_LEVEL_NUMBER) * 3,
-              getFontHeight(FONT_LEVEL_NUMBER) - 1,
-              DX_LEVEL - 1, DY_LEVEL + 1);
+    if (nr != i)
+    {
+      Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
+      Error(ERR_EXIT, "this should not happen -- please debug");
+    }
 
-    /* restore the area for displaying emeralds */
-    DrawGameValue_Emeralds(local_player->gems_still_needed);
+    /* force update of game controls after initialization */
+    gpc->value = gpc->last_value = -1;
+    gpc->frame = gpc->last_frame = -1;
+    gpc->gfx_frame = -1;
+
+    /* determine panel value width for later calculation of alignment */
+    if (type == TYPE_INTEGER || type == TYPE_STRING)
+    {
+      pos->width = pos->size * getFontWidth(pos->font);
+      pos->height = getFontHeight(pos->font);
+    }
+    else if (type == TYPE_ELEMENT)
+    {
+      pos->width = pos->size;
+      pos->height = pos->size;
+    }
 
-    /* yes, this is all really ugly :-) */
+    /* 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 DrawAllGameValues(int emeralds, int dynamite, int score, int time,
-                      int key_bits)
+void UpdatePlayfieldElementCount()
 {
-  int key[MAX_NUM_KEYS];
-  int i;
+  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 = (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 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 = gems;
+
+  game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
   for (i = 0; i < MAX_NUM_KEYS; i++)
-    key[i] = key_bits & (1 << i);
+    game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
+  game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
+  game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
 
-  DrawGameValue_Level(level_nr);
+  if (game.centered_player_nr == -1)
+  {
+    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;
 
-  DrawGameValue_Emeralds(emeralds);
-  DrawGameValue_Dynamite(dynamite);
-  DrawGameValue_Score(score);
-  DrawGameValue_Time(time);
+      for (k = 0; k < MAX_NUM_KEYS; 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);
+      }
 
-  DrawGameValue_Keys(key);
-}
+      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;
 
-void DrawGameDoorValues()
-{
-  int i;
+      if (stored_player[i].num_white_keys > 0)
+       game_panel_controls[GAME_PANEL_KEY_WHITE].value =
+         EL_DC_KEY_WHITE;
 
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+      game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+       stored_player[i].num_white_keys;
+    }
+  }
+  else
   {
-    DrawGameDoorValues_EM();
+    int player_nr = game.centered_player_nr;
 
-    return;
+    for (k = 0; k < MAX_NUM_KEYS; 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);
+    }
+
+    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;
+
+    game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+      stored_player[player_nr].num_white_keys;
   }
 
-  DrawGameValue_Level(level_nr);
+  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);
+    game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
+      get_inventory_element_from_pos(local_player, -i - 1);
+  }
 
-  DrawGameValue_Emeralds(local_player->gems_still_needed);
-  DrawGameValue_Dynamite(local_player->inventory_size);
-  DrawGameValue_Score(local_player->score);
-  DrawGameValue_Time(TimeLeft);
+  game_panel_controls[GAME_PANEL_SCORE].value = score;
+  game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    DrawGameValue_Keys(stored_player[i].key);
+  game_panel_controls[GAME_PANEL_TIME].value = time;
+
+  game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
+  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);
+  game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
+    local_player->shield_normal_time_left;
+  game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
+    (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
+     EL_EMPTY);
+  game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
+    local_player->shield_deadly_time_left;
+
+  game_panel_controls[GAME_PANEL_EXIT].value =
+    (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
+
+  game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
+    (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
+  game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
+    (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
+     EL_EMC_MAGIC_BALL_SWITCH);
+
+  game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
+    (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
+  game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
+    game.light_time_left;
+
+  game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
+    (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
+  game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
+    game.timegate_time_left;
+
+  game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
+    EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
+
+  game_panel_controls[GAME_PANEL_EMC_LENSES].value =
+    (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
+  game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
+    game.lenses_time_left;
+
+  game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
+    (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
+  game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
+    game.magnify_time_left;
+
+  game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
+    (game.wind_direction == MV_LEFT  ? EL_BALLOON_SWITCH_LEFT  :
+     game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
+     game.wind_direction == MV_UP    ? EL_BALLOON_SWITCH_UP    :
+     game.wind_direction == MV_DOWN  ? EL_BALLOON_SWITCH_DOWN  :
+     EL_BALLOON_SWITCH_NONE);
+
+  game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
+    local_player->dynabomb_count;
+  game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
+    local_player->dynabomb_size;
+  game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
+    (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
+
+  game_panel_controls[GAME_PANEL_PENGUINS].value =
+    local_player->friends_still_needed;
+
+  game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
+    local_player->sokobanfields_still_needed;
+  game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
+    local_player->sokobanfields_still_needed;
+
+  game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
+    (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
+
+  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;
+
+  game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
+    local_player->gravity;
+
+  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 != EL_UNDEFINED && gpc->value != EL_EMPTY)
+      {
+       int last_anim_random_frame = gfx.anim_random_frame;
+       int element = gpc->value;
+       int graphic = el2panelimg(element);
+
+       if (gpc->value != gpc->last_value)
+       {
+         gpc->gfx_frame = 0;
+         gpc->gfx_random = INIT_GFX_RANDOM();
+       }
+       else
+       {
+         gpc->gfx_frame++;
+
+         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;
+      }
+    }
+  }
 }
 
-static void resolve_group_element(int group_element, int recursion_depth)
+void DisplayGameControlValues()
 {
-  static int group_nr;
-  static struct ElementGroupInfo *group;
-  struct ElementGroupInfo *actual_group = element_info[group_element].group;
+  boolean redraw_panel = FALSE;
   int i;
 
-  if (recursion_depth > NUM_GROUP_ELEMENTS)    /* recursion too deep */
+  for (i = 0; game_panel_controls[i].nr != -1; i++)
   {
-    Error(ERR_WARN, "recursion too deep when resolving group element %d",
-         group_element - EL_GROUP_START + 1);
+    struct GamePanelControlInfo *gpc = &game_panel_controls[i];
 
-    /* replace element which caused too deep recursion by question mark */
-    group->element_resolved[group->num_elements_resolved++] = EL_UNKNOWN;
+    if (PANEL_DEACTIVATED(gpc->pos))
+      continue;
 
-    return;
+    if (gpc->value == gpc->last_value &&
+       gpc->frame == gpc->last_frame)
+      continue;
+
+    redraw_panel = TRUE;
   }
 
-  if (recursion_depth == 0)                    /* initialization */
-  {
-    group = element_info[group_element].group;
-    group_nr = group_element - EL_GROUP_START;
+  if (!redraw_panel)
+    return;
 
-    group->num_elements_resolved = 0;
-    group->choice_pos = 0;
-  }
+  /* 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 < actual_group->num_elements; i++)
+  for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
   {
-    int element = actual_group->element[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 size = pos->size;
+    int font = pos->font;
+    boolean draw_masked = pos->draw_masked;
+    int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
 
-    if (group->num_elements_resolved == NUM_FILE_ELEMENTS)
-      break;
+    if (PANEL_DEACTIVATED(pos))
+      continue;
 
-    if (IS_GROUP_ELEMENT(element))
-      resolve_group_element(element, recursion_depth + 1);
-    else
+    gpc->last_value = value;
+    gpc->last_frame = frame;
+
+    if (type == TYPE_INTEGER)
     {
-      group->element_resolved[group->num_elements_resolved++] = element;
-      element_info[element].in_group[group_nr] = TRUE;
+      if (nr == GAME_PANEL_LEVEL_NUMBER ||
+         nr == GAME_PANEL_TIME)
+      {
+       boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
+
+       if (use_dynamic_size)           /* use dynamic number of digits */
+       {
+         int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
+         int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
+         int size2 = size1 + 1;
+         int font1 = pos->font;
+         int font2 = pos->font_alt;
+
+         size = (value < value_change ? size1 : size2);
+         font = (value < value_change ? font1 : font2);
+       }
+      }
+
+      /* 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 0
-  if (recursion_depth == 0 && group_element <= EL_GROUP_4)
-  {
-    printf("::: group %d: %d resolved elements\n",
-          group_element - EL_GROUP_START, group->num_elements_resolved);
-    for (i = 0; i < group->num_elements_resolved; i++)
-      printf("::: - %d ['%s']\n", group->element_resolved[i],
-            element_info[group->element_resolved[i]].token_name);
+      if (value != EL_UNDEFINED && value != EL_EMPTY)
+      {
+       element = value;
+       graphic = el2panelimg(value);
+
+       // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), 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)
+    {
+      boolean active = (value != 0);
+      char *state_normal = "off";
+      char *state_active = "on";
+      char *state = (active ? state_active : state_normal);
+      char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
+                nr == GAME_PANEL_PLAYER_NAME   ? setup.player_name :
+                nr == GAME_PANEL_LEVEL_NAME    ? level.name :
+                nr == GAME_PANEL_LEVEL_AUTHOR  ? level.author : NULL);
+
+      if (nr == GAME_PANEL_GRAVITY_STATE)
+      {
+       int font1 = pos->font;          /* (used for normal state) */
+       int font2 = pos->font_alt;      /* (used for active state) */
+
+       font = (active ? font2 : font1);
+      }
+
+      if (s != NULL)
+      {
+       char *s_cut;
+
+       if (size <= 0)
+       {
+         /* don't truncate output if "chars" is zero or less */
+         size = strlen(s);
+
+         /* dynamically correct text alignment */
+         pos->width = size * getFontWidth(font);
+       }
+
+       s_cut = getStringCopyN(s, size);
+
+       DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+                   s_cut, font, mask_mode);
+
+       free(s_cut);
+      }
+    }
+
+    redraw_mask |= REDRAW_DOOR_1;
   }
-#endif
+
+  SetGameStatus(GAME_MODE_PLAYING);
+}
+
+void UpdateAndDisplayGameControlValues()
+{
+  if (tape.deactivate_display)
+    return;
+
+  UpdateGameControlValues();
+  DisplayGameControlValues();
+}
+
+void UpdateGameDoorValues()
+{
+  UpdateGameControlValues();
+}
+
+void DrawGameDoorValues()
+{
+  DisplayGameControlValues();
 }
 
 
@@ -1277,12 +2608,27 @@ static void resolve_group_element(int group_element, int recursion_depth)
 
 static void InitGameEngine()
 {
-  int i, j, k, l;
+  int i, j, k, l, x, y;
 
   /* set game engine from tape file when re-playing, else from level file */
   game.engine_version = (tape.playing ? tape.engine_version :
                         level.game_version);
 
+  /* set 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 */
   /* ---------------------------------------------------------------------- */
@@ -1351,6 +2697,12 @@ static void InitGameEngine()
 
   /* ---------------------------------------------------------------------- */
 
+  /* 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);
+
   /* dynamically adjust element properties according to game engine version */
   InitElementPropertiesEngine(game.engine_version);
 
@@ -1362,35 +2714,18 @@ static void InitGameEngine()
   printf("       => game.engine_version == %06d\n", game.engine_version);
 #endif
 
-  /* ---------- recursively resolve group elements ------------------------- */
-
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    for (j = 0; j < NUM_GROUP_ELEMENTS; j++)
-      element_info[i].in_group[j] = FALSE;
-
-  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
-    resolve_group_element(EL_GROUP_START + i, 0);
-
   /* ---------- initialize player's initial move delay --------------------- */
 
-#if USE_NEW_MOVE_DELAY
   /* dynamically adjust player properties according to level information */
-  game.initial_move_delay_value =
-    (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.initial_move_delay_value[i] =
+      get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
 
   /* dynamically adjust player properties according to game engine version */
-  game.initial_move_delay = (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
-                            game.initial_move_delay_value : 0);
-#else
-  /* dynamically adjust player properties according to game engine version */
-  game.initial_move_delay =
-    (game.engine_version <= VERSION_IDENT(2,0,1,0) ? INITIAL_MOVE_DELAY_ON :
-     INITIAL_MOVE_DELAY_OFF);
-
-  /* dynamically adjust player properties according to level information */
-  game.initial_move_delay_value =
-    (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
-#endif
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.initial_move_delay[i] =
+      (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
+       game.initial_move_delay_value[i] : 0);
 
   /* ---------- initialize player's initial push delay --------------------- */
 
@@ -1438,14 +2773,29 @@ static void InitGameEngine()
     ei->change->change_function      = ch_delay->change_function;
     ei->change->post_change_function = ch_delay->post_change_function;
 
+    ei->change->can_change = TRUE;
+    ei->change->can_change_or_has_action = TRUE;
+
     ei->has_change_event[CE_DELAY] = TRUE;
 
-#if 1
     SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
-#endif
+    SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
+  }
+
+  /* ---------- initialize internal run-time variables --------------------- */
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      ei->change_page[j].can_change_or_has_action =
+       (ei->change_page[j].can_change |
+        ei->change_page[j].has_action);
+    }
   }
 
-#if 1
   /* add change events from custom element configuration */
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
@@ -1453,7 +2803,7 @@ static void InitGameEngine()
 
     for (j = 0; j < ei->num_change_pages; j++)
     {
-      if (!ei->change_page[j].can_change)
+      if (!ei->change_page[j].can_change_or_has_action)
        continue;
 
       for (k = 0; k < NUM_CHANGE_EVENTS; k++)
@@ -1470,20 +2820,26 @@ static void InitGameEngine()
     }
   }
 
-#else
+  /* ---------- initialize reference elements in change conditions --------- */
 
-  /* add change events from custom element configuration */
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
+    struct ElementInfo *ei = &element_info[element];
 
-    /* only add custom elements that change after fixed/random frame delay */
-    if (CAN_CHANGE(element) && HAS_CHANGE_EVENT(element, CE_DELAY))
-      element_info[element].has_change_event[CE_DELAY] = TRUE;
-  }
-#endif
+    for (j = 0; j < ei->num_change_pages; j++)
+    {
+      int trigger_element = ei->change_page[j].initial_trigger_element;
 
-  /* ---------- initialize run-time trigger player and 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++)
   {
@@ -1492,7 +2848,11 @@ 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;
     }
   }
 
@@ -1503,7 +2863,6 @@ static void InitGameEngine()
     for (j = 0; j < NUM_CHANGE_EVENTS; j++)
       trigger_events[i][j] = FALSE;
 
-#if 1
   /* add trigger events from element change event properties */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
   {
@@ -1511,7 +2870,7 @@ static void InitGameEngine()
 
     for (j = 0; j < ei->num_change_pages; j++)
     {
-      if (!ei->change_page[j].can_change)
+      if (!ei->change_page[j].can_change_or_has_action)
        continue;
 
       if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
@@ -1530,6 +2889,9 @@ static void InitGameEngine()
              for (l = 0; l < group->num_elements_resolved; l++)
                trigger_events[group->element_resolved[l]][k] = TRUE;
            }
+           else if (trigger_element == EL_ANY_ELEMENT)
+             for (l = 0; l < MAX_NUM_ELEMENTS; l++)
+               trigger_events[l][k] = TRUE;
            else
              trigger_events[trigger_element][k] = TRUE;
          }
@@ -1537,14 +2899,6 @@ static void InitGameEngine()
       }
     }
   }
-#else
-  /* add trigger events from element change event properties */
-  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    if (HAS_CHANGE_EVENT(i, CE_BY_OTHER_ACTION))
-      for (j = 0; j < NUM_CHANGE_EVENTS; j++)
-       if (element_info[i].change->has_event[j])
-         trigger_events[element_info[i].change->trigger_element][j] = TRUE;
-#endif
 
   /* ---------- initialize push delay -------------------------------------- */
 
@@ -1553,8 +2907,17 @@ static void InitGameEngine()
   {
     if (!IS_CUSTOM_ELEMENT(i))
     {
-      element_info[i].push_delay_fixed  = game.default_push_delay_fixed;
-      element_info[i].push_delay_random = game.default_push_delay_random;
+      /* set default push delay values (corrected since version 3.0.7-1) */
+      if (game.engine_version < VERSION_IDENT(3,0,7,1))
+      {
+       element_info[i].push_delay_fixed = 2;
+       element_info[i].push_delay_random = 8;
+      }
+      else
+      {
+       element_info[i].push_delay_fixed = 8;
+       element_info[i].push_delay_random = 8;
+      }
     }
   }
 
@@ -1574,16 +2937,11 @@ static void InitGameEngine()
     {
       if (IS_SP_ELEMENT(i))
       {
-#if USE_NEW_MOVE_STYLE
        /* set SP push delay to just enough to push under a falling zonk */
        int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
 
        element_info[i].push_delay_fixed  = delay;
        element_info[i].push_delay_random = 0;
-#else
-       element_info[i].push_delay_fixed  = 6;  /* just enough to escape ... */
-       element_info[i].push_delay_random = 0;  /* ... from falling zonk     */
-#endif
       }
     }
   }
@@ -1603,26 +2961,23 @@ static void InitGameEngine()
     element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
   }
 
-#if 0
-  /* ---------- initialize move dig/leave ---------------------------------- */
+  /* ---------- initialize collect score ----------------------------------- */
 
+  /* initialize collect score values for custom elements from initial value */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-  {
-    element_info[i].can_leave_element = FALSE;
-    element_info[i].can_leave_element_last = FALSE;
-  }
-#endif
+    if (IS_CUSTOM_ELEMENT(i))
+      element_info[i].collect_score = element_info[i].collect_score_initial;
 
-  /* ---------- initialize gem count --------------------------------------- */
+  /* ---------- initialize collect count ----------------------------------- */
 
-  /* initialize gem count values for each element */
+  /* initialize collect count values for non-custom elements */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
     if (!IS_CUSTOM_ELEMENT(i))
-      element_info[i].collect_count = 0;
+      element_info[i].collect_count_initial = 0;
 
-  /* add gem count values for all elements from pre-defined list */
+  /* add collect count values for all elements from pre-defined list */
   for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
-    element_info[collect_count_list[i].element].collect_count =
+    element_info[collect_count_list[i].element].collect_count_initial =
       collect_count_list[i].count;
 
   /* ---------- initialize access direction -------------------------------- */
@@ -1636,6 +2991,88 @@ static void InitGameEngine()
   for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
     element_info[access_direction_list[i].element].access_direction =
       access_direction_list[i].direction;
+
+  /* ---------- initialize explosion content ------------------------------- */
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (IS_CUSTOM_ELEMENT(i))
+      continue;
+
+    for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
+    {
+      /* (content for EL_YAMYAM set at run-time with game.yamyam_content_nr) */
+
+      element_info[i].content.e[x][y] =
+       (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
+        i == EL_PLAYER_2 ? EL_EMERALD_RED :
+        i == EL_PLAYER_3 ? EL_EMERALD :
+        i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
+        i == EL_MOLE ? EL_EMERALD_RED :
+        i == EL_PENGUIN ? EL_EMERALD_PURPLE :
+        i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
+        i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
+        i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
+        i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
+        i == EL_WALL_EMERALD ? EL_EMERALD :
+        i == EL_WALL_DIAMOND ? EL_DIAMOND :
+        i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
+        i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
+        i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
+        i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
+        i == EL_WALL_PEARL ? EL_PEARL :
+        i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
+        EL_EMPTY);
+    }
+  }
+
+  /* ---------- initialize recursion detection ------------------------------ */
+  recursion_loop_depth = 0;
+  recursion_loop_detected = FALSE;
+  recursion_loop_element = EL_UNDEFINED;
+
+  /* ---------- initialize graphics engine ---------------------------------- */
+  game.scroll_delay_value =
+    (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
+     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)
+{
+  int num_special_action = 0;
+  int i, j;
+
+  for (i = action_first; i <= action_last; i++)
+  {
+    boolean found = FALSE;
+
+    for (j = 0; j < NUM_DIRECTIONS; j++)
+      if (el_act_dir2img(element, i, j) !=
+         el_act_dir2img(element, ACTION_DEFAULT, j))
+       found = TRUE;
+
+    if (found)
+      num_special_action++;
+    else
+      break;
+  }
+
+  return num_special_action;
 }
 
 
@@ -1649,22 +3086,49 @@ static void InitGameEngine()
 
 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 */
-  int i, j, k, x, y;
+  int initial_move_dir = MV_DOWN;
+  int i, j, x, y;
 
-  InitGameEngine();
+  // required here to update video display before fading (FIX THIS)
+  DrawMaskedBorder(REDRAW_DOOR_2);
 
-#if 0
-#if DEBUG
-#if USE_NEW_AMOEBA_CODE
-  printf("Using new amoeba code.\n");
-#else
-  printf("Using old amoeba code.\n");
-#endif
-#endif
-#endif
+  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();
 
   /* don't play tapes over network */
   network_playing = (options.network && !tape.playing);
@@ -1679,12 +3143,18 @@ void InitGame()
 
     player->present = FALSE;
     player->active = FALSE;
+    player->mapped = FALSE;
+
+    player->killed = FALSE;
+    player->reanimated = FALSE;
 
     player->action = 0;
     player->effective_action = 0;
     player->programmed_action = 0;
 
     player->score = 0;
+    player->score_final = 0;
+
     player->gems_still_needed = level.gems_needed;
     player->sokobanfields_still_needed = 0;
     player->lights_still_needed = 0;
@@ -1693,31 +3163,41 @@ void InitGame()
     for (j = 0; j < MAX_NUM_KEYS; j++)
       player->key[j] = FALSE;
 
+    player->num_white_keys = 0;
+
     player->dynabomb_count = 0;
     player->dynabomb_size = 1;
     player->dynabombs_left = 0;
     player->dynabomb_xl = FALSE;
 
-    player->MovDir = MV_NO_MOVING;
+    player->MovDir = initial_move_dir;
     player->MovPos = 0;
     player->GfxPos = 0;
-    player->GfxDir = MV_NO_MOVING;
+    player->GfxDir = initial_move_dir;
     player->GfxAction = ACTION_DEFAULT;
     player->Frame = 0;
     player->StepFrame = 0;
 
-    player->use_murphy_graphic = 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() */
 
+    player->gravity = level.initial_player_gravity[i];
+
     player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
 
     player->actual_frame_counter = 0;
 
     player->step_counter = 0;
 
-    player->last_move_dir = MV_NO_MOVING;
+    player->last_move_dir = initial_move_dir;
+
+    player->is_active = FALSE;
 
     player->is_waiting = FALSE;
     player->is_moving = FALSE;
@@ -1728,81 +3208,48 @@ void InitGame()
     player->is_pushing = FALSE;
     player->is_switching = FALSE;
     player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
 
     player->is_bored = FALSE;
     player->is_sleeping = FALSE;
 
+    player->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 = initial_move_dir;
     player->action_waiting = ACTION_DEFAULT;
     player->last_action_waiting = ACTION_DEFAULT;
     player->special_action_bored = ACTION_DEFAULT;
     player->special_action_sleeping = ACTION_DEFAULT;
 
-    player->num_special_action_bored = 0;
-    player->num_special_action_sleeping = 0;
-
-    /* determine number of special actions for bored and sleeping animation */
-    for (j = ACTION_BORING_1; j <= ACTION_BORING_LAST; j++)
-    {
-      boolean found = FALSE;
-
-      for (k = 0; k < NUM_DIRECTIONS; k++)
-       if (el_act_dir2img(player->element_nr, j, k) !=
-           el_act_dir2img(player->element_nr, ACTION_DEFAULT, k))
-         found = TRUE;
-
-      if (found)
-       player->num_special_action_bored++;
-      else
-       break;
-    }
-    for (j = ACTION_SLEEPING_1; j <= ACTION_SLEEPING_LAST; j++)
-    {
-      boolean found = FALSE;
-
-      for (k = 0; k < NUM_DIRECTIONS; k++)
-       if (el_act_dir2img(player->element_nr, j, k) !=
-           el_act_dir2img(player->element_nr, ACTION_DEFAULT, k))
-         found = TRUE;
-
-      if (found)
-       player->num_special_action_sleeping++;
-      else
-       break;
-    }
-
     player->switch_x = -1;
     player->switch_y = -1;
 
-#if USE_DROP_BUGFIX
     player->drop_x = -1;
     player->drop_y = -1;
-#endif
 
     player->show_envelope = 0;
 
-    player->move_delay       = game.initial_move_delay;
-    player->move_delay_value = game.initial_move_delay_value;
-
-    player->move_delay_reset_counter = 0;
+    SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
 
-#if USE_NEW_PUSH_DELAY
     player->push_delay       = -1;     /* initialized when pushing starts */
     player->push_delay_value = game.initial_push_delay_value;
-#else
-    player->push_delay       = 0;
-    player->push_delay_value = game.initial_push_delay_value;
-#endif
 
     player->drop_delay = 0;
+    player->drop_pressed_delay = 0;
 
-    player->last_jx = player->last_jy = 0;
-    player->jx = player->jy = 0;
+    player->last_jx = -1;
+    player->last_jy = -1;
+    player->jx = -1;
+    player->jy = -1;
 
     player->shield_normal_time_left = 0;
     player->shield_deadly_time_left = 0;
@@ -1810,11 +3257,41 @@ 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);
 
     player->LevelSolved = FALSE;
     player->GameOver = FALSE;
+
+    player->LevelSolved_GameWon = FALSE;
+    player->LevelSolved_GameEnd = FALSE;
+    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;
@@ -1822,7 +3299,7 @@ void InitGame()
 #if defined(NETWORK_AVALIABLE)
   /* initial null action */
   if (network_playing)
-    SendToServer_MovePlayer(MV_NO_MOVING);
+    SendToServer_MovePlayer(MV_NONE);
 #endif
 
   ZX = ZY = -1;
@@ -1834,7 +3311,7 @@ void InitGame()
   TimeLeft = level.time;
   TapeTime = 0;
 
-  ScreenMovDir = MV_NO_MOVING;
+  ScreenMovDir = MV_NONE;
   ScreenMovPos = 0;
   ScreenGfxPos = 0;
 
@@ -1842,82 +3319,138 @@ 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;
   game.magic_wall_time_left = 0;
   game.light_time_left = 0;
   game.timegate_time_left = 0;
   game.switchgate_pos = 0;
-  game.balloon_dir = MV_NO_MOVING;
-  game.gravity = level.initial_gravity;
-  game.explosions_delayed = TRUE;
+  game.wind_direction = level.wind_direction_initial;
+
+  game.lenses_time_left = 0;
+  game.magnify_time_left = 0;
+
+  game.ball_state = level.ball_state_initial;
+  game.ball_content_nr = 0;
 
   game.envelope_active = FALSE;
 
+  /* set focus to local player for network games, else to all players */
+  game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
+  game.centered_player_nr_next = game.centered_player_nr;
+  game.set_centered_player = FALSE;
+
+  if (network_playing && tape.recording)
+  {
+    /* store client dependent player focus when recording network games */
+    tape.centered_player_nr_next = game.centered_player_nr_next;
+    tape.set_centered_player = TRUE;
+  }
+
   for (i = 0; i < NUM_BELTS; i++)
   {
-    game.belt_dir[i] = MV_NO_MOVING;
+    game.belt_dir[i] = MV_NONE;
     game.belt_dir_nr[i] = 3;           /* not moving, next moving left */
   }
 
   for (i = 0; i < MAX_NUM_AMOEBA; i++)
     AmoebaCnt[i] = AmoebaCnt2[i] = 0;
 
-  for (x = 0; x < lev_fieldx; x++)
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
   {
-    for (y = 0; y < lev_fieldy; 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;
-      Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
-      AmoebaNr[x][y] = 0;
-      WasJustMoving[x][y] = 0;
-      WasJustFalling[x][y] = 0;
-      CheckCollision[x][y] = 0;
-      Stop[x][y] = FALSE;
-      Pushed[x][y] = FALSE;
+    printf("Player status at level initialization:\n");
+  }
+#endif
 
-      Changed[x][y] = FALSE;
-      ChangeEvent[x][y] = -1;
+  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;
+    CustomValue[x][y] = 0;             /* initialized in InitField() */
+    Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
+    AmoebaNr[x][y] = 0;
+    WasJustMoving[x][y] = 0;
+    WasJustFalling[x][y] = 0;
+    CheckCollision[x][y] = 0;
+    CheckImpact[x][y] = 0;
+    Stop[x][y] = FALSE;
+    Pushed[x][y] = FALSE;
+
+    ChangeCount[x][y] = 0;
+    ChangeEvent[x][y] = -1;
 
-      ExplodePhase[x][y] = 0;
-      ExplodeDelay[x][y] = 0;
-      ExplodeField[x][y] = EX_TYPE_NONE;
+    ExplodePhase[x][y] = 0;
+    ExplodeDelay[x][y] = 0;
+    ExplodeField[x][y] = EX_TYPE_NONE;
 
-      RunnerVisit[x][y] = 0;
-      PlayerVisit[x][y] = 0;
+    RunnerVisit[x][y] = 0;
+    PlayerVisit[x][y] = 0;
 
-      GfxFrame[x][y] = 0;
-      GfxRandom[x][y] = INIT_GFX_RANDOM();
-      GfxElement[x][y] = EL_UNDEFINED;
-      GfxAction[x][y] = ACTION_DEFAULT;
-      GfxDir[x][y] = MV_NO_MOVING;
-    }
+    GfxFrame[x][y] = 0;
+    GfxRandom[x][y] = INIT_GFX_RANDOM();
+    GfxElement[x][y] = EL_UNDEFINED;
+    GfxAction[x][y] = ACTION_DEFAULT;
+    GfxDir[x][y] = MV_NONE;
+    GfxRedraw[x][y] = GFX_REDRAW_NONE;
   }
 
-  for (y = 0; y < lev_fieldy; y++)
+  SCAN_PLAYFIELD(x, y)
   {
-    for (x = 0; x < lev_fieldx; x++)
-    {
-      if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
-       emulate_bd = FALSE;
-      if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
-       emulate_sb = FALSE;
-      if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
-       emulate_sp = FALSE;
+    if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
+      emulate_bd = FALSE;
+    if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
+      emulate_sb = FALSE;
+    if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
+      emulate_sp = FALSE;
 
-      InitField(x, y, TRUE);
-    }
+    InitField(x, y, TRUE);
+
+    ResetGfxAnimation(x, y);
   }
 
   InitBeltMovement();
 
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    /* set number of special actions for bored and sleeping animation */
+    player->num_special_action_bored =
+      get_num_special_action(player->artwork_element,
+                            ACTION_BORING_1, ACTION_BORING_LAST);
+    player->num_special_action_sleeping =
+      get_num_special_action(player->artwork_element,
+                            ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+  }
+
   game.emulation = (emulate_bd ? EMU_BOULDERDASH :
                    emulate_sb ? EMU_SOKOBAN :
                    emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
 
+  /* initialize type of slippery elements */
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      /* default: elements slip down either to the left or right randomly */
+      element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
+
+      /* SP style elements prefer to slip down on the left side */
+      if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
+       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+
+      /* BD style elements prefer to slip down on the left side */
+      if (game.emulation == EMU_BOULDERDASH)
+       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+    }
+  }
+
   /* initialize explosion and ignition delay */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
   {
@@ -1933,29 +3466,71 @@ void InitGame()
       element_info[i].explosion_delay = last_phase - 1;
       element_info[i].ignition_delay = half_phase;
 
-#if 0
-      if (i == EL_BLACK_ORB)
-       element_info[i].ignition_delay = 0;
-#else
       if (i == EL_BLACK_ORB)
        element_info[i].ignition_delay = 1;
-#endif
     }
-
-#if 0
-    if (element_info[i].explosion_delay < 1)   /* !!! check again !!! */
-      element_info[i].explosion_delay = 1;
-
-    if (element_info[i].ignition_delay < 1)    /* !!! check again !!! */
-      element_info[i].ignition_delay = 1;
-#endif
   }
 
   /* correct non-moving belts to start moving left */
   for (i = 0; i < NUM_BELTS; i++)
-    if (game.belt_dir[i] == MV_NO_MOVING)
+    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++)
   {
@@ -1963,110 +3538,257 @@ void InitGame()
 
     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++)
-      {
-       struct PlayerInfo *some_player = &stored_player[j];
-       int jx = some_player->jx, jy = some_player->jy;
+       if (field_player == NULL &&
+           stored_player[j].present &&
+           !stored_player[j].mapped &&
+           !stored_player[j].connected)
+         field_player = &stored_player[j];
 
-       /* assign first free player found that is present in the playfield */
-       if (some_player->present && !some_player->connected)
-       {
-         player->present = TRUE;
-         player->active = TRUE;
+      /* 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];
 
-         some_player->present = FALSE;
-         some_player->active = FALSE;
+      if (field_player != NULL)
+      {
+       int jx = field_player->jx, jy = field_player->jy;
 
-#if 0
-         player->element_nr = some_player->element_nr;
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("- found player %d\n", field_player->index_nr + 1);
 #endif
 
-#if USE_NEW_BLOCK_STYLE
-         player->block_last_field       = some_player->block_last_field;
-         player->block_delay_adjustment = some_player->block_delay_adjustment;
-#endif
+       player->present = FALSE;
+       player->active = FALSE;
 
-         StorePlayer[jx][jy] = player->element_nr;
-         player->jx = player->last_jx = jx;
-         player->jy = player->last_jy = jy;
+       field_player->present = TRUE;
+       field_player->active = TRUE;
 
-         break;
-       }
-      }
-    }
-  }
+       /*
+       player->initial_element = field_player->initial_element;
+       player->artwork_element = field_player->artwork_element;
 
-  if (tape.playing)
-  {
-    /* when playing a tape, eliminate all players which do not participate */
+       player->block_last_field       = field_player->block_last_field;
+       player->block_delay_adjustment = field_player->block_delay_adjustment;
+       */
 
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      if (stored_player[i].active && !tape.player_participates[i])
-      {
-       struct PlayerInfo *player = &stored_player[i];
-       int jx = player->jx, jy = player->jy;
+       StorePlayer[jx][jy] = field_player->element_nr;
 
-       player->active = FALSE;
-       StorePlayer[jx][jy] = 0;
-       Feld[jx][jy] = EL_EMPTY;
-      }
-    }
-  }
-  else if (!options.network && !setup.team_mode)       /* && !tape.playing */
-  {
-    /* when in single player mode, eliminate all but the first active player */
+       field_player->jx = field_player->last_jx = jx;
+       field_player->jy = field_player->last_jy = jy;
 
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      if (stored_player[i].active)
-      {
-       for (j = i + 1; j < MAX_PLAYERS; j++)
-       {
-         if (stored_player[j].active)
-         {
-           struct PlayerInfo *player = &stored_player[j];
-           int jx = player->jx, jy = player->jy;
+       if (local_player == player)
+         local_player = field_player;
 
-           player->active = FALSE;
-           player->present = FALSE;
+       map_player_action[field_player->index_nr] = i;
 
-           StorePlayer[jx][jy] = 0;
-           Feld[jx][jy] = EL_EMPTY;
-         }
-       }
+       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
       }
     }
-  }
 
-  /* when recording the game, store which players take part in the game */
-  if (tape.recording)
-  {
-    for (i = 0; i < MAX_PLAYERS; i++)
-      if (stored_player[i].active)
-       tape.player_participates[i] = TRUE;
+    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.\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)
-  {
-    SBX_Left = 0;
-    SBX_Right = lev_fieldx - SCR_FIELDX;
+#else
+
+  /* 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)
+    {
+      for (j = 0; j < MAX_PLAYERS; j++)
+      {
+       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 (field_player->present && !field_player->connected)
+       {
+         player->present = TRUE;
+         player->active = TRUE;
+
+         field_player->present = FALSE;
+         field_player->active = FALSE;
+
+         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] = player->element_nr;
+
+         player->jx = player->last_jx = jx;
+         player->jy = player->last_jy = jy;
+
+         break;
+       }
+      }
+    }
+  }
+#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])
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int jx = player->jx, jy = player->jy;
+
+       player->active = FALSE;
+       StorePlayer[jx][jy] = 0;
+       Feld[jx][jy] = EL_EMPTY;
+      }
+    }
+#endif
+  }
+  else if (!options.network && !game.team_mode)                /* && !tape.playing */
+  {
+    /* when in single player mode, eliminate all but the first active player */
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      if (stored_player[i].active)
+      {
+       for (j = i + 1; j < MAX_PLAYERS; j++)
+       {
+         if (stored_player[j].active)
+         {
+           struct PlayerInfo *player = &stored_player[j];
+           int jx = player->jx, jy = player->jy;
+
+           player->active = FALSE;
+           player->present = FALSE;
+
+           StorePlayer[jx][jy] = 0;
+           Feld[jx][jy] = EL_EMPTY;
+         }
+       }
+      }
+    }
+  }
+
+  /* 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",
+            i + 1,
+            player->present,
+            player->connected,
+            player->active);
+
+      if (local_player == player)
+       printf(" (local player)");
+
+      printf("\n");
+    }
+  }
+#endif
+
+  if (BorderElement == EL_EMPTY)
+  {
+    SBX_Left = 0;
+    SBX_Right = lev_fieldx - SCR_FIELDX;
     SBY_Upper = 0;
     SBY_Lower = lev_fieldy - SCR_FIELDY;
   }
@@ -2078,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)
@@ -2091,14 +3817,26 @@ void InitGame()
     int start_x = 0, start_y = 0;
     int found_rating = 0;
     int found_element = EL_UNDEFINED;
+    int player_nr = local_player->index_nr;
 
-    for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+    SCAN_PLAYFIELD(x, y)
     {
       int element = Feld[x][y];
       int content;
       int xx, yy;
       boolean is_player;
 
+      if (level.use_start_element[player_nr] &&
+         level.start_element[player_nr] == element &&
+         found_rating < 4)
+      {
+       start_x = x;
+       start_y = y;
+
+       found_rating = 4;
+       found_element = element;
+      }
+
       if (!IS_CUSTOM_ELEMENT(element))
        continue;
 
@@ -2106,10 +3844,12 @@ void InitGame()
       {
        for (i = 0; i < element_info[element].num_change_pages; i++)
        {
+         /* check for player created from custom element as single target */
          content = element_info[element].change_page[i].target_element;
          is_player = ELEM_IS_PLAYER(content);
 
-         if (is_player && (found_rating < 3 || element < found_element))
+         if (is_player && (found_rating < 3 ||
+                           (found_rating == 3 && element < found_element)))
          {
            start_x = x;
            start_y = y;
@@ -2122,10 +3862,12 @@ void InitGame()
 
       for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
       {
-       content = element_info[element].content[xx][yy];
+       /* check for player created from custom element as explosion content */
+       content = element_info[element].content.e[xx][yy];
        is_player = ELEM_IS_PLAYER(content);
 
-       if (is_player && (found_rating < 2 || element < found_element))
+       if (is_player && (found_rating < 2 ||
+                         (found_rating == 2 && element < found_element)))
        {
          start_x = x + xx - 1;
          start_y = y + yy - 1;
@@ -2139,10 +3881,14 @@ void InitGame()
 
        for (i = 0; i < element_info[element].num_change_pages; i++)
        {
-         content= element_info[element].change_page[i].target_content[xx][yy];
+         /* check for player created from custom element as extended target */
+         content =
+           element_info[element].change_page[i].target_content.e[xx][yy];
+
          is_player = ELEM_IS_PLAYER(content);
 
-         if (is_player && (found_rating < 1 || element < found_element))
+         if (is_player && (found_rating < 1 ||
+                           (found_rating == 1 && element < found_element)))
          {
            start_x = x + xx - 1;
            start_y = y + yy - 1;
@@ -2164,7 +3910,6 @@ void InitGame()
   }
   else
   {
-#if 1
     scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
                local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
                local_player->jx - MIDPOSX);
@@ -2172,90 +3917,144 @@ void InitGame()
     scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
                local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
                local_player->jy - MIDPOSY);
-#else
-    scroll_x = SBX_Left;
-    scroll_y = SBY_Upper;
-    if (local_player->jx >= SBX_Left + MIDPOSX)
-      scroll_x = (local_player->jx <= SBX_Right + MIDPOSX ?
-                 local_player->jx - MIDPOSX :
-                 SBX_Right);
-    if (local_player->jy >= SBY_Upper + MIDPOSY)
-      scroll_y = (local_player->jy <= SBY_Lower + MIDPOSY ?
-                 local_player->jy - MIDPOSY :
-                 SBY_Lower);
-#endif
   }
 
-  CloseDoor(DOOR_CLOSE_1);
-
   /* !!! FIX THIS (START) !!! */
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     InitGameEngine_EM();
   }
+  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 */
+  BlitScreenToBitmap(backbuffer);
+  /* !!! FIX THIS (END) !!! */
+
+  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 (setup.soft_scrolling)
-      BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
+  if (!game.restart_level)
+  {
+    /* copy default game door content to main double buffer */
 
-    redraw_mask |= REDRAW_FROM_BACKBUFFER;
-    FadeToFront();
+    /* !!! CHECK AGAIN !!! */
+    SetPanelBackground();
+    // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+    DrawBackground(DX, DY, DXSIZE, DYSIZE);
   }
-  /* !!! FIX THIS (END) !!! */
 
-  /* 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);
+  SetPanelBackground();
+  SetDrawBackgroundMask(REDRAW_DOOR_1);
 
-  DrawGameDoorValues();
+  UpdateAndDisplayGameControlValues();
 
-  UnmapGameButtons();
-  UnmapTapeButtons();
-  game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
-  game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
-  game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
-  MapGameButtons();
-  MapTapeButtons();
+  if (!game.restart_level)
+  {
+    UnmapGameButtons();
+    UnmapTapeButtons();
 
-  /* 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);
+    FreeGameButtons();
+    CreateGameButtons();
 
-  OpenDoor(DOOR_OPEN_ALL);
+    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;
 
-  PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
+    MapGameButtons();
+    MapTapeButtons();
 
-  if (setup.sound_music)
-    PlayLevelMusic();
+    /* copy actual game door content to door double buffer for OpenDoor() */
+    BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
 
-  KeyboardAutoRepeatOffUnlessAutoplay();
+    OpenDoor(DOOR_OPEN_ALL);
 
-  if (options.debug)
+    PlaySound(SND_GAME_STARTING);
+
+    if (setup.sound_music)
+      PlayLevelMusic();
+
+    KeyboardAutoRepeatOffUnlessAutoplay();
+
+#if DEBUG_INIT_PLAYER
+    if (options.debug)
+    {
+      printf("Player status (final):\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
+  }
+
+  UnmapAllGadgets();
+
+  MapGameButtons();
+  MapTapeButtons();
+
+  if (!game.restart_level && !tape.playing)
   {
-    for (i = 0; i < MAX_PLAYERS; i++)
-      printf("Player %d %sactive.\n",
-            i + 1, (stored_player[i].active ? "" : "not "));
+    LevelStats_incPlayed(level_nr);
+
+    SaveLevelSetup_SeriesInfo();
   }
 
-#if 0
-  printf("::: starting game [%d]\n", FrameCounter);
-#endif
+  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)
@@ -2275,7 +4074,7 @@ void InitMovDir(int x, int y)
     { MV_LEFT,  MV_RIGHT, MV_UP, MV_DOWN }
   };
 
-  switch(element)
+  switch (element)
   {
     case EL_BUG_RIGHT:
     case EL_BUG_UP:
@@ -2317,6 +4116,14 @@ void InitMovDir(int x, int y)
       MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
       break;
 
+    case EL_YAMYAM_LEFT:
+    case EL_YAMYAM_RIGHT:
+    case EL_YAMYAM_UP:
+    case EL_YAMYAM_DOWN:
+      Feld[x][y] = EL_YAMYAM;
+      MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
+      break;
+
     case EL_SP_SNIKSNAK:
       MovDir[x][y] = MV_UP;
       break;
@@ -2342,7 +4149,7 @@ void InitMovDir(int x, int y)
 
        if (move_direction_initial == MV_START_PREVIOUS)
        {
-         if (MovDir[x][y] != MV_NO_MOVING)
+         if (MovDir[x][y] != MV_NONE)
            return;
 
          move_direction_initial = MV_START_AUTOMATIC;
@@ -2368,11 +4175,9 @@ void InitMovDir(int x, int y)
        else if (move_pattern == MV_ALONG_LEFT_SIDE ||
                 move_pattern == MV_ALONG_RIGHT_SIDE)
        {
-#if 1
          /* use random direction as default start direction */
          if (game.engine_version >= VERSION_IDENT(3,1,0,0))
            MovDir[x][y] = 1 << RND(4);
-#endif
 
          for (i = 0; i < NUM_DIRECTIONS; i++)
          {
@@ -2450,128 +4255,227 @@ void InitAmoebaNr(int x, int y)
   AmoebaCnt2[group_nr]++;
 }
 
-void GameWon()
+static void PlayerWins(struct PlayerInfo *player)
 {
-  int hi_pos;
-  boolean raise_level = FALSE;
-
-  if (local_player->MovPos)
-    return;
+  player->LevelSolved = TRUE;
+  player->GameOver = TRUE;
 
-#if 1
-  if (tape.auto_play)          /* tape might already be stopped here */
-    tape.auto_play_level_solved = TRUE;
-#else
-  if (tape.playing && tape.auto_play)
-    tape.auto_play_level_solved = TRUE;
-#endif
+  player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                        level.native_em_level->lev->score : player->score);
 
-  local_player->LevelSolved = FALSE;
+  player->LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed :
+                                     TimeLeft);
+  player->LevelSolved_CountingScore = player->score_final;
+}
 
-  PlaySoundStereo(SND_GAME_WINNING, SOUND_MIDDLE);
+void GameWon()
+{
+  static int time, time_final;
+  static int score, score_final;
+  static int game_over_delay_1 = 0;
+  static int game_over_delay_2 = 0;
+  int game_over_delay_value_1 = 50;
+  int game_over_delay_value_2 = 50;
 
-  if (TimeLeft)
+  if (!local_player->LevelSolved_GameWon)
   {
-    if (!tape.playing && setup.sound_loops)
-      PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
-                  SND_CTRL_PLAY_LOOP);
+    int i;
+
+    /* do not start end game actions before the player stops moving (to exit) */
+    if (local_player->MovPos)
+      return;
+
+    local_player->LevelSolved_GameWon = TRUE;
+    local_player->LevelSolved_SaveTape = tape.recording;
+    local_player->LevelSolved_SaveScore = !tape.playing;
 
-    while (TimeLeft > 0)
+    if (!tape.playing)
     {
-      if (!tape.playing && !setup.sound_loops)
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
-      if (TimeLeft > 0 && !(TimeLeft % 10))
-       RaiseScore(level.score[SC_TIME_BONUS]);
-      if (TimeLeft > 100 && !(TimeLeft % 10))
-       TimeLeft -= 10;
-      else
-       TimeLeft--;
+      LevelStats_incSolved(level_nr);
+
+      SaveLevelSetup_SeriesInfo();
+    }
 
-      DrawGameValue_Time(TimeLeft);
+    if (tape.auto_play)                /* tape might already be stopped here */
+      tape.auto_play_level_solved = TRUE;
 
-      BackToFront();
+    TapeStop();
+
+    game_over_delay_1 = game_over_delay_value_1;
+    game_over_delay_2 = game_over_delay_value_2;
 
-      if (!tape.playing)
-       Delay(10);
+    time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
+    score = score_final = local_player->score_final;
+
+    if (TimeLeft > 0)
+    {
+      time_final = 0;
+      score_final += TimeLeft * level.score[SC_TIME_BONUS];
+    }
+    else if (game.no_time_limit && TimePlayed < 999)
+    {
+      time_final = 999;
+      score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
     }
 
-    if (!tape.playing && setup.sound_loops)
-      StopSound(SND_GAME_LEVELTIME_BONUS);
-  }
-  else if (level.time == 0)            /* level without time limit */
-  {
-    if (!tape.playing && setup.sound_loops)
-      PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
-                  SND_CTRL_PLAY_LOOP);
+    local_player->score_final = score_final;
 
-    while (TimePlayed < 999)
+    if (level_editor_test_game)
     {
-      if (!tape.playing && !setup.sound_loops)
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
-      if (TimePlayed < 999 && !(TimePlayed % 10))
-       RaiseScore(level.score[SC_TIME_BONUS]);
-      if (TimePlayed < 900 && !(TimePlayed % 10))
-       TimePlayed += 10;
-      else
-       TimePlayed++;
+      time = time_final;
+      score = score_final;
 
-      DrawGameValue_Time(TimePlayed);
+      local_player->LevelSolved_CountingTime = time;
+      local_player->LevelSolved_CountingScore = score;
 
-      BackToFront();
+      game_panel_controls[GAME_PANEL_TIME].value = time;
+      game_panel_controls[GAME_PANEL_SCORE].value = score;
 
-      if (!tape.playing)
-       Delay(10);
+      DisplayGameControlValues();
     }
 
-    if (!tape.playing && setup.sound_loops)
-      StopSound(SND_GAME_LEVELTIME_BONUS);
-  }
+    if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    {
+      if (ExitX >= 0 && ExitY >= 0)    /* local player has left the level */
+      {
+       /* close exit door after last player */
+       if ((AllPlayersGone &&
+            (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
+             Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN ||
+             Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) ||
+           Feld[ExitX][ExitY] == EL_EM_EXIT_OPEN ||
+           Feld[ExitX][ExitY] == EL_EM_STEEL_EXIT_OPEN)
+       {
+         int element = Feld[ExitX][ExitY];
 
-  /* close exit door after last player */
-  if (AllPlayersGone && ExitX >= 0 && ExitY >= 0 &&
-      (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
-       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN))
-  {
-    int element = Feld[ExitX][ExitY];
+         Feld[ExitX][ExitY] =
+           (element == EL_EXIT_OPEN            ? EL_EXIT_CLOSING :
+            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);
 
-    Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
-                         EL_SP_EXIT_CLOSING);
+         PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
+       }
 
-    PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
-  }
+       /* player disappears */
+       DrawLevelField(ExitX, ExitY);
+      }
 
-  /* Hero disappears */
-  if (ExitX >= 0 && ExitY >= 0)
-    DrawLevelField(ExitX, ExitY);
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
 
-  BackToFront();
+       if (player->present)
+       {
+         RemovePlayer(player);
 
-  if (tape.playing)
-    return;
+         /* player disappears */
+         DrawLevelField(player->jx, player->jy);
+       }
+      }
+    }
 
-  CloseDoor(DOOR_CLOSE_1);
+    PlaySound(SND_GAME_WINNING);
+  }
 
-  if (tape.recording)
+  if (game_over_delay_1 > 0)
   {
-    TapeStop();
-    SaveTape(tape.level_nr);           /* Ask to save tape */
+    game_over_delay_1--;
+
+    return;
   }
 
-  if (level_nr == leveldir_current->handicap_level)
+  if (time != time_final)
   {
-    leveldir_current->handicap_level++;
-    SaveLevelSetup_SeriesInfo();
+    int time_to_go = ABS(time_final - time);
+    int time_count_dir = (time < time_final ? +1 : -1);
+    int time_count_steps = (time_to_go > 100 && time_to_go % 10 == 0 ? 10 : 1);
+
+    time  += time_count_steps * time_count_dir;
+    score += time_count_steps * level.score[SC_TIME_BONUS];
+
+    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();
+
+    if (time == time_final)
+      StopSound(SND_GAME_LEVELTIME_BONUS);
+    else if (setup.sound_loops)
+      PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
+    else
+      PlaySound(SND_GAME_LEVELTIME_BONUS);
+
+    return;
   }
 
-  if (level_editor_test_game)
-    local_player->score = -1;  /* no highscore when playing from editor */
-  else if (level_nr < leveldir_current->last_level)
-    raise_level = TRUE;                /* advance to next level */
+  local_player->LevelSolved_PanelOff = TRUE;
 
-  if ((hi_pos = NewHiScore()) >= 0) 
+  if (game_over_delay_2 > 0)
   {
-    game_status = GAME_MODE_SCORES;
+    game_over_delay_2--;
+
+    return;
+  }
+
+  GameEnd();
+}
+
+void GameEnd()
+{
+  int hi_pos;
+  boolean raise_level = FALSE;
+
+  local_player->LevelSolved_GameEnd = TRUE;
+
+  if (!global.use_envelope_request)
+    CloseDoor(DOOR_CLOSE_1);
+
+  if (local_player->LevelSolved_SaveTape)
+  {
+    SaveTapeChecked(tape.level_nr);    /* ask to save tape */
+  }
+
+  CloseDoor(DOOR_CLOSE_ALL);
+
+  if (level_editor_test_game)
+  {
+    SetGameStatus(GAME_MODE_MAIN);
+
+    DrawMainMenu();
+
+    return;
+  }
+
+  if (!local_player->LevelSolved_SaveScore)
+  {
+    SetGameStatus(GAME_MODE_MAIN);
+
+    DrawMainMenu();
+
+    return;
+  }
+
+  if (level_nr == leveldir_current->handicap_level)
+  {
+    leveldir_current->handicap_level++;
+
+    SaveLevelSetup_SeriesInfo();
+  }
+
+  if (level_nr < leveldir_current->last_level)
+    raise_level = TRUE;                        /* advance to next level */
+
+  if ((hi_pos = NewHiScore()) >= 0) 
+  {
+    SetGameStatus(GAME_MODE_SCORES);
+
     DrawHallOfFame(hi_pos);
+
     if (raise_level)
     {
       level_nr++;
@@ -2580,16 +4484,16 @@ void GameWon()
   }
   else
   {
-    game_status = GAME_MODE_MAIN;
+    SetGameStatus(GAME_MODE_MAIN);
+
     if (raise_level)
     {
       level_nr++;
       TapeErase();
     }
+
     DrawMainMenu();
   }
-
-  BackToFront();
 }
 
 int NewHiScore()
@@ -2599,13 +4503,13 @@ int NewHiScore()
 
   LoadScore(level_nr);
 
-  if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
-      local_player->score < highscore[MAX_SCORE_ENTRIES - 1].Score) 
+  if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
+      local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score) 
     return -1;
 
   for (k = 0; k < MAX_SCORE_ENTRIES; k++) 
   {
-    if (local_player->score > highscore[k].Score)
+    if (local_player->score_final > highscore[k].Score)
     {
       /* player has made it to the hall of fame */
 
@@ -2615,7 +4519,7 @@ int NewHiScore()
 
 #ifdef ONE_PER_NAME
        for (l = k; l < MAX_SCORE_ENTRIES; l++)
-         if (!strcmp(setup.player_name, highscore[l].Name))
+         if (strEqual(setup.player_name, highscore[l].Name))
            m = l;
        if (m == k)     /* player's new highscore overwrites his old one */
          goto put_into_list;
@@ -2633,7 +4537,7 @@ int NewHiScore()
 #endif
       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
-      highscore[k].Score = local_player->score; 
+      highscore[k].Score = local_player->score_final
       position = k;
       break;
     }
@@ -2652,10 +4556,9 @@ int NewHiScore()
   return position;
 }
 
-inline static int getElementMoveStepsize(int x, int y)
+inline static int getElementMoveStepsizeExt(int x, int y, int direction)
 {
   int element = Feld[x][y];
-  int direction = MovDir[x][y];
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
   int horiz_move = (dx != 0);
@@ -2665,33 +4568,25 @@ inline static int getElementMoveStepsize(int x, int y)
   /* special values for move stepsize for spring and things on conveyor belt */
   if (horiz_move)
   {
-#if 0
-    if (element == EL_SPRING)
-      step = sign * MOVE_STEPSIZE_NORMAL * 2;
-    else if (CAN_FALL(element) && !CAN_MOVE(element) &&
-            y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
-      step = sign * MOVE_STEPSIZE_NORMAL / 2;
-#else
     if (CAN_FALL(element) &&
        y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
       step = sign * MOVE_STEPSIZE_NORMAL / 2;
     else if (element == EL_SPRING)
       step = sign * MOVE_STEPSIZE_NORMAL * 2;
-#endif
   }
 
   return step;
 }
 
+inline static int getElementMoveStepsize(int x, int y)
+{
+  return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
+}
+
 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;
@@ -2699,16 +4594,37 @@ void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
   }
 }
 
-static void ResetRandomAnimationValue(int x, int y)
+static void ResetGfxFrame(int x, int y, boolean redraw)
 {
-  GfxRandom[x][y] = INIT_GFX_RANDOM();
+  int element = Feld[x][y];
+  int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+  int last_gfx_frame = GfxFrame[x][y];
+
+  if (graphic_info[graphic].anim_global_sync)
+    GfxFrame[x][y] = FrameCounter;
+  else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+    GfxFrame[x][y] = CustomValue[x][y];
+  else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+    GfxFrame[x][y] = element_info[element].collect_score;
+  else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+    GfxFrame[x][y] = ChangeDelay[x][y];
+
+  if (redraw && GfxFrame[x][y] != last_gfx_frame)
+    DrawLevelGraphicAnimation(x, y, graphic);
 }
 
 static void ResetGfxAnimation(int x, int y)
 {
-  GfxFrame[x][y] = 0;
   GfxAction[x][y] = ACTION_DEFAULT;
   GfxDir[x][y] = MovDir[x][y];
+  GfxFrame[x][y] = 0;
+
+  ResetGfxFrame(x, y, FALSE);
+}
+
+static void ResetRandomAnimationValue(int x, int y)
+{
+  GfxRandom[x][y] = INIT_GFX_RANDOM();
 }
 
 void InitMovingField(int x, int y, int direction)
@@ -2718,54 +4634,49 @@ void InitMovingField(int x, int y, int direction)
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
   int newx = x + dx;
   int newy = y + dy;
+  boolean is_moving_before, is_moving_after;
 
-  if (!WasJustMoving[x][y] || direction != MovDir[x][y])
-    ResetGfxAnimation(x, y);
+  /* check if element was/is moving or being moved before/after mode change */
+  is_moving_before = (WasJustMoving[x][y] != 0);
+  is_moving_after  = (getElementMoveStepsizeExt(x, y, direction)    != 0);
 
-#if USE_CAN_MOVE_NOT_MOVING
+  /* 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 (is_moving_before != is_moving_after ||
+      direction != MovDir[x][y])
+    ResetGfxAnimation(x, y);
 
   MovDir[x][y] = direction;
   GfxDir[x][y] = direction;
-  GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
+
+  GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
+                    direction == MV_DOWN && CAN_FALL(element) ?
                     ACTION_FALLING : ACTION_MOVING);
 
-  if (getElementMoveStepsize(x, y) != 0)       /* moving or being moved */
+  /* this is needed for CEs with property "can move" / "not moving" */
+
+  if (is_moving_after)
   {
     if (Feld[newx][newy] == EL_EMPTY)
       Feld[newx][newy] = EL_BLOCKED;
 
     MovDir[newx][newy] = MovDir[x][y];
+
+    CustomValue[newx][newy] = CustomValue[x][y];
+
     GfxFrame[newx][newy] = GfxFrame[x][y];
     GfxRandom[newx][newy] = GfxRandom[x][y];
     GfxAction[newx][newy] = GfxAction[x][y];
     GfxDir[newx][newy] = GfxDir[x][y];
   }
-
-#else
-
-  MovDir[newx][newy] = MovDir[x][y] = direction;
-  GfxDir[x][y] = direction;
-
-  if (Feld[newx][newy] == EL_EMPTY)
-    Feld[newx][newy] = EL_BLOCKED;
-
-  if (direction == MV_DOWN && CAN_FALL(element))
-    GfxAction[x][y] = ACTION_FALLING;
-  else
-    GfxAction[x][y] = ACTION_MOVING;
-
-  GfxFrame[newx][newy] = GfxFrame[x][y];
-  GfxRandom[newx][newy] = GfxRandom[x][y];
-  GfxAction[newx][newy] = GfxAction[x][y];
-  GfxDir[newx][newy] = GfxDir[x][y];
-#endif
 }
 
 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
 {
   int direction = MovDir[x][y];
-  int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
+  int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
+  int newy = y + (direction & MV_UP   ? -1 : direction & MV_DOWN  ? +1 : 0);
 
   *goes_to_x = newx;
   *goes_to_y = newy;
@@ -2835,18 +4746,16 @@ static void RemoveField(int x, int y)
   MovDir[x][y] = 0;
   MovDelay[x][y] = 0;
 
+  CustomValue[x][y] = 0;
+
   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_NO_MOVING;
+  GfxDir[x][y] = MV_NONE;
 }
 
 void RemoveMovingField(int x, int y)
@@ -2861,10 +4770,7 @@ void RemoveMovingField(int x, int y)
   if (IS_MOVING(x, y))
   {
     Moving2Blocked(x, y, &newx, &newy);
-#if 0
-    if (Feld[newx][newy] != EL_BLOCKED)
-      return;
-#else
+
     if (Feld[newx][newy] != EL_BLOCKED)
     {
       /* element is moving, but target field is not free (blocked), but
@@ -2875,11 +4781,10 @@ void RemoveMovingField(int x, int y)
 
       Store[oldx][oldy] = Store2[oldx][oldy] = 0;
 
-      DrawLevelField(oldx, oldy);
+      TEST_DrawLevelField(oldx, oldy);
 
       return;
     }
-#endif
   }
   else if (element == EL_BLOCKED)
   {
@@ -2890,8 +4795,10 @@ void RemoveMovingField(int x, int y)
 
   if (element == EL_BLOCKED &&
       (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
+       Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
        Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
        Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
+       Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
        Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
     next_element = get_next_element(Feld[oldx][oldy]);
 
@@ -2903,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)
@@ -2926,19 +4833,10 @@ void DrawDynamite(int x, int y)
 
   frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
-#if 1
   if (Back[x][y] || Store[x][y])
     DrawGraphicThruMask(sx, sy, graphic, frame);
   else
     DrawGraphic(sx, sy, graphic, frame);
-#else
-  if (game.emulation == EMU_SUPAPLEX)
-    DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
-  else if (Store[x][y])
-    DrawGraphicThruMask(sx, sy, graphic, frame);
-  else
-    DrawGraphic(sx, sy, graphic, frame);
-#endif
 }
 
 void CheckDynamite(int x, int y)
@@ -2956,191 +4854,172 @@ void CheckDynamite(int x, int y)
     }
   }
 
-#if 1
   StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
-#else
-  if (Feld[x][y] == EL_DYNAMITE_ACTIVE ||
-      Feld[x][y] == EL_SP_DISK_RED_ACTIVE)
-    StopSound(SND_DYNAMITE_ACTIVE);
-  else
-    StopSound(SND_DYNABOMB_ACTIVE);
-#endif
 
   Bang(x, y);
 }
 
-void DrawRelocatePlayer(struct PlayerInfo *player)
+static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
 {
-  boolean ffwd_delay = (tape.playing && tape.fast_forward);
-  boolean no_delay = (tape.warp_forward);
-  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
-  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
-  int jx = player->jx;
-  int jy = player->jy;
+  boolean num_checked_players = 0;
+  int i;
 
-  if (level.instant_relocation)
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-#if 1
-    int offset = (setup.scroll_delay ? 3 : 0);
-
-    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+    if (stored_player[i].active)
     {
-      scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-                 local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-                 local_player->jx - MIDPOSX);
+      int sx = stored_player[i].jx;
+      int sy = stored_player[i].jy;
 
-      scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
-                 local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
-                 local_player->jy - MIDPOSY);
+      if (num_checked_players == 0)
+      {
+       *sx1 = *sx2 = sx;
+       *sy1 = *sy2 = sy;
+      }
+      else
+      {
+       *sx1 = MIN(*sx1, sx);
+       *sy1 = MIN(*sy1, sy);
+       *sx2 = MAX(*sx2, sx);
+       *sy2 = MAX(*sy2, sy);
+      }
+
+      num_checked_players++;
     }
-    else
-    {
-      if ((player->MovDir == MV_LEFT  && scroll_x > jx - MIDPOSX + offset) ||
-         (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
-       scroll_x = jx - MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
+  }
+}
 
-      if ((player->MovDir == MV_UP  && scroll_y > jy - MIDPOSY + offset) ||
-         (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
-       scroll_y = jy - MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
+static boolean checkIfAllPlayersFitToScreen_RND()
+{
+  int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
 
-      /* 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);
+  setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
 
-      /* don't scroll over playfield boundaries */
-      if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
-       scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
-    }
-#else
-    scroll_x += (local_player->jx - old_jx);
-    scroll_y += (local_player->jy - old_jy);
+  return (sx2 - sx1 < SCR_FIELDX &&
+         sy2 - sy1 < SCR_FIELDY);
+}
 
-    /* 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);
+static void setScreenCenteredToAllPlayers(int *sx, int *sy)
+{
+  int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
 
-    /* don't scroll over playfield boundaries */
-    if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
-      scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
-#endif
+  setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
 
-    RedrawPlayfield(TRUE, 0,0,0,0);
-  }
-  else
+  *sx = (sx1 + sx2) / 2;
+  *sy = (sy1 + sy2) / 2;
+}
+
+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 (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
   {
-#if 1
-#if 0
-    int offset = (setup.scroll_delay ? 3 : 0);
-#endif
-    int scroll_xx = -999, scroll_yy = -999;
+    /* case 1: quick relocation inside visible screen (without scrolling) */
 
-    ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
+    RedrawPlayfield();
 
-    while (scroll_xx != scroll_x || scroll_yy != scroll_y)
-    {
-      int dx = 0, dy = 0;
-      int fx = FX, fy = FY;
+    return;
+  }
 
-      scroll_xx = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-                  local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-                  local_player->jx - MIDPOSX);
+  if (!level.shifted_relocation || center_screen)
+  {
+    /* relocation _with_ centering of screen */
 
-      scroll_yy = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
-                  local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
-                  local_player->jy - MIDPOSY);
+    new_scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+                   x > SBX_Right + MIDPOSX ? SBX_Right :
+                   x - MIDPOSX);
 
-      dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
-      dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
+    new_scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                   y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                   y - MIDPOSY);
+  }
+  else
+  {
+    /* relocation _without_ centering of screen */
 
-#if 1
-      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
-       break;
-#else
-      if (scroll_xx == scroll_x && scroll_yy == scroll_y)
-       break;
-#endif
+    int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                          old_x > SBX_Right + MIDPOSX ? SBX_Right :
+                          old_x - MIDPOSX);
 
-      scroll_x -= dx;
-      scroll_y -= dy;
+    int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                          old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                          old_y - MIDPOSY);
 
-      fx += dx * TILEX / 2;
-      fy += dy * TILEY / 2;
+    int offset_x = x + (scroll_x - center_scroll_x);
+    int offset_y = y + (scroll_y - center_scroll_y);
 
-      ScrollLevel(dx, dy);
-      DrawAllPlayers();
+    new_scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                   offset_x > SBX_Right + MIDPOSX ? SBX_Right :
+                   offset_x - MIDPOSX);
 
-      /* 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);
+    new_scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                   offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                   offset_y - MIDPOSY);
+  }
 
-      /* scroll second step to align at full tile size */
-      BackToFront();
-      Delay(wait_delay_value);
-    }
-#else
-    int scroll_xx = -999, scroll_yy = -999;
+  if (quick_relocation)
+  {
+    /* case 2: quick relocation (redraw without visible scrolling) */
 
-    ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
+    scroll_x = new_scroll_x;
+    scroll_y = new_scroll_y;
 
-    while (scroll_xx != scroll_x || scroll_yy != scroll_y)
-    {
-      int dx = 0, dy = 0;
-      int fx = FX, fy = FY;
+    RedrawPlayfield();
 
-      scroll_xx = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
-                  local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
-                  local_player->jx - MIDPOSX);
+    return;
+  }
 
-      scroll_yy = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
-                  local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
-                  local_player->jy - MIDPOSY);
+  /* case 3: visible relocation (with scrolling to new position) */
 
-      dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
-      dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
+  ScrollScreen(NULL, SCROLL_GO_ON);    /* scroll last frame to full tile */
 
-#if 1
-      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
-       break;
-#else
-      if (scroll_xx == scroll_x && scroll_yy == scroll_y)
-       break;
-#endif
+  SetVideoFrameDelay(wait_delay_value);
 
-      scroll_x -= dx;
-      scroll_y -= dy;
+  while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
+  {
+    int dx = 0, dy = 0;
+    int fx = FX, fy = FY;
 
-      fx += dx * TILEX / 2;
-      fy += dy * TILEY / 2;
+    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);
 
-      ScrollLevel(dx, dy);
-      DrawAllPlayers();
+    if (dx == 0 && dy == 0)            /* no scrolling needed at all */
+      break;
 
-      /* 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_x -= dx;
+    scroll_y -= dy;
 
-      /* scroll second step to align at full tile size */
-      BackToFront();
-      Delay(wait_delay_value);
-    }
-#endif
+    fx += dx * TILEX / 2;
+    fy += dy * TILEY / 2;
+
+    ScrollLevel(dx, dy);
+    DrawAllPlayers();
+
+    /* scroll in two steps of half tile size to make things smoother */
+    BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
 
-    DrawPlayer(player);
-    BackToFront();
-    Delay(wait_delay_value);
+    /* scroll second step to align at full tile size */
+    BlitScreenToBitmap(window);
   }
+
+  DrawAllPlayers();
+  BackToFront();
+
+  SetVideoFrameDelay(frame_delay_value_old);
 }
 
 void RelocatePlayer(int jx, int jy, int el_player_raw)
 {
-#if 1
-  int el_player = GET_VALID_PLAYER_ELEMENT(el_player_raw);
-#else
-  int el_player = (el_player_raw == EL_SP_MURPHY ? EL_PLAYER_1 :el_player_raw);
-#endif
-  struct PlayerInfo *player = &stored_player[el_player - EL_PLAYER_1];
+  int el_player = GET_PLAYER_ELEMENT(el_player_raw);
+  int player_nr = GET_PLAYER_NR(el_player);
+  struct PlayerInfo *player = &stored_player[player_nr];
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
@@ -3153,25 +5032,10 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
 
   int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
   int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
-#if 1
   int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
   int enter_side_vert  = MV_DIR_OPPOSITE(move_dir_vert);
   int leave_side_horiz = move_dir_horiz;
   int leave_side_vert  = move_dir_vert;
-#else
-  static int trigger_sides[4][2] =
-  {
-    /* enter side               leave side */
-    { CH_SIDE_RIGHT,           CH_SIDE_LEFT    },      /* moving left  */
-    { CH_SIDE_LEFT,            CH_SIDE_RIGHT   },      /* moving right */
-    { CH_SIDE_BOTTOM,          CH_SIDE_TOP     },      /* moving up    */
-    { CH_SIDE_TOP,             CH_SIDE_BOTTOM  }       /* moving down  */
-  };
-  int enter_side_horiz = trigger_sides[MV_DIR_BIT(move_dir_horiz)][0];
-  int enter_side_vert  = trigger_sides[MV_DIR_BIT(move_dir_vert)][0];
-  int leave_side_horiz = trigger_sides[MV_DIR_BIT(move_dir_horiz)][1];
-  int leave_side_vert  = trigger_sides[MV_DIR_BIT(move_dir_vert)][1];
-#endif
   int enter_side = enter_side_horiz | enter_side_vert;
   int leave_side = leave_side_horiz | leave_side_vert;
 
@@ -3194,16 +5058,11 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
       ScrollPlayer(player, SCROLL_GO_ON);
       ScrollScreen(NULL, SCROLL_GO_ON);
 
-#if USE_NEW_MOVE_DELAY
       AdvanceFrameAndPlayerCounters(player->index_nr);
-#else
-      FrameCounter++;
-#endif
 
       DrawPlayer(player);
 
-      BackToFront();
-      Delay(wait_delay_value);
+      BackToFront_WithFrameDelay(wait_delay_value);
     }
 
     DrawPlayer(player);                /* needed here only to cleanup last field */
@@ -3212,7 +5071,6 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
     player->is_moving = FALSE;
   }
 
-#if 1
   if (IS_CUSTOM_ELEMENT(old_element))
     CheckElementChangeByPlayer(old_jx, old_jy, old_element,
                               CE_LEFT_BY_PLAYER,
@@ -3221,80 +5079,56 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
   CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
                                      CE_PLAYER_LEAVES_X,
                                      player->index_bit, leave_side);
-#endif
 
   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 */
   }
 
-#if 1
-  if (player == local_player)  /* only visually relocate local player */
-    DrawRelocatePlayer(player);
-#endif
+  /* only visually relocate centered player */
+  DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
+                    FALSE, level.instant_relocation);
 
-#if 1
-  TestIfHeroTouchesBadThing(jx, jy);
+  TestIfPlayerTouchesBadThing(jx, jy);
   TestIfPlayerTouchesCustomElement(jx, jy);
-#endif
-
-#if 0
-  printf("::: %d,%d: %d\n", jx, jy-1, Changed[jx][jy-1]);
-#endif
-
-#if 0
-#if 0
-  /* needed to allow change of walkable custom element by entering player */
-  if (!(Changed[jx][jy] & CH_EVENT_BIT(CE_ENTERED_BY_PLAYER)))
-    Changed[jx][jy] = 0;       /* allow another change (but prevent loop) */
-#else
-  /* needed to allow change of walkable custom element by entering player */
-  Changed[jx][jy] = 0;         /* allow another change */
-#endif
-#endif
 
-#if 0
-  printf("::: player entering %d, %d from %s ...\n", jx, jy,
-        enter_side == MV_LEFT  ? "left" :
-        enter_side == MV_RIGHT ? "right" :
-        enter_side == MV_UP    ? "top" :
-        enter_side == MV_DOWN  ? "bottom" : "oops! no idea!");
-#endif
-
-#if 1
   if (IS_CUSTOM_ELEMENT(element))
     CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
                               player->index_bit, enter_side);
 
-  CheckTriggeredElementChangeByPlayer(jx, jy, element,
-                                     CE_PLAYER_ENTERS_X,
+  CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
                                      player->index_bit, enter_side);
-#endif
+
+  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)
 {
   int x, y;
-#if 0
-  int num_phase = 9;
-#endif
+  int last_phase;
+  int border_element;
 
   /* !!! eliminate this variable !!! */
   int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
 
-#if 1
-  int last_phase;
-#else
-  int last_phase = num_phase * delay;
-  int half_phase = (num_phase / 2) * delay;
-  int first_phase_after_start = EX_PHASE_START + 1;
-#endif
-  int border_element;
-
   if (game.explosions_delayed)
   {
     ExplodeField[ex][ey] = mode;
@@ -3304,29 +5138,7 @@ void Explode(int ex, int ey, int phase, int mode)
   if (phase == EX_PHASE_START)         /* initialize 'Store[][]' field */
   {
     int center_element = Feld[ex][ey];
-
-#if 0
-    printf("::: start explosion %d,%d [%d]\n", ex, ey, FrameCounter);
-#endif
-
-#if 0
-    /* --- This is only really needed (and now handled) in "Impact()". --- */
-    /* do not explode moving elements that left the explode field in time */
-    if (game.engine_version >= VERSION_IDENT(2,2,0,7) &&
-       center_element == EL_EMPTY &&
-       (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER))
-      return;
-#endif
-
-#if 1
-    if (mode == EX_TYPE_NORMAL ||
-       mode == EX_TYPE_CENTER ||
-       mode == EX_TYPE_CROSS)
-      PlayLevelSoundAction(ex, ey, ACTION_EXPLODING);
-#else
-    if (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER)
-      PlayLevelSoundAction(ex, ey, ACTION_EXPLODING);
-#endif
+    int artwork_element, explosion_element;    /* set these values later */
 
     /* remove things displayed in background while burning dynamite */
     if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
@@ -3340,43 +5152,40 @@ void Explode(int ex, int ey, int phase, int mode)
       Feld[ex][ey] = center_element;
     }
 
-#if 1
+    /* now "center_element" is finally determined -- set related values now */
+    artwork_element = center_element;          /* for custom player artwork */
+    explosion_element = center_element;                /* for custom player artwork */
 
-#if 1
-    last_phase = element_info[center_element].explosion_delay + 1;
-#else
-    last_phase = element_info[center_element].explosion_delay;
-#endif
+    if (IS_PLAYER(ex, ey))
+    {
+      int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
 
-#if 0
-    printf("::: %d -> %d\n", center_element, last_phase);
-#endif
-#endif
+      artwork_element = stored_player[player_nr].artwork_element;
 
-    for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
-    {
-      int xx = x - ex + 1;
+      if (level.use_explosion_element[player_nr])
+      {
+       explosion_element = level.explosion_element[player_nr];
+       artwork_element = explosion_element;
+      }
+    }
+
+    if (mode == EX_TYPE_NORMAL ||
+       mode == EX_TYPE_CENTER ||
+       mode == EX_TYPE_CROSS)
+      PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
+
+    last_phase = element_info[explosion_element].explosion_delay + 1;
+
+    for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
+    {
+      int xx = x - ex + 1;
       int yy = y - ey + 1;
       int element;
 
-#if 1
-#if 1
       if (!IN_LEV_FIELD(x, y) ||
          (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
          (mode == EX_TYPE_CROSS      && (x != ex && y != ey)))
        continue;
-#else
-      if (!IN_LEV_FIELD(x, y) ||
-         (mode != EX_TYPE_NORMAL && (x != ex || y != ey)))
-       continue;
-#endif
-#else
-      if (!IN_LEV_FIELD(x, y) ||
-         ((mode != EX_TYPE_NORMAL ||
-           center_element == EL_AMOEBA_TO_DIAMOND) &&
-          (x != ex || y != ey)))
-       continue;
-#endif
 
       element = Feld[x][y];
 
@@ -3388,32 +5197,11 @@ void Explode(int ex, int ey, int phase, int mode)
          RemoveMovingField(x, y);
       }
 
-#if 1
-
-#if 0
-      if (IS_EXPLOSION_PROOF(element))
-       continue;
-#else
       /* indestructible elements can only explode in center (but not flames) */
-#if 1
       if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
                                           mode == EX_TYPE_BORDER)) ||
          element == EL_FLAMES)
        continue;
-#else
-      if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey)) ||
-         element == EL_FLAMES)
-       continue;
-#endif
-#endif
-
-#else
-      if ((IS_INDESTRUCTIBLE(element) &&
-          (game.engine_version < VERSION_IDENT(2,2,0,0) ||
-           (!IS_WALKABLE_OVER(element) && !IS_WALKABLE_UNDER(element)))) ||
-         element == EL_FLAMES)
-       continue;
-#endif
 
       /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
         behaviour, for example when touching a yamyam that explodes to rocks
@@ -3430,50 +5218,22 @@ void Explode(int ex, int ey, int phase, int mode)
        if (IS_ACTIVE_BOMB(element))
        {
          /* re-activate things under the bomb like gate or penguin */
-#if 1
          Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
          Back[x][y] = 0;
-#else
-         Feld[x][y] = (Store[x][y] ? Store[x][y] : EL_EMPTY);
-         Store[x][y] = 0;
-#endif
-
-#if 0
-       printf("::: %d,%d: %d %s [%d, %d]\n", x, y, Feld[x][y],
-              element_info[Feld[x][y]].token_name,
-              Store[x][y], Store2[x][y]);
-#endif
        }
 
        continue;
       }
 
       /* save walkable background elements while explosion on same tile */
-#if 0
-      if (IS_INDESTRUCTIBLE(element))
-       Back[x][y] = element;
-#else
-#if 1
-#if 1
       if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
          (x != ex || y != ey || mode == EX_TYPE_BORDER))
        Back[x][y] = element;
-#else
-      if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
-         (x != ex || y != ey))
-       Back[x][y] = element;
-#endif
-#else
-      if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element))
-       Back[x][y] = element;
-#endif
-#endif
 
       /* ignite explodable elements reached by other explosion */
       if (element == EL_EXPLOSION)
        element = Store2[x][y];
 
-#if 1
       if (AmoebaNr[x][y] &&
          (element == EL_AMOEBA_FULL ||
           element == EL_BD_AMOEBA ||
@@ -3484,70 +5244,35 @@ void Explode(int ex, int ey, int phase, int mode)
       }
 
       RemoveField(x, y);
-#endif
 
       if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
       {
-       switch(StorePlayer[ex][ey])
-       {
-         case EL_PLAYER_2:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_2;
-           break;
-         case EL_PLAYER_3:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_3;
-           break;
-         case EL_PLAYER_4:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_4;
-           break;
-         case EL_PLAYER_1:
-         default:
-           Store[x][y] = EL_PLAYER_IS_EXPLODING_1;
-           break;
-       }
+       int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
 
-#if 1
-       if (PLAYERINFO(ex, ey)->use_murphy_graphic)
-         Store[x][y] = EL_EMPTY;
-#else
-       if (game.emulation == EMU_SUPAPLEX)
+       Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
+
+       if (PLAYERINFO(ex, ey)->use_murphy)
          Store[x][y] = EL_EMPTY;
-#endif
       }
-      else if (center_element == EL_MOLE)
-       Store[x][y] = EL_EMERALD_RED;
-      else if (center_element == EL_PENGUIN)
-       Store[x][y] = EL_EMERALD_PURPLE;
-      else if (center_element == EL_BUG)
-       Store[x][y] = ((x == ex && y == ey) ? EL_DIAMOND : EL_EMERALD);
-      else if (center_element == EL_BD_BUTTERFLY)
-       Store[x][y] = EL_BD_DIAMOND;
-      else if (center_element == EL_SP_ELECTRON)
-       Store[x][y] = EL_SP_INFOTRON;
-      else if (center_element == EL_AMOEBA_TO_DIAMOND)
-       Store[x][y] = level.amoeba_content;
+
+      /* !!! check this case -- currently needed for rnd_rado_negundo_v,
+        !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
+      else if (ELEM_IS_PLAYER(center_element))
+       Store[x][y] = EL_EMPTY;
       else if (center_element == EL_YAMYAM)
-       Store[x][y] = level.yamyam_content[game.yamyam_content_nr][xx][yy];
-      else if (IS_CUSTOM_ELEMENT(center_element) &&
-              element_info[center_element].content[xx][yy] != EL_EMPTY)
-       Store[x][y] = element_info[center_element].content[xx][yy];
-      else if (element == EL_WALL_EMERALD)
-       Store[x][y] = EL_EMERALD;
-      else if (element == EL_WALL_DIAMOND)
-       Store[x][y] = EL_DIAMOND;
-      else if (element == EL_WALL_BD_DIAMOND)
-       Store[x][y] = EL_BD_DIAMOND;
-      else if (element == EL_WALL_EMERALD_YELLOW)
-       Store[x][y] = EL_EMERALD_YELLOW;
-      else if (element == EL_WALL_EMERALD_RED)
-       Store[x][y] = EL_EMERALD_RED;
-      else if (element == EL_WALL_EMERALD_PURPLE)
-       Store[x][y] = EL_EMERALD_PURPLE;
-      else if (element == EL_WALL_PEARL)
-       Store[x][y] = EL_PEARL;
-      else if (element == EL_WALL_CRYSTAL)
-       Store[x][y] = EL_CRYSTAL;
-      else if (IS_CUSTOM_ELEMENT(element) && !CAN_EXPLODE(element))
-       Store[x][y] = element_info[element].content[1][1];
+       Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
+      else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
+       Store[x][y] = element_info[center_element].content.e[xx][yy];
+#if 1
+      /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
+        (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
+        otherwise) -- FIX THIS !!! */
+      else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
+       Store[x][y] = element_info[element].content.e[1][1];
+#else
+      else if (!CAN_EXPLODE(element))
+       Store[x][y] = element_info[element].content.e[1][1];
+#endif
       else
        Store[x][y] = EL_EMPTY;
 
@@ -3555,49 +5280,11 @@ void Explode(int ex, int ey, int phase, int mode)
          center_element == EL_AMOEBA_TO_DIAMOND)
        Store2[x][y] = element;
 
-#if 0
-      printf("::: %d,%d: %d %s\n", x, y, Store2[x][y],
-            element_info[Store2[x][y]].token_name);
-#endif
-
-#if 0
-      if (AmoebaNr[x][y] &&
-         (element == EL_AMOEBA_FULL ||
-          element == EL_BD_AMOEBA ||
-          element == EL_AMOEBA_GROWING))
-      {
-       AmoebaCnt[AmoebaNr[x][y]]--;
-       AmoebaCnt2[AmoebaNr[x][y]]--;
-      }
-
-#if 1
-      RemoveField(x, y);
-#else
-      MovDir[x][y] = MovPos[x][y] = 0;
-      GfxDir[x][y] = MovDir[x][y];
-      AmoebaNr[x][y] = 0;
-#endif
-#endif
-
       Feld[x][y] = EL_EXPLOSION;
-#if 1
-      GfxElement[x][y] = center_element;
-#else
-      GfxElement[x][y] = EL_UNDEFINED;
-#endif
+      GfxElement[x][y] = artwork_element;
 
       ExplodePhase[x][y] = 1;
-#if 1
       ExplodeDelay[x][y] = last_phase;
-#endif
-
-#if 0
-#if 1
-      GfxFrame[x][y] = 0;      /* animation does not start until next frame */
-#else
-      GfxFrame[x][y] = -1;     /* animation does not start until next frame */
-#endif
-#endif
 
       Stop[x][y] = TRUE;
     }
@@ -3606,11 +5293,6 @@ void Explode(int ex, int ey, int phase, int mode)
       game.yamyam_content_nr =
        (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
 
-#if 0
-  printf("::: %d,%d: %d %s [%d]\n", ex + 1, ey, Feld[ex + 1][ey],
-        element_info[Feld[ex + 1][ey]].token_name, Store2[ex + 1][ey]);
-#endif
-
     return;
   }
 
@@ -3620,166 +5302,57 @@ void Explode(int ex, int ey, int phase, int mode)
   x = ex;
   y = ey;
 
-#if 1
   if (phase == 1)
     GfxFrame[x][y] = 0;                /* restart explosion animation */
-#endif
-
-#if 0
-  printf(":X: phase == %d [%d]\n", phase, GfxFrame[x][y]);
-#endif
 
-#if 1
   last_phase = ExplodeDelay[x][y];
-#endif
 
   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
-
+  /* this can happen if the player leaves an explosion just in time */
   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
-
-#if 1
 
   border_element = Store2[x][y];
-#if 1
   if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
     border_element = StorePlayer[x][y];
-#else
-  if (IS_PLAYER(x, y))
-    border_element = StorePlayer[x][y];
-#endif
-
-#if 0
-  printf("::: %d,%d: %d %s [%d]\n", x, y, border_element,
-        element_info[border_element].token_name, Store2[x][y]);
-#endif
-
-#if 0
-  printf("::: phase == %d\n", phase);
-#endif
 
   if (phase == element_info[border_element].ignition_delay ||
       phase == last_phase)
   {
     boolean border_explosion = FALSE;
 
-#if 1
-#if 1
     if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
        !PLAYER_EXPLOSION_PROTECTED(x, y))
-#else
-    if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present)
-#endif
-#else
-    if (IS_PLAYER(x, y))
-#endif
     {
-      KillHeroUnlessExplosionProtected(x, y);
+      KillPlayerUnlessExplosionProtected(x, y);
       border_explosion = TRUE;
-
-#if 0
-      if (phase == last_phase)
-       printf("::: IS_PLAYER\n");
-#endif
     }
     else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
     {
-#if 0
-      printf("::: %d,%d: %d %s\n", x, y, border_element,
-            element_info[border_element].token_name);
-#endif
-
       Feld[x][y] = Store2[x][y];
       Store2[x][y] = 0;
       Bang(x, y);
       border_explosion = TRUE;
-
-#if 0
-      if (phase == last_phase)
-       printf("::: CAN_EXPLODE_BY_EXPLOSION\n");
-#endif
     }
     else if (border_element == EL_AMOEBA_TO_DIAMOND)
     {
       AmoebeUmwandeln(x, y);
       Store2[x][y] = 0;
       border_explosion = TRUE;
-
-#if 0
-      if (phase == last_phase)
-       printf("::: EL_AMOEBA_TO_DIAMOND [%d, %d] [%d]\n",
-              element_info[border_element].explosion_delay,
-              element_info[border_element].ignition_delay,
-              phase);
-#endif
     }
 
-#if 1
     /* if an element just explodes due to another explosion (chain-reaction),
        do not immediately end the new explosion when it was the last frame of
        the explosion (as it would be done in the following "if"-statement!) */
     if (border_explosion && phase == last_phase)
       return;
-#endif
-  }
-
-#else
-
-  if (phase == first_phase_after_start)
-  {
-    int element = Store2[x][y];
-
-    if (element == EL_BLACK_ORB)
-    {
-      Feld[x][y] = Store2[x][y];
-      Store2[x][y] = 0;
-      Bang(x, y);
-    }
-  }
-  else if (phase == half_phase)
-  {
-    int element = Store2[x][y];
-
-    if (IS_PLAYER(x, y))
-      KillHeroUnlessExplosionProtected(x, y);
-    else if (CAN_EXPLODE_BY_EXPLOSION(element))
-    {
-      Feld[x][y] = Store2[x][y];
-      Store2[x][y] = 0;
-      Bang(x, y);
-    }
-    else if (element == EL_AMOEBA_TO_DIAMOND)
-      AmoebeUmwandeln(x, y);
   }
-#endif
 
   if (phase == last_phase)
   {
     int element;
 
-#if 0
-  printf("::: done: phase == %d\n", phase);
-#endif
-
-#if 0
-    printf("::: explosion %d,%d done [%d]\n", x, y, FrameCounter);
-#endif
-
     element = Feld[x][y] = Store[x][y];
     Store[x][y] = Store2[x][y] = 0;
     GfxElement[x][y] = EL_UNDEFINED;
@@ -3787,12 +5360,18 @@ void Explode(int ex, int ey, int phase, int mode)
     /* player can escape from explosions and might therefore be still alive */
     if (element >= EL_PLAYER_IS_EXPLODING_1 &&
        element <= EL_PLAYER_IS_EXPLODING_4)
-      Feld[x][y] = (stored_player[element - EL_PLAYER_IS_EXPLODING_1].active ?
-                   EL_EMPTY :
-                   element == EL_PLAYER_IS_EXPLODING_1 ? EL_EMERALD_YELLOW :
-                   element == EL_PLAYER_IS_EXPLODING_2 ? EL_EMERALD_RED :
-                   element == EL_PLAYER_IS_EXPLODING_3 ? EL_EMERALD :
-                   EL_EMERALD_PURPLE);
+    {
+      int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
+      int explosion_element = EL_PLAYER_1 + player_nr;
+      int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
+      int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
+
+      if (level.use_explosion_element[player_nr])
+       explosion_element = level.explosion_element[player_nr];
+
+      Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
+                   element_info[explosion_element].content.e[xx][yy]);
+    }
 
     /* restore probably existing indestructible background element */
     if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
@@ -3800,32 +5379,20 @@ void Explode(int ex, int ey, int phase, int mode)
     Back[x][y] = 0;
 
     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
-    GfxDir[x][y] = MV_NO_MOVING;
+    GfxDir[x][y] = MV_NONE;
     ChangeDelay[x][y] = 0;
     ChangePage[x][y] = -1;
 
-#if 1
+    CustomValue[x][y] = 0;
+
     InitField_WithBug2(x, y, FALSE);
-#else
-    InitField(x, y, FALSE);
-#if 1
-    /* !!! not needed !!! */
-#if 1
-    if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
-       CAN_MOVE(Feld[x][y]) && Feld[x][y] != EL_MOLE)
-      InitMovDir(x, y);
-#else
-    if (CAN_MOVE(element))
-      InitMovDir(x, y);
-#endif
-#endif
-#endif
-    DrawLevelField(x, y);
+
+    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;
@@ -3833,43 +5400,13 @@ void Explode(int ex, int ey, int phase, int mode)
     if (ELEM_IS_PLAYER(element))
       RelocatePlayer(x, y, element);
   }
-#if 1
   else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
-#else
-  else if (phase >= delay && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
-#endif
   {
-#if 1
     int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
-#else
-    int stored = Store[x][y];
-    int graphic = (game.emulation != EMU_SUPAPLEX ? IMG_EXPLOSION :
-                  stored == EL_SP_INFOTRON ? IMG_SP_EXPLOSION_INFOTRON :
-                  IMG_SP_EXPLOSION);
-#endif
-#if 1
     int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
-#else
-    int frame = getGraphicAnimationFrame(graphic, phase - delay);
-#endif
-
-#if 0
-  printf("::: phase == %d [%d]\n", phase, GfxFrame[x][y]);
-#endif
-
-#if 0
-    printf("::: %d / %d [%d - %d]\n",
-          GfxFrame[x][y], phase - delay, phase, delay);
-#endif
-
-#if 0
-    printf("::: %d ['%s'] -> %d\n", GfxElement[x][y],
-          element_info[GfxElement[x][y]].token_name,
-          graphic);
-#endif
 
     if (phase == delay)
-      DrawLevelFieldCrumbledSand(x, y);
+      TEST_DrawLevelFieldCrumbled(x, y);
 
     if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
     {
@@ -3930,63 +5467,36 @@ void DynaExplode(int ex, int ey)
 
       Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
 
-#if 1
-#if 1
       if (element != EL_EMPTY && element != EL_EXPLOSION &&
          !IS_DIGGABLE(element) && !dynabomb_xl)
        break;
-#else
-      if (element != EL_EMPTY && element != EL_EXPLOSION &&
-         !CAN_GROW_INTO(element) && !dynabomb_xl)
-       break;
-#endif
-#else
-      /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
-      if (element != EL_EMPTY && element != EL_EXPLOSION &&
-         element != EL_SAND && !dynabomb_xl)
-       break;
-#endif
     }
   }
 }
 
 void Bang(int x, int y)
 {
-#if 1
   int element = MovingOrBlocked2Element(x, y);
-#else
-  int element = Feld[x][y];
-#endif
+  int explosion_type = EX_TYPE_NORMAL;
 
-#if 1
   if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
-#else
-  if (IS_PLAYER(x, y))
-#endif
   {
     struct PlayerInfo *player = PLAYERINFO(x, y);
 
-    element = Feld[x][y] = (player->use_murphy_graphic ? EL_SP_MURPHY :
-                           player->element_nr);
-  }
+    element = Feld[x][y] = player->initial_element;
 
-#if 0
-#if 1
-  PlayLevelSoundAction(x, y, ACTION_EXPLODING);
-#else
-  if (game.emulation == EMU_SUPAPLEX)
-    PlayLevelSound(x, y, SND_SP_ELEMENT_EXPLODING);
-  else
-    PlayLevelSound(x, y, SND_ELEMENT_EXPLODING);
-#endif
-#endif
+    if (level.use_explosion_element[player->index_nr])
+    {
+      int explosion_element = level.explosion_element[player->index_nr];
 
-#if 0
-  if (IS_PLAYER(x, y)) /* remove objects that might cause smaller explosion */
-    element = EL_EMPTY;
-#endif
+      if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
+       explosion_type = EX_TYPE_CROSS;
+      else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
+       explosion_type = EX_TYPE_CENTER;
+    }
+  }
 
-  switch(element)
+  switch (element)
   {
     case EL_BUG:
     case EL_SPACESHIP:
@@ -3998,8 +5508,8 @@ void Bang(int x, int y)
     case EL_PACMAN:
     case EL_MOLE:
       RaiseScoreElement(element);
-      Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
       break;
+
     case EL_DYNABOMB_PLAYER_1_ACTIVE:
     case EL_DYNABOMB_PLAYER_2_ACTIVE:
     case EL_DYNABOMB_PLAYER_3_ACTIVE:
@@ -4007,47 +5517,39 @@ void Bang(int x, int y)
     case EL_DYNABOMB_INCREASE_NUMBER:
     case EL_DYNABOMB_INCREASE_SIZE:
     case EL_DYNABOMB_INCREASE_POWER:
-      DynaExplode(x, y);
+      explosion_type = EX_TYPE_DYNA;
       break;
+
+    case EL_DC_LANDMINE:
+      explosion_type = EX_TYPE_CENTER;
+      break;
+
     case EL_PENGUIN:
     case EL_LAMP:
     case EL_LAMP_ACTIVE:
-#if 1
     case EL_AMOEBA_TO_DIAMOND:
-#endif
-      if (IS_PLAYER(x, y))
-       Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
-      else
-       Explode(x, y, EX_PHASE_START, EX_TYPE_CENTER);
+      if (!IS_PLAYER(x, y))    /* penguin and player may be at same field */
+       explosion_type = EX_TYPE_CENTER;
       break;
+
     default:
-#if 1
       if (element_info[element].explosion_type == EXPLODES_CROSS)
-#else
-      if (CAN_EXPLODE_CROSS(element))
-#endif
-#if 1
-       Explode(x, y, EX_PHASE_START, EX_TYPE_CROSS);
-#else
-       DynaExplode(x, y);
-#endif
-#if 1
+       explosion_type = EX_TYPE_CROSS;
       else if (element_info[element].explosion_type == EXPLODES_1X1)
-#else
-      else if (CAN_EXPLODE_1X1(element))
-#endif
-       Explode(x, y, EX_PHASE_START, EX_TYPE_CENTER);
-      else
-       Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
+       explosion_type = EX_TYPE_CENTER;
       break;
   }
 
+  if (explosion_type == EX_TYPE_DYNA)
+    DynaExplode(x, y);
+  else
+    Explode(x, y, EX_PHASE_START, explosion_type);
+
   CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
 }
 
 void SplashAcid(int x, int y)
 {
-#if 1
   if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
       (!IN_LEV_FIELD(x - 1, y - 2) ||
        !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
@@ -4059,30 +5561,6 @@ void SplashAcid(int x, int y)
     Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
 
   PlayLevelSound(x, y, SND_ACID_SPLASHING);
-#else
-  /* input: position of element entering acid (obsolete) */
-
-  int element = Feld[x][y];
-
-  if (!IN_LEV_FIELD(x, y + 1) || Feld[x][y + 1] != EL_ACID)
-    return;
-
-  if (element != EL_ACID_SPLASH_LEFT &&
-      element != EL_ACID_SPLASH_RIGHT)
-  {
-    PlayLevelSound(x, y, SND_ACID_SPLASHING);
-
-    if (IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y) &&
-       (!IN_LEV_FIELD(x - 1, y - 1) ||
-        !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 1))))
-      Feld[x - 1][y] = EL_ACID_SPLASH_LEFT;
-
-    if (IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y) &&
-       (!IN_LEV_FIELD(x + 1, y - 1) ||
-        !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 1))))
-      Feld[x + 1][y] = EL_ACID_SPLASH_RIGHT;
-  }
-#endif
 }
 
 static void InitBeltMovement()
@@ -4112,34 +5590,38 @@ static void InitBeltMovement()
     for (j = 0; j < NUM_BELT_PARTS; j++)
     {
       int element = belt_base_active_element[belt_nr] + j;
-      int graphic = el2img(element);
+      int graphic_1 = el2img(element);
+      int graphic_2 = el2panelimg(element);
 
       if (game.belt_dir[i] == MV_LEFT)
-       graphic_info[graphic].anim_mode &= ~ANIM_REVERSE;
+      {
+       graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+       graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
+      }
       else
-       graphic_info[graphic].anim_mode |=  ANIM_REVERSE;
+      {
+       graphic_info[graphic_1].anim_mode |=  ANIM_REVERSE;
+       graphic_info[graphic_2].anim_mode |=  ANIM_REVERSE;
+      }
     }
   }
 
-  for (y = 0; y < lev_fieldy; y++)
+  SCAN_PLAYFIELD(x, y)
   {
-    for (x = 0; x < lev_fieldx; x++)
-    {
-      int element = Feld[x][y];
+    int element = Feld[x][y];
 
-      for (i = 0; i < NUM_BELTS; i++)
+    for (i = 0; i < NUM_BELTS; i++)
+    {
+      if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
       {
-       if (IS_BELT(element) && game.belt_dir[i] != MV_NO_MOVING)
-       {
-         int e_belt_nr = getBeltNrFromBeltElement(element);
-         int belt_nr = i;
+       int e_belt_nr = getBeltNrFromBeltElement(element);
+       int belt_nr = i;
 
-         if (e_belt_nr == belt_nr)
-         {
-           int belt_part = Feld[x][y] - belt_base_element[belt_nr];
+       if (e_belt_nr == belt_nr)
+       {
+         int belt_part = Feld[x][y] - belt_base_element[belt_nr];
 
-           Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
-         }
+         Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
        }
       }
     }
@@ -4172,9 +5654,9 @@ static void ToggleBeltSwitch(int x, int y)
   static int belt_move_dir[4] =
   {
     MV_LEFT,
-    MV_NO_MOVING,
+    MV_NONE,
     MV_RIGHT,
-    MV_NO_MOVING,
+    MV_NONE,
   };
 
   int element = Feld[x][y];
@@ -4196,53 +5678,57 @@ static void ToggleBeltSwitch(int x, int y)
   for (i = 0; i < NUM_BELT_PARTS; i++)
   {
     int element = belt_base_active_element[belt_nr] + i;
-    int graphic = el2img(element);
+    int graphic_1 = el2img(element);
+    int graphic_2 = el2panelimg(element);
 
     if (belt_dir == MV_LEFT)
-      graphic_info[graphic].anim_mode &= ~ANIM_REVERSE;
+    {
+      graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+      graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
+    }
     else
-      graphic_info[graphic].anim_mode |=  ANIM_REVERSE;
+    {
+      graphic_info[graphic_1].anim_mode |=  ANIM_REVERSE;
+      graphic_info[graphic_2].anim_mode |=  ANIM_REVERSE;
+    }
   }
 
-  for (yy = 0; yy < lev_fieldy; yy++)
+  SCAN_PLAYFIELD(xx, yy)
   {
-    for (xx = 0; xx < lev_fieldx; xx++)
+    int element = Feld[xx][yy];
+
+    if (IS_BELT_SWITCH(element))
     {
-      int element = Feld[xx][yy];
+      int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
 
-      if (IS_BELT_SWITCH(element))
+      if (e_belt_nr == belt_nr)
       {
-       int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
-
-       if (e_belt_nr == belt_nr)
-       {
-         Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
-         DrawLevelField(xx, yy);
-       }
+       Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
+       TEST_DrawLevelField(xx, yy);
       }
-      else if (IS_BELT(element) && belt_dir != MV_NO_MOVING)
-      {
-       int e_belt_nr = getBeltNrFromBeltElement(element);
+    }
+    else if (IS_BELT(element) && belt_dir != MV_NONE)
+    {
+      int e_belt_nr = getBeltNrFromBeltElement(element);
 
-       if (e_belt_nr == belt_nr)
-       {
-         int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
+      if (e_belt_nr == belt_nr)
+      {
+       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);
-       }
+       Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
+       TEST_DrawLevelField(xx, yy);
       }
-      else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NO_MOVING)
-      {
-       int e_belt_nr = getBeltNrFromBeltActiveElement(element);
+    }
+    else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
+    {
+      int e_belt_nr = getBeltNrFromBeltActiveElement(element);
 
-       if (e_belt_nr == belt_nr)
-       {
-         int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
+      if (e_belt_nr == belt_nr)
+      {
+       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);
-       }
+       Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
+       TEST_DrawLevelField(xx, yy);
       }
     }
   }
@@ -4254,38 +5740,43 @@ static void ToggleSwitchgateSwitch(int x, int y)
 
   game.switchgate_pos = !game.switchgate_pos;
 
-  for (yy = 0; yy < lev_fieldy; yy++)
+  SCAN_PLAYFIELD(xx, yy)
   {
-    for (xx = 0; xx < lev_fieldx; xx++)
+    int element = Feld[xx][yy];
+
+    if (element == EL_SWITCHGATE_SWITCH_UP)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_SWITCHGATE_SWITCH_DOWN)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
     {
-      int element = Feld[xx][yy];
+      Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+    {
+      Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
+      TEST_DrawLevelField(xx, yy);
+    }
+    else if (element == EL_SWITCHGATE_OPEN ||
+            element == EL_SWITCHGATE_OPENING)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
 
-      if (element == EL_SWITCHGATE_SWITCH_UP ||
-         element == EL_SWITCHGATE_SWITCH_DOWN)
-      {
-       Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
-       DrawLevelField(xx, yy);
-      }
-      else if (element == EL_SWITCHGATE_OPEN ||
-              element == EL_SWITCHGATE_OPENING)
-      {
-       Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
-#if 1
-       PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
-#else
-       PlayLevelSound(xx, yy, SND_SWITCHGATE_CLOSING);
-#endif
-      }
-      else if (element == EL_SWITCHGATE_CLOSED ||
-              element == EL_SWITCHGATE_CLOSING)
-      {
-       Feld[xx][yy] = EL_SWITCHGATE_OPENING;
-#if 1
-       PlayLevelSoundAction(xx, yy, ACTION_OPENING);
-#else
-       PlayLevelSound(xx, yy, SND_SWITCHGATE_OPENING);
-#endif
-      }
+      PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
+    }
+    else if (element == EL_SWITCHGATE_CLOSED ||
+            element == EL_SWITCHGATE_CLOSING)
+    {
+      Feld[xx][yy] = EL_SWITCHGATE_OPENING;
+
+      PlayLevelSoundAction(xx, yy, ACTION_OPENING);
     }
   }
 }
@@ -4310,50 +5801,159 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
 {
   int x, y;
 
-  for (y = 0; y < lev_fieldy; y++)
+  SCAN_PLAYFIELD(x, y)
   {
-    for (x = 0; x < lev_fieldx; x++)
+    int element = Feld[x][y];
+
+    if (element == EL_LIGHT_SWITCH &&
+       game.light_time_left > 0)
     {
-      int element = Feld[x][y];
+      Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_LIGHT_SWITCH_ACTIVE &&
+            game.light_time_left == 0)
+    {
+      Feld[x][y] = EL_LIGHT_SWITCH;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_DRIPPER &&
+            game.light_time_left > 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_DRIPPER_ACTIVE &&
+            game.light_time_left == 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL ||
+            element == EL_INVISIBLE_WALL ||
+            element == EL_INVISIBLE_SAND)
+    {
+      if (game.light_time_left > 0)
+       Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
 
-      if (element == EL_LIGHT_SWITCH &&
-         game.light_time_left > 0)
-      {
-       Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
-       DrawLevelField(x, y);
-      }
-      else if (element == EL_LIGHT_SWITCH_ACTIVE &&
-              game.light_time_left == 0)
-      {
-       Feld[x][y] = EL_LIGHT_SWITCH;
-       DrawLevelField(x, y);
-      }
-      else if (element == EL_INVISIBLE_STEELWALL ||
-              element == EL_INVISIBLE_WALL ||
-              element == EL_INVISIBLE_SAND)
-      {
-       if (game.light_time_left > 0)
-         Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+      TEST_DrawLevelField(x, y);
 
-       DrawLevelField(x, y);
+      /* uncrumble neighbour fields, if needed */
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+            element == EL_INVISIBLE_WALL_ACTIVE ||
+            element == EL_INVISIBLE_SAND_ACTIVE)
+    {
+      if (game.light_time_left == 0)
+       Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
 
-       /* uncrumble neighbour fields, if needed */
-       if (element == EL_INVISIBLE_SAND)
-         DrawLevelFieldCrumbledSandNeighbours(x, y);
-      }
-      else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
-              element == EL_INVISIBLE_WALL_ACTIVE ||
-              element == EL_INVISIBLE_SAND_ACTIVE)
-      {
-       if (game.light_time_left == 0)
-         Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+      TEST_DrawLevelField(x, y);
 
-       DrawLevelField(x, y);
+      /* re-crumble neighbour fields, if needed */
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+    }
+  }
+}
 
-       /* re-crumble neighbour fields, if needed */
-       if (element == EL_INVISIBLE_SAND)
-         DrawLevelFieldCrumbledSandNeighbours(x, y);
-      }
+static void RedrawAllInvisibleElementsForLenses()
+{
+  int x, y;
+
+  SCAN_PLAYFIELD(x, y)
+  {
+    int element = Feld[x][y];
+
+    if (element == EL_EMC_DRIPPER &&
+       game.lenses_time_left > 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_DRIPPER_ACTIVE &&
+            game.lenses_time_left == 0)
+    {
+      Feld[x][y] = EL_EMC_DRIPPER;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL ||
+            element == EL_INVISIBLE_WALL ||
+            element == EL_INVISIBLE_SAND)
+    {
+      if (game.lenses_time_left > 0)
+       Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+
+      TEST_DrawLevelField(x, y);
+
+      /* uncrumble neighbour fields, if needed */
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+    }
+    else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+            element == EL_INVISIBLE_WALL_ACTIVE ||
+            element == EL_INVISIBLE_SAND_ACTIVE)
+    {
+      if (game.lenses_time_left == 0)
+       Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+
+      TEST_DrawLevelField(x, y);
+
+      /* re-crumble neighbour fields, if needed */
+      if (element == EL_INVISIBLE_SAND)
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+    }
+  }
+}
+
+static void RedrawAllInvisibleElementsForMagnifier()
+{
+  int x, y;
+
+  SCAN_PLAYFIELD(x, y)
+  {
+    int element = Feld[x][y];
+
+    if (element == EL_EMC_FAKE_GRASS &&
+       game.magnify_time_left > 0)
+    {
+      Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
+            game.magnify_time_left == 0)
+    {
+      Feld[x][y] = EL_EMC_FAKE_GRASS;
+      TEST_DrawLevelField(x, y);
+    }
+    else if (IS_GATE_GRAY(element) &&
+            game.magnify_time_left > 0)
+    {
+      Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
+                   element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
+                   IS_EM_GATE_GRAY(element) ?
+                   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);
+      TEST_DrawLevelField(x, y);
+    }
+    else if (IS_GATE_GRAY_ACTIVE(element) &&
+            game.magnify_time_left == 0)
+    {
+      Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
+                   element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
+                   IS_EM_GATE_GRAY_ACTIVE(element) ?
+                   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);
+      TEST_DrawLevelField(x, y);
     }
   }
 }
@@ -4375,31 +5975,29 @@ static void ActivateTimegateSwitch(int x, int y)
 
   game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
 
-  for (yy = 0; yy < lev_fieldy; yy++)
+  SCAN_PLAYFIELD(xx, yy)
   {
-    for (xx = 0; xx < lev_fieldx; xx++)
-    {
-      int element = Feld[xx][yy];
-
-      if (element == EL_TIMEGATE_CLOSED ||
-         element == EL_TIMEGATE_CLOSING)
-      {
-       Feld[xx][yy] = EL_TIMEGATE_OPENING;
-       PlayLevelSound(xx, yy, SND_TIMEGATE_OPENING);
-      }
+    int element = Feld[xx][yy];
 
-      /*
-      else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
-      {
-       Feld[xx][yy] = EL_TIMEGATE_SWITCH;
-       DrawLevelField(xx, yy);
-      }
-      */
+    if (element == EL_TIMEGATE_CLOSED ||
+       element == EL_TIMEGATE_CLOSING)
+    {
+      Feld[xx][yy] = EL_TIMEGATE_OPENING;
+      PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
+    }
 
+    /*
+    else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
+    {
+      Feld[xx][yy] = EL_TIMEGATE_SWITCH;
+      TEST_DrawLevelField(xx, yy);
     }
+    */
+
   }
 
-  Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE;
+  Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
+               EL_DC_TIMEGATE_SWITCH_ACTIVE);
 }
 
 void Impact(int x, int y)
@@ -4410,10 +6008,6 @@ void Impact(int x, int y)
   int element = Feld[x][y];
   int smashed = EL_STEELWALL;
 
-#if 0
-  printf("IMPACT!\n");
-#endif
-
   if (!last_line)      /* check if element below was hit */
   {
     if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
@@ -4423,15 +6017,33 @@ void Impact(int x, int y)
                                         MovDir[x][y + 1] != MV_DOWN ||
                                         MovPos[x][y + 1] <= TILEY / 2));
 
-#if 0
-    object_hit = !IS_FREE(x, y + 1);
-#endif
-
     /* do not smash moving elements that left the smashed field in time */
     if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
        ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
       object_hit = FALSE;
 
+#if USE_QUICKSAND_IMPACT_BUGFIX
+    if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
+    {
+      RemoveMovingField(x, y + 1);
+      Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
+      Feld[x][y + 2] = EL_ROCK;
+      TEST_DrawLevelField(x, y + 2);
+
+      object_hit = TRUE;
+    }
+
+    if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
+    {
+      RemoveMovingField(x, y + 1);
+      Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
+      Feld[x][y + 2] = EL_ROCK;
+      TEST_DrawLevelField(x, y + 2);
+
+      object_hit = TRUE;
+    }
+#endif
+
     if (object_hit)
       smashed = MovingOrBlocked2Element(x, y + 1);
 
@@ -4450,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))
@@ -4458,7 +6070,8 @@ void Impact(int x, int y)
     Bang(x, y);
     return;
   }
-  else if (impact && element == EL_PEARL)
+  else if (impact && element == EL_PEARL &&
+          smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
   {
     ResetGfxAnimation(x, y);
 
@@ -4476,7 +6089,7 @@ void Impact(int x, int y)
   if (impact && element == EL_AMOEBA_DROP)
   {
     if (object_hit && IS_PLAYER(x, y + 1))
-      KillHeroUnlessEnemyProtected(x, y + 1);
+      KillPlayerUnlessEnemyProtected(x, y + 1);
     else if (object_hit && smashed == EL_PENGUIN)
       Bang(x, y + 1);
     else
@@ -4491,34 +6104,40 @@ void Impact(int x, int y)
 
   if (object_hit)              /* check which object was hit */
   {
-    if (CAN_PASS_MAGIC_WALL(element) && 
-       (smashed == EL_MAGIC_WALL ||
-        smashed == EL_BD_MAGIC_WALL))
+    if ((CAN_PASS_MAGIC_WALL(element) && 
+        (smashed == EL_MAGIC_WALL ||
+         smashed == EL_BD_MAGIC_WALL)) ||
+       (CAN_PASS_DC_MAGIC_WALL(element) &&
+        smashed == EL_DC_MAGIC_WALL))
     {
       int xx, yy;
       int activated_magic_wall =
        (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
-        EL_BD_MAGIC_WALL_ACTIVE);
+        smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
+        EL_DC_MAGIC_WALL_ACTIVE);
 
       /* activate magic wall / mill */
-      for (yy = 0; yy < lev_fieldy; yy++)
-       for (xx = 0; xx < lev_fieldx; xx++)
-         if (Feld[xx][yy] == smashed)
-           Feld[xx][yy] = activated_magic_wall;
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       if (Feld[xx][yy] == smashed)
+         Feld[xx][yy] = activated_magic_wall;
+      }
 
       game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
       game.magic_wall_active = TRUE;
 
       PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
                            SND_MAGIC_WALL_ACTIVATING :
-                           SND_BD_MAGIC_WALL_ACTIVATING));
+                           smashed == EL_BD_MAGIC_WALL ?
+                           SND_BD_MAGIC_WALL_ACTIVATING :
+                           SND_DC_MAGIC_WALL_ACTIVATING));
     }
 
     if (IS_PLAYER(x, y + 1))
     {
       if (CAN_SMASH_PLAYER(element))
       {
-       KillHeroUnlessEnemyProtected(x, y + 1);
+       KillPlayerUnlessEnemyProtected(x, y + 1);
        return;
       }
     }
@@ -4549,13 +6168,6 @@ void Impact(int x, int y)
       Bang(x, y + 1);
       return;
     }
-#if 0
-    else if (CAN_SMASH_ENEMIES(element) && IS_CLASSIC_ENEMY(smashed))
-    {
-      Bang(x, y + 1);
-      return;
-    }
-#endif
     else if (CAN_SMASH_EVERYTHING(element))
     {
       if (IS_CLASSIC_ENEMY(smashed) ||
@@ -4598,7 +6210,9 @@ void Impact(int x, int y)
          ToggleBeltSwitch(x, y + 1);
        }
        else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
-                smashed == EL_SWITCHGATE_SWITCH_DOWN)
+                smashed == EL_SWITCHGATE_SWITCH_DOWN ||
+                smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
+                smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
        {
          ToggleSwitchgateSwitch(x, y + 1);
        }
@@ -4609,24 +6223,12 @@ void Impact(int x, int y)
        }
        else
        {
-#if 0
-         TestIfElementSmashesCustomElement(x, y, MV_DOWN);
-#endif
-
          CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
 
-#if 1
-         /* !!! TEST ONLY !!! */
-         CheckElementChangeBySide(x, y + 1, smashed, element,
-                                  CE_SWITCHED, CH_SIDE_TOP);
-         CheckTriggeredElementChangeBySide(x, y + 1, smashed,
-                                           CE_SWITCH_OF_X, CH_SIDE_TOP);
-#else
-         CheckTriggeredElementChangeBySide(x, y + 1, smashed,
-                                           CE_SWITCH_OF_X, CH_SIDE_TOP);
          CheckElementChangeBySide(x, y + 1, smashed, element,
                                   CE_SWITCHED, CH_SIDE_TOP);
-#endif
+         CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
+                                           CH_SIDE_TOP);
        }
       }
       else
@@ -4639,12 +6241,15 @@ void Impact(int x, int y)
   /* play sound of magic wall / mill */
   if (!last_line &&
       (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
-       Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
+       Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
+       Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
   {
     if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
       PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
     else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
       PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
+    else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
+      PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
 
     return;
   }
@@ -4658,7 +6263,7 @@ inline static void TurnRoundExt(int x, int y)
 {
   static struct
   {
-    int x, y;
+    int dx, dy;
   } move_xy[] =
   {
     {  0,  0 },
@@ -4693,10 +6298,10 @@ inline static void TurnRoundExt(int x, int y)
   int right_dir = turn[old_move_dir].right;
   int back_dir  = turn[old_move_dir].back;
 
-  int left_dx  = move_xy[left_dir].x,     left_dy  = move_xy[left_dir].y;
-  int right_dx = move_xy[right_dir].x,    right_dy = move_xy[right_dir].y;
-  int move_dx  = move_xy[old_move_dir].x, move_dy  = move_xy[old_move_dir].y;
-  int back_dx  = move_xy[back_dir].x,     back_dy  = move_xy[back_dir].y;
+  int left_dx  = move_xy[left_dir].dx,     left_dy  = move_xy[left_dir].dy;
+  int right_dx = move_xy[right_dir].dx,    right_dy = move_xy[right_dir].dy;
+  int move_dx  = move_xy[old_move_dir].dx, move_dy  = move_xy[old_move_dir].dy;
+  int back_dx  = move_xy[back_dir].dx,     back_dy  = move_xy[back_dir].dy;
 
   int left_x  = x + left_dx,  left_y  = y + left_dy;
   int right_x = x + right_dx, right_y = y + right_dy;
@@ -4718,26 +6323,6 @@ inline static void TurnRoundExt(int x, int y)
     else if (element == EL_BD_BUTTERFLY)     /* && MovDir[x][y] == left_dir) */
       MovDelay[x][y] = 1;
   }
-#if 0
-  else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
-          element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
-  {
-    TestIfBadThingTouchesOtherBadThing(x, y);
-
-    if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
-      MovDir[x][y] = left_dir;
-    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = right_dir;
-
-    if ((element == EL_SPACESHIP ||
-        element == EL_SP_SNIKSNAK ||
-        element == EL_SP_ELECTRON)
-       && MovDir[x][y] != old_move_dir)
-      MovDelay[x][y] = 9;
-    else if (element == EL_BD_FIREFLY)     /* && MovDir[x][y] == right_dir) */
-      MovDelay[x][y] = 1;
-  }
-#else
   else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
   {
     TestIfBadThingTouchesOtherBadThing(x, y);
@@ -4764,7 +6349,6 @@ inline static void TurnRoundExt(int x, int y)
     if (MovDir[x][y] != old_move_dir)
       MovDelay[x][y] = 9;
   }
-#endif
   else if (element == EL_YAMYAM)
   {
     boolean can_turn_left  = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
@@ -4870,18 +6454,12 @@ inline static void TurnRoundExt(int x, int y)
     else
       MovDir[x][y] = back_dir;
 
-    xx = x + move_xy[MovDir[x][y]].x;
-    yy = y + move_xy[MovDir[x][y]].y;
+    xx = x + move_xy[MovDir[x][y]].dx;
+    yy = y + move_xy[MovDir[x][y]].dy;
 
-#if 1
-    /* !!! this bugfix breaks at least BD2K3, level 010 !!! [re-recorded] */
     if (!IN_LEV_FIELD(xx, yy) ||
         (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
       MovDir[x][y] = old_move_dir;
-#else
-    if (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy]))
-      MovDir[x][y] = old_move_dir;
-#endif
 
     MovDelay[x][y] = 0;
   }
@@ -4893,11 +6471,6 @@ inline static void TurnRoundExt(int x, int y)
     int rnd_value = 24;
     int rnd = RND(rnd_value);
 
-#if 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":2: %d/%d: %d [%d]\n", x, y, MovDir[x][y], FrameCounter);
-#endif
-
     if (can_move_on && rnd > rnd_value / 8)
       MovDir[x][y] = old_move_dir;
     else if (can_turn_left && can_turn_right)
@@ -4909,28 +6482,11 @@ inline static void TurnRoundExt(int x, int y)
     else
       MovDir[x][y] = back_dir;
 
-    xx = x + move_xy[MovDir[x][y]].x;
-    yy = y + move_xy[MovDir[x][y]].y;
+    xx = x + move_xy[MovDir[x][y]].dx;
+    yy = y + move_xy[MovDir[x][y]].dy;
 
-#if 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":3: %d/%d: %d (%d/%d: %d) [%d]\n", x, y, MovDir[x][y],
-            xx, yy, Feld[xx][yy],
-            FrameCounter);
-#endif
-
-#if 1
     if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
       MovDir[x][y] = old_move_dir;
-#else
-    if (!IS_FREE(xx, yy))
-      MovDir[x][y] = old_move_dir;
-#endif
-
-#if 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":4: %d/%d: %d [%d]\n", x, y, MovDir[x][y], FrameCounter);
-#endif
 
     MovDelay[x][y] = 0;
   }
@@ -4963,27 +6519,33 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (element == EL_BALLOON)
   {
-    MovDir[x][y] = game.balloon_dir;
+    MovDir[x][y] = game.wind_direction;
     MovDelay[x][y] = 0;
   }
   else if (element == EL_SPRING)
   {
-#if 0
-    if (MovDir[x][y] & MV_HORIZONTAL &&
-       !SPRING_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = MV_NO_MOVING;
-#else
-    if (MovDir[x][y] & MV_HORIZONTAL &&
-       (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
-        SPRING_CAN_ENTER_FIELD(element, x, y + 1)))
-      MovDir[x][y] = MV_NO_MOVING;
-#endif
+    if (MovDir[x][y] & MV_HORIZONTAL)
+    {
+      if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
+         !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+      {
+       Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
+       ResetGfxAnimation(move_x, move_y);
+       TEST_DrawLevelField(move_x, move_y);
+
+       MovDir[x][y] = back_dir;
+      }
+      else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
+              SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+       MovDir[x][y] = MV_NONE;
+    }
 
     MovDelay[x][y] = 0;
   }
   else if (element == EL_ROBOT ||
           element == EL_SATELLITE ||
-          element == EL_PENGUIN)
+          element == EL_PENGUIN ||
+          element == EL_EMC_ANDROID)
   {
     int attr_x = -1, attr_y = -1;
 
@@ -5013,13 +6575,9 @@ inline static void TurnRoundExt(int x, int y)
       }
     }
 
-#if 1
     if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
        (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
         game.engine_version < VERSION_IDENT(3,1,0,0)))
-#else
-    if (element == EL_ROBOT && ZX >= 0 && ZY >= 0)
-#endif
     {
       attr_x = ZX;
       attr_y = ZY;
@@ -5041,7 +6599,10 @@ inline static void TurnRoundExt(int x, int y)
        int ex = x + xy[i][0];
        int ey = y + xy[i][1];
 
-       if (IN_LEV_FIELD(ex, ey) && Feld[ex][ey] == EL_EXIT_OPEN)
+       if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
+                                    Feld[ex][ey] == EL_EM_EXIT_OPEN ||
+                                    Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
+                                    Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
        {
          attr_x = ex;
          attr_y = ey;
@@ -5050,7 +6611,7 @@ inline static void TurnRoundExt(int x, int y)
       }
     }
 
-    MovDir[x][y] = MV_NO_MOVING;
+    MovDir[x][y] = MV_NONE;
     if (attr_x < x)
       MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
     else if (attr_x > x)
@@ -5088,21 +6649,21 @@ inline static void TurnRoundExt(int x, int y)
          new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
-       if (PENGUIN_CAN_ENTER_FIELD(EL_PENGUIN, newx, newy))
+       if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
          return;
 
        MovDir[x][y] =
          new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
-       if (PENGUIN_CAN_ENTER_FIELD(EL_PENGUIN, newx, newy))
+       if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
          return;
 
        MovDir[x][y] = old_move_dir;
        return;
       }
     }
-    else       /* (element == EL_SATELLITE) */
+    else if (element == EL_SATELLITE)
     {
       int newx, newy;
 
@@ -5131,23 +6692,159 @@ inline static void TurnRoundExt(int x, int y)
        return;
       }
     }
-  }
-  else if (move_pattern == MV_TURNING_LEFT ||
-          move_pattern == MV_TURNING_RIGHT ||
-          move_pattern == MV_TURNING_LEFT_RIGHT ||
-          move_pattern == MV_TURNING_RIGHT_LEFT ||
-          move_pattern == MV_TURNING_RANDOM ||
-          move_pattern == MV_ALL_DIRECTIONS)
-  {
-    boolean can_turn_left =
-      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
-    boolean can_turn_right =
-      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
+    else if (element == EL_EMC_ANDROID)
+    {
+      static int check_pos[16] =
+      {
+       -1,             /*  0 => (invalid)          */
+       7,              /*  1 => MV_LEFT            */
+       3,              /*  2 => MV_RIGHT           */
+       -1,             /*  3 => (invalid)          */
+       1,              /*  4 =>            MV_UP   */
+       0,              /*  5 => MV_LEFT  | MV_UP   */
+       2,              /*  6 => MV_RIGHT | MV_UP   */
+       -1,             /*  7 => (invalid)          */
+       5,              /*  8 =>            MV_DOWN */
+       6,              /*  9 => MV_LEFT  | MV_DOWN */
+       4,              /* 10 => MV_RIGHT | MV_DOWN */
+       -1,             /* 11 => (invalid)          */
+       -1,             /* 12 => (invalid)          */
+       -1,             /* 13 => (invalid)          */
+       -1,             /* 14 => (invalid)          */
+       -1,             /* 15 => (invalid)          */
+      };
+      static struct
+      {
+       int dx, dy;
+       int dir;
+      } check_xy[8] =
+      {
+        { -1, -1,      MV_LEFT  | MV_UP   },
+               {  0, -1,                  MV_UP   },
+       { +1, -1,       MV_RIGHT | MV_UP   },
+       { +1,  0,       MV_RIGHT           },
+       { +1, +1,       MV_RIGHT | MV_DOWN },
+       {  0, +1,                  MV_DOWN },
+       { -1, +1,       MV_LEFT  | MV_DOWN },
+       { -1,  0,       MV_LEFT            },
+      };
+      int start_pos, check_order;
+      boolean can_clone = FALSE;
+      int i;
 
-#if USE_CAN_MOVE_NOT_MOVING
-    if (element_info[element].move_stepsize == 0)      /* not moving */
+      /* check if there is any free field around current position */
+      for (i = 0; i < 8; i++)
+      {
+       int newx = x + check_xy[i].dx;
+       int newy = y + check_xy[i].dy;
+
+       if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+       {
+         can_clone = TRUE;
+
+         break;
+       }
+      }
+
+      if (can_clone)           /* randomly find an element to clone */
+      {
+       can_clone = FALSE;
+
+       start_pos = check_pos[RND(8)];
+       check_order = (RND(2) ? -1 : +1);
+
+       for (i = 0; i < 8; i++)
+       {
+         int pos_raw = start_pos + i * check_order;
+         int pos = (pos_raw + 8) % 8;
+         int newx = x + check_xy[pos].dx;
+         int newy = y + check_xy[pos].dy;
+
+         if (ANDROID_CAN_CLONE_FIELD(newx, newy))
+         {
+           element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
+           element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
+
+           Store[x][y] = Feld[newx][newy];
+
+           can_clone = TRUE;
+
+           break;
+         }
+       }
+      }
+
+      if (can_clone)           /* randomly find a direction to move */
+      {
+       can_clone = FALSE;
+
+       start_pos = check_pos[RND(8)];
+       check_order = (RND(2) ? -1 : +1);
+
+       for (i = 0; i < 8; i++)
+       {
+         int pos_raw = start_pos + i * check_order;
+         int pos = (pos_raw + 8) % 8;
+         int newx = x + check_xy[pos].dx;
+         int newy = y + check_xy[pos].dy;
+         int new_move_dir = check_xy[pos].dir;
+
+         if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+         {
+           MovDir[x][y] = new_move_dir;
+           MovDelay[x][y] = level.android_clone_time * 8 + 1;
+
+           can_clone = TRUE;
+
+           break;
+         }
+       }
+      }
+
+      if (can_clone)           /* cloning and moving successful */
+       return;
+
+      /* cannot clone -- try to move towards player */
+
+      start_pos = check_pos[MovDir[x][y] & 0x0f];
+      check_order = (RND(2) ? -1 : +1);
+
+      for (i = 0; i < 3; i++)
+      {
+       /* first check start_pos, then previous/next or (next/previous) pos */
+       int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
+       int pos = (pos_raw + 8) % 8;
+       int newx = x + check_xy[pos].dx;
+       int newy = y + check_xy[pos].dy;
+       int new_move_dir = check_xy[pos].dir;
+
+       if (IS_PLAYER(newx, newy))
+         break;
+
+       if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+       {
+         MovDir[x][y] = new_move_dir;
+         MovDelay[x][y] = level.android_move_time * 8 + 1;
+
+         break;
+       }
+      }
+    }
+  }
+  else if (move_pattern == MV_TURNING_LEFT ||
+          move_pattern == MV_TURNING_RIGHT ||
+          move_pattern == MV_TURNING_LEFT_RIGHT ||
+          move_pattern == MV_TURNING_RIGHT_LEFT ||
+          move_pattern == MV_TURNING_RANDOM ||
+          move_pattern == MV_ALL_DIRECTIONS)
+  {
+    boolean can_turn_left =
+      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right =
+      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
+
+    if (element_info[element].move_stepsize == 0)      /* "not moving" */
       return;
-#endif
 
     if (move_pattern == MV_TURNING_LEFT)
       MovDir[x][y] = left_dir;
@@ -5189,6 +6886,11 @@ inline static void TurnRoundExt(int x, int y)
     MovDir[x][y] = move_pattern;
     MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
   }
+  else if (move_pattern & MV_WIND_DIRECTION)
+  {
+    MovDir[x][y] = game.wind_direction;
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
   else if (move_pattern == MV_ALONG_LEFT_SIDE)
   {
     if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
@@ -5242,7 +6944,7 @@ inline static void TurnRoundExt(int x, int y)
       }
     }
 
-    MovDir[x][y] = MV_NO_MOVING;
+    MovDir[x][y] = MV_NONE;
     if (attr_x < x)
       MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
     else if (attr_x > x)
@@ -5259,15 +6961,13 @@ inline static void TurnRoundExt(int x, int y)
       boolean first_horiz = RND(2);
       int new_move_dir = MovDir[x][y];
 
-#if USE_CAN_MOVE_NOT_MOVING
-      if (element_info[element].move_stepsize == 0)    /* not moving */
+      if (element_info[element].move_stepsize == 0)    /* "not moving" */
       {
        first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
        MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
 
        return;
       }
-#endif
 
       MovDir[x][y] =
        new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
@@ -5290,7 +6990,7 @@ inline static void TurnRoundExt(int x, int y)
           move_pattern == MV_WHEN_DROPPED)
   {
     if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
-      MovDir[x][y] = MV_NO_MOVING;
+      MovDir[x][y] = MV_NONE;
 
     MovDelay[x][y] = 0;
   }
@@ -5318,7 +7018,7 @@ inline static void TurnRoundExt(int x, int y)
     };
     boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
     int move_preference = -1000000;    /* start with very low preference */
-    int new_move_dir = MV_NO_MOVING;
+    int new_move_dir = MV_NONE;
     int start_test = RND(4);
     int i;
 
@@ -5362,13 +7062,7 @@ inline static void TurnRoundExt(int x, int y)
 
     MovDir[x][y] = new_move_dir;
     if (old_move_dir != new_move_dir)
-    {
-#if 1
       MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
-#else
-      MovDelay[x][y] = 9;
-#endif
-    }
   }
 }
 
@@ -5376,26 +7070,17 @@ static void TurnRound(int x, int y)
 {
   int direction = MovDir[x][y];
 
-#if 0
-  GfxDir[x][y] = MovDir[x][y];
-#endif
-
   TurnRoundExt(x, y);
 
-#if 1
   GfxDir[x][y] = MovDir[x][y];
-#endif
 
   if (direction != MovDir[x][y])
     GfxFrame[x][y] = 0;
 
-#if 1
   if (MovDelay[x][y])
-    GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_BIT(direction);
-#else
-  if (MovDelay[x][y])
-    GfxAction[x][y] = ACTION_WAITING;
-#endif
+    GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
+
+  ResetGfxFrame(x, y, FALSE);
 }
 
 static boolean JustBeingPushed(int x, int y)
@@ -5421,23 +7106,14 @@ static boolean JustBeingPushed(int x, int y)
 
 void StartMoving(int x, int y)
 {
-#if 0
-  boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0,0));
-#endif
   boolean started_moving = FALSE;      /* some elements can fall _and_ move */
   int element = Feld[x][y];
 
   if (Stop[x][y])
     return;
 
-#if 1
   if (MovDelay[x][y] == 0)
     GfxAction[x][y] = ACTION_DEFAULT;
-#else
-  /* !!! this should be handled more generic (not only for mole) !!! */
-  if (element != EL_MOLE && GfxAction[x][y] != ACTION_DIGGING)
-    GfxAction[x][y] = ACTION_DEFAULT;
-#endif
 
   if (CAN_FALL(element) && y < lev_fieldy - 1)
   {
@@ -5454,20 +7130,30 @@ void StartMoving(int x, int y)
        started_moving = TRUE;
 
        Feld[x][y] = EL_QUICKSAND_EMPTYING;
-       Store[x][y] = EL_ROCK;
-#if 1
-       PlayLevelSoundAction(x, y, ACTION_EMPTYING);
+#if USE_QUICKSAND_BD_ROCK_BUGFIX
+       if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
+         Store[x][y] = EL_ROCK;
 #else
-       PlayLevelSound(x, y, SND_QUICKSAND_EMPTYING);
+       Store[x][y] = EL_ROCK;
 #endif
+
+       PlayLevelSoundAction(x, y, ACTION_EMPTYING);
       }
       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;
@@ -5477,11 +7163,107 @@ void StartMoving(int x, int y)
        Feld[x][y + 1] = EL_QUICKSAND_FULL;
        Store[x][y + 1] = Store[x][y];
        Store[x][y] = 0;
-#if 1
+
+       PlayLevelSoundAction(x, y, ACTION_FILLING);
+      }
+      else 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);
+      }
+    }
+    else if (element == EL_QUICKSAND_FAST_FULL)
+    {
+      if (IS_FREE(x, y + 1))
+      {
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
+
+       Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
+#if USE_QUICKSAND_BD_ROCK_BUGFIX
+       if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
+         Store[x][y] = EL_ROCK;
 #else
-       PlayLevelSound(x, y, SND_QUICKSAND_FILLING);
+       Store[x][y] = EL_ROCK;
 #endif
+
+       PlayLevelSoundAction(x, y, ACTION_EMPTYING);
+      }
+      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;
+       }
+
+       Feld[x][y] = EL_QUICKSAND_FAST_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);
+      }
+      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);
       }
     }
     else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
@@ -5492,11 +7274,19 @@ void StartMoving(int x, int y)
 
       Feld[x][y] = EL_QUICKSAND_FILLING;
       Store[x][y] = element;
-#if 1
+
+      PlayLevelSoundAction(x, y, ACTION_FILLING);
+    }
+    else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
+            Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
+    {
+      InitMovingField(x, y, MV_DOWN);
+      started_moving = TRUE;
+
+      Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
+      Store[x][y] = element;
+
       PlayLevelSoundAction(x, y, ACTION_FILLING);
-#else
-      PlayLevelSound(x, y, SND_QUICKSAND_FILLING);
-#endif
     }
     else if (element == EL_MAGIC_WALL_FULL)
     {
@@ -5511,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])
        {
@@ -5534,12 +7324,12 @@ void StartMoving(int x, int y)
        started_moving = TRUE;
 
        Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
-       Store[x][y] = EL_CHANGED2(Store[x][y]);
+       Store[x][y] = EL_CHANGED_BD(Store[x][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])
        {
@@ -5550,27 +7340,55 @@ void StartMoving(int x, int y)
 
        Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
        Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
-       Store[x][y + 1] = EL_CHANGED2(Store[x][y]);
+       Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
+       Store[x][y] = 0;
+      }
+    }
+    else if (element == EL_DC_MAGIC_WALL_FULL)
+    {
+      if (IS_FREE(x, y + 1))
+      {
+       InitMovingField(x, y, MV_DOWN);
+       started_moving = TRUE;
+
+       Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
+       Store[x][y] = EL_CHANGED_DC(Store[x][y]);
+      }
+      else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
+      {
+       if (!MovDelay[x][y])
+         MovDelay[x][y] = TILEY / 4 + 1;
+
+       if (MovDelay[x][y])
+       {
+         MovDelay[x][y]--;
+         if (MovDelay[x][y])
+           return;
+       }
+
+       Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
+       Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
+       Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
        Store[x][y] = 0;
       }
     }
-    else if (CAN_PASS_MAGIC_WALL(element) &&
-            (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
-             Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
+    else if ((CAN_PASS_MAGIC_WALL(element) &&
+             (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
+              Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
+            (CAN_PASS_DC_MAGIC_WALL(element) &&
+             (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
+
     {
       InitMovingField(x, y, MV_DOWN);
       started_moving = TRUE;
 
       Feld[x][y] =
        (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
-        EL_BD_MAGIC_WALL_FILLING);
+        Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
+        EL_DC_MAGIC_WALL_FILLING);
       Store[x][y] = element;
     }
-#if 0
-    else if (CAN_SMASH(element) && Feld[x][y + 1] == EL_ACID)
-#else
     else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
-#endif
     {
       SplashAcid(x, y + 1);
 
@@ -5578,16 +7396,10 @@ void StartMoving(int x, int y)
       started_moving = TRUE;
 
       Store[x][y] = EL_ACID;
-#if 0
-      /* !!! TEST !!! better use "_FALLING" etc. !!! */
-      GfxAction[x][y + 1] = ACTION_ACTIVE;
-#endif
     }
-#if 1
-    else if ((game.engine_version >= VERSION_IDENT(3,1,0,0) &&
-             CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
-
-#if USE_IMPACT_BUGFIX
+    else if (
+            (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+             CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
             (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))) ||
@@ -5595,27 +7407,6 @@ void StartMoving(int x, int y)
             (game.engine_version < VERSION_IDENT(2,2,0,7) &&
              CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
              (Feld[x][y + 1] == EL_BLOCKED)))
-#else
-            (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
-             CAN_SMASH(element) && WasJustFalling[x][y] &&
-             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
-
-            (game.engine_version < VERSION_IDENT(2,2,0,7) &&
-             CAN_SMASH(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
-             (Feld[x][y + 1] == EL_BLOCKED)))
-#endif
-
-#else
-#if 1
-    else if (game.engine_version < VERSION_IDENT(2,2,0,7) &&
-            CAN_SMASH(element) && Feld[x][y + 1] == EL_BLOCKED &&
-            WasJustMoving[x][y] && !Pushed[x][y + 1])
-#else
-    else if (CAN_SMASH(element) && Feld[x][y + 1] == EL_BLOCKED &&
-            WasJustMoving[x][y])
-#endif
-#endif
-
     {
       /* this is needed for a special case not covered by calling "Impact()"
         from "ContinueMoving()": if an element moves to a tile directly below
@@ -5627,23 +7418,14 @@ void StartMoving(int x, int y)
         element; also, the case of the player being the element to smash was
         simply not covered here... :-/ ) */
 
-#if 0
-      WasJustMoving[x][y] = 0;
-      WasJustFalling[x][y] = 0;
-#endif
-
       CheckCollision[x][y] = 0;
-
-#if 0
-      if (IS_PLAYER(x, y + 1))
-       printf("::: we ARE now killing the player [%d]\n", FrameCounter);
-#endif
+      CheckImpact[x][y] = 0;
 
       Impact(x, y);
     }
     else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
     {
-      if (MovDir[x][y] == MV_NO_MOVING)
+      if (MovDir[x][y] == MV_NONE)
       {
        InitMovingField(x, y, MV_DOWN);
        started_moving = TRUE;
@@ -5662,23 +7444,10 @@ void StartMoving(int x, int y)
       Feld[x][y] = EL_AMOEBA_GROWING;
       Store[x][y] = EL_AMOEBA_WET;
     }
-    /* Store[x][y + 1] must be zero, because:
-       (EL_QUICKSAND_FULL -> EL_ROCK): Store[x][y + 1] == EL_QUICKSAND_EMPTY
-    */
-#if 0
-#if OLD_GAME_BEHAVIOUR
-    else if (IS_SLIPPERY(Feld[x][y + 1]) && !Store[x][y + 1])
-#else
-    else if (IS_SLIPPERY(Feld[x][y + 1]) && !Store[x][y + 1] &&
-            !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
-            element != EL_DX_SUPABOMB)
-#endif
-#else
     else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
              (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
             !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
             element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
-#endif
     {
       boolean can_fall_left  = (x > 0 && IS_FREE(x - 1, y) &&
                                (IS_FREE(x - 1, y + 1) ||
@@ -5688,66 +7457,41 @@ void StartMoving(int x, int y)
                                 Feld[x + 1][y + 1] == EL_ACID));
       boolean can_fall_any  = (can_fall_left || can_fall_right);
       boolean can_fall_both = (can_fall_left && can_fall_right);
+      int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
 
-      if (can_fall_any && IS_CUSTOM_ELEMENT(Feld[x][y + 1]))
+      if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
       {
-       int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
-
-       if (slippery_type == SLIPPERY_ONLY_LEFT)
+       if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
          can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ONLY_RIGHT)
+       else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
          can_fall_left = FALSE;
-       else if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
+       else if (slippery_type == SLIPPERY_ONLY_LEFT)
          can_fall_right = FALSE;
-       else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
+       else if (slippery_type == SLIPPERY_ONLY_RIGHT)
          can_fall_left = FALSE;
 
        can_fall_any  = (can_fall_left || can_fall_right);
-       can_fall_both = (can_fall_left && can_fall_right);
+       can_fall_both = FALSE;
       }
 
-#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]))
+      if (can_fall_both)
       {
-       can_fall_right = FALSE;         /* slip down on left side */
-       can_fall_both = FALSE;
-      }
-#endif
-
-#if 1
-      if (can_fall_both)
-      {
-       if (game.emulation == EMU_BOULDERDASH ||
-           element == EL_BD_ROCK || element == EL_BD_DIAMOND)
+       if (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)
       {
-#if 0
-       if (can_fall_both &&
-           (game.emulation != EMU_BOULDERDASH &&
-            element != EL_BD_ROCK && element != EL_BD_DIAMOND))
-         can_fall_left = !(can_fall_right = RND(2));
-#endif
-
        /* if not determined otherwise, prefer left side for slipping down */
        InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
        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));
@@ -5757,17 +7501,13 @@ void StartMoving(int x, int y)
       if ((belt_dir == MV_LEFT  && left_is_free) ||
          (belt_dir == MV_RIGHT && right_is_free))
       {
-#if 1
        int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
-#endif
 
        InitMovingField(x, y, belt_dir);
        started_moving = TRUE;
 
-#if 1
        Pushed[x][y] = TRUE;
        Pushed[nextx][y] = TRUE;
-#endif
 
        GfxAction[x][y] = ACTION_DEFAULT;
       }
@@ -5779,102 +7519,27 @@ void StartMoving(int x, int y)
   }
 
   /* not "else if" because of elements that can fall and move (EL_SPRING) */
-#if 0
-  if (CAN_MOVE(element) && !started_moving && MovDir[x][y] != MV_NO_MOVING)
-#else
   if (CAN_MOVE(element) && !started_moving)
-#endif
   {
     int move_pattern = element_info[element].move_pattern;
     int newx, newy;
 
-#if 0
-#if DEBUG
-    if (MovDir[x][y] == MV_NO_MOVING)
-    {
-      printf("StartMoving(): %d,%d: element %d ['%s'] not moving\n",
-            x, y, element, element_info[element].token_name);
-      printf("StartMoving(): This should never happen!\n");
-    }
-#endif
-#endif
-
     Moving2Blocked(x, y, &newx, &newy);
 
-#if 1
     if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
       return;
-#else
-    if ((element == EL_SATELLITE ||
-        element == EL_BALLOON ||
-        element == EL_SPRING)
-       && JustBeingPushed(x, y))
-      return;
-#endif
-
-#if 1
 
-#if 1
     if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
        CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
-#else
-    if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
-       WasJustMoving[x][y] && IN_LEV_FIELD(newx, newy) &&
-       (Feld[newx][newy] == EL_BLOCKED || IS_PLAYER(newx, newy)))
-#endif
     {
-#if 0
-      printf("::: element %d '%s' WasJustMoving %d [%d, %d, %d, %d]\n",
-            element, element_info[element].token_name,
-            WasJustMoving[x][y],
-            HAS_ANY_CHANGE_EVENT(element, CE_HITTING_SOMETHING),
-            HAS_ANY_CHANGE_EVENT(element, CE_HIT_BY_SOMETHING),
-            HAS_ANY_CHANGE_EVENT(element, CE_HITTING_X),
-            HAS_ANY_CHANGE_EVENT(element, CE_HIT_BY_X));
-#endif
-
-#if 1
       WasJustMoving[x][y] = 0;
-#endif
-
       CheckCollision[x][y] = 0;
 
       TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
 
-#if 0
-      if (Feld[x][y] != element)       /* element has changed */
-      {
-       element = Feld[x][y];
-       move_pattern = element_info[element].move_pattern;
-
-       if (!CAN_MOVE(element))
-         return;
-      }
-#else
       if (Feld[x][y] != element)       /* element has changed */
        return;
-#endif
-    }
-#endif
-
-#if 0
-#if 0
-    if (element == EL_SPRING && MovDir[x][y] == MV_DOWN)
-      Feld[x][y + 1] = EL_EMPTY;       /* was set to EL_BLOCKED above */
-#else
-    if (element == EL_SPRING && MovDir[x][y] != MV_NO_MOVING)
-    {
-      Moving2Blocked(x, y, &newx, &newy);
-      if (Feld[newx][newy] == EL_BLOCKED)
-       Feld[newx][newy] = EL_EMPTY;    /* was set to EL_BLOCKED above */
     }
-#endif
-#endif
-
-#if 0
-    if (FrameCounter < 1 && x == 0 && y == 29)
-      printf(":1: %d/%d: %d [%d]\n", x, y, MovDir[x][y], FrameCounter);
-#endif
 
     if (!MovDelay[x][y])       /* start new movement phase */
     {
@@ -5893,17 +7558,12 @@ void StartMoving(int x, int y)
       {
        TurnRound(x, y);
 
-#if 0
-       if (FrameCounter < 1 && x == 0 && y == 29)
-         printf(":9: %d: %d [%d]\n", y, MovDir[x][y], FrameCounter);
-#endif
-
        if (MovDelay[x][y] && (element == EL_BUG ||
                               element == EL_SPACESHIP ||
                               element == EL_SP_SNIKSNAK ||
                               element == EL_SP_ELECTRON ||
                               element == EL_MOLE))
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
       }
     }
 
@@ -5911,42 +7571,11 @@ void StartMoving(int x, int y)
     {
       MovDelay[x][y]--;
 
-#if 0
-      if (element == EL_YAMYAM)
-      {
-       printf("::: %d\n",
-              el_act_dir2img(EL_YAMYAM, ACTION_WAITING, MV_LEFT));
-       DrawLevelElementAnimation(x, y, element);
-      }
-#endif
-
-      if (MovDelay[x][y])      /* element still has to wait some time */
-      {
-#if 0
-       /* !!! PLACE THIS SOMEWHERE AFTER "TurnRound()" !!! */
-       ResetGfxAnimation(x, y);
-#endif
-
-#if 0
-       if (GfxAction[x][y] != ACTION_WAITING)
-         printf("::: %d: %d != ACTION_WAITING\n", element, GfxAction[x][y]);
-
-       GfxAction[x][y] = ACTION_WAITING;
-#endif
-      }
-
       if (element == EL_ROBOT ||
-#if 0
-         element == EL_PACMAN ||
-#endif
          element == EL_YAMYAM ||
          element == EL_DARK_YAMYAM)
       {
-#if 0
-       DrawLevelElementAnimation(x, y, element);
-#else
        DrawLevelElementAnimationIfNeeded(x, y, element);
-#endif
        PlayLevelSoundAction(x, y, ACTION_WAITING);
       }
       else if (element == EL_SP_ELECTRON)
@@ -5963,16 +7592,12 @@ void StartMoving(int x, int y)
                       dir == MV_DOWN   ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
        int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
-#if 0
-       printf("::: %d, %d\n", GfxAction[x][y], GfxFrame[x][y]);
-#endif
-
        GfxAction[x][y] = ACTION_ATTACKING;
 
        if (IS_PLAYER(x, y))
          DrawPlayerField(x, y);
        else
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
 
        PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
 
@@ -5991,34 +7616,18 @@ void StartMoving(int x, int y)
          {
            int flamed = MovingOrBlocked2Element(xx, yy);
 
-           /* !!! */
-#if 0
-           if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
-             Bang(xx, yy);
-           else if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
-             RemoveMovingField(xx, yy);
-           else
-             RemoveField(xx, yy);
-#else
            if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
              Bang(xx, yy);
            else
              RemoveMovingField(xx, yy);
-#endif
-
-#if 0
-           if (ChangeDelay[xx][yy])
-             printf("::: !!! [%d]\n", (IS_MOVING(xx, yy) ||
-                                       Feld[xx][yy] == EL_BLOCKED));
-#endif
 
-#if 1
            ChangeDelay[xx][yy] = 0;
-#endif
+
            Feld[xx][yy] = EL_FLAMES;
+
            if (IN_SCR_FIELD(sx, sy))
            {
-             DrawLevelFieldCrumbledSand(xx, yy);
+             TEST_DrawLevelFieldCrumbled(xx, yy);
              DrawGraphic(sx, sy, flame_graphic, frame);
            }
          }
@@ -6026,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);
          }
        }
       }
@@ -6037,13 +7646,6 @@ void StartMoving(int x, int y)
 
        return;
       }
-
-#if 0
-      /* special case of "moving" animation of waiting elements (FIX THIS !!!);
-        for all other elements GfxAction will be set by InitMovingField() */
-      if (element == EL_BD_BUTTERFLY || element == EL_BD_FIREFLY)
-       GfxAction[x][y] = ACTION_MOVING;
-#endif
     }
 
     /* now make next step */
@@ -6054,53 +7656,29 @@ void StartMoving(int x, int y)
        IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
        !PLAYER_ENEMY_PROTECTED(newx, newy))
     {
-#if 1
-      TestIfBadThingRunsIntoHero(x, y, MovDir[x][y]);
+      TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
 
       return;
-#else
-      /* player killed by element which is deadly when colliding with */
-      MovDir[x][y] = 0;
-      KillHero(PLAYERINFO(newx, newy));
-      return;
-#endif
-
     }
-#if 1
-#if 1
+
     else if (CAN_MOVE_INTO_ACID(element) &&
             IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
+            !IS_MV_DIAGONAL(MovDir[x][y]) &&
             (MovDir[x][y] == MV_DOWN ||
              game.engine_version >= VERSION_IDENT(3,1,0,0)))
-#else
-    else if (CAN_MOVE_INTO_ACID(element) && MovDir[x][y] == MV_DOWN &&
-            IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID)
-#endif
-#else
-
-    else if ((element == EL_PENGUIN ||
-             element == EL_ROBOT ||
-             element == EL_SATELLITE ||
-             element == EL_BALLOON ||
-             IS_CUSTOM_ELEMENT(element)) &&
-            IN_LEV_FIELD(newx, newy) &&
-            MovDir[x][y] == MV_DOWN && Feld[newx][newy] == EL_ACID)
-#endif
     {
       SplashAcid(newx, newy);
       Store[x][y] = EL_ACID;
     }
     else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
     {
-      if (Feld[newx][newy] == EL_EXIT_OPEN)
+      if (Feld[newx][newy] == EL_EXIT_OPEN ||
+         Feld[newx][newy] == EL_EM_EXIT_OPEN ||
+         Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
+         Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
       {
-#if 1
        RemoveField(x, y);
-       DrawLevelField(x, y);
-#else
-       Feld[x][y] = EL_EMPTY;
-       DrawLevelField(x, y);
-#endif
+       TEST_DrawLevelField(x, y);
 
        PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
        if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
@@ -6109,16 +7687,16 @@ void StartMoving(int x, int y)
        local_player->friends_still_needed--;
        if (!local_player->friends_still_needed &&
            !local_player->GameOver && AllPlayersGone)
-         local_player->LevelSolved = local_player->GameOver = TRUE;
+         PlayerWins(local_player);
 
        return;
       }
       else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
       {
-       if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MF_MOVING)
-         DrawLevelField(newx, newy);
+       if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
+         TEST_DrawLevelField(newx, newy);
        else
-         GfxDir[x][y] = MovDir[x][y] = MV_NO_MOVING;
+         GfxDir[x][y] = MovDir[x][y] = MV_NONE;
       }
       else if (!IS_FREE(newx, newy))
       {
@@ -6127,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;
       }
@@ -6141,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);
@@ -6151,96 +7729,100 @@ void StartMoving(int x, int y)
        if (IS_PLAYER(x, y))
          DrawPlayerField(x, y);
        else
-         DrawLevelField(x, y);
+         TEST_DrawLevelField(x, y);
 
        return;
       }
     }
+    else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
+    {
+      if (Store[x][y] != EL_EMPTY)
+      {
+       boolean can_clone = FALSE;
+       int xx, yy;
 
-#if 1
+       /* check if element to clone is still there */
+       for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
+       {
+         if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
+         {
+           can_clone = TRUE;
 
-    /*
-    else if (move_pattern & MV_MAZE_RUNNER_STYLE && IN_LEV_FIELD(newx, newy))
-    */
+           break;
+         }
+       }
 
-    else if (IS_CUSTOM_ELEMENT(element) &&
-            CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy)
+       /* cannot clone or target field not free anymore -- do not clone */
+       if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+         Store[x][y] = EL_EMPTY;
+      }
 
-#if 0
- &&
-            !IS_FREE(newx, newy)
-#endif
+      if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+      {
+       if (IS_MV_DIAGONAL(MovDir[x][y]))
+       {
+         int diagonal_move_dir = MovDir[x][y];
+         int stored = Store[x][y];
+         int change_delay = 8;
+         int graphic;
 
-)
-    {
-      int new_element = Feld[newx][newy];
+         /* android is moving diagonally */
 
-#if 0
-      printf("::: '%s' digs '%s' [%d]\n",
-            element_info[element].token_name,
-            element_info[Feld[newx][newy]].token_name,
-            StorePlayer[newx][newy]);
-#endif
+         CreateField(x, y, EL_DIAGONAL_SHRINKING);
 
-      if (!IS_FREE(newx, newy))
-      {
-       int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING :
-                     IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING :
-                     ACTION_BREAKING);
+         Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
+         GfxElement[x][y] = EL_EMC_ANDROID;
+         GfxAction[x][y] = ACTION_SHRINKING;
+         GfxDir[x][y] = diagonal_move_dir;
+         ChangeDelay[x][y] = change_delay;
 
-       /* no element can dig solid indestructible elements */
-       if (IS_INDESTRUCTIBLE(new_element) &&
-           !IS_DIGGABLE(new_element) &&
-           !IS_COLLECTIBLE(new_element))
-         return;
+         graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
+                                  GfxDir[x][y]);
 
-       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]]--;
-       }
+         DrawLevelGraphicAnimation(x, y, graphic);
+         PlayLevelSoundAction(x, y, ACTION_SHRINKING);
 
-       if (IS_MOVING(newx, newy))
-         RemoveMovingField(newx, newy);
+         if (Feld[newx][newy] == EL_ACID)
+         {
+           SplashAcid(newx, newy);
+
+           return;
+         }
+
+         CreateField(newx, newy, EL_DIAGONAL_GROWING);
+
+         Store[newx][newy] = EL_EMC_ANDROID;
+         GfxElement[newx][newy] = EL_EMC_ANDROID;
+         GfxAction[newx][newy] = ACTION_GROWING;
+         GfxDir[newx][newy] = diagonal_move_dir;
+         ChangeDelay[newx][newy] = change_delay;
+
+         graphic = el_act_dir2img(GfxElement[newx][newy],
+                                  GfxAction[newx][newy], GfxDir[newx][newy]);
+
+         DrawLevelGraphicAnimation(newx, newy, graphic);
+         PlayLevelSoundAction(newx, newy, ACTION_GROWING);
+
+         return;
+       }
        else
        {
-         RemoveField(newx, newy);
-         DrawLevelField(newx, newy);
-       }
-
-       /* if digged element was about to explode, prevent the explosion */
-       ExplodeField[newx][newy] = EX_TYPE_NONE;
+         Feld[newx][newy] = EL_EMPTY;
+         TEST_DrawLevelField(newx, newy);
 
-       PlayLevelSoundAction(x, y, action);
+         PlayLevelSoundAction(x, y, ACTION_DIGGING);
+       }
       }
-
-#if 1
-#if 1
-      Store[newx][newy] = EL_EMPTY;
-      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
+      else if (!IS_FREE(newx, newy))
       {
-#if USE_CHANGE_TO_TRIGGERED
-       int move_leave_element = element_info[element].move_leave_element;
-
-       Store[newx][newy] = (move_leave_element == EL_TRIGGER_ELEMENT ?
-                            new_element : move_leave_element);
-#else
-       Store[newx][newy] = element_info[element].move_leave_element;
-#endif
+       return;
       }
-#else
-      Store[newx][newy] = EL_EMPTY;
-      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)) ||
-         element_info[element].move_leave_type == LEAVE_TYPE_UNLIMITED)
-       Store[newx][newy] = element_info[element].move_leave_element;
-#endif
-#else
-      if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
-       element_info[element].can_leave_element = TRUE;
-#endif
+    }
+    else if (IS_CUSTOM_ELEMENT(element) &&
+            CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
+    {
+      if (!DigFieldByCE(newx, newy, element))
+       return;
 
       if (move_pattern & MV_MAZE_RUNNER_STYLE)
       {
@@ -6248,9 +7830,6 @@ void StartMoving(int x, int y)
        PlayerVisit[x][y] /= 8;         /* expire player visit path */
       }
     }
-
-#endif
-
     else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
     {
       if (!IS_FREE(newx, newy))
@@ -6258,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;
       }
@@ -6279,39 +7858,23 @@ void StartMoving(int x, int y)
            element1 != EL_DRAGON && element2 != EL_DRAGON &&
            element1 != EL_FLAMES && element2 != EL_FLAMES)
        {
-#if 1
          ResetGfxAnimation(x, y);
          GfxAction[x][y] = ACTION_ATTACKING;
-#endif
 
          if (IS_PLAYER(x, y))
            DrawPlayerField(x, y);
          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;
        }
@@ -6325,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);
@@ -6341,19 +7904,14 @@ void StartMoving(int x, int y)
          AmoebaCnt[AmoebaNr[newx][newy]]--;
       }
 
-#if 0
-      /* !!! test !!! */
-      if (IS_MOVING(newx, newy) || IS_BLOCKED(newx, newy))
-#else
       if (IS_MOVING(newx, newy))
-#endif
       {
        RemoveMovingField(newx, newy);
       }
       else
       {
        Feld[newx][newy] = EL_EMPTY;
-       DrawLevelField(newx, newy);
+       TEST_DrawLevelField(newx, newy);
       }
 
       PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
@@ -6376,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 */
 
@@ -6385,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);
       }
     }
@@ -6402,52 +7960,11 @@ void StartMoving(int x, int y)
 
       TurnRound(x, y);
 
-#if 0
-      if (move_pattern & MV_ANY_DIRECTION &&
-         move_pattern == MovDir[x][y])
-      {
-       int blocking_element =
-         (IN_LEV_FIELD(newx, newy) ? Feld[newx][newy] : BorderElement);
-
-#if 0
-       printf("::: '%s' is blocked by '%s'! [%d,%d -> %d,%d]\n",
-              element_info[element].token_name,
-              element_info[blocking_element].token_name,
-              x, y, newx, newy);
-#endif
-
-       CheckElementChangeBySide(x, y, element, blocking_element, CE_BLOCKED,
-                                MovDir[x][y]);
-
-       element = Feld[x][y];   /* element might have changed */
-      }
-#endif
-
-#if 1
       if (GFX_ELEMENT(element) != EL_SAND)     /* !!! FIX THIS (crumble) !!! */
        DrawLevelElementAnimation(x, y, element);
-#else
-      if (element == EL_BUG ||
-         element == EL_SPACESHIP ||
-         element == EL_SP_SNIKSNAK)
-       DrawLevelField(x, y);
-      else if (element == EL_MOLE)
-       DrawLevelField(x, y);
-      else if (element == EL_BD_BUTTERFLY ||
-              element == EL_BD_FIREFLY)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-      else if (element == EL_SATELLITE)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-      else if (element == EL_SP_ELECTRON)
-       DrawLevelElementAnimationIfNeeded(x, y, element);
-#endif
 
       if (DONT_TOUCH(element))
-       TestIfBadThingTouchesHero(x, y);
-
-#if 0
-      PlayLevelSoundAction(x, y, ACTION_WAITING);
-#endif
+       TestIfBadThingTouchesPlayer(x, y);
 
       return;
     }
@@ -6461,46 +7978,28 @@ void StartMoving(int x, int y)
     ContinueMoving(x, y);
 }
 
-void dummy()
-{
-}
-
 void ContinueMoving(int x, int y)
 {
   int element = Feld[x][y];
-  int stored = Store[x][y];
   struct ElementInfo *ei = &element_info[element];
   int direction = MovDir[x][y];
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
   int newx = x + dx, newy = y + dy;
-#if 0
-  int nextx = newx + dx, nexty = newy + dy;
-#endif
-#if 1
+  int stored = Store[x][y];
+  int stored_new = Store[newx][newy];
   boolean pushed_by_player   = (Pushed[x][y] && IS_PLAYER(x, y));
   boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
-#else
-  boolean pushed_by_player = Pushed[x][y];
-#endif
   boolean last_line = (newy == lev_fieldy - 1);
 
   MovPos[x][y] += getElementMoveStepsize(x, y);
 
-#if 0
-  if (pushed_by_player && IS_PLAYER(x, y))
-  {
-    /* special case: moving object pushed by player */
-    MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
-  }
-#else
   if (pushed_by_player)        /* special case: moving object pushed by player */
     MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
-#endif
 
   if (ABS(MovPos[x][y]) < TILEX)
   {
-    DrawLevelField(x, y);
+    TEST_DrawLevelField(x, y);
 
     return;    /* element is still moving */
   }
@@ -6511,17 +8010,15 @@ void ContinueMoving(int x, int y)
   Feld[newx][newy] = element;
   MovPos[x][y] = 0;    /* force "not moving" for "crumbled sand" */
 
-#if 1
   if (Store[x][y] == EL_ACID)  /* element is moving into acid pool */
   {
     element = Feld[newx][newy] = EL_ACID;
   }
-#endif
   else if (element == EL_MOLE)
   {
     Feld[x][y] = EL_SAND;
 
-    DrawLevelFieldCrumbledSandNeighbours(x, y);
+    TEST_DrawLevelFieldCrumbledNeighbours(x, y);
   }
   else if (element == EL_QUICKSAND_FILLING)
   {
@@ -6533,21 +8030,33 @@ void ContinueMoving(int x, int y)
     Feld[x][y] = get_next_element(element);
     element = Feld[newx][newy] = Store[x][y];
   }
-  else if (element == EL_MAGIC_WALL_FILLING)
+  else if (element == EL_QUICKSAND_FAST_FILLING)
   {
     element = Feld[newx][newy] = get_next_element(element);
-    if (!game.magic_wall_active)
-      element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
     Store[newx][newy] = Store[x][y];
   }
-  else if (element == EL_MAGIC_WALL_EMPTYING)
+  else if (element == EL_QUICKSAND_FAST_EMPTYING)
   {
     Feld[x][y] = get_next_element(element);
-    if (!game.magic_wall_active)
-      Feld[x][y] = EL_MAGIC_WALL_DEAD;
     element = Feld[newx][newy] = Store[x][y];
   }
-  else if (element == EL_BD_MAGIC_WALL_FILLING)
+  else if (element == EL_MAGIC_WALL_FILLING)
+  {
+    element = Feld[newx][newy] = get_next_element(element);
+    if (!game.magic_wall_active)
+      element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_MAGIC_WALL_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    if (!game.magic_wall_active)
+      Feld[x][y] = EL_MAGIC_WALL_DEAD;
+    element = Feld[newx][newy] = Store[x][y];
+
+    InitField(newx, newy, FALSE);
+  }
+  else if (element == EL_BD_MAGIC_WALL_FILLING)
   {
     element = Feld[newx][newy] = get_next_element(element);
     if (!game.magic_wall_active)
@@ -6560,6 +8069,24 @@ void ContinueMoving(int x, int y)
     if (!game.magic_wall_active)
       Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
     element = Feld[newx][newy] = Store[x][y];
+
+    InitField(newx, newy, FALSE);
+  }
+  else if (element == EL_DC_MAGIC_WALL_FILLING)
+  {
+    element = Feld[newx][newy] = get_next_element(element);
+    if (!game.magic_wall_active)
+      element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_DC_MAGIC_WALL_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    if (!game.magic_wall_active)
+      Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
+    element = Feld[newx][newy] = Store[x][y];
+
+    InitField(newx, newy, FALSE);
   }
   else if (element == EL_AMOEBA_DROPPING)
   {
@@ -6576,48 +8103,32 @@ void ContinueMoving(int x, int y)
 
     Back[x][y] = Back[newx][newy] = 0;
   }
-#if 0
-  else if (Store[x][y] == EL_ACID)
-  {
-    element = Feld[newx][newy] = EL_ACID;
-  }
-#endif
-#if 0
-  else if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
-          ei->move_leave_element != EL_EMPTY &&
-          (ei->move_leave_type == LEAVE_TYPE_UNLIMITED ||
-           Store[x][y] != EL_EMPTY))
-  {
-    /* some elements can leave other elements behind after moving */
-
-    Feld[x][y] = ei->move_leave_element;
-    InitField(x, y, FALSE);
-
-    if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
-  }
-#endif
 
   Store[x][y] = EL_EMPTY;
   MovPos[x][y] = 0;
   MovDir[x][y] = 0;
   MovDelay[x][y] = 0;
+
   MovDelay[newx][newy] = 0;
 
-  if (CAN_CHANGE(element))
+  if (CAN_CHANGE_OR_HAS_ACTION(element))
   {
     /* copy element change control values to new field */
     ChangeDelay[newx][newy] = ChangeDelay[x][y];
     ChangePage[newx][newy]  = ChangePage[x][y];
-    Changed[newx][newy]     = Changed[x][y];
+    ChangeCount[newx][newy] = ChangeCount[x][y];
     ChangeEvent[newx][newy] = ChangeEvent[x][y];
   }
 
+  CustomValue[newx][newy] = CustomValue[x][y];
+
   ChangeDelay[x][y] = 0;
   ChangePage[x][y] = -1;
-  Changed[x][y] = FALSE;
+  ChangeCount[x][y] = 0;
   ChangeEvent[x][y] = -1;
 
+  CustomValue[x][y] = 0;
+
   /* copy animation control values to new field */
   GfxFrame[newx][newy]  = GfxFrame[x][y];
   GfxRandom[newx][newy] = GfxRandom[x][y];     /* keep same random value */
@@ -6626,106 +8137,43 @@ void ContinueMoving(int x, int y)
 
   Pushed[x][y] = Pushed[newx][newy] = FALSE;
 
-#if 0
-  /* do this after checking for left-behind element */
-  ResetGfxAnimation(x, y);     /* reset animation values for old field */
-#endif
-
-#if 1
   /* some elements can leave other elements behind after moving */
-#if 1
-  if (IS_CUSTOM_ELEMENT(element) && ei->move_leave_element != EL_EMPTY &&
+  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))
-#endif
   {
     int move_leave_element = ei->move_leave_element;
 
-#if USE_CHANGE_TO_TRIGGERED
-    if (ei->move_leave_type == LEAVE_TYPE_LIMITED &&
-        ei->move_leave_element == EL_TRIGGER_ELEMENT)
-      move_leave_element = stored;
-#endif
+    /* 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);
 
     Feld[x][y] = move_leave_element;
 
-#if USE_PREVIOUS_MOVE_DIR
     if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
       MovDir[x][y] = direction;
-#endif
 
     InitField(x, y, FALSE);
 
     if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
     if (ELEM_IS_PLAYER(move_leave_element))
       RelocatePlayer(x, y, move_leave_element);
   }
-#endif
-
-#if 0
-  /* some elements can leave other elements behind after moving */
-  if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
-      ei->move_leave_element != EL_EMPTY &&
-      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED ||
-       ei->can_leave_element_last))
-  {
-    Feld[x][y] = ei->move_leave_element;
-    InitField(x, y, FALSE);
-
-    if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
-  }
-
-  ei->can_leave_element_last = ei->can_leave_element;
-  ei->can_leave_element = FALSE;
-#endif
 
-#if 1
   /* do this after checking for left-behind element */
   ResetGfxAnimation(x, y);     /* reset animation values for old field */
-#endif
-
-#if 0
-  /* 2.1.1 (does not work correctly for spring) */
-  if (!CAN_MOVE(element))
-    MovDir[newx][newy] = 0;
-#else
-
-#if 0
-  /* (does not work for falling objects that slide horizontally) */
-  if (CAN_FALL(element) && MovDir[newx][newy] == MV_DOWN)
-    MovDir[newx][newy] = 0;
-#else
-  /*
-  if (!CAN_MOVE(element) ||
-      (element == EL_SPRING && MovDir[newx][newy] == MV_DOWN))
-    MovDir[newx][newy] = 0;
-  */
 
-#if 0
-  if (!CAN_MOVE(element) ||
-      (CAN_FALL(element) && direction == MV_DOWN))
-    GfxDir[x][y] = MovDir[newx][newy] = 0;
-#else
   if (!CAN_MOVE(element) ||
       (CAN_FALL(element) && direction == MV_DOWN &&
        (element == EL_SPRING ||
        element_info[element].move_pattern == MV_WHEN_PUSHED ||
        element_info[element].move_pattern == MV_WHEN_DROPPED)))
     GfxDir[x][y] = MovDir[newx][newy] = 0;
-#endif
-
-#endif
-#endif
 
-  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 */
 
@@ -6735,39 +8183,31 @@ void ContinueMoving(int x, int y)
       !(element_info[element].move_pattern & direction))
     TurnRound(newx, newy);
 
-#if 1
   /* prevent elements on conveyor belt from moving on in last direction */
   if (pushed_by_conveyor && CAN_FALL(element) &&
       direction & MV_HORIZONTAL)
-  {
-#if 0
-    if (CAN_MOVE(element))
-      InitMovDir(newx, newy);
-    else
-      MovDir[newx][newy] = 0;
-#else
     MovDir[newx][newy] = 0;
-#endif
-  }
-#endif
 
   if (!pushed_by_player)
   {
     int nextx = newx + dx, nexty = newy + dy;
     boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
 
-    WasJustMoving[newx][newy] = 3;
+    WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
 
     if (CAN_FALL(element) && direction == MV_DOWN)
-      WasJustFalling[newx][newy] = 3;
+      WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
 
     if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
-      CheckCollision[newx][newy] = 2;
+      CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
+
+    if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
+      CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
   }
 
   if (DONT_TOUCH(element))     /* object may be nasty to player or others */
   {
-    TestIfBadThingTouchesHero(newx, newy);
+    TestIfBadThingTouchesPlayer(newx, newy);
     TestIfBadThingTouchesFriend(newx, newy);
 
     if (!IS_CUSTOM_ELEMENT(element))
@@ -6776,12 +8216,10 @@ void ContinueMoving(int x, int y)
   else if (element == EL_PENGUIN)
     TestIfFriendTouchesBadThing(newx, newy);
 
-#if USE_NEW_MOVE_STYLE
-#if 0
-  if (CAN_FALL(element) && direction == MV_DOWN &&
-      !last_line && IS_PLAYER(x, newy + 1))
-    printf("::: we would now kill the player [%d]\n", FrameCounter);
-#endif
+  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 &&
@@ -6789,134 +8227,32 @@ void ContinueMoving(int x, int y)
                     (!IS_PLAYER(x, newy + 1) ||
                      game.engine_version < VERSION_IDENT(3,1,1,0)))))
     Impact(x, newy);
-#else
-  if (CAN_FALL(element) && direction == MV_DOWN &&
-      (last_line || !IS_FREE(x, newy + 1)))
-    Impact(x, newy);
-#endif
-
-#if 1
 
-#if USE_PUSH_BUGFIX
-#if 1
   if (pushed_by_player && !game.use_change_when_pushing_bug)
-#else
-  if (pushed_by_player && game.engine_version >= VERSION_IDENT(3,1,0,0))
-#endif
-#else
-  if (pushed_by_player)
-#endif
-
   {
-#if 1
-    int dig_side = MV_DIR_OPPOSITE(direction);
-#else
-    static int trigger_sides[4] =
-    {
-      CH_SIDE_RIGHT,   /* moving left  */
-      CH_SIDE_LEFT,    /* moving right */
-      CH_SIDE_BOTTOM,  /* moving up    */
-      CH_SIDE_TOP,     /* moving down  */
-    };
-    int dig_side = trigger_sides[MV_DIR_BIT(direction)];
-#endif
+    int push_side = MV_DIR_OPPOSITE(direction);
     struct PlayerInfo *player = PLAYERINFO(x, y);
 
     CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
-                              player->index_bit, dig_side);
+                              player->index_bit, push_side);
     CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
-                                       player->index_bit, dig_side);
+                                       player->index_bit, push_side);
   }
-#endif
-
-#if 1
-  TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
-#endif
 
-#if 0
-  if (ChangePage[newx][newy] != -1)                    /* delayed change */
-    ChangeElement(newx, newy, ChangePage[newx][newy]);
-#endif
+  if (element == EL_EMC_ANDROID && pushed_by_player)   /* make another move */
+    MovDelay[newx][newy] = 1;
 
-#if 1
+  CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
 
+  TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
   TestIfElementHitsCustomElement(newx, newy, direction);
-
-#else
-
-  if (!IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty))
-  {
-    int hitting_element = Feld[newx][newy];
-
-    /* !!! fix side (direction) orientation here and elsewhere !!! */
-    CheckElementChangeBySide(newx, newy, hitting_element, CE_HITTING_SOMETHING,
-                            direction);
-
-#if 0
-    if (IN_LEV_FIELD(nextx, nexty))
-    {
-      int opposite_direction = MV_DIR_OPPOSITE(direction);
-      int hitting_side = direction;
-      int touched_side = opposite_direction;
-      int touched_element = MovingOrBlocked2Element(nextx, nexty);
-      boolean object_hit = (!IS_MOVING(nextx, nexty) ||
-                           MovDir[nextx][nexty] != direction ||
-                           ABS(MovPos[nextx][nexty]) <= TILEY / 2);
-
-      if (object_hit)
-      {
-       int i;
-
-       CheckElementChangeBySide(nextx, nexty, touched_element,
-                                CE_HIT_BY_SOMETHING, opposite_direction);
-
-       if (IS_CUSTOM_ELEMENT(hitting_element) &&
-           HAS_ANY_CHANGE_EVENT(hitting_element, CE_HITTING_X))
-       {
-         for (i = 0; i < element_info[hitting_element].num_change_pages; i++)
-         {
-           struct ElementChangeInfo *change =
-             &element_info[hitting_element].change_page[i];
-
-           if (change->can_change &&
-               change->has_event[CE_HITTING_X] &&
-               change->trigger_side & touched_side &&
-               change->trigger_element == touched_element)
-           {
-             CheckElementChangeByPage(newx, newy, hitting_element,
-                                      touched_element, CE_HITTING_X, i);
-             break;
-           }
-         }
-       }
-
-       if (IS_CUSTOM_ELEMENT(touched_element) &&
-           HAS_ANY_CHANGE_EVENT(touched_element, CE_HIT_BY_X))
-       {
-         for (i = 0; i < element_info[touched_element].num_change_pages; i++)
-         {
-           struct ElementChangeInfo *change =
-             &element_info[touched_element].change_page[i];
-
-           if (change->can_change &&
-               change->has_event[CE_HIT_BY_X] &&
-               change->trigger_side & hitting_side &&
-               change->trigger_element == hitting_element)
-           {
-             CheckElementChangeByPage(nextx, nexty, touched_element,
-                                      hitting_element, CE_HIT_BY_X,i);
-             break;
-           }
-         }
-       }
-      }
-    }
-#endif
-  }
-#endif
-
   TestIfPlayerTouchesCustomElement(newx, newy);
   TestIfElementTouchesCustomElement(newx, newy);
+
+  if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
+      IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
+    CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
+                            MV_DIR_OPPOSITE(direction));
 }
 
 int AmoebeNachbarNr(int ax, int ay)
@@ -6985,13 +8321,10 @@ void AmoebenVereinigen(int ax, int ay)
       AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
       AmoebaCnt2[old_group_nr] = 0;
 
-      for (yy = 0; yy < lev_fieldy; yy++)
+      SCAN_PLAYFIELD(xx, yy)
       {
-       for (xx = 0; xx < lev_fieldx; xx++)
-       {
-         if (AmoebaNr[xx][yy] == old_group_nr)
-           AmoebaNr[xx][yy] = new_group_nr;
-       }
+       if (AmoebaNr[xx][yy] == old_group_nr)
+         AmoebaNr[xx][yy] = new_group_nr;
       }
     }
   }
@@ -7014,17 +8347,15 @@ void AmoebeUmwandeln(int ax, int ay)
     }
 #endif
 
-    for (y = 0; y < lev_fieldy; y++)
+    SCAN_PLAYFIELD(x, y)
     {
-      for (x = 0; x < lev_fieldx; x++)
+      if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
       {
-       if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
-       {
-         AmoebaNr[x][y] = 0;
-         Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
-       }
+       AmoebaNr[x][y] = 0;
+       Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
       }
     }
+
     PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
                            SND_AMOEBA_TURNING_TO_GEM :
                            SND_AMOEBA_TURNING_TO_ROCK));
@@ -7074,21 +8405,18 @@ void AmoebeUmwandelnBD(int ax, int ay, int new_element)
   }
 #endif
 
-  for (y = 0; y < lev_fieldy; y++)
+  SCAN_PLAYFIELD(x, y)
   {
-    for (x = 0; x < lev_fieldx; x++)
+    if (AmoebaNr[x][y] == group_nr &&
+       (Feld[x][y] == EL_AMOEBA_DEAD ||
+        Feld[x][y] == EL_BD_AMOEBA ||
+        Feld[x][y] == EL_AMOEBA_GROWING))
     {
-      if (AmoebaNr[x][y] == group_nr &&
-         (Feld[x][y] == EL_AMOEBA_DEAD ||
-          Feld[x][y] == EL_BD_AMOEBA ||
-          Feld[x][y] == EL_AMOEBA_GROWING))
-      {
-       AmoebaNr[x][y] = 0;
-       Feld[x][y] = new_element;
-       InitField(x, y, FALSE);
-       DrawLevelField(x, y);
-       done = TRUE;
-      }
+      AmoebaNr[x][y] = 0;
+      Feld[x][y] = new_element;
+      InitField(x, y, FALSE);
+      TEST_DrawLevelField(x, y);
+      done = TRUE;
     }
   }
 
@@ -7100,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 */
   {
@@ -7109,14 +8437,7 @@ void AmoebeWaechst(int x, int y)
 
     if (DelayReached(&sound_delay, sound_delay_value))
     {
-#if 1
       PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
-#else
-      if (Store[x][y] == EL_BD_AMOEBA)
-       PlayLevelSound(x, y, SND_BD_AMOEBA_GROWING);
-      else
-       PlayLevelSound(x, y, SND_AMOEBA_GROWING);
-#endif
       sound_delay_value = 30;
     }
   }
@@ -7136,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 */
   {
@@ -7168,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) */
@@ -7183,6 +8504,7 @@ void AmoebeAbleger(int ax, int ay)
   int element = Feld[ax][ay];
   int graphic = el2img(element);
   int newax = ax, neway = ay;
+  boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
   static int xy[4][2] =
   {
     { 0, -1 },
@@ -7191,10 +8513,10 @@ void AmoebeAbleger(int ax, int ay)
     { 0, +1 }
   };
 
-  if (!level.amoeba_speed)
+  if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
   {
     Feld[ax][ay] = EL_AMOEBA_DEAD;
-    DrawLevelField(ax, ay);
+    TEST_DrawLevelField(ax, ay);
     return;
   }
 
@@ -7211,7 +8533,7 @@ void AmoebeAbleger(int ax, int ay)
       return;
   }
 
-  if (element == EL_AMOEBA_WET)        /* object is an acid / amoeba drop */
+  if (can_drop)                        /* EL_AMOEBA_WET or EL_EMC_DRIPPER */
   {
     int start = RND(4);
     int x = ax + xy[start][0];
@@ -7220,23 +8542,14 @@ void AmoebeAbleger(int ax, int ay)
     if (!IN_LEV_FIELD(x, y))
       return;
 
-#if 1
     if (IS_FREE(x, y) ||
        CAN_GROW_INTO(Feld[x][y]) ||
-       Feld[x][y] == EL_QUICKSAND_EMPTY)
-    {
-      newax = x;
-      neway = y;
-    }
-#else
-    /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
-    if (IS_FREE(x, y) ||
-       Feld[x][y] == EL_SAND || Feld[x][y] == EL_QUICKSAND_EMPTY)
+       Feld[x][y] == EL_QUICKSAND_EMPTY ||
+       Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
     {
       newax = x;
       neway = y;
     }
-#endif
 
     if (newax == ax && neway == ay)
       return;
@@ -7255,39 +8568,25 @@ void AmoebeAbleger(int ax, int ay)
       if (!IN_LEV_FIELD(x, y))
        continue;
 
-#if 1
       if (IS_FREE(x, y) ||
          CAN_GROW_INTO(Feld[x][y]) ||
-         Feld[x][y] == EL_QUICKSAND_EMPTY)
-      {
-       newax = x;
-       neway = y;
-       break;
-      }
-#else
-      /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
-      if (IS_FREE(x, y) ||
-         Feld[x][y] == EL_SAND || Feld[x][y] == EL_QUICKSAND_EMPTY)
+         Feld[x][y] == EL_QUICKSAND_EMPTY ||
+         Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
       {
        newax = x;
        neway = y;
        break;
       }
-#endif
       else if (IS_PLAYER(x, y))
        waiting_for_player = TRUE;
     }
 
     if (newax == ax && neway == ay)            /* amoeba cannot grow */
     {
-#if 1
       if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
-#else
-      if (i == 4 && (!waiting_for_player || game.emulation == EMU_BOULDERDASH))
-#endif
       {
        Feld[ax][ay] = EL_AMOEBA_DEAD;
-       DrawLevelField(ax, ay);
+       TEST_DrawLevelField(ax, ay);
        AmoebaCnt[AmoebaNr[ax][ay]]--;
 
        if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0)   /* amoeba is completely dead */
@@ -7330,20 +8629,17 @@ void AmoebeAbleger(int ax, int ay)
     }
   }
 
-  if (element != EL_AMOEBA_WET || neway < ay || !IS_FREE(newax, neway) ||
+  if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
       (neway == lev_fieldy - 1 && newax != ax))
   {
     Feld[newax][neway] = EL_AMOEBA_GROWING;    /* creation of new amoeba */
     Store[newax][neway] = element;
   }
-  else if (neway == ay)
+  else if (neway == ay || element == EL_EMC_DRIPPER)
   {
     Feld[newax][neway] = EL_AMOEBA_DROP;       /* drop left/right of amoeba */
-#if 1
+
     PlayLevelSoundAction(newax, neway, ACTION_GROWING);
-#else
-    PlayLevelSound(newax, neway, SND_AMOEBA_GROWING);
-#endif
   }
   else
   {
@@ -7354,16 +8650,17 @@ void AmoebeAbleger(int ax, int ay)
     return;
   }
 
-  DrawLevelField(newax, neway);
+  TEST_DrawLevelField(newax, neway);
 }
 
 void Life(int ax, int ay)
 {
   int x1, y1, x2, y2;
-  static int life[4] = { 2, 3, 3, 3 }; /* parameters for "game of life" */
   int life_time = 40;
   int element = Feld[ax][ay];
   int graphic = el2img(element);
+  int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
+                        level.biomaze);
   boolean changed = FALSE;
 
   if (IS_ANIMATED(graphic))
@@ -7406,43 +8703,29 @@ void Life(int ax, int ay)
 
     if (xx == ax && yy == ay)          /* field in the middle */
     {
-      if (nachbarn < life[0] || nachbarn > life[1])
+      if (nachbarn < life_parameter[0] ||
+         nachbarn > life_parameter[1])
       {
        Feld[xx][yy] = EL_EMPTY;
        if (!Stop[xx][yy])
-         DrawLevelField(xx, yy);
+         TEST_DrawLevelField(xx, yy);
        Stop[xx][yy] = TRUE;
        changed = TRUE;
       }
     }
-#if 1
     else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
     {                                  /* free border field */
-      if (nachbarn >= life[2] && nachbarn <= life[3])
-      {
-       Feld[xx][yy] = element;
-       MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
-       if (!Stop[xx][yy])
-         DrawLevelField(xx, yy);
-       Stop[xx][yy] = TRUE;
-       changed = TRUE;
-      }
-    }
-#else
-    /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
-    else if (IS_FREE(xx, yy) || Feld[xx][yy] == EL_SAND)
-    {                                  /* free border field */
-      if (nachbarn >= life[2] && nachbarn <= life[3])
+      if (nachbarn >= life_parameter[2] &&
+         nachbarn <= life_parameter[3])
       {
        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;
       }
     }
-#endif
   }
 
   if (changed)
@@ -7463,22 +8746,58 @@ static void RunRobotWheel(int x, int y)
 static void StopRobotWheel(int x, int y)
 {
   if (ZX == x && ZY == y)
+  {
     ZX = ZY = -1;
+
+    game.robot_wheel_active = FALSE;
+  }
 }
 
 static void InitTimegateWheel(int x, int y)
 {
-#if 1
   ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
-#else
-  /* another brainless, "type style" bug ... :-( */
-  ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
-#endif
 }
 
 static void RunTimegateWheel(int x, int y)
 {
-  PlayLevelSound(x, y, SND_TIMEGATE_SWITCH_ACTIVE);
+  PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
+}
+
+static void InitMagicBallDelay(int x, int y)
+{
+  ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
+}
+
+static void ActivateMagicBall(int bx, int by)
+{
+  int x, y;
+
+  if (level.ball_random)
+  {
+    int pos_border = RND(8);   /* select one of the eight border elements */
+    int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
+    int xx = pos_content % 3;
+    int yy = pos_content / 3;
+
+    x = bx - 1 + xx;
+    y = by - 1 + yy;
+
+    if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
+      CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
+  }
+  else
+  {
+    for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
+    {
+      int xx = x - bx + 1;
+      int yy = y - by + 1;
+
+      if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
+       CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
+    }
+  }
+
+  game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
 }
 
 void CheckExit(int x, int y)
@@ -7504,9 +8823,11 @@ void CheckExit(int x, int y)
   PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
 }
 
-void CheckExitSP(int x, int y)
+void CheckExitEM(int x, int y)
 {
-  if (local_player->gems_still_needed > 0)
+  if (local_player->gems_still_needed > 0 ||
+      local_player->sokobanfields_still_needed > 0 ||
+      local_player->lights_still_needed > 0)
   {
     int element = Feld[x][y];
     int graphic = el2img(element);
@@ -7520,35 +8841,96 @@ void CheckExitSP(int x, int y)
   if (AllPlayersGone)  /* do not re-open exit door closed after last player */
     return;
 
-  Feld[x][y] = EL_SP_EXIT_OPENING;
+  Feld[x][y] = EL_EM_EXIT_OPENING;
 
-  PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
+  PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
 }
 
-static void CloseAllOpenTimegates()
+void CheckExitSteel(int x, int y)
 {
-  int x, y;
-
-  for (y = 0; y < lev_fieldy; y++)
+  if (local_player->gems_still_needed > 0 ||
+      local_player->sokobanfields_still_needed > 0 ||
+      local_player->lights_still_needed > 0)
+  {
+    int element = Feld[x][y];
+    int graphic = el2img(element);
+
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+    return;
+  }
+
+  if (AllPlayersGone)  /* do not re-open exit door closed after last player */
+    return;
+
+  Feld[x][y] = EL_STEEL_EXIT_OPENING;
+
+  PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
+}
+
+void CheckExitSteelEM(int x, int y)
+{
+  if (local_player->gems_still_needed > 0 ||
+      local_player->sokobanfields_still_needed > 0 ||
+      local_player->lights_still_needed > 0)
+  {
+    int element = Feld[x][y];
+    int graphic = el2img(element);
+
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+    return;
+  }
+
+  if (AllPlayersGone)  /* do not re-open exit door closed after last player */
+    return;
+
+  Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
+
+  PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
+}
+
+void CheckExitSP(int x, int y)
+{
+  if (local_player->gems_still_needed > 0)
+  {
+    int element = Feld[x][y];
+    int graphic = el2img(element);
+
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+    return;
+  }
+
+  if (AllPlayersGone)  /* do not re-open exit door closed after last player */
+    return;
+
+  Feld[x][y] = EL_SP_EXIT_OPENING;
+
+  PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
+}
+
+static void CloseAllOpenTimegates()
+{
+  int x, y;
+
+  SCAN_PLAYFIELD(x, y)
   {
-    for (x = 0; x < lev_fieldx; x++)
+    int element = Feld[x][y];
+
+    if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
     {
-      int element = Feld[x][y];
+      Feld[x][y] = EL_TIMEGATE_CLOSING;
 
-      if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
-      {
-       Feld[x][y] = EL_TIMEGATE_CLOSING;
-#if 1
-       PlayLevelSoundAction(x, y, ACTION_CLOSING);
-#else
-       PlayLevelSound(x, y, SND_TIMEGATE_CLOSING);
-#endif
-      }
+      PlayLevelSoundAction(x, y, ACTION_CLOSING);
     }
   }
 }
 
-void EdelsteinFunkeln(int x, int y)
+void DrawTwinkleOnField(int x, int y)
 {
   if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
     return;
@@ -7557,15 +8939,12 @@ void EdelsteinFunkeln(int x, int y)
     return;
 
   if (MovDelay[x][y] == 0)     /* next animation frame */
-    MovDelay[x][y] = 11 * !SimpleRND(500);
+    MovDelay[x][y] = 11 * !GetSimpleRandom(500);
 
   if (MovDelay[x][y] != 0)     /* wait some time before next frame */
   {
     MovDelay[x][y]--;
 
-    if (setup.direct_draw && MovDelay[x][y])
-      SetDrawtoField(DRAW_BUFFERED);
-
     DrawLevelElementAnimation(x, y, Feld[x][y]);
 
     if (MovDelay[x][y] != 0)
@@ -7574,18 +8953,6 @@ void EdelsteinFunkeln(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);
-      }
     }
   }
 }
@@ -7614,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_NO_MOVING;
-      DrawLevelField(x, y);
+      GfxDir[x][y] = MovDir[x][y] = MV_NONE;
+      TEST_DrawLevelField(x, y);
     }
   }
 }
@@ -7699,7 +9066,8 @@ void MauerAbleger(int ax, int ay)
 
   if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
       element == EL_EXPANDABLE_WALL_ANY ||
-      element == EL_EXPANDABLE_WALL)
+      element == EL_EXPANDABLE_WALL ||
+      element == EL_BD_EXPANDABLE_WALL)
   {
     if (links_frei)
     {
@@ -7725,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;
@@ -7744,11 +9112,109 @@ void MauerAbleger(int ax, int ay)
     Feld[ax][ay] = EL_WALL;
 
   if (new_wall)
-#if 1
     PlayLevelSoundAction(ax, ay, ACTION_GROWING);
-#else
-    PlayLevelSound(ax, ay, SND_EXPANDABLE_WALL_GROWING);
-#endif
+}
+
+void MauerAblegerStahl(int ax, int ay)
+{
+  int element = Feld[ax][ay];
+  int graphic = el2img(element);
+  boolean oben_frei = FALSE, unten_frei = FALSE;
+  boolean links_frei = FALSE, rechts_frei = FALSE;
+  boolean oben_massiv = FALSE, unten_massiv = FALSE;
+  boolean links_massiv = FALSE, rechts_massiv = FALSE;
+  boolean new_wall = FALSE;
+
+  if (IS_ANIMATED(graphic))
+    DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
+
+  if (!MovDelay[ax][ay])       /* start building new wall */
+    MovDelay[ax][ay] = 6;
+
+  if (MovDelay[ax][ay])                /* wait some time before building new wall */
+  {
+    MovDelay[ax][ay]--;
+    if (MovDelay[ax][ay])
+      return;
+  }
+
+  if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
+    oben_frei = TRUE;
+  if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
+    unten_frei = TRUE;
+  if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
+    links_frei = TRUE;
+  if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
+    rechts_frei = TRUE;
+
+  if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
+      element == EL_EXPANDABLE_STEELWALL_ANY)
+  {
+    if (oben_frei)
+    {
+      Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax][ay-1] = element;
+      GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
+      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
+       DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
+      new_wall = TRUE;
+    }
+    if (unten_frei)
+    {
+      Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax][ay+1] = element;
+      GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
+      if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
+       DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
+      new_wall = TRUE;
+    }
+  }
+
+  if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
+      element == EL_EXPANDABLE_STEELWALL_ANY)
+  {
+    if (links_frei)
+    {
+      Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax-1][ay] = element;
+      GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
+      if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
+       DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
+      new_wall = TRUE;
+    }
+
+    if (rechts_frei)
+    {
+      Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
+      Store[ax+1][ay] = element;
+      GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
+      if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
+       DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
+                   IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
+      new_wall = TRUE;
+    }
+  }
+
+  if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
+    oben_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
+    unten_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
+    links_massiv = TRUE;
+  if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
+    rechts_massiv = TRUE;
+
+  if (((oben_massiv && unten_massiv) ||
+       element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
+      ((links_massiv && rechts_massiv) ||
+       element == EL_EXPANDABLE_STEELWALL_VERTICAL))
+    Feld[ax][ay] = EL_STEELWALL;
+
+  if (new_wall)
+    PlayLevelSoundAction(ax, ay, ACTION_GROWING);
 }
 
 void CheckForDragon(int x, int y)
@@ -7791,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;
@@ -7827,9 +9293,10 @@ static void WarnBuggyBase(int x, int y)
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int xx = x + xy[i][0], yy = y + xy[i][1];
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
 
-    if (IS_PLAYER(xx, yy))
+    if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
     {
       PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
 
@@ -7854,2729 +9321,3154 @@ 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 void ChangeElementNowExt(int x, int y, int target_element)
+static int getSpecialActionElement(int element, int number, int base_element)
 {
-  int previous_move_direction = MovDir[x][y];
-#if 1
-  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
-                       IS_WALKABLE(Feld[x][y]));
-#else
-  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
-                       IS_WALKABLE(Feld[x][y]) &&
-                       !IS_MOVING(x, y));
-#endif
+  return (element != EL_EMPTY ? element :
+         number != -1 ? base_element + number - 1 :
+         EL_EMPTY);
+}
 
-  /* check if element under player changes from accessible to unaccessible
-     (needed for special case of dropping element which then changes) */
-  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
-      IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element))
-  {
-#if 0
-    printf("::: BOOOM! [%d, '%s']\n", target_element,
-          element_info[target_element].token_name);
-#endif
+static int getModifiedActionNumber(int value_old, int operator, int operand,
+                                  int value_min, int value_max)
+{
+  int value_new = (operator == CA_MODE_SET      ? operand :
+                  operator == CA_MODE_ADD      ? value_old + operand :
+                  operator == CA_MODE_SUBTRACT ? value_old - operand :
+                  operator == CA_MODE_MULTIPLY ? value_old * operand :
+                  operator == CA_MODE_DIVIDE   ? value_old / MAX(1, operand) :
+                  operator == CA_MODE_MODULO   ? value_old % MAX(1, operand) :
+                  value_old);
 
-    Bang(x, y);
+  return (value_new < value_min ? value_min :
+         value_new > value_max ? value_max :
+         value_new);
+}
+
+static void ExecuteCustomElementAction(int x, int y, int element, int page)
+{
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  int target_element = change->target_element;
+  int action_type = change->action_type;
+  int action_mode = change->action_mode;
+  int action_arg = change->action_arg;
+  int action_element = change->action_element;
+  int i;
+
+  if (!change->has_action)
     return;
-  }
 
-#if 1
-  if (!add_player)
-#endif
-  {
-#if 1
-    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
-      RemoveMovingField(x, y);
-    else
-      RemoveField(x, y);
+  /* ---------- determine action paramater values -------------------------- */
+
+  int level_time_value =
+    (level.time > 0 ? TimeLeft :
+     TimePlayed);
+
+  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 &&
+     action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
+     action_arg == CA_ARG_DIRECTION_TRIGGER ?
+     change->actual_trigger_side :
+     action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
+     MV_DIR_OPPOSITE(change->actual_trigger_side) :
+     MV_NONE);
+
+  int action_arg_number_min =
+    (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
+     CA_ARG_MIN);
+
+  int action_arg_number_max =
+    (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
+     action_type == CA_SET_LEVEL_GEMS ? 999 :
+     action_type == CA_SET_LEVEL_TIME ? 9999 :
+     action_type == CA_SET_LEVEL_SCORE ? 99999 :
+     action_type == CA_SET_CE_VALUE ? 9999 :
+     action_type == CA_SET_CE_SCORE ? 9999 :
+     CA_ARG_MAX);
+
+  int action_arg_number_reset =
+    (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
+     action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
+     action_type == CA_SET_LEVEL_TIME ? level.time :
+     action_type == CA_SET_LEVEL_SCORE ? 0 :
+     action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
+     action_type == CA_SET_CE_SCORE ? 0 :
+     0);
+
+  int action_arg_number =
+    (action_arg <= CA_ARG_MAX ? action_arg :
+     action_arg >= CA_ARG_SPEED_NOT_MOVING &&
+     action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
+     action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
+     action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
+     action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
+     action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
+     action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
+     action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
+     action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
+     action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
+     action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
+     action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
+     action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_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 =
+    (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
+     action_type == CA_SET_LEVEL_TIME ? TimeLeft :
+     action_type == CA_SET_LEVEL_SCORE ? local_player->score :
+     action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
+     action_type == CA_SET_CE_SCORE ? ei->collect_score :
+     0);
+
+  int action_arg_number_new =
+    getModifiedActionNumber(action_arg_number_old,
+                           action_mode, action_arg_number,
+                           action_arg_number_min, action_arg_number_max);
+
+  int trigger_player_bits =
+    (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  -------------------------------------------- */
+
+  switch (action_type)
+  {
+    case CA_NO_ACTION:
+    {
+      return;
+    }
 
-    Feld[x][y] = target_element;
-#else
-    RemoveField(x, y);
-    Feld[x][y] = target_element;
-#endif
+    /* ---------- level actions  ------------------------------------------- */
 
-    ResetGfxAnimation(x, y);
-    ResetRandomAnimationValue(x, y);
+    case CA_RESTART_LEVEL:
+    {
+      game.restart_level = TRUE;
 
-    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
-      MovDir[x][y] = previous_move_direction;
+      break;
+    }
 
-#if 1
-    InitField_WithBug1(x, y, FALSE);
-#else
-    InitField(x, y, FALSE);
-    if (CAN_MOVE(Feld[x][y]))
-      InitMovDir(x, y);
-#endif
+    case CA_SHOW_ENVELOPE:
+    {
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_ENVELOPE_1);
 
-    DrawLevelField(x, y);
+      if (IS_ENVELOPE(element))
+       local_player->show_envelope = element;
 
-    if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
-  }
+      break;
+    }
 
-#if 0
-  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
-#endif
+    case CA_SET_LEVEL_TIME:
+    {
+      if (level.time > 0)      /* only modify limited time value */
+      {
+       TimeLeft = action_arg_number_new;
 
-#if 0
-  TestIfBadThingTouchesHero(x, y);
-  TestIfPlayerTouchesCustomElement(x, y);
-  TestIfElementTouchesCustomElement(x, y);
-#endif
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
-  /* "Changed[][]" not set yet to allow "entered by player" change one time */
-  if (ELEM_IS_PLAYER(target_element))
-    RelocatePlayer(x, y, target_element);
+       DisplayGameControlValues();
 
-#if 1
-  Changed[x][y] = TRUE;                /* ignore all further changes in this frame */
-#else
-  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
-#endif
+       if (!TimeLeft && setup.time_limit)
+         for (i = 0; i < MAX_PLAYERS; i++)
+           KillPlayer(&stored_player[i]);
+      }
 
-#if 1
-  TestIfBadThingTouchesHero(x, y);
-  TestIfPlayerTouchesCustomElement(x, y);
-  TestIfElementTouchesCustomElement(x, y);
-#endif
-}
+      break;
+    }
 
-static boolean ChangeElementNow(int x, int y, int element, int page)
-{
-  struct ElementChangeInfo *change = &element_info[element].change_page[page];
-  int target_element;
-  int old_element = Feld[x][y];
+    case CA_SET_LEVEL_SCORE:
+    {
+      local_player->score = action_arg_number_new;
 
-  /* always use default change event to prevent running into a loop */
-  if (ChangeEvent[x][y] == -1)
-    ChangeEvent[x][y] = CE_DELAY;
+      game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
 
-  if (ChangeEvent[x][y] == CE_DELAY)
-  {
-    /* reset actual trigger element and player */
-    change->actual_trigger_element = EL_EMPTY;
-    change->actual_trigger_player = EL_PLAYER_1;
-  }
+      DisplayGameControlValues();
 
-#if 1
-  /* do not change any elements that have already changed in this frame */
-  if (Changed[x][y])
-    return FALSE;
-#else
-  /* do not change already changed elements with same change event */
-  if (Changed[x][y] & ChangeEvent[x][y])
-    return FALSE;
-#endif
+      break;
+    }
 
-#if 1
-  Changed[x][y] = TRUE;                /* ignore all further changes in this frame */
-#else
-  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
-#endif
+    case CA_SET_LEVEL_GEMS:
+    {
+      local_player->gems_still_needed = action_arg_number_new;
 
-#if 0
-  /* !!! indirect change before direct change !!! */
-  CheckTriggeredElementChangeByPage(x, y, Feld[x][y], CE_CHANGE_OF_X, page);
-#endif
+      game.snapshot.collected_item = TRUE;
 
-  if (change->explode)
-  {
-    Bang(x, y);
+      game_panel_controls[GAME_PANEL_GEMS].value =
+       local_player->gems_still_needed;
 
-    return TRUE;
-  }
+      DisplayGameControlValues();
 
-  if (change->use_target_content)
-  {
-    boolean complete_replace = TRUE;
-    boolean can_replace[3][3];
-    int xx, yy;
+      break;
+    }
 
-    for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+    case CA_SET_LEVEL_WIND:
     {
-      boolean is_empty;
-      boolean is_walkable;
-      boolean is_diggable;
-      boolean is_collectible;
-      boolean is_removable;
-      boolean is_destructible;
-      int ex = x + xx - 1;
-      int ey = y + yy - 1;
-      int content_element = change->target_content[xx][yy];
-      int e;
+      game.wind_direction = action_arg_direction;
 
-      can_replace[xx][yy] = TRUE;
+      break;
+    }
 
-      if (ex == x && ey == y)  /* do not check changing element itself */
-       continue;
+    case CA_SET_LEVEL_RANDOM_SEED:
+    {
+      /* ensure that setting a new random seed while playing is predictable */
+      InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
 
-      if (content_element == EL_EMPTY_SPACE)
-      {
-       can_replace[xx][yy] = FALSE;    /* do not replace border with space */
+      break;
+    }
 
-       continue;
-      }
+    /* ---------- player actions  ------------------------------------------ */
 
-      if (!IN_LEV_FIELD(ex, ey))
-      {
-       can_replace[xx][yy] = FALSE;
-       complete_replace = FALSE;
+    case CA_MOVE_PLAYER:
+    {
+      /* automatically move to the next field in specified direction */
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (trigger_player_bits & (1 << i))
+         stored_player[i].programmed_action = action_arg_direction;
 
-       continue;
-      }
+      break;
+    }
 
-#if 0
-      if (Changed[ex][ey])     /* do not change already changed elements */
-      {
-       can_replace[xx][yy] = FALSE;
-       complete_replace = FALSE;
+    case CA_EXIT_PLAYER:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (action_arg_player_bits & (1 << i))
+         PlayerWins(&stored_player[i]);
 
-       continue;
-      }
-#endif
+      break;
+    }
 
-      e = Feld[ex][ey];
+    case CA_KILL_PLAYER:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (action_arg_player_bits & (1 << i))
+         KillPlayer(&stored_player[i]);
 
-      if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
-       e = MovingOrBlocked2Element(ex, ey);
+      break;
+    }
 
-#if 1
+    case CA_SET_PLAYER_KEYS:
+    {
+      int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_KEY_1);
 
-#if 0
-      is_empty = (IS_FREE(ex, ey) ||
-                 (IS_PLAYER(ex, ey) && IS_WALKABLE(content_element)) ||
-                 (IS_WALKABLE(e) && ELEM_IS_PLAYER(content_element) &&
-                  !IS_MOVING(ex, ey) && !IS_BLOCKED(ex, ey)));
-#else
+      if (IS_KEY(element))
+      {
+       for (i = 0; i < MAX_PLAYERS; i++)
+       {
+         if (trigger_player_bits & (1 << i))
+         {
+           stored_player[i].key[KEY_NR(element)] = key_state;
 
-#if 0
-      is_empty = (IS_FREE(ex, ey) ||
-                 (IS_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
-#else
-      is_empty = (IS_FREE(ex, ey) ||
-                 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
-#endif
+           DrawGameDoorValues();
+         }
+       }
+      }
 
-#endif
+      break;
+    }
 
-      is_walkable     = (is_empty || IS_WALKABLE(e));
-      is_diggable     = (is_empty || IS_DIGGABLE(e));
-      is_collectible  = (is_empty || IS_COLLECTIBLE(e));
-      is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
-      is_removable    = (is_diggable || is_collectible);
+    case CA_SET_PLAYER_SPEED:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (trigger_player_bits & (1 << i))
+       {
+         int move_stepsize = TILEX / stored_player[i].move_delay_value;
 
-      can_replace[xx][yy] =
-       (((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
-         (change->replace_when == CP_WHEN_WALKABLE     && is_walkable) ||
-         (change->replace_when == CP_WHEN_DIGGABLE     && is_diggable) ||
-         (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
-         (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
-         (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
-        !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
+         if (action_arg == CA_ARG_SPEED_FASTER &&
+             stored_player[i].cannot_move)
+         {
+           action_arg_number = STEPSIZE_VERY_SLOW;
+         }
+         else if (action_arg == CA_ARG_SPEED_SLOWER ||
+                  action_arg == CA_ARG_SPEED_FASTER)
+         {
+           action_arg_number = 2;
+           action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
+                          CA_MODE_MULTIPLY);
+         }
+         else if (action_arg == CA_ARG_NUMBER_RESET)
+         {
+           action_arg_number = level.initial_player_stepsize[i];
+         }
 
-      if (!can_replace[xx][yy])
-       complete_replace = FALSE;
-#else
-      empty_for_element = (IS_FREE(ex, ey) || (IS_FREE_OR_PLAYER(ex, ey) &&
-                                              IS_WALKABLE(content_element)));
-#if 1
-      half_destructible = (empty_for_element || IS_DIGGABLE(e));
-#else
-      half_destructible = (IS_FREE(ex, ey) || IS_DIGGABLE(e));
-#endif
+         move_stepsize =
+           getModifiedActionNumber(move_stepsize,
+                                   action_mode,
+                                   action_arg_number,
+                                   action_arg_number_min,
+                                   action_arg_number_max);
 
-      if ((change->replace_when <= CP_WHEN_EMPTY  && !empty_for_element) ||
-         (change->replace_when <= CP_WHEN_DIGGABLE && !half_destructible) ||
-         (change->replace_when <= CP_WHEN_DESTRUCTIBLE && IS_INDESTRUCTIBLE(e)))
-      {
-       can_replace[xx][yy] = FALSE;
-       complete_replace = FALSE;
+         SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
+       }
       }
-#endif
+
+      break;
     }
 
-    if (!change->only_if_complete || complete_replace)
+    case CA_SET_PLAYER_SHIELD:
     {
-      boolean something_has_changed = FALSE;
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (trigger_player_bits & (1 << i))
+       {
+         if (action_arg == CA_ARG_SHIELD_OFF)
+         {
+           stored_player[i].shield_normal_time_left = 0;
+           stored_player[i].shield_deadly_time_left = 0;
+         }
+         else if (action_arg == CA_ARG_SHIELD_NORMAL)
+         {
+           stored_player[i].shield_normal_time_left = 999999;
+         }
+         else if (action_arg == CA_ARG_SHIELD_DEADLY)
+         {
+           stored_player[i].shield_normal_time_left = 999999;
+           stored_player[i].shield_deadly_time_left = 999999;
+         }
+       }
+      }
 
-      if (change->only_if_complete && change->use_random_replace &&
-         RND(100) < change->random_percentage)
-       return FALSE;
+      break;
+    }
 
-      for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+    case CA_SET_PLAYER_GRAVITY:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
       {
-       int ex = x + xx - 1;
-       int ey = y + yy - 1;
-       int content_element;
+       if (trigger_player_bits & (1 << i))
+       {
+         stored_player[i].gravity =
+           (action_arg == CA_ARG_GRAVITY_OFF    ? FALSE                     :
+            action_arg == CA_ARG_GRAVITY_ON     ? TRUE                      :
+            action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
+            stored_player[i].gravity);
+       }
+      }
 
-       if (can_replace[xx][yy] && (!change->use_random_replace ||
-                                   RND(100) < change->random_percentage))
+      break;
+    }
+
+    case CA_SET_PLAYER_ARTWORK:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (trigger_player_bits & (1 << i))
        {
-         if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
-           RemoveMovingField(ex, ey);
+         int artwork_element = action_arg_element;
 
-         ChangeEvent[ex][ey] = ChangeEvent[x][y];
+         if (action_arg == CA_ARG_ELEMENT_RESET)
+           artwork_element =
+             (level.use_artwork_element[i] ? level.artwork_element[i] :
+              stored_player[i].element_nr);
 
-         content_element = change->target_content[xx][yy];
-         target_element = GET_TARGET_ELEMENT(content_element, change);
+         if (stored_player[i].artwork_element != artwork_element)
+           stored_player[i].Frame = 0;
 
-         ChangeElementNowExt(ex, ey, target_element);
+         stored_player[i].artwork_element = artwork_element;
 
-         something_has_changed = TRUE;
+         SetPlayerWaiting(&stored_player[i], FALSE);
 
-         /* for symmetry reasons, freeze newly created border elements */
-         if (ex != x || ey != y)
-           Stop[ex][ey] = TRUE;        /* no more moving in this frame */
+         /* set number of special actions for bored and sleeping animation */
+         stored_player[i].num_special_action_bored =
+           get_num_special_action(artwork_element,
+                                  ACTION_BORING_1, ACTION_BORING_LAST);
+         stored_player[i].num_special_action_sleeping =
+           get_num_special_action(artwork_element,
+                                  ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
        }
       }
 
-      if (something_has_changed)
-       PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+      break;
     }
-  }
-  else
-  {
-    target_element = GET_TARGET_ELEMENT(change->target_element, change);
 
-    ChangeElementNowExt(x, y, target_element);
+    case CA_SET_PLAYER_INVENTORY:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int j, k;
 
-    PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
-  }
+       if (trigger_player_bits & (1 << i))
+       {
+         int inventory_element = action_arg_element;
 
-#if 1
-  /* this uses direct change before indirect change */
-  CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
-#endif
+         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;
 
-  return TRUE;
-}
+           if (!IS_CUSTOM_ELEMENT(element))
+             collect_count = 1;
 
-static void ChangeElement(int x, int y, int page)
-{
-  int element = MovingOrBlocked2Element(x, y);
-  struct ElementInfo *ei = &element_info[element];
-  struct ElementChangeInfo *change = &ei->change_page[page];
+           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;
 
-#ifdef DEBUG
-  if (!CAN_CHANGE(element) && !CAN_CHANGE(Back[x][y]))
-  {
-    printf("\n\n");
-    printf("ChangeElement(): %d,%d: element = %d ('%s')\n",
-          x, y, element, element_info[element].token_name);
-    printf("ChangeElement(): This should never happen!\n");
-    printf("\n\n");
-  }
-#endif
+           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];
+           }
 
-  /* 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
+           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];
 
-    return;
-  }
+             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 (ChangeDelay[x][y] == 0)          /* initialize element change */
-  {
-    ChangeDelay[x][y] = (    change->delay_fixed  * change->delay_frames +
-                        RND(change->delay_random * change->delay_frames)) + 1;
+           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;
+             }
+           }
+         }
+       }
+      }
 
-    ResetGfxAnimation(x, y);
-    ResetRandomAnimationValue(x, y);
+      break;
+    }
 
-    if (change->pre_change_function)
-      change->pre_change_function(x, y);
-  }
+    /* ---------- CE actions  ---------------------------------------------- */
 
-  ChangeDelay[x][y]--;
+    case CA_SET_CE_VALUE:
+    {
+      int last_ce_value = CustomValue[x][y];
 
-  if (ChangeDelay[x][y] != 0)          /* continue element change */
-  {
-    int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+      CustomValue[x][y] = action_arg_number_new;
 
-    if (IS_ANIMATED(graphic))
-      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+      if (CustomValue[x][y] != last_ce_value)
+      {
+       CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
+       CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
 
-    if (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;
+       if (CustomValue[x][y] == 0)
+       {
+         CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
+         CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
+       }
+      }
 
-      change = &ei->change_page[page];
+      break;
     }
 
-#if 0
-    if (IS_MOVING(x, y) && !change->explode)
-#else
-    if (IS_MOVING(x, y))               /* never change a running system ;-) */
-#endif
-    {
-      ChangeDelay[x][y] = 1;           /* try change after next move step */
-      ChangePage[x][y] = page;         /* remember page to use for change */
-
-      return;
-    }
-
-    if (ChangeElementNow(x, y, element, page))
+    case CA_SET_CE_SCORE:
     {
-      if (change->post_change_function)
-       change->post_change_function(x, y);
-    }
-  }
-}
+      int last_ce_score = ei->collect_score;
 
-static boolean CheckTriggeredElementChangeExt(int lx, int ly,
-                                             int trigger_element,
-                                             int trigger_event,
-                                             int trigger_player,
-                                             int trigger_side,
-                                             int trigger_page)
-{
-  int i, j, x, y;
-  int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
+      ei->collect_score = action_arg_number_new;
 
-  if (!(trigger_events[trigger_element][trigger_event]))
-    return FALSE;
+      if (ei->collect_score != last_ce_score)
+      {
+       CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
+       CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
 
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
-  {
-    int element = EL_CUSTOM_START + i;
+       if (ei->collect_score == 0)
+       {
+         int xx, yy;
 
-    boolean change_element = FALSE;
-    int page = 0;
+         CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
+         CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
+
+         /*
+           This is a very special case that seems to be a mixture between
+           CheckElementChange() and CheckTriggeredElementChange(): while
+           the first one only affects single elements that are triggered
+           directly, the second one affects multiple elements in the playfield
+           that are triggered indirectly by another element. This is a third
+           case: Changing the CE score always affects multiple identical CEs,
+           so every affected CE must be checked, not only the single CE for
+           which the CE score was changed in the first place (as every instance
+           of that CE shares the same CE score, and therefore also can change)!
+         */
+         SCAN_PLAYFIELD(xx, yy)
+         {
+           if (Feld[xx][yy] == element)
+             CheckElementChange(xx, yy, element, EL_UNDEFINED,
+                                CE_SCORE_GETS_ZERO);
+         }
+       }
+      }
 
-    if (!CAN_CHANGE(element) || !HAS_ANY_CHANGE_EVENT(element, trigger_event))
-      continue;
+      break;
+    }
 
-    for (j = 0; j < element_info[element].num_change_pages; j++)
+    case CA_SET_CE_ARTWORK:
     {
-      struct ElementChangeInfo *change = &element_info[element].change_page[j];
+      int artwork_element = action_arg_element;
+      boolean reset_frame = FALSE;
+      int xx, yy;
 
-      if (change->can_change &&
-         change->has_event[trigger_event] &&
-         change->trigger_side & trigger_side &&
-         change->trigger_player & trigger_player &&
-         change->trigger_page & trigger_page_bits &&
-         IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
-      {
-#if 0
-       if (!(change->has_event[trigger_event]))
-         printf("::: !!! %d triggers %d: using wrong page %d [event %d]\n",
-                trigger_element-EL_CUSTOM_START+1, i+1, j, trigger_event);
-#endif
+      if (action_arg == CA_ARG_ELEMENT_RESET)
+       artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
+                          element);
 
-       change_element = TRUE;
-       page = j;
+      if (ei->gfx_element != artwork_element)
+       reset_frame = TRUE;
 
-       change->actual_trigger_element = trigger_element;
-       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+      ei->gfx_element = artwork_element;
 
-       break;
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       if (Feld[xx][yy] == element)
+       {
+         if (reset_frame)
+         {
+           ResetGfxAnimation(xx, yy);
+           ResetRandomAnimationValue(xx, yy);
+         }
+
+         TEST_DrawLevelField(xx, yy);
+       }
       }
+
+      break;
     }
 
-    if (!change_element)
-      continue;
+    /* ---------- engine actions  ------------------------------------------ */
 
-    for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+    case CA_SET_ENGINE_SCAN_MODE:
     {
-#if 0
-      if (x == lx && y == ly)  /* do not change trigger element itself */
-       continue;
-#endif
+      InitPlayfieldScanMode(action_arg);
 
-      if (Feld[x][y] == element)
-      {
-       ChangeDelay[x][y] = 1;
-       ChangeEvent[x][y] = trigger_event;
-       ChangeElement(x, y, page);
-      }
+      break;
     }
-  }
 
-  return TRUE;
+    default:
+      break;
+  }
 }
 
-static boolean CheckElementChangeExt(int x, int y,
-                                    int element,
-                                    int trigger_element,
-                                    int trigger_event,
-                                    int trigger_player,
-                                    int trigger_side,
-                                    int trigger_page)
+static void CreateFieldExt(int x, int y, int element, boolean is_change)
 {
-  if (!CAN_CHANGE(element) || !HAS_ANY_CHANGE_EVENT(element, trigger_event))
-    return FALSE;
+  int old_element = Feld[x][y];
+  int new_element = GetElementFromGroupElement(element);
+  int previous_move_direction = MovDir[x][y];
+  int last_ce_value = CustomValue[x][y];
+  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 &&
+                                    new_element != EL_SOKOBAN_FIELD_PLAYER &&
+                                    IS_WALKABLE(old_element));
 
-  if (Feld[x][y] == EL_BLOCKED)
+  if (!add_player_onto_element)
   {
-    Blocked2Moving(x, y, &x, &y);
-    element = Feld[x][y];
-  }
+    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+      RemoveMovingField(x, y);
+    else
+      RemoveField(x, y);
 
-#if 1
-  if (Feld[x][y] != element)   /* check if element has already changed */
-  {
-#if 0
-    printf("::: %d ('%s') != %d ('%s') [%d]\n",
-          Feld[x][y], element_info[Feld[x][y]].token_name,
-          element, element_info[element].token_name,
-          trigger_event);
-#endif
+    Feld[x][y] = new_element;
 
-    return FALSE;
-  }
-#endif
+    if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = previous_move_direction;
 
-#if 1
-  if (trigger_page < 0)
-  {
-    boolean change_element = FALSE;
-    int i;
+    if (element_info[new_element].use_last_ce_value)
+      CustomValue[x][y] = last_ce_value;
 
-    for (i = 0; i < element_info[element].num_change_pages; i++)
-    {
-      struct ElementChangeInfo *change = &element_info[element].change_page[i];
+    InitField_WithBug1(x, y, FALSE);
 
-      if (change->can_change &&
-         change->has_event[trigger_event] &&
-         change->trigger_side & trigger_side &&
-         change->trigger_player & trigger_player)
-      {
-       change_element = TRUE;
-       trigger_page = i;
+    new_element = Feld[x][y];  /* element may have changed */
 
-       change->actual_trigger_element = trigger_element;
-       change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+    ResetGfxAnimation(x, y);
+    ResetRandomAnimationValue(x, y);
 
-       break;
-      }
-    }
+    TEST_DrawLevelField(x, y);
 
-    if (!change_element)
-      return FALSE;
+    if (GFX_CRUMBLED(new_element))
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
   }
-  else
+
+  /* check if element under the player changes from accessible to unaccessible
+     (needed for special case of dropping element which then changes) */
+  /* (must be checked after creating new element for walkable group elements) */
+  if (IS_PLAYER(x, y) && !player_explosion_protected &&
+      IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
   {
-    struct ElementInfo *ei = &element_info[element];
-    struct ElementChangeInfo *change = &ei->change_page[trigger_page];
+    Bang(x, y);
 
-    change->actual_trigger_element = trigger_element;
-    change->actual_trigger_player = EL_PLAYER_1;       /* unused */
+    return;
   }
 
-#else
-
-  /* !!! this check misses pages with same event, but different side !!! */
-
-  if (trigger_page < 0)
-    trigger_page = element_info[element].event_page_nr[trigger_event];
+  /* "ChangeCount" not set yet to allow "entered by player" change one time */
+  if (new_element_is_player)
+    RelocatePlayer(x, y, new_element);
 
-  if (!(element_info[element].change_page[trigger_page].trigger_side & trigger_side))
-    return FALSE;
-#endif
+  if (is_change)
+    ChangeCount[x][y]++;       /* count number of changes in the same frame */
 
-  ChangeDelay[x][y] = 1;
-  ChangeEvent[x][y] = trigger_event;
-  ChangeElement(x, y, trigger_page);
+  TestIfBadThingTouchesPlayer(x, y);
+  TestIfPlayerTouchesCustomElement(x, y);
+  TestIfElementTouchesCustomElement(x, y);
+}
 
-  return TRUE;
+static void CreateField(int x, int y, int element)
+{
+  CreateFieldExt(x, y, element, FALSE);
 }
 
-static void PlayPlayerSound(struct PlayerInfo *player)
+static void CreateElementFromChange(int x, int y, int element)
 {
-  int jx = player->jx, jy = player->jy;
-  int element = player->element_nr;
-  int last_action = player->last_action_waiting;
-  int action = player->action_waiting;
+  element = GET_VALID_RUNTIME_ELEMENT(element);
 
-  if (player->is_waiting)
+  if (game.engine_version >= VERSION_IDENT(3,2,0,7))
   {
-    if (action != last_action)
-      PlayLevelSoundElementAction(jx, jy, element, action);
-    else
-      PlayLevelSoundElementActionIfLoop(jx, jy, element, action);
-  }
-  else
-  {
-    if (action != last_action)
-      StopSound(element_info[element].sound[last_action]);
+    int old_element = Feld[x][y];
 
-    if (last_action == ACTION_SLEEPING)
-      PlayLevelSoundElementAction(jx, jy, element, ACTION_AWAKENING);
+    /* prevent changed element from moving in same engine frame
+       unless both old and new element can either fall or move */
+    if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
+       (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
+      Stop[x][y] = TRUE;
   }
-}
-
-static void PlayAllPlayersSound()
-{
-  int i;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    if (stored_player[i].active)
-      PlayPlayerSound(&stored_player[i]);
+  CreateFieldExt(x, y, element, TRUE);
 }
 
-static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
+static boolean ChangeElement(int x, int y, int element, int page)
 {
-  boolean last_waiting = player->is_waiting;
-  int move_dir = player->MovDir;
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  int ce_value = CustomValue[x][y];
+  int ce_score = ei->collect_score;
+  int target_element;
+  int old_element = Feld[x][y];
 
-  player->last_action_waiting = player->action_waiting;
+  /* always use default change event to prevent running into a loop */
+  if (ChangeEvent[x][y] == -1)
+    ChangeEvent[x][y] = CE_DELAY;
 
-  if (is_waiting)
+  if (ChangeEvent[x][y] == CE_DELAY)
   {
-    if (!last_waiting)         /* not waiting -> waiting */
-    {
-      player->is_waiting = TRUE;
+    /* reset actual trigger element, trigger player and action element */
+    change->actual_trigger_element = EL_EMPTY;
+    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;
+  }
 
-      player->frame_counter_bored =
-       FrameCounter +
-       game.player_boring_delay_fixed +
-       SimpleRND(game.player_boring_delay_random);
-      player->frame_counter_sleeping =
-       FrameCounter +
-       game.player_sleeping_delay_fixed +
-       SimpleRND(game.player_sleeping_delay_random);
+  /* do not change elements more than a specified maximum number of changes */
+  if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
+    return FALSE;
 
-      InitPlayerGfxAnimation(player, ACTION_WAITING, player->MovDir);
-    }
+  ChangeCount[x][y]++;         /* count number of changes in the same frame */
 
-    if (game.player_sleeping_delay_fixed +
-       game.player_sleeping_delay_random > 0 &&
-       player->anim_delay_counter == 0 &&
-       player->post_delay_counter == 0 &&
-       FrameCounter >= player->frame_counter_sleeping)
-      player->is_sleeping = TRUE;
-    else if (game.player_boring_delay_fixed +
-            game.player_boring_delay_random > 0 &&
-            FrameCounter >= player->frame_counter_bored)
-      player->is_bored = TRUE;
+  if (change->explode)
+  {
+    Bang(x, y);
 
-    player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
-                             player->is_bored ? ACTION_BORING :
-                             ACTION_WAITING);
+    return TRUE;
+  }
 
-    if (player->is_sleeping)
+  if (change->use_target_content)
+  {
+    boolean complete_replace = TRUE;
+    boolean can_replace[3][3];
+    int xx, yy;
+
+    for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
     {
-      if (player->num_special_action_sleeping > 0)
-      {
-       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
-       {
-         int last_special_action = player->special_action_sleeping;
-         int num_special_action = player->num_special_action_sleeping;
-         int special_action =
-           (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
-            last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
-            last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
-            last_special_action + 1 : ACTION_SLEEPING);
-         int special_graphic =
-           el_act_dir2img(player->element_nr, special_action, move_dir);
+      boolean is_empty;
+      boolean is_walkable;
+      boolean is_diggable;
+      boolean is_collectible;
+      boolean is_removable;
+      boolean is_destructible;
+      int ex = x + xx - 1;
+      int ey = y + yy - 1;
+      int content_element = change->target_content.e[xx][yy];
+      int e;
 
-         player->anim_delay_counter =
-           graphic_info[special_graphic].anim_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].anim_delay_random);
-         player->post_delay_counter =
-           graphic_info[special_graphic].post_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].post_delay_random);
+      can_replace[xx][yy] = TRUE;
 
-         player->special_action_sleeping = special_action;
-       }
+      if (ex == x && ey == y)  /* do not check changing element itself */
+       continue;
 
-       if (player->anim_delay_counter > 0)
-       {
-         player->action_waiting = player->special_action_sleeping;
-         player->anim_delay_counter--;
-       }
-       else if (player->post_delay_counter > 0)
-       {
-         player->post_delay_counter--;
-       }
-      }
-    }
-    else if (player->is_bored)
-    {
-      if (player->num_special_action_bored > 0)
+      if (content_element == EL_EMPTY_SPACE)
       {
-       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
-       {
-         int special_action =
-           ACTION_BORING_1 + SimpleRND(player->num_special_action_bored);
-         int special_graphic =
-           el_act_dir2img(player->element_nr, special_action, move_dir);
-
-         player->anim_delay_counter =
-           graphic_info[special_graphic].anim_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].anim_delay_random);
-         player->post_delay_counter =
-           graphic_info[special_graphic].post_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].post_delay_random);
+       can_replace[xx][yy] = FALSE;    /* do not replace border with space */
 
-         player->special_action_bored = special_action;
-       }
+       continue;
+      }
 
-       if (player->anim_delay_counter > 0)
-       {
-         player->action_waiting = player->special_action_bored;
-         player->anim_delay_counter--;
-       }
-       else if (player->post_delay_counter > 0)
-       {
-         player->post_delay_counter--;
-       }
+      if (!IN_LEV_FIELD(ex, ey))
+      {
+       can_replace[xx][yy] = FALSE;
+       complete_replace = FALSE;
+
+       continue;
       }
-    }
-  }
-  else if (last_waiting)       /* waiting -> not waiting */
-  {
-    player->is_waiting = FALSE;
-    player->is_bored = FALSE;
-    player->is_sleeping = FALSE;
 
-    player->frame_counter_bored = -1;
-    player->frame_counter_sleeping = -1;
+      e = Feld[ex][ey];
 
-    player->anim_delay_counter = 0;
-    player->post_delay_counter = 0;
+      if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+       e = MovingOrBlocked2Element(ex, ey);
 
-    player->action_waiting = ACTION_DEFAULT;
+      is_empty = (IS_FREE(ex, ey) ||
+                 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
 
-    player->special_action_bored = ACTION_DEFAULT;
-    player->special_action_sleeping = ACTION_DEFAULT;
-  }
-}
+      is_walkable     = (is_empty || IS_WALKABLE(e));
+      is_diggable     = (is_empty || IS_DIGGABLE(e));
+      is_collectible  = (is_empty || IS_COLLECTIBLE(e));
+      is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
+      is_removable    = (is_diggable || is_collectible);
 
-#if 1
-static byte PlayerActions(struct PlayerInfo *player, byte player_action)
-{
-#if 0
-  static byte stored_player_action[MAX_PLAYERS];
-  static int num_stored_actions = 0;
-#endif
-  boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
-  int left     = player_action & JOY_LEFT;
-  int right    = player_action & JOY_RIGHT;
-  int up       = player_action & JOY_UP;
-  int down     = player_action & JOY_DOWN;
-  int button1  = player_action & JOY_BUTTON_1;
-  int button2  = player_action & JOY_BUTTON_2;
-  int dx       = (left ? -1    : right ? 1     : 0);
-  int dy       = (up   ? -1    : down  ? 1     : 0);
+      can_replace[xx][yy] =
+       (((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
+         (change->replace_when == CP_WHEN_WALKABLE     && is_walkable) ||
+         (change->replace_when == CP_WHEN_DIGGABLE     && is_diggable) ||
+         (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
+         (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
+         (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
+        !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
 
-#if 0
-  stored_player_action[player->index_nr] = 0;
-  num_stored_actions++;
-#endif
+      if (!can_replace[xx][yy])
+       complete_replace = FALSE;
+    }
 
-#if 0
-  printf("::: player %d [%d]\n", player->index_nr, FrameCounter);
-#endif
+    if (!change->only_if_complete || complete_replace)
+    {
+      boolean something_has_changed = FALSE;
 
-  if (!player->active || tape.pausing)
-    return 0;
+      if (change->only_if_complete && change->use_random_replace &&
+         RND(100) < change->random_percentage)
+       return FALSE;
 
-#if 0
-  printf("::: [%d %d %d %d] [%d %d]\n",
-        left, right, up, down, button1, button2);
-#endif
+      for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+      {
+       int ex = x + xx - 1;
+       int ey = y + yy - 1;
+       int content_element;
 
-  if (player_action)
-  {
-#if 0
-    printf("::: player %d acts [%d]\n", player->index_nr, FrameCounter);
-#endif
+       if (can_replace[xx][yy] && (!change->use_random_replace ||
+                                   RND(100) < change->random_percentage))
+       {
+         if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+           RemoveMovingField(ex, ey);
 
-#if 0
-    /* !!! TEST !!! */
-    if (player->MovPos == 0)
-      CheckGravityMovement(player);
-#endif
-    if (button1)
-      snapped = SnapField(player, dx, dy);
-    else
-    {
-      if (button2)
-       dropped = DropElement(player);
+         ChangeEvent[ex][ey] = ChangeEvent[x][y];
 
-      moved = MovePlayer(player, dx, dy);
-    }
+         content_element = change->target_content.e[xx][yy];
+         target_element = GET_TARGET_ELEMENT(element, content_element, change,
+                                             ce_value, ce_score);
 
-    if (tape.single_step && tape.recording && !tape.pausing)
-    {
-      if (button1 || (dropped && !moved))
+         CreateElementFromChange(ex, ey, target_element);
+
+         something_has_changed = TRUE;
+
+         /* for symmetry reasons, freeze newly created border elements */
+         if (ex != x || ey != y)
+           Stop[ex][ey] = TRUE;        /* no more moving in this frame */
+       }
+      }
+
+      if (something_has_changed)
       {
-       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
-       SnapField(player, 0, 0);                /* stop snapping */
+       PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
       }
     }
-
-    SetPlayerWaiting(player, FALSE);
-
-#if 1
-    return player_action;
-#else
-    stored_player_action[player->index_nr] = player_action;
-#endif
   }
   else
   {
-#if 0
-    printf("::: player %d waits [%d]\n", player->index_nr, FrameCounter);
-#endif
-
-    /* no actions for this player (no input at player's configured device) */
-
-    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
-    SnapField(player, 0, 0);
-    CheckGravityMovementWhenNotMoving(player);
+    target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
+                                       ce_value, ce_score);
 
-    if (player->MovPos == 0)
-      SetPlayerWaiting(player, TRUE);
+    if (element == EL_DIAGONAL_GROWING ||
+       element == EL_DIAGONAL_SHRINKING)
+    {
+      target_element = Store[x][y];
 
-    if (player->MovPos == 0)   /* needed for tape.playing */
-      player->is_moving = FALSE;
+      Store[x][y] = EL_EMPTY;
+    }
 
-    player->is_dropping = FALSE;
+    CreateElementFromChange(x, y, target_element);
 
-    return 0;
+    PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+    PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
   }
 
-#if 0
-  if (tape.recording && num_stored_actions >= MAX_PLAYERS)
-  {
-    printf("::: player %d recorded [%d]\n", player->index_nr, FrameCounter);
+  /* this uses direct change before indirect change */
+  CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
 
-    TapeRecordAction(stored_player_action);
-    num_stored_actions = 0;
-  }
-#endif
+  return TRUE;
 }
 
-#else
-
-static void PlayerActions(struct PlayerInfo *player, byte player_action)
+static void HandleElementChange(int x, int y, int page)
 {
-  static byte stored_player_action[MAX_PLAYERS];
-  static int num_stored_actions = 0;
-  boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
-  int left     = player_action & JOY_LEFT;
-  int right    = player_action & JOY_RIGHT;
-  int up       = player_action & JOY_UP;
-  int down     = player_action & JOY_DOWN;
-  int button1  = player_action & JOY_BUTTON_1;
-  int button2  = player_action & JOY_BUTTON_2;
-  int dx       = (left ? -1    : right ? 1     : 0);
-  int dy       = (up   ? -1    : down  ? 1     : 0);
-
-  stored_player_action[player->index_nr] = 0;
-  num_stored_actions++;
+  int element = MovingOrBlocked2Element(x, y);
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  boolean handle_action_before_change = FALSE;
 
-  printf("::: player %d [%d]\n", player->index_nr, FrameCounter);
+#ifdef DEBUG
+  if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
+      !CAN_CHANGE_OR_HAS_ACTION(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
 
-  if (!player->active || tape.pausing)
+  /* this can happen with classic bombs on walkable, changing elements */
+  if (!CAN_CHANGE_OR_HAS_ACTION(element))
+  {
     return;
+  }
 
-  if (player_action)
+  if (ChangeDelay[x][y] == 0)          /* initialize element change */
   {
-    printf("::: player %d acts [%d]\n", player->index_nr, FrameCounter);
+    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
 
-    if (button1)
-      snapped = SnapField(player, dx, dy);
-    else
+    if (change->can_change)
     {
-      if (button2)
-       dropped = DropElement(player);
+      /* !!! not clear why graphic animation should be reset at all here !!! */
+      /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
+      /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */
 
-      moved = MovePlayer(player, dx, dy);
-    }
+      /*
+       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.)
+      */
 
-    if (tape.single_step && tape.recording && !tape.pausing)
-    {
-      if (button1 || (dropped && !moved))
+      /* 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 (game.graphics_engine_version < 4 &&
+         !IS_MOVING(x, y))
       {
-       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
-       SnapField(player, 0, 0);                /* stop snapping */
+       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 */
+  {
+    if (change->can_change)
+    {
+      int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
 
-    stored_player_action[player->index_nr] = player_action;
+      if (IS_ANIMATED(graphic))
+       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+      if (change->change_function)
+       change->change_function(x, y);
+    }
   }
-  else
+  else                                 /* finish element change */
   {
-    printf("::: player %d waits [%d]\n", player->index_nr, FrameCounter);
+    if (ChangePage[x][y] != -1)                /* remember page from delayed change */
+    {
+      page = ChangePage[x][y];
+      ChangePage[x][y] = -1;
 
-    /* no actions for this player (no input at player's configured device) */
+      change = &ei->change_page[page];
+    }
 
-    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
-    SnapField(player, 0, 0);
-    CheckGravityMovementWhenNotMoving(player);
+    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 */
 
-    if (player->MovPos == 0)
-      InitPlayerGfxAnimation(player, ACTION_DEFAULT, player->MovDir);
+      return;
+    }
 
-    if (player->MovPos == 0)   /* needed for tape.playing */
-      player->is_moving = FALSE;
-  }
+    /* 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 (tape.recording && num_stored_actions >= MAX_PLAYERS)
-  {
-    printf("::: player %d recorded [%d]\n", player->index_nr, FrameCounter);
+    if (change->has_action && handle_action_before_change)
+      ExecuteCustomElementAction(x, y, element, page);
+
+    if (change->can_change)
+    {
+      if (ChangeElement(x, y, element, page))
+      {
+       if (change->post_change_function)
+         change->post_change_function(x, y);
+      }
+    }
 
-    TapeRecordAction(stored_player_action);
-    num_stored_actions = 0;
+    if (change->has_action && !handle_action_before_change)
+      ExecuteCustomElementAction(x, y, element, page);
   }
 }
-#endif
 
-void AdvanceFrameAndPlayerCounters(int player_nr)
+static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
+                                             int trigger_element,
+                                             int trigger_event,
+                                             int trigger_player,
+                                             int trigger_side,
+                                             int trigger_page)
 {
+  boolean change_done_any = FALSE;
+  int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
   int i;
 
-  /* advance frame counters (global frame counter and time frame counter) */
-  FrameCounter++;
-  TimeFrames++;
+  if (!(trigger_events[trigger_element][trigger_event]))
+    return FALSE;
 
-  /* advance player counters (counters for move delay, move animation etc.) */
-  for (i = 0; i < MAX_PLAYERS; i++)
+  RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
-    boolean advance_player_counters = (player_nr == -1 || player_nr == i);
-    int move_frames =
-      MOVE_DELAY_NORMAL_SPEED /  stored_player[i].move_delay_value;
+    int element = EL_CUSTOM_START + i;
+    boolean change_done = FALSE;
+    int p;
 
-    if (!advance_player_counters)      /* not all players may be affected */
+    if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+       !HAS_ANY_CHANGE_EVENT(element, trigger_event))
       continue;
 
-    stored_player[i].Frame += move_frames;
-
-    if (stored_player[i].MovPos != 0)
-      stored_player[i].StepFrame += move_frames;
+    for (p = 0; p < element_info[element].num_change_pages; p++)
+    {
+      struct ElementChangeInfo *change = &element_info[element].change_page[p];
 
-#if USE_NEW_MOVE_DELAY
-    if (stored_player[i].move_delay > 0)
-      stored_player[i].move_delay--;
-#endif
+      if (change->can_change_or_has_action &&
+         change->has_event[trigger_event] &&
+         change->trigger_side & trigger_side &&
+         change->trigger_player & trigger_player &&
+         change->trigger_page & trigger_page_bits &&
+         IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
+      {
+       change->actual_trigger_element = trigger_element;
+       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);
 
-#if USE_NEW_PUSH_DELAY
-    /* due to bugs in previous versions, counter must count up, not down */
-    if (stored_player[i].push_delay != -1)
-      stored_player[i].push_delay++;
-#endif
+       if ((change->can_change && !change_done) || change->has_action)
+       {
+         int x, y;
 
-    if (stored_player[i].drop_delay > 0)
-      stored_player[i].drop_delay--;
+         SCAN_PLAYFIELD(x, y)
+         {
+           if (Feld[x][y] == element)
+           {
+             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);
+             }
+             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);
+             }
+           }
+         }
+
+         if (change->can_change)
+         {
+           change_done = TRUE;
+           change_done_any = TRUE;
+         }
+       }
+      }
+    }
   }
+
+  RECURSION_LOOP_DETECTION_END();
+
+  return change_done_any;
 }
 
-void GameActions()
+static boolean CheckElementChangeExt(int x, int y,
+                                    int element,
+                                    int trigger_element,
+                                    int trigger_event,
+                                    int trigger_player,
+                                    int trigger_side)
 {
-  static unsigned long game_frame_delay = 0;
-  unsigned long game_frame_delay_value;
-  int magic_wall_x = 0, magic_wall_y = 0;
-  int i, x, y, element, graphic;
-  byte *recorded_player_action;
-  byte summarized_player_action = 0;
-#if 1
-  byte tape_action[MAX_PLAYERS];
-#endif
+  boolean change_done = FALSE;
+  int p;
 
-  if (game_status != GAME_MODE_PLAYING)
-    return;
+  if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+      !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+    return FALSE;
 
-  game_frame_delay_value =
-    (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
+  if (Feld[x][y] == EL_BLOCKED)
+  {
+    Blocked2Moving(x, y, &x, &y);
+    element = Feld[x][y];
+  }
 
-  if (tape.playing && tape.warp_forward && !tape.pausing)
-    game_frame_delay_value = 0;
+  /* 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) ||
 
-  /* ---------- main game synchronization point ---------- */
+      (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
+       (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
+       ChangePage[x][y] != -1)))
+    return FALSE;
 
-  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+  RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
 
-  if (network_playing && !network_player_action_received)
+  for (p = 0; p < element_info[element].num_change_pages; p++)
   {
-    /*
-#ifdef DEBUG
-    printf("DEBUG: try to get network player actions in time\n");
-#endif
-    */
-
-#if defined(NETWORK_AVALIABLE)
-    /* last chance to get network player actions without main loop delay */
-    HandleNetworking();
-#endif
+    struct ElementChangeInfo *change = &element_info[element].change_page[p];
 
-    if (game_status != GAME_MODE_PLAYING)
-      return;
+    /* check trigger element for all events where the element that is checked
+       for changing interacts with a directly adjacent element -- this is
+       different to element changes that affect other elements to change on the
+       whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
+    boolean check_trigger_element =
+      (trigger_event == CE_TOUCHING_X ||
+       trigger_event == CE_HITTING_X ||
+       trigger_event == CE_HIT_BY_X ||
+       trigger_event == CE_DIGGING_X); /* this one was forgotten until 3.2.3 */
 
-    if (!network_player_action_received)
+    if (change->can_change_or_has_action &&
+       change->has_event[trigger_event] &&
+       change->trigger_side & trigger_side &&
+       change->trigger_player & trigger_player &&
+       (!check_trigger_element ||
+        IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
     {
-      /*
-#ifdef DEBUG
-      printf("DEBUG: failed to get network player actions in time\n");
-#endif
-      */
-      return;
-    }
-  }
-
-  if (tape.pausing)
-    return;
+      change->actual_trigger_element = trigger_element;
+      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);
 
-#if 0
-  printf("::: getting new tape action [%d]\n", FrameCounter);
-#endif
-
-  recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+      /* special case: trigger element not at (x,y) position for some events */
+      if (check_trigger_element)
+      {
+       static struct
+       {
+         int dx, dy;
+       } move_xy[] =
+         {
+           {  0,  0 },
+           { -1,  0 },
+           { +1,  0 },
+           {  0,  0 },
+           {  0, -1 },
+           {  0,  0 }, { 0, 0 }, { 0, 0 },
+           {  0, +1 }
+         };
 
-#if 1
-  /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
-  if (recorded_player_action == NULL && tape.pausing)
-    return;
-#endif
+       int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
+       int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
 
-#if 0
-  printf("::: %d\n", stored_player[0].action);
-#endif
+       change->actual_trigger_ce_value = CustomValue[xx][yy];
+       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+      }
 
-#if 0
-  if (recorded_player_action != NULL)
-    for (i = 0; i < MAX_PLAYERS; i++)
-      stored_player[i].action = recorded_player_action[i];
-#endif
+      if (change->can_change && !change_done)
+      {
+       ChangeDelay[x][y] = 1;
+       ChangeEvent[x][y] = trigger_event;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    summarized_player_action |= stored_player[i].action;
+       HandleElementChange(x, y, p);
 
-    if (!network_playing)
-      stored_player[i].effective_action = stored_player[i].action;
+       change_done = TRUE;
+      }
+      else if (change->has_action)
+      {
+       ExecuteCustomElementAction(x, y, element, p);
+       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+      }
+    }
   }
 
-#if defined(NETWORK_AVALIABLE)
-  if (network_playing)
-    SendToServer_MovePlayer(summarized_player_action);
-#endif
+  RECURSION_LOOP_DETECTION_END();
 
-  if (!options.network && !setup.team_mode)
-    local_player->effective_action = summarized_player_action;
+  return change_done;
+}
 
-#if 1
-  if (recorded_player_action != NULL)
-    for (i = 0; i < MAX_PLAYERS; i++)
-      stored_player[i].effective_action = recorded_player_action[i];
-#endif
+static void PlayPlayerSound(struct PlayerInfo *player)
+{
+  int jx = player->jx, jy = player->jy;
+  int sound_element = player->artwork_element;
+  int last_action = player->last_action_waiting;
+  int action = player->action_waiting;
 
-#if 1
-  for (i = 0; i < MAX_PLAYERS; i++)
+  if (player->is_waiting)
   {
-    tape_action[i] = stored_player[i].effective_action;
+    if (action != last_action)
+      PlayLevelSoundElementAction(jx, jy, sound_element, action);
+    else
+      PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
+  }
+  else
+  {
+    if (action != last_action)
+      StopSound(element_info[sound_element].sound[last_action]);
 
-    if (tape.recording && tape_action[i] && !tape.player_participates[i])
-      tape.player_participates[i] = TRUE;    /* player just appeared from CE */
+    if (last_action == ACTION_SLEEPING)
+      PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
   }
+}
 
-  /* only save actions from input devices, but not programmed actions */
-  if (tape.recording)
-    TapeRecordAction(tape_action);
-#endif
+static void PlayAllPlayersSound()
+{
+  int i;
 
   for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    int actual_player_action = stored_player[i].effective_action;
-
-#if 1
-    /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
-       - rnd_equinox_tetrachloride 048
-       - rnd_equinox_tetrachloride_ii 096
-       - rnd_emanuel_schmieg 002
-       - doctor_sloan_ww 001, 020
-    */
-    if (stored_player[i].MovPos == 0)
-      CheckGravityMovement(&stored_player[i]);
-#endif
+    if (stored_player[i].active)
+      PlayPlayerSound(&stored_player[i]);
+}
 
-#if 1
-    /* overwrite programmed action with tape action */
-    if (stored_player[i].programmed_action)
-      actual_player_action = stored_player[i].programmed_action;
-#endif
+static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
+{
+  boolean last_waiting = player->is_waiting;
+  int move_dir = player->MovDir;
 
-#if 0
-    if (stored_player[i].programmed_action)
-      printf("::: %d\n", stored_player[i].programmed_action);
-#endif
+  player->dir_waiting = move_dir;
+  player->last_action_waiting = player->action_waiting;
 
-    if (recorded_player_action)
+  if (is_waiting)
+  {
+    if (!last_waiting)         /* not waiting -> waiting */
     {
-#if 0
-      if (stored_player[i].programmed_action &&
-         stored_player[i].programmed_action != recorded_player_action[i])
-       printf("::: %d: %d <-> %d\n", i,
-              stored_player[i].programmed_action, recorded_player_action[i]);
-#endif
+      player->is_waiting = TRUE;
 
-#if 0
-      actual_player_action = recorded_player_action[i];
-#endif
+      player->frame_counter_bored =
+       FrameCounter +
+       game.player_boring_delay_fixed +
+       GetSimpleRandom(game.player_boring_delay_random);
+      player->frame_counter_sleeping =
+       FrameCounter +
+       game.player_sleeping_delay_fixed +
+       GetSimpleRandom(game.player_sleeping_delay_random);
+
+      InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
     }
 
-#if 0
-    /* overwrite tape action with programmed action */
-    if (stored_player[i].programmed_action)
-      actual_player_action = stored_player[i].programmed_action;
-#endif
+    if (game.player_sleeping_delay_fixed +
+       game.player_sleeping_delay_random > 0 &&
+       player->anim_delay_counter == 0 &&
+       player->post_delay_counter == 0 &&
+       FrameCounter >= player->frame_counter_sleeping)
+      player->is_sleeping = TRUE;
+    else if (game.player_boring_delay_fixed +
+            game.player_boring_delay_random > 0 &&
+            FrameCounter >= player->frame_counter_bored)
+      player->is_bored = TRUE;
 
-#if 0
-    if (i == 0)
-      printf("::: action: %d: %x [%d]\n",
-            stored_player[i].MovPos, actual_player_action, FrameCounter);
-#endif
+    player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
+                             player->is_bored ? ACTION_BORING :
+                             ACTION_WAITING);
 
-#if 1
-    PlayerActions(&stored_player[i], actual_player_action);
-#else
-    tape_action[i] = PlayerActions(&stored_player[i], actual_player_action);
+    if (player->is_sleeping && player->use_murphy)
+    {
+      /* special case for sleeping Murphy when leaning against non-free tile */
 
-    if (tape.recording && tape_action[i] && !tape.player_participates[i])
-      tape.player_participates[i] = TRUE;    /* player just appeared from CE */
-#endif
+      if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
+         (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
+          !IS_MOVING(player->jx - 1, player->jy)))
+       move_dir = MV_LEFT;
+      else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
+              (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
+               !IS_MOVING(player->jx + 1, player->jy)))
+       move_dir = MV_RIGHT;
+      else
+       player->is_sleeping = FALSE;
 
-    ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
-  }
+      player->dir_waiting = move_dir;
+    }
 
-#if 0
-  if (tape.recording)
-    TapeRecordAction(tape_action);
-#endif
+    if (player->is_sleeping)
+    {
+      if (player->num_special_action_sleeping > 0)
+      {
+       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
+       {
+         int last_special_action = player->special_action_sleeping;
+         int num_special_action = player->num_special_action_sleeping;
+         int special_action =
+           (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
+            last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
+            last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
+            last_special_action + 1 : ACTION_SLEEPING);
+         int special_graphic =
+           el_act_dir2img(player->artwork_element, special_action, move_dir);
 
-  network_player_action_received = FALSE;
+         player->anim_delay_counter =
+           graphic_info[special_graphic].anim_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
+         player->post_delay_counter =
+           graphic_info[special_graphic].post_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
 
-  ScrollScreen(NULL, SCROLL_GO_ON);
+         player->special_action_sleeping = special_action;
+       }
 
-#if 0
-  FrameCounter++;
-  TimeFrames++;
+       if (player->anim_delay_counter > 0)
+       {
+         player->action_waiting = player->special_action_sleeping;
+         player->anim_delay_counter--;
+       }
+       else if (player->post_delay_counter > 0)
+       {
+         player->post_delay_counter--;
+       }
+      }
+    }
+    else if (player->is_bored)
+    {
+      if (player->num_special_action_bored > 0)
+      {
+       if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
+       {
+         int special_action =
+           ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
+         int special_graphic =
+           el_act_dir2img(player->artwork_element, special_action, move_dir);
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    stored_player[i].Frame++;
-#endif
+         player->anim_delay_counter =
+           graphic_info[special_graphic].anim_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
+         player->post_delay_counter =
+           graphic_info[special_graphic].post_delay_fixed +
+           GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
 
-#if 1
-  /* for backwards compatibility, the following code emulates a fixed bug that
-     occured when pushing elements (causing elements that just made their last
-     pushing step to already (if possible) make their first falling step in the
-     same game frame, which is bad); this code is also needed to use the famous
-     "spring push bug" which is used in older levels and might be wanted to be
-     used also in newer levels, but in this case the buggy pushing code is only
-     affecting the "spring" element and no other elements */
-
-#if 1
-  if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
-#else
-  if (game.engine_version < VERSION_IDENT(2,2,0,7))
-#endif
-  {
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      struct PlayerInfo *player = &stored_player[i];
-      int x = player->jx;
-      int y = player->jy;
-
-#if 1
-      if (player->active && player->is_pushing && player->is_moving &&
-         IS_MOVING(x, y) &&
-         (game.engine_version < VERSION_IDENT(2,2,0,7) ||
-          Feld[x][y] == EL_SPRING))
-#else
-      if (player->active && player->is_pushing && player->is_moving &&
-         IS_MOVING(x, y))
-#endif
-      {
-       ContinueMoving(x, y);
+         player->special_action_bored = special_action;
+       }
 
-       /* continue moving after pushing (this is actually a bug) */
-       if (!IS_MOVING(x, y))
+       if (player->anim_delay_counter > 0)
        {
-         Stop[x][y] = FALSE;
+         player->action_waiting = player->special_action_bored;
+         player->anim_delay_counter--;
+       }
+       else if (player->post_delay_counter > 0)
+       {
+         player->post_delay_counter--;
        }
       }
     }
   }
-#endif
-
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+  else if (last_waiting)       /* waiting -> not waiting */
   {
-    Changed[x][y] = FALSE;
-    ChangeEvent[x][y] = -1;
+    player->is_waiting = FALSE;
+    player->is_bored = FALSE;
+    player->is_sleeping = FALSE;
 
-#if USE_NEW_BLOCK_STYLE
-    /* this must be handled before main playfield loop */
-    if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
-    {
-      MovDelay[x][y]--;
-      if (MovDelay[x][y] <= 0)
-       RemoveField(x, y);
-    }
-#endif
+    player->frame_counter_bored = -1;
+    player->frame_counter_sleeping = -1;
 
-#if DEBUG
-    if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
-    {
-      printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
-      printf("GameActions(): This should never happen!\n");
+    player->anim_delay_counter = 0;
+    player->post_delay_counter = 0;
 
-      ChangePage[x][y] = -1;
-    }
-#endif
+    player->dir_waiting = player->MovDir;
+    player->action_waiting = ACTION_DEFAULT;
 
-    Stop[x][y] = FALSE;
-    if (WasJustMoving[x][y] > 0)
-      WasJustMoving[x][y]--;
-    if (WasJustFalling[x][y] > 0)
-      WasJustFalling[x][y]--;
-    if (CheckCollision[x][y] > 0)
-      CheckCollision[x][y]--;
+    player->special_action_bored = ACTION_DEFAULT;
+    player->special_action_sleeping = ACTION_DEFAULT;
+  }
+}
 
-    GfxFrame[x][y]++;
+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;
 
-#if 1
-    /* reset finished pushing action (not done in ContinueMoving() to allow
-       continous pushing animation for elements with zero push delay) */
-    if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
+    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)
     {
-      ResetGfxAnimation(x, y);
-      DrawLevelField(x, y);
+      TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+      SnapField(player, 0, 0);                 /* stop snapping */
     }
-#endif
+  }
 
-#if DEBUG
-    if (IS_BLOCKED(x, y))
+  CheckSaveEngineSnapshot(player);
+}
+
+static byte PlayerActions(struct PlayerInfo *player, byte player_action)
+{
+  int left     = player_action & JOY_LEFT;
+  int right    = player_action & JOY_RIGHT;
+  int up       = player_action & JOY_UP;
+  int down     = player_action & JOY_DOWN;
+  int button1  = player_action & JOY_BUTTON_1;
+  int button2  = player_action & JOY_BUTTON_2;
+  int dx       = (left ? -1 : right ? 1 : 0);
+  int dy       = (up   ? -1 : down  ? 1 : 0);
+
+  if (!player->active || tape.pausing)
+    return 0;
+
+  if (player_action)
+  {
+    if (button1)
+      SnapField(player, dx, dy);
+    else
     {
-      int oldx, oldy;
+      if (button2)
+       DropElement(player);
 
-      Blocked2Moving(x, y, &oldx, &oldy);
-      if (!IS_MOVING(oldx, oldy))
-      {
-       printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
-       printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
-       printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
-       printf("GameActions(): This should never happen!\n");
-      }
+      MovePlayer(player, dx, dy);
     }
-#endif
-  }
 
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+    CheckSingleStepMode(player);
+
+    SetPlayerWaiting(player, FALSE);
+
+    return player_action;
+  }
+  else
   {
-    element = Feld[x][y];
-#if 1
-    graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-#else
-    graphic = el2img(element);
-#endif
+    /* no actions for this player (no input at player's configured device) */
 
-#if 0
-    if (element == -1)
-    {
-      printf("::: %d,%d: %d [%d]\n", x, y, element, FrameCounter);
+    DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
+    SnapField(player, 0, 0);
+    CheckGravityMovementWhenNotMoving(player);
 
-      element = graphic = 0;
-    }
-#endif
+    if (player->MovPos == 0)
+      SetPlayerWaiting(player, TRUE);
 
-    if (graphic_info[graphic].anim_global_sync)
-      GfxFrame[x][y] = FrameCounter;
+    if (player->MovPos == 0)   /* needed for tape.playing */
+      player->is_moving = FALSE;
 
-    if (ANIM_MODE(graphic) == ANIM_RANDOM &&
-       IS_NEXT_FRAME(GfxFrame[x][y], graphic))
-      ResetRandomAnimationValue(x, y);
+    player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
+    player->drop_pressed_delay = 0;
 
-    SetRandomAnimationValue(x, y);
+    CheckSingleStepMode(player);
 
-#if 1
-    PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
-#endif
+    return 0;
+  }
+}
 
-    if (IS_INACTIVE(element))
+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 */
     {
-      if (IS_ANIMATED(graphic))
-       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+      PlayerWins(local_player);
 
-      continue;
+      AllPlayersGone = TRUE;
+
+      level.native_em_level->lev->home = -1;
     }
 
-#if 1
-    /* this may take place after moving, so 'element' may have changed */
-#if 0
-    if (IS_CHANGING(x, y))
-#else
-    if (IS_CHANGING(x, y) &&
-       (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
-#endif
+    if (level.native_em_level->ply[0]->alive == 0 &&
+       level.native_em_level->ply[1]->alive == 0 &&
+       level.native_em_level->ply[2]->alive == 0 &&
+       level.native_em_level->ply[3]->alive == 0)      /* all dead */
+      AllPlayersGone = TRUE;
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    if (game_sp.LevelSolved &&
+       !game_sp.GameOver)                              /* game won */
     {
-#if 0
-      ChangeElement(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] :
-                   element_info[element].event_page_nr[CE_DELAY]);
-#else
-      ChangeElement(x, y, element_info[element].event_page_nr[CE_DELAY]);
-#endif
+      PlayerWins(local_player);
 
-      element = Feld[x][y];
-      graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+      game_sp.GameOver = TRUE;
+
+      AllPlayersGone = TRUE;
     }
-#endif
 
-    if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
+    if (game_sp.GameOver)                              /* game lost */
+      AllPlayersGone = TRUE;
+  }
+
+  if (TimeFrames >= FRAMES_PER_SECOND)
+  {
+    TimeFrames = 0;
+    TapeTime++;
+
+    for (i = 0; i < MAX_PLAYERS; i++)
     {
-      StartMoving(x, y);
+      struct PlayerInfo *player = &stored_player[i];
 
-#if 1
-      element = Feld[x][y];
-      graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
-#if 0
-      if (element == EL_MOLE)
-       printf("::: %d, %d, %d [%d]\n",
-              IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y],
-              GfxAction[x][y]);
-#endif
-#if 0
-      if (element == EL_YAMYAM)
-       printf("::: %d, %d, %d\n",
-              IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y]);
-#endif
-#endif
+      if (SHIELD_ON(player))
+      {
+       player->shield_normal_time_left--;
 
-      if (IS_ANIMATED(graphic) &&
-         !IS_MOVING(x, y) &&
-         !Stop[x][y])
+       if (player->shield_deadly_time_left > 0)
+         player->shield_deadly_time_left--;
+      }
+    }
+
+    if (!local_player->LevelSolved && !level.use_step_counter)
+    {
+      TimePlayed++;
+
+      if (TimeLeft > 0)
       {
-       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+       TimeLeft--;
 
-#if 0
-       if (element == EL_BUG)
-         printf("::: %d, %d\n", graphic, GfxFrame[x][y]);
-#endif
+       if (TimeLeft <= 10 && setup.time_limit)
+         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
 
-#if 0
-       if (element == EL_MOLE)
-         printf("::: %d, %d\n", graphic, GfxFrame[x][y]);
-#endif
+       /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
+          is reset from other values in UpdateGameDoorValues() -- FIX THIS */
+
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+       if (!TimeLeft && setup.time_limit)
+       {
+         if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+           level.native_em_level->lev->killed_out_of_time = TRUE;
+         else
+           for (i = 0; i < MAX_PLAYERS; i++)
+             KillPlayer(&stored_player[i]);
+       }
+      }
+      else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
+      {
+       game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
       }
 
-      if (IS_GEM(element) || element == EL_SP_INFOTRON)
-       EdelsteinFunkeln(x, y);
+      level.native_em_level->lev->time =
+       (game.no_time_limit ? TimePlayed : TimeLeft);
     }
-    else if ((element == EL_ACID ||
-             element == EL_EXIT_OPEN ||
-             element == EL_SP_EXIT_OPEN ||
-             element == EL_SP_TERMINAL ||
-             element == EL_SP_TERMINAL_ACTIVE ||
-             element == EL_EXTRA_TIME ||
-             element == EL_SHIELD_NORMAL ||
-             element == EL_SHIELD_DEADLY) &&
-            IS_ANIMATED(graphic))
-      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
-    else if (IS_MOVING(x, y))
-      ContinueMoving(x, y);
-    else if (IS_ACTIVE_BOMB(element))
-      CheckDynamite(x, y);
-#if 0
-    else if (element == EL_EXPLOSION && !game.explosions_delayed)
-      Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
-#endif
-    else if (element == EL_AMOEBA_GROWING)
-      AmoebeWaechst(x, y);
-    else if (element == EL_AMOEBA_SHRINKING)
-      AmoebaDisappearing(x, y);
 
-#if !USE_NEW_AMOEBA_CODE
-    else if (IS_AMOEBALIVE(element))
-      AmoebeAbleger(x, y);
-#endif
+    if (tape.recording || tape.playing)
+      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+  }
 
-    else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
-      Life(x, y);
-    else if (element == EL_EXIT_CLOSED)
-      CheckExit(x, y);
-    else if (element == EL_SP_EXIT_CLOSED)
-      CheckExitSP(x, y);
-    else if (element == EL_EXPANDABLE_WALL_GROWING)
-      MauerWaechst(x, y);
-    else if (element == EL_EXPANDABLE_WALL ||
-            element == EL_EXPANDABLE_WALL_HORIZONTAL ||
-            element == EL_EXPANDABLE_WALL_VERTICAL ||
-            element == EL_EXPANDABLE_WALL_ANY)
-      MauerAbleger(x, y);
-    else if (element == EL_FLAMES)
-      CheckForDragon(x, y);
-#if 0
-    else if (IS_AUTO_CHANGING(element))
-      ChangeElement(x, y);
-#endif
-    else if (element == EL_EXPLOSION)
-      ;        /* drawing of correct explosion animation is handled separately */
-    else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
-      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+  if (tape.recording || tape.playing)
+    DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
 
-#if 0
-    /* this may take place after moving, so 'element' may have changed */
-    if (IS_AUTO_CHANGING(Feld[x][y]))
-      ChangeElement(x, y);
-#endif
+  UpdateAndDisplayGameControlValues();
+}
 
-    if (IS_BELT_ACTIVE(element))
-      PlayLevelSoundAction(x, y, ACTION_ACTIVE);
+void AdvanceFrameAndPlayerCounters(int player_nr)
+{
+  int i;
 
-    if (game.magic_wall_active)
+  /* advance frame counters (global frame counter and time frame counter) */
+  FrameCounter++;
+  TimeFrames++;
+
+  /* advance player counters (counters for move delay, move animation etc.) */
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    boolean advance_player_counters = (player_nr == -1 || player_nr == i);
+    int move_delay_value = stored_player[i].move_delay_value;
+    int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
+
+    if (!advance_player_counters)      /* not all players may be affected */
+      continue;
+
+    if (move_frames == 0)      /* less than one move per game frame */
     {
-      int jx = local_player->jx, jy = local_player->jy;
+      int stepsize = TILEX / move_delay_value;
+      int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
+      int count = (stored_player[i].is_moving ?
+                  ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
 
-      /* play the element sound at the position nearest to the player */
-      if ((element == EL_MAGIC_WALL_FULL ||
-          element == EL_MAGIC_WALL_ACTIVE ||
-          element == EL_MAGIC_WALL_EMPTYING ||
-          element == EL_BD_MAGIC_WALL_FULL ||
-          element == EL_BD_MAGIC_WALL_ACTIVE ||
-          element == EL_BD_MAGIC_WALL_EMPTYING) &&
-         ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
-      {
-       magic_wall_x = x;
-       magic_wall_y = y;
-      }
+      if (count % delay == 0)
+       move_frames = 1;
     }
+
+    stored_player[i].Frame += move_frames;
+
+    if (stored_player[i].MovPos != 0)
+      stored_player[i].StepFrame += move_frames;
+
+    if (stored_player[i].move_delay > 0)
+      stored_player[i].move_delay--;
+
+    /* due to bugs in previous versions, counter must count up, not down */
+    if (stored_player[i].push_delay != -1)
+      stored_player[i].push_delay++;
+
+    if (stored_player[i].drop_delay > 0)
+      stored_player[i].drop_delay--;
+
+    if (stored_player[i].is_dropping_pressed)
+      stored_player[i].drop_pressed_delay++;
   }
+}
 
-#if USE_NEW_AMOEBA_CODE
-  /* new experimental amoeba growth stuff */
-#if 1
-  if (!(FrameCounter % 8))
-#endif
-  {
-    static unsigned long random = 1684108901;
+void StartGameActions(boolean init_network_game, boolean record_tape,
+                     int random_seed)
+{
+  unsigned int new_random_seed = InitRND(random_seed);
 
-    for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
-    {
-#if 0
-      x = (random >> 10) % lev_fieldx;
-      y = (random >> 20) % lev_fieldy;
-#else
-      x = RND(lev_fieldx);
-      y = RND(lev_fieldy);
-#endif
-      element = Feld[x][y];
+  if (record_tape)
+    TapeStartRecording(new_random_seed);
 
-#if 1
-      if (!IS_PLAYER(x,y) &&
-         (element == EL_EMPTY ||
-          CAN_GROW_INTO(element) ||
-          element == EL_QUICKSAND_EMPTY ||
-          element == EL_ACID_SPLASH_LEFT ||
-          element == EL_ACID_SPLASH_RIGHT))
-      {
-       if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
-           (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
-           (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
-           (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
-         Feld[x][y] = EL_AMOEBA_DROP;
-      }
-#else
-      /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
-      if (!IS_PLAYER(x,y) &&
-         (element == EL_EMPTY ||
-          element == EL_SAND ||
-          element == EL_QUICKSAND_EMPTY ||
-          element == EL_ACID_SPLASH_LEFT ||
-          element == EL_ACID_SPLASH_RIGHT))
-      {
-       if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
-           (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
-           (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
-           (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
-         Feld[x][y] = EL_AMOEBA_DROP;
-      }
-#endif
+#if defined(NETWORK_AVALIABLE)
+  if (init_network_game)
+  {
+    SendToServer_StartPlaying();
 
-      random = random * 129 + 1;
-    }
+    return;
   }
 #endif
 
+  InitGame();
+}
+
+void GameActionsExt()
+{
 #if 0
-  if (game.explosions_delayed)
+  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];
+  int i;
+
+  /* detect endless loops, caused by custom element programming */
+  if (recursion_loop_detected && recursion_loop_depth == 0)
   {
-    game.explosions_delayed = FALSE;
+    char *message = getStringCat3("Internal Error! Element ",
+                                 EL_NAME(recursion_loop_element),
+                                 " caused endless loop! Quit the game?");
 
-    for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-    {
-      element = Feld[x][y];
+    Error(ERR_WARN, "element '%s' caused endless loop in game engine",
+         EL_NAME(recursion_loop_element));
 
-      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);
+    RequestQuitGameExt(FALSE, level_editor_test_game, message);
 
-      ExplodeField[x][y] = EX_TYPE_NONE;
-    }
+    recursion_loop_detected = FALSE;   /* if game should be continued */
 
-    game.explosions_delayed = TRUE;
+    free(message);
+
+    return;
   }
 
-  if (game.magic_wall_active)
+  if (game.restart_level)
+    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 (!(game.magic_wall_time_left % 4))
+    if (level.native_em_level->lev->home == 0) /* all players at home */
     {
-      int element = Feld[magic_wall_x][magic_wall_y];
+      PlayerWins(local_player);
 
-      if (element == EL_BD_MAGIC_WALL_FULL ||
-         element == EL_BD_MAGIC_WALL_ACTIVE ||
-         element == EL_BD_MAGIC_WALL_EMPTYING)
-       PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
-      else
-       PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
+      AllPlayersGone = TRUE;
+
+      level.native_em_level->lev->home = -1;
     }
 
-    if (game.magic_wall_time_left > 0)
+    if (level.native_em_level->ply[0]->alive == 0 &&
+       level.native_em_level->ply[1]->alive == 0 &&
+       level.native_em_level->ply[2]->alive == 0 &&
+       level.native_em_level->ply[3]->alive == 0)      /* all dead */
+      AllPlayersGone = TRUE;
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    if (game_sp.LevelSolved &&
+       !game_sp.GameOver)                              /* game won */
     {
-      game.magic_wall_time_left--;
-      if (!game.magic_wall_time_left)
-      {
-       for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
-       {
-         element = Feld[x][y];
+      PlayerWins(local_player);
 
-         if (element == EL_MAGIC_WALL_ACTIVE ||
-             element == EL_MAGIC_WALL_FULL)
-         {
-           Feld[x][y] = EL_MAGIC_WALL_DEAD;
-           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);
-         }
-       }
+      game_sp.GameOver = TRUE;
 
-       game.magic_wall_active = FALSE;
-      }
+      AllPlayersGone = TRUE;
     }
+
+    if (game_sp.GameOver)                              /* game lost */
+      AllPlayersGone = TRUE;
   }
 
-  if (game.light_time_left > 0)
-  {
-    game.light_time_left--;
+  if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
+    GameWon();
 
-    if (game.light_time_left == 0)
-      RedrawAllLightSwitchesAndInvisibleElements();
-  }
+  if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
+    TapeStop();
 
-  if (game.timegate_time_left > 0)
-  {
-    game.timegate_time_left--;
+  if (game_status != GAME_MODE_PLAYING)                /* status might have changed */
+    return;
 
-    if (game.timegate_time_left == 0)
-      CloseAllOpenTimegates();
-  }
+  game_frame_delay_value =
+    (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    struct PlayerInfo *player = &stored_player[i];
+  if (tape.playing && tape.warp_forward && !tape.pausing)
+    game_frame_delay_value = 0;
 
-    if (SHIELD_ON(player))
-    {
-      if (player->shield_deadly_time_left)
-       PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
-      else if (player->shield_normal_time_left)
-       PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
-    }
-  }
+  SetVideoFrameDelay(game_frame_delay_value);
 
-  if (TimeFrames >= FRAMES_PER_SECOND)
-  {
-    TimeFrames = 0;
-    TapeTime++;
+#if 0
+#if 0
+  /* ---------- main game synchronization point ---------- */
 
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      struct PlayerInfo *player = &stored_player[i];
+  int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
 
-      if (SHIELD_ON(player))
-      {
-       player->shield_normal_time_left--;
+  printf("::: skip == %d\n", skip);
 
-       if (player->shield_deadly_time_left > 0)
-         player->shield_deadly_time_left--;
-      }
-    }
+#else
+  /* ---------- main game synchronization point ---------- */
 
-    if (!level.use_step_counter)
-    {
-      TimePlayed++;
+  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+#endif
+#endif
 
-      if (TimeLeft > 0)
-      {
-       TimeLeft--;
+  if (network_playing && !network_player_action_received)
+  {
+    /* try to get network player actions in time */
 
-       if (TimeLeft <= 10 && setup.time_limit)
-         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+#if defined(NETWORK_AVALIABLE)
+    /* last chance to get network player actions without main loop delay */
+    HandleNetworking();
+#endif
 
-       DrawGameValue_Time(TimeLeft);
+    /* game was quit by network peer */
+    if (game_status != GAME_MODE_PLAYING)
+      return;
 
-       if (!TimeLeft && setup.time_limit)
-         for (i = 0; i < MAX_PLAYERS; i++)
-           KillHero(&stored_player[i]);
-      }
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
-       DrawGameValue_Time(TimePlayed);
-    }
+    if (!network_player_action_received)
+      return;          /* failed to get network player actions in time */
 
-    if (tape.recording || tape.playing)
-      DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+    /* do not yet reset "network_player_action_received" (for tape.pausing) */
   }
 
-  DrawAllPlayers();
-  PlayAllPlayersSound();
+  if (tape.pausing)
+    return;
 
-  if (options.debug)                   /* calculate frames per second */
-  {
-    static unsigned long fps_counter = 0;
-    static int fps_frames = 0;
-    unsigned long fps_delay_ms = Counter() - fps_counter;
+  /* at this point we know that we really continue executing the game */
 
-    fps_frames++;
+  network_player_action_received = FALSE;
 
-    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
-    {
-      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
+  /* when playing tape, read previously recorded player input from tape data */
+  recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
 
-      fps_frames = 0;
-      fps_counter = Counter();
-    }
+  /* TapePlayAction() may return NULL when toggling to "pause before death" */
+  if (tape.pausing)
+    return;
 
-    redraw_mask |= REDRAW_FPS;
+  if (tape.set_centered_player)
+  {
+    game.centered_player_nr_next = tape.centered_player_nr_next;
+    game.set_centered_player = TRUE;
   }
 
-#if 0
-  if (stored_player[0].jx != stored_player[0].last_jx ||
-      stored_player[0].jy != stored_player[0].last_jy)
-    printf("::: %d, %d, %d, %d, %d\n",
-          stored_player[0].MovDir,
-          stored_player[0].MovPos,
-          stored_player[0].GfxPos,
-          stored_player[0].Frame,
-          stored_player[0].StepFrame);
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    summarized_player_action |= stored_player[i].action;
+
+    if (!network_playing && (game.team_mode || tape.playing))
+      stored_player[i].effective_action = stored_player[i].action;
+  }
+
+#if defined(NETWORK_AVALIABLE)
+  if (network_playing)
+    SendToServer_MovePlayer(summarized_player_action);
 #endif
 
-#if USE_NEW_MOVE_DELAY
-  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
-#else
-  FrameCounter++;
-  TimeFrames++;
+  // 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++)
   {
-    int move_frames =
-      MOVE_DELAY_NORMAL_SPEED /  stored_player[i].move_delay_value;
+    tape_action[i] = stored_player[i].effective_action;
 
-    stored_player[i].Frame += move_frames;
+    /* (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;
+  }
 
-    if (stored_player[i].MovPos != 0)
-      stored_player[i].StepFrame += move_frames;
+  /* only record actions from input devices, but not programmed actions */
+  if (tape.recording)
+    TapeRecordAction(tape_action);
 
-#if USE_NEW_MOVE_DELAY
-    if (stored_player[i].move_delay > 0)
-      stored_player[i].move_delay--;
-#endif
+#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 (stored_player[i].drop_delay > 0)
-      stored_player[i].drop_delay--;
-  }
+#if DEBUG_PLAYER_ACTIONS
+    printf(":::");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
 #endif
 
-#if 1
-  if (local_player->show_envelope != 0 && local_player->MovPos == 0)
-  {
-    ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
+    for (i = 0; i < MAX_PLAYERS; i++)
+      mapped_action[i] = stored_player[map_player_action[i]].effective_action;
 
-    local_player->show_envelope = 0;
+    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
-
-#if USE_NEW_RANDOMIZE
-  /* use random number generator in every frame to make it less predictable */
-  if (game.engine_version >= VERSION_IDENT(3,1,1,0))
-    RND(1);
 #endif
-}
-
-static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
-{
-  int min_x = x, min_y = y, max_x = x, max_y = y;
-  int i;
 
   for (i = 0; i < MAX_PLAYERS; i++)
   {
-    int jx = stored_player[i].jx, jy = stored_player[i].jy;
+    // 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;
 
-    if (!stored_player[i].active || &stored_player[i] == player)
-      continue;
+    // 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;
 
-    min_x = MIN(min_x, jx);
-    min_y = MIN(min_y, jy);
-    max_x = MAX(max_x, jx);
-    max_y = MAX(max_y, jy);
+    game.snapshot.last_action[i] = stored_player[i].effective_action;
   }
 
-  return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
-}
+  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();
+  }
 
-static boolean AllPlayersInVisibleScreen()
-{
-  int i;
+  BlitScreenToBitmap(backbuffer);
 
-  for (i = 0; i < MAX_PLAYERS; i++)
+  CheckLevelTime();
+
+  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+
+  if (options.debug)                   /* calculate frames per second */
   {
-    int jx = stored_player[i].jx, jy = stored_player[i].jy;
+    static unsigned int fps_counter = 0;
+    static int fps_frames = 0;
+    unsigned int fps_delay_ms = Counter() - fps_counter;
 
-    if (!stored_player[i].active)
-      continue;
+    fps_frames++;
 
-    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
-      return FALSE;
-  }
+    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
+    {
+      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
 
-  return TRUE;
+      fps_frames = 0;
+      fps_counter = Counter();
+    }
+
+    redraw_mask |= REDRAW_FPS;
+  }
 }
 
-void ScrollLevel(int dx, int dy)
+static void GameActions_CheckSaveEngineSnapshot()
 {
-  int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
-  int x, y;
+  if (!game.snapshot.save_snapshot)
+    return;
 
-  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);
+  // clear flag for saving snapshot _before_ saving snapshot
+  game.snapshot.save_snapshot = FALSE;
 
-  if (dx)
-  {
-    x = (dx == 1 ? BX1 : BX2);
-    for (y = BY1; y <= BY2; y++)
-      DrawScreenField(x, y);
-  }
+  SaveEngineSnapshotToList();
+}
 
-  if (dy)
-  {
-    y = (dy == 1 ? BY1 : BY2);
-    for (x = BX1; x <= BX2; x++)
-      DrawScreenField(x, y);
-  }
+void GameActions()
+{
+  GameActionsExt();
 
-  redraw_mask |= REDRAW_FIELD;
+  GameActions_CheckSaveEngineSnapshot();
 }
 
-#if 0
-static boolean canEnterSupaplexPort(int x, int y, int dx, int dy)
+void GameActions_EM_Main()
 {
-  int nextx = x + dx, nexty = y + dy;
-  int element = Feld[x][y];
+  byte effective_action[MAX_PLAYERS];
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+  int i;
 
-  if ((dx == -1 &&
-       element != EL_SP_PORT_LEFT &&
-       element != EL_SP_GRAVITY_PORT_LEFT &&
-       element != EL_SP_PORT_HORIZONTAL &&
-       element != EL_SP_PORT_ANY) ||
-      (dx == +1 &&
-       element != EL_SP_PORT_RIGHT &&
-       element != EL_SP_GRAVITY_PORT_RIGHT &&
-       element != EL_SP_PORT_HORIZONTAL &&
-       element != EL_SP_PORT_ANY) ||
-      (dy == -1 &&
-       element != EL_SP_PORT_UP &&
-       element != EL_SP_GRAVITY_PORT_UP &&
-       element != EL_SP_PORT_VERTICAL &&
-       element != EL_SP_PORT_ANY) ||
-      (dy == +1 &&
-       element != EL_SP_PORT_DOWN &&
-       element != EL_SP_GRAVITY_PORT_DOWN &&
-       element != EL_SP_PORT_VERTICAL &&
-       element != EL_SP_PORT_ANY) ||
-      !IN_LEV_FIELD(nextx, nexty) ||
-      !IS_FREE(nextx, nexty))
-    return FALSE;
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
 
-  return TRUE;
+  GameActions_EM(effective_action, warp_mode);
 }
-#endif
 
-static boolean canFallDown(struct PlayerInfo *player)
+void GameActions_SP_Main()
 {
-  int jx = player->jx, jy = player->jy;
+  byte effective_action[MAX_PLAYERS];
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+  int i;
 
-  return (IN_LEV_FIELD(jx, jy + 1) &&
-         (IS_FREE(jx, jy + 1) ||
-#if USE_NEW_BLOCK_STYLE
-#if USE_GRAVITY_BUGFIX_OLD
-          Feld[jx][jy + 1] == EL_PLAYER_IS_LEAVING ||
-#endif
-#endif
-          (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
-         IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
-         !IS_WALKABLE_INSIDE(Feld[jx][jy]));
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
+
+  GameActions_SP(effective_action, warp_mode);
 }
 
-static boolean canPassField(int x, int y, int move_dir)
+void GameActions_RND_Main()
 {
-  int opposite_dir = MV_DIR_OPPOSITE(move_dir);
-  int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
-  int dy = (move_dir & MV_UP   ? -1 : move_dir & MV_DOWN  ? +1 : 0);
-  int nextx = x + dx;
-  int nexty = y + dy;
-  int element = Feld[x][y];
-
-  return (IS_PASSABLE_FROM(element, opposite_dir) &&
-         !CAN_MOVE(element) &&
-         IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
-         IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
-         (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
+  GameActions_RND();
 }
 
-static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
+void GameActions_RND()
 {
-  int opposite_dir = MV_DIR_OPPOSITE(move_dir);
-  int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
-  int dy = (move_dir & MV_UP   ? -1 : move_dir & MV_DOWN  ? +1 : 0);
-  int newx = x + dx;
-  int newy = y + dy;
-#if 0
-  int nextx = newx + dx;
-  int nexty = newy + dy;
-#endif
+  int magic_wall_x = 0, magic_wall_y = 0;
+  int i, x, y, element, graphic;
 
-#if 1
-  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
-         IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
-#if 0
-         (!IS_SP_PORT(Feld[newx][newy]) || move_dir == MV_UP) &&
-#endif
-         (IS_DIGGABLE(Feld[newx][newy]) ||
-          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
-          canPassField(newx, newy, move_dir)));
-#else
-#if 1
-  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
-         IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
-         (IS_DIGGABLE(Feld[newx][newy]) ||
-          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
-          canPassField(newx, newy, move_dir)));
-#else
-#if 1
-  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
-         (IS_DIGGABLE_WITH_GRAVITY(Feld[newx][newy]) ||
-          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
-          canPassField(newx, newy, move_dir)));
-#else
-  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
-         (IS_DIGGABLE(Feld[newx][newy]) ||
-          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
-          (IS_PASSABLE_FROM(Feld[newx][newy], opposite_dir) &&
-           !CAN_MOVE(Feld[newx][newy]) &&
-           IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
-           IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
-           (level.can_pass_to_walkable || IS_FREE(nextx, nexty)))));
-#endif
-#endif
-#endif
-}
+  InitPlayfieldScanModeVars();
 
-static void CheckGravityMovement(struct PlayerInfo *player)
-{
-  if (game.gravity && !player->programmed_action)
+  if (game.engine_version >= VERSION_IDENT(3,2,0,7))
   {
-#if 1
-    int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
-    int move_dir_vertical   = player->effective_action & MV_VERTICAL;
-#else
-    int move_dir_horizontal = player->action & MV_HORIZONTAL;
-    int move_dir_vertical   = player->action & MV_VERTICAL;
-#endif
+    SCAN_PLAYFIELD(x, y)
+    {
+      ChangeCount[x][y] = 0;
+      ChangeEvent[x][y] = -1;
+    }
+  }
 
-#if 1
-    boolean player_is_snapping = player->effective_action & JOY_BUTTON_1;
-#else
-    boolean player_is_snapping = player->action & JOY_BUTTON_1;
-#endif
+  if (game.set_centered_player)
+  {
+    boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
 
-    int jx = player->jx, jy = player->jy;
+    /* switching to "all players" only possible if all players fit to screen */
+    if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
+    {
+      game.centered_player_nr_next = game.centered_player_nr;
+      game.set_centered_player = FALSE;
+    }
 
-    boolean player_is_moving_to_valid_field =
-      (!player_is_snapping &&
-       (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
-       canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
+    /* do not switch focus to non-existing (or non-active) player */
+    if (game.centered_player_nr_next >= 0 &&
+       !stored_player[game.centered_player_nr_next].active)
+    {
+      game.centered_player_nr_next = game.centered_player_nr;
+      game.set_centered_player = FALSE;
+    }
+  }
 
-#if 0
-    int move_dir =
-      (player->last_move_dir & MV_HORIZONTAL ?
-       (move_dir_vertical ? move_dir_vertical : move_dir_horizontal) :
-       (move_dir_horizontal ? move_dir_horizontal : move_dir_vertical));
-#endif
+  if (game.set_centered_player &&
+      ScreenMovPos == 0)       /* screen currently aligned at tile position */
+  {
+    int sx, sy;
 
-#if 0
-    int opposite_dir = MV_DIR_OPPOSITE(move_dir);
-    int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
-    int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
-    int new_jx = jx + dx, new_jy = jy + dy;
-    int nextx = new_jx + dx, nexty = new_jy + dy;
-#endif
+    if (game.centered_player_nr_next == -1)
+    {
+      setScreenCenteredToAllPlayers(&sx, &sy);
+    }
+    else
+    {
+      sx = stored_player[game.centered_player_nr_next].jx;
+      sy = stored_player[game.centered_player_nr_next].jy;
+    }
 
-#if 1
+    game.centered_player_nr = game.centered_player_nr_next;
+    game.set_centered_player = FALSE;
 
-#if 1
-    boolean player_can_fall_down = canFallDown(player);
-#else
-    boolean player_can_fall_down =
-      (IN_LEV_FIELD(jx, jy + 1) &&
-       (IS_FREE(jx, jy + 1) ||
-       (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)));
-#endif
+    DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
+    DrawGameDoorValues();
+  }
 
-#else
-    boolean player_can_fall_down =
-      (IN_LEV_FIELD(jx, jy + 1) &&
-       (IS_FREE(jx, jy + 1)));
-#endif
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    int actual_player_action = stored_player[i].effective_action;
 
-#if 0
-    boolean player_is_moving_to_valid_field =
-      (
 #if 1
-       !player_is_snapping &&
-#endif
-
-#if 1
-       IN_LEV_FIELD(new_jx, new_jy) &&
-       (IS_DIGGABLE(Feld[new_jx][new_jy]) ||
-       (IS_SP_PORT(Feld[new_jx][new_jy]) &&
-        element_info[Feld[new_jx][new_jy]].access_direction & opposite_dir &&
-        IN_LEV_FIELD(nextx, nexty) &&
-        element_info[Feld[nextx][nexty]].access_direction & move_dir))
-#else
-       IN_LEV_FIELD(new_jx, new_jy) &&
-       (Feld[new_jx][new_jy] == EL_SP_BASE ||
-       Feld[new_jx][new_jy] == EL_SAND ||
-       (IS_SP_PORT(Feld[new_jx][new_jy]) &&
-        canEnterSupaplexPort(new_jx, new_jy, dx, dy)))
-    /* !!! extend EL_SAND to anything diggable !!! */
-#endif
-       );
-#endif
-
-#if 0
-    boolean player_is_standing_on_valid_field =
-      (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
-       (IS_WALKABLE(Feld[jx][jy]) && !ACCESS_FROM(Feld[jx][jy], MV_DOWN)));
+    /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
+       - rnd_equinox_tetrachloride 048
+       - rnd_equinox_tetrachloride_ii 096
+       - rnd_emanuel_schmieg 002
+       - doctor_sloan_ww 001, 020
+    */
+    if (stored_player[i].MovPos == 0)
+      CheckGravityMovement(&stored_player[i]);
 #endif
 
-#if 0
-    printf("::: checking gravity NOW [%d, %d, %d] [%d] [%d / %d] ...\n",
-          player_can_fall_down,
-          player_is_standing_on_valid_field,
-          player_is_moving_to_valid_field,
-          (player_is_moving_to_valid_field ? Feld[new_jx][new_jy] : -1),
-          player->effective_action,
-          player->can_fall_into_acid);
-#endif
+    /* overwrite programmed action with tape action */
+    if (stored_player[i].programmed_action)
+      actual_player_action = stored_player[i].programmed_action;
 
-    if (player_can_fall_down &&
-#if 0
-       !player_is_standing_on_valid_field &&
-#endif
-       !player_is_moving_to_valid_field)
-    {
-#if 0
-      printf("::: setting programmed_action to MV_DOWN [%d,%d - %d] ...\n",
-            jx, jy, FrameCounter);
-#endif
+    PlayerActions(&stored_player[i], actual_player_action);
 
-      player->programmed_action = MV_DOWN;
-    }
+    ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
   }
-}
 
-static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
-{
-#if 1
-  return CheckGravityMovement(player);
-#endif
+  ScrollScreen(NULL, SCROLL_GO_ON);
+
+  /* for backwards compatibility, the following code emulates a fixed bug that
+     occured when pushing elements (causing elements that just made their last
+     pushing step to already (if possible) make their first falling step in the
+     same game frame, which is bad); this code is also needed to use the famous
+     "spring push bug" which is used in older levels and might be wanted to be
+     used also in newer levels, but in this case the buggy pushing code is only
+     affecting the "spring" element and no other elements */
 
-  if (game.gravity && !player->programmed_action)
+  if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
   {
-    int jx = player->jx, jy = player->jy;
-    boolean field_under_player_is_free =
-      (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
-    boolean player_is_standing_on_valid_field =
-      (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
-       (IS_WALKABLE(Feld[jx][jy]) &&
-       !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
+      int x = player->jx;
+      int y = player->jy;
 
-    if (field_under_player_is_free && !player_is_standing_on_valid_field)
-      player->programmed_action = MV_DOWN;
-  }
-}
+      if (player->active && player->is_pushing && player->is_moving &&
+         IS_MOVING(x, y) &&
+         (game.engine_version < VERSION_IDENT(2,2,0,7) ||
+          Feld[x][y] == EL_SPRING))
+      {
+       ContinueMoving(x, y);
 
-/*
-  MovePlayerOneStep()
-  -----------------------------------------------------------------------------
-  dx, dy:              direction (non-diagonal) to try to move the player to
-  real_dx, real_dy:    direction as read from input device (can be diagonal)
-*/
+       /* continue moving after pushing (this is actually a bug) */
+       if (!IS_MOVING(x, y))
+         Stop[x][y] = FALSE;
+      }
+    }
+  }
 
-boolean MovePlayerOneStep(struct PlayerInfo *player,
-                         int dx, int dy, int real_dx, int real_dy)
-{
-#if 0
-  static int trigger_sides[4][2] =
+  SCAN_PLAYFIELD(x, y)
   {
-    /* enter side        leave side */
-    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* moving left  */
-    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* moving right */
-    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     },      /* moving up    */
-    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  }       /* moving down  */
-  };
-  int move_direction = (dx == -1 ? MV_LEFT :
-                       dx == +1 ? MV_RIGHT :
-                       dy == -1 ? MV_UP :
-                       dy == +1 ? MV_DOWN : MV_NO_MOVING);
-  int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
-  int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
-#endif
-  int jx = player->jx, jy = player->jy;
-  int new_jx = jx + dx, new_jy = jy + dy;
-  int element;
-  int can_move;
+    ChangeCount[x][y] = 0;
+    ChangeEvent[x][y] = -1;
 
-  if (!player->active || (!dx && !dy))
-    return MF_NO_ACTION;
+    /* this must be handled before main playfield loop */
+    if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
+    {
+      MovDelay[x][y]--;
+      if (MovDelay[x][y] <= 0)
+       RemoveField(x, y);
+    }
 
-  player->MovDir = (dx < 0 ? MV_LEFT :
-                   dx > 0 ? MV_RIGHT :
-                   dy < 0 ? MV_UP :
-                   dy > 0 ? MV_DOWN :  MV_NO_MOVING);
+    if (Feld[x][y] == EL_ELEMENT_SNAPPING)
+    {
+      MovDelay[x][y]--;
+      if (MovDelay[x][y] <= 0)
+      {
+       RemoveField(x, y);
+       TEST_DrawLevelField(x, y);
 
-  if (!IN_LEV_FIELD(new_jx, new_jy))
-    return MF_NO_ACTION;
+       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
+      }
+    }
 
-  if (!options.network && !AllPlayersInSight(player, new_jx, new_jy))
-    return MF_NO_ACTION;
+#if DEBUG
+    if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
+    {
+      printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
+      printf("GameActions(): This should never happen!\n");
 
-#if 0
-  element = MovingOrBlocked2Element(new_jx, new_jy);
-#else
-  element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
+      ChangePage[x][y] = -1;
+    }
 #endif
 
-  if (DONT_RUN_INTO(element))
-  {
-    if (element == EL_ACID && dx == 0 && dy == 1)
+    Stop[x][y] = FALSE;
+    if (WasJustMoving[x][y] > 0)
+      WasJustMoving[x][y]--;
+    if (WasJustFalling[x][y] > 0)
+      WasJustFalling[x][y]--;
+    if (CheckCollision[x][y] > 0)
+      CheckCollision[x][y]--;
+    if (CheckImpact[x][y] > 0)
+      CheckImpact[x][y]--;
+
+    GfxFrame[x][y]++;
+
+    /* reset finished pushing action (not done in ContinueMoving() to allow
+       continuous pushing animation for elements with zero push delay) */
+    if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
     {
-      SplashAcid(new_jx, new_jy);
-      Feld[jx][jy] = EL_PLAYER_1;
-      InitMovingField(jx, jy, MV_DOWN);
-      Store[jx][jy] = EL_ACID;
-      ContinueMoving(jx, jy);
-      BuryHero(player);
+      ResetGfxAnimation(x, y);
+      TEST_DrawLevelField(x, y);
     }
-    else
-      TestIfHeroRunsIntoBadThing(jx, jy, player->MovDir);
 
-    return MF_MOVING;
+#if DEBUG
+    if (IS_BLOCKED(x, y))
+    {
+      int oldx, oldy;
+
+      Blocked2Moving(x, y, &oldx, &oldy);
+      if (!IS_MOVING(oldx, oldy))
+      {
+       printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
+       printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
+       printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
+       printf("GameActions(): This should never happen!\n");
+      }
+    }
+#endif
   }
 
-  can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
-  if (can_move != MF_MOVING)
-    return can_move;
+  SCAN_PLAYFIELD(x, y)
+  {
+    element = Feld[x][y];
+    graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
 
-  /* check if DigField() has caused relocation of the player */
-  if (player->jx != jx || player->jy != jy)
-    return MF_NO_ACTION;       /* <-- !!! CHECK THIS [-> MF_ACTION ?] !!! */
+    ResetGfxFrame(x, y, TRUE);
 
-  StorePlayer[jx][jy] = 0;
-  player->last_jx = jx;
-  player->last_jy = jy;
-  player->jx = new_jx;
-  player->jy = new_jy;
-  StorePlayer[new_jx][new_jy] = player->element_nr;
+    if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+       IS_NEXT_FRAME(GfxFrame[x][y], graphic))
+      ResetRandomAnimationValue(x, y);
 
-  player->MovPos =
-    (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
+    SetRandomAnimationValue(x, y);
 
-  player->step_counter++;
+    PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
 
-#if 0
-  player->drop_delay = 0;
-#endif
+    if (IS_INACTIVE(element))
+    {
+      if (IS_ANIMATED(graphic))
+       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-  PlayerVisit[jx][jy] = FrameCounter;
+      continue;
+    }
 
-  ScrollPlayer(player, SCROLL_INIT);
+    /* this may take place after moving, so 'element' may have changed */
+    if (IS_CHANGING(x, y) &&
+       (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
+    {
+      int page = element_info[element].event_page_nr[CE_DELAY];
 
-#if 0
-  if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
-  {
-    CheckTriggeredElementChangeBySide(jx, jy, Feld[jx][jy], CE_PLAYER_LEAVES_X,
-                                     leave_side);
-    CheckElementChangeBySide(jx,jy, Feld[jx][jy],CE_LEFT_BY_PLAYER,leave_side);
-  }
+      HandleElementChange(x, y, page);
 
-  if (IS_CUSTOM_ELEMENT(Feld[new_jx][new_jy]))
-  {
-    CheckTriggeredElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
-                                     CE_PLAYER_ENTERS_X, enter_side);
-    CheckElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
-                            CE_ENTERED_BY_PLAYER, enter_side);
-  }
-#endif
+      element = Feld[x][y];
+      graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+    }
 
-  return MF_MOVING;
-}
+    if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
+    {
+      StartMoving(x, y);
 
-boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
-{
-  int jx = player->jx, jy = player->jy;
-  int old_jx = jx, old_jy = jy;
-  int moved = MF_NO_ACTION;
+      element = Feld[x][y];
+      graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
 
-#if 1
-  if (!player->active)
-    return FALSE;
+      if (IS_ANIMATED(graphic) &&
+         !IS_MOVING(x, y) &&
+         !Stop[x][y])
+       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-  if (!dx && !dy)
-  {
-    if (player->MovPos == 0)
-    {
-      player->is_moving = FALSE;
-      player->is_digging = FALSE;
-      player->is_collecting = FALSE;
-      player->is_snapping = FALSE;
-      player->is_pushing = FALSE;
+      if (IS_GEM(element) || element == EL_SP_INFOTRON)
+       TEST_DrawTwinkleOnField(x, y);
     }
+    else if ((element == EL_ACID ||
+             element == EL_EXIT_OPEN ||
+             element == EL_EM_EXIT_OPEN ||
+             element == EL_SP_EXIT_OPEN ||
+             element == EL_STEEL_EXIT_OPEN ||
+             element == EL_EM_STEEL_EXIT_OPEN ||
+             element == EL_SP_TERMINAL ||
+             element == EL_SP_TERMINAL_ACTIVE ||
+             element == EL_EXTRA_TIME ||
+             element == EL_SHIELD_NORMAL ||
+             element == EL_SHIELD_DEADLY) &&
+            IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+    else if (IS_MOVING(x, y))
+      ContinueMoving(x, y);
+    else if (IS_ACTIVE_BOMB(element))
+      CheckDynamite(x, y);
+    else if (element == EL_AMOEBA_GROWING)
+      AmoebeWaechst(x, y);
+    else if (element == EL_AMOEBA_SHRINKING)
+      AmoebaDisappearing(x, y);
 
-    return FALSE;
-  }
-#else
-  if (!player->active || (!dx && !dy))
-    return FALSE;
+#if !USE_NEW_AMOEBA_CODE
+    else if (IS_AMOEBALIVE(element))
+      AmoebeAbleger(x, y);
 #endif
 
-#if 0
-  if (!FrameReached(&player->move_delay, player->move_delay_value) &&
-      !tape.playing)
-    return FALSE;
-#else
-
-#if 1
+    else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
+      Life(x, y);
+    else if (element == EL_EXIT_CLOSED)
+      CheckExit(x, y);
+    else if (element == EL_EM_EXIT_CLOSED)
+      CheckExitEM(x, y);
+    else if (element == EL_STEEL_EXIT_CLOSED)
+      CheckExitSteel(x, y);
+    else if (element == EL_EM_STEEL_EXIT_CLOSED)
+      CheckExitSteelEM(x, y);
+    else if (element == EL_SP_EXIT_CLOSED)
+      CheckExitSP(x, y);
+    else if (element == EL_EXPANDABLE_WALL_GROWING ||
+            element == EL_EXPANDABLE_STEELWALL_GROWING)
+      MauerWaechst(x, y);
+    else if (element == EL_EXPANDABLE_WALL ||
+            element == EL_EXPANDABLE_WALL_HORIZONTAL ||
+            element == EL_EXPANDABLE_WALL_VERTICAL ||
+            element == EL_EXPANDABLE_WALL_ANY ||
+            element == EL_BD_EXPANDABLE_WALL)
+      MauerAbleger(x, y);
+    else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
+            element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
+            element == EL_EXPANDABLE_STEELWALL_ANY)
+      MauerAblegerStahl(x, y);
+    else if (element == EL_FLAMES)
+      CheckForDragon(x, y);
+    else if (element == EL_EXPLOSION)
+      ;        /* drawing of correct explosion animation is handled separately */
+    else if (element == EL_ELEMENT_SNAPPING ||
+            element == EL_DIAGONAL_SHRINKING ||
+            element == EL_DIAGONAL_GROWING)
+    {
+      graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
 
-#if 0
-  printf("::: %d <= %d < %d ?\n", player->move_delay, FrameCounter,
-        player->move_delay + player->move_delay_value);
-#endif
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+    }
+    else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
-#if USE_NEW_MOVE_DELAY
-  if (player->move_delay > 0)
-#else
-  if (!FrameReached(&player->move_delay, player->move_delay_value))
-#endif
-  {
-#if 0
-    printf("::: can NOT move\n");
-#endif
+    if (IS_BELT_ACTIVE(element))
+      PlayLevelSoundAction(x, y, ACTION_ACTIVE);
 
-    return FALSE;
+    if (game.magic_wall_active)
+    {
+      int jx = local_player->jx, jy = local_player->jy;
+
+      /* play the element sound at the position nearest to the player */
+      if ((element == EL_MAGIC_WALL_FULL ||
+          element == EL_MAGIC_WALL_ACTIVE ||
+          element == EL_MAGIC_WALL_EMPTYING ||
+          element == EL_BD_MAGIC_WALL_FULL ||
+          element == EL_BD_MAGIC_WALL_ACTIVE ||
+          element == EL_BD_MAGIC_WALL_EMPTYING ||
+          element == EL_DC_MAGIC_WALL_FULL ||
+          element == EL_DC_MAGIC_WALL_ACTIVE ||
+          element == EL_DC_MAGIC_WALL_EMPTYING) &&
+         ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
+      {
+       magic_wall_x = x;
+       magic_wall_y = y;
+      }
+    }
   }
-#else
-  if (!FrameReached(&player->move_delay, player->move_delay_value) &&
-      !(tape.playing && tape.file_version < FILE_VERSION_2_0))
-    return FALSE;
-#endif
 
-#endif
+#if USE_NEW_AMOEBA_CODE
+  /* new experimental amoeba growth stuff */
+  if (!(FrameCounter % 8))
+  {
+    static unsigned int random = 1684108901;
 
-#if 0
-  printf("::: COULD move now\n");
-#endif
+    for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
+    {
+      x = RND(lev_fieldx);
+      y = RND(lev_fieldy);
+      element = Feld[x][y];
 
-#if USE_NEW_MOVE_DELAY
-  player->move_delay = -1;             /* set to "uninitialized" value */
-#endif
+      if (!IS_PLAYER(x,y) &&
+         (element == EL_EMPTY ||
+          CAN_GROW_INTO(element) ||
+          element == EL_QUICKSAND_EMPTY ||
+          element == EL_QUICKSAND_FAST_EMPTY ||
+          element == EL_ACID_SPLASH_LEFT ||
+          element == EL_ACID_SPLASH_RIGHT))
+      {
+       if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
+           (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
+           (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
+           (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
+         Feld[x][y] = EL_AMOEBA_DROP;
+      }
 
-  /* store if player is automatically moved to next field */
-  player->is_auto_moving = (player->programmed_action != MV_NO_MOVING);
+      random = random * 129 + 1;
+    }
+  }
+#endif
 
-  /* remove the last programmed player action */
-  player->programmed_action = 0;
+  game.explosions_delayed = FALSE;
 
-  if (player->MovPos)
+  SCAN_PLAYFIELD(x, y)
   {
-    /* should only happen if pre-1.2 tape recordings are played */
-    /* this is only for backward compatibility */
+    element = Feld[x][y];
 
-    int original_move_delay_value = player->move_delay_value;
+    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);
 
-#if DEBUG
-    printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%ld]\n",
-          tape.counter);
-#endif
+    ExplodeField[x][y] = EX_TYPE_NONE;
+  }
 
-    /* scroll remaining steps with finest movement resolution */
-    player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
+  game.explosions_delayed = TRUE;
 
-    while (player->MovPos)
+  if (game.magic_wall_active)
+  {
+    if (!(game.magic_wall_time_left % 4))
     {
-      ScrollPlayer(player, SCROLL_GO_ON);
-      ScrollScreen(NULL, SCROLL_GO_ON);
+      int element = Feld[magic_wall_x][magic_wall_y];
 
-#if USE_NEW_MOVE_DELAY
-      AdvanceFrameAndPlayerCounters(player->index_nr);
-#else
-      FrameCounter++;
-#endif
+      if (element == EL_BD_MAGIC_WALL_FULL ||
+         element == EL_BD_MAGIC_WALL_ACTIVE ||
+         element == EL_BD_MAGIC_WALL_EMPTYING)
+       PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
+      else if (element == EL_DC_MAGIC_WALL_FULL ||
+              element == EL_DC_MAGIC_WALL_ACTIVE ||
+              element == EL_DC_MAGIC_WALL_EMPTYING)
+       PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
+      else
+       PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
+    }
 
-      DrawAllPlayers();
-      BackToFront();
+    if (game.magic_wall_time_left > 0)
+    {
+      game.magic_wall_time_left--;
+
+      if (!game.magic_wall_time_left)
+      {
+       SCAN_PLAYFIELD(x, y)
+       {
+         element = Feld[x][y];
+
+         if (element == EL_MAGIC_WALL_ACTIVE ||
+             element == EL_MAGIC_WALL_FULL)
+         {
+           Feld[x][y] = EL_MAGIC_WALL_DEAD;
+           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;
+           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;
+           TEST_DrawLevelField(x, y);
+         }
+       }
+
+       game.magic_wall_active = FALSE;
+      }
     }
+  }
 
-    player->move_delay_value = original_move_delay_value;
+  if (game.light_time_left > 0)
+  {
+    game.light_time_left--;
+
+    if (game.light_time_left == 0)
+      RedrawAllLightSwitchesAndInvisibleElements();
   }
 
-  if (player->last_move_dir & MV_HORIZONTAL)
+  if (game.timegate_time_left > 0)
   {
-    if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
-      moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
+    game.timegate_time_left--;
+
+    if (game.timegate_time_left == 0)
+      CloseAllOpenTimegates();
   }
-  else
+
+  if (game.lenses_time_left > 0)
   {
-    if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
-      moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
+    game.lenses_time_left--;
+
+    if (game.lenses_time_left == 0)
+      RedrawAllInvisibleElementsForLenses();
   }
 
-  jx = player->jx;
-  jy = player->jy;
+  if (game.magnify_time_left > 0)
+  {
+    game.magnify_time_left--;
+
+    if (game.magnify_time_left == 0)
+      RedrawAllInvisibleElementsForMagnifier();
+  }
 
-  if (moved & MF_MOVING && !ScreenMovPos &&
-      (player == local_player || !options.network))
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
-    int offset = (setup.scroll_delay ? 3 : 0);
+    struct PlayerInfo *player = &stored_player[i];
 
-    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+    if (SHIELD_ON(player))
     {
-      /* actual player has left the screen -- scroll in that direction */
-      if (jx != old_jx)                /* player has moved horizontally */
-       scroll_x += (jx - old_jx);
-      else                     /* player has moved vertically */
-       scroll_y += (jy - old_jy);
+      if (player->shield_deadly_time_left)
+       PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
+      else if (player->shield_normal_time_left)
+       PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
     }
-    else
-    {
-      if (jx != old_jx)                /* player has moved horizontally */
-      {
-       if ((player->MovDir == MV_LEFT  && scroll_x > jx - MIDPOSX + offset) ||
-           (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
-         scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
-
-       /* 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 more than one field at a time */
-       scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
+#if USE_DELAYED_GFX_REDRAW
+  SCAN_PLAYFIELD(x, y)
+  {
+    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 !!! */
 
-       /* don't scroll against the player's moving direction */
-       if ((player->MovDir == MV_LEFT  && scroll_x > old_scroll_x) ||
-           (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
-         scroll_x = old_scroll_x;
-      }
-      else                     /* player has moved vertically */
-      {
-       if ((player->MovDir == MV_UP   && scroll_y > jy - MIDPOSY + offset) ||
-           (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
-         scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
+       DrawLevelField(x, y);
 
-       /* don't scroll over playfield boundaries */
-       if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
-         scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
+       DrawLevelFieldCrumbled(x, y);
 
-       /* don't scroll more than one field at a time */
-       scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
+       DrawLevelFieldCrumbledNeighbours(x, y);
 
-       /* don't scroll against the player's moving direction */
-       if ((player->MovDir == MV_UP   && scroll_y > old_scroll_y) ||
-           (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
-         scroll_y = old_scroll_y;
-      }
+      if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
+       DrawTwinkleOnField(x, y);
     }
 
-    if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
-    {
-      if (!options.network && !AllPlayersInVisibleScreen())
-      {
-       scroll_x = old_scroll_x;
-       scroll_y = old_scroll_y;
-      }
-      else
-      {
-       ScrollScreen(player, SCROLL_INIT);
-       ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
-      }
-    }
+    GfxRedraw[x][y] = GFX_REDRAW_NONE;
   }
-
-#if 0
-#if 1
-  InitPlayerGfxAnimation(player, ACTION_DEFAULT);
-#else
-  if (!(moved & MF_MOVING) && !player->is_pushing)
-    player->Frame = 0;
-#endif
 #endif
 
-  player->StepFrame = 0;
+  DrawAllPlayers();
+  PlayAllPlayersSound();
 
-  if (moved & MF_MOVING)
+  if (local_player->show_envelope != 0 && local_player->MovPos == 0)
   {
-#if 0
-    printf("::: REALLY moves now\n");
-#endif
+    ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
 
-    if (old_jx != jx && old_jy == jy)
-      player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
-    else if (old_jx == jx && old_jy != jy)
-      player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
+    local_player->show_envelope = 0;
+  }
 
-    DrawLevelField(jx, jy);    /* for "crumbled sand" */
+  /* use random number generator in every frame to make it less predictable */
+  if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+    RND(1);
+}
 
-    player->last_move_dir = player->MovDir;
-    player->is_moving = TRUE;
-#if 1
-    player->is_snapping = FALSE;
-#endif
+static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
+{
+  int min_x = x, min_y = y, max_x = x, max_y = y;
+  int i;
 
-#if 1
-    player->is_switching = FALSE;
-#endif
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    int jx = stored_player[i].jx, jy = stored_player[i].jy;
 
-    player->is_dropping = FALSE;
+    if (!stored_player[i].active || &stored_player[i] == player)
+      continue;
 
+    min_x = MIN(min_x, jx);
+    min_y = MIN(min_y, jy);
+    max_x = MAX(max_x, jx);
+    max_y = MAX(max_y, jy);
+  }
 
-#if 0
-    /* !!! ENABLE THIS FOR OLD VERSIONS !!! */
+  return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
+}
 
-#if 1
-    if (game.engine_version < VERSION_IDENT(3,1,0,0))
-#endif
-    {
-      int move_direction = player->MovDir;
-#if 1
-      int enter_side = MV_DIR_OPPOSITE(move_direction);
-      int leave_side = move_direction;
-#else
-      static int trigger_sides[4][2] =
-      {
-       /* enter side           leave side */
-       { CH_SIDE_RIGHT,        CH_SIDE_LEFT    },      /* moving left  */
-       { CH_SIDE_LEFT,         CH_SIDE_RIGHT   },      /* moving right */
-       { CH_SIDE_BOTTOM,       CH_SIDE_TOP     },      /* moving up    */
-       { CH_SIDE_TOP,          CH_SIDE_BOTTOM  }       /* moving down  */
-      };
-      int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
-      int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
-#endif
-      int old_element = Feld[old_jx][old_jy];
-      int new_element = Feld[jx][jy];
+static boolean AllPlayersInVisibleScreen()
+{
+  int i;
 
-#if 1
-      /* !!! TEST ONLY !!! */
-      if (IS_CUSTOM_ELEMENT(old_element))
-       CheckElementChangeByPlayer(old_jx, old_jy, old_element,
-                                  CE_LEFT_BY_PLAYER,
-                                  player->index_bit, leave_side);
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    int jx = stored_player[i].jx, jy = stored_player[i].jy;
 
-      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
-                                         CE_PLAYER_LEAVES_X,
-                                         player->index_bit, leave_side);
+    if (!stored_player[i].active)
+      continue;
 
-      if (IS_CUSTOM_ELEMENT(new_element))
-       CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
-                                  player->index_bit, enter_side);
+    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+      return FALSE;
+  }
 
-      CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
-                                         CE_PLAYER_ENTERS_X,
-                                         player->index_bit, enter_side);
-#endif
+  return TRUE;
+}
 
-    }
-#endif
+void ScrollLevel(int dx, int dy)
+{
+  int scroll_offset = 2 * TILEX_VAR;
+  int x, y;
 
+  BlitBitmap(drawto_field, drawto_field,
+            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)
+  {
+    x = (dx == 1 ? BX1 : BX2);
+    for (y = BY1; y <= BY2; y++)
+      DrawScreenField(x, y);
   }
-  else
+
+  if (dy != 0)
   {
-    CheckGravityMovementWhenNotMoving(player);
+    y = (dy == 1 ? BY1 : BY2);
+    for (x = BX1; x <= BX2; x++)
+      DrawScreenField(x, y);
+  }
 
-    /*
-    player->last_move_dir = MV_NO_MOVING;
-    */
-    player->is_moving = FALSE;
+  redraw_mask |= REDRAW_FIELD;
+}
 
-#if USE_NEW_MOVE_STYLE
-    /* player is ALLOWED to move, but CANNOT move (something blocks his way) */
-    /* ensure that the player is also allowed to move in the next frame */
-    /* (currently, the player is forced to wait eight frames before he can try
-       again!!!) */
+static boolean canFallDown(struct PlayerInfo *player)
+{
+  int jx = player->jx, jy = player->jy;
 
-    if (game.engine_version >= VERSION_IDENT(3,1,1,0))
-      player->move_delay = 0;  /* allow direct movement in the next frame */
-#endif
-  }
+  return (IN_LEV_FIELD(jx, jy + 1) &&
+         (IS_FREE(jx, jy + 1) ||
+          (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
+         IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
+         !IS_WALKABLE_INSIDE(Feld[jx][jy]));
+}
 
-#if USE_NEW_MOVE_DELAY
-  if (player->move_delay == -1)                /* not yet initialized by DigField() */
-    player->move_delay = player->move_delay_value;
-#endif
+static boolean canPassField(int x, int y, int move_dir)
+{
+  int opposite_dir = MV_DIR_OPPOSITE(move_dir);
+  int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+  int dy = (move_dir & MV_UP   ? -1 : move_dir & MV_DOWN  ? +1 : 0);
+  int nextx = x + dx;
+  int nexty = y + dy;
+  int element = Feld[x][y];
 
-  if (game.engine_version < VERSION_IDENT(3,0,7,0))
+  return (IS_PASSABLE_FROM(element, opposite_dir) &&
+         !CAN_MOVE(element) &&
+         IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
+         IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
+         (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
+}
+
+static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
+{
+  int opposite_dir = MV_DIR_OPPOSITE(move_dir);
+  int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+  int dy = (move_dir & MV_UP   ? -1 : move_dir & MV_DOWN  ? +1 : 0);
+  int newx = x + dx;
+  int newy = y + dy;
+
+  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+         IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
+         (IS_DIGGABLE(Feld[newx][newy]) ||
+          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+          canPassField(newx, newy, move_dir)));
+}
+
+static void CheckGravityMovement(struct PlayerInfo *player)
+{
+  if (player->gravity && !player->programmed_action)
   {
-    TestIfHeroTouchesBadThing(jx, jy);
-    TestIfPlayerTouchesCustomElement(jx, jy);
+    int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
+    int move_dir_vertical   = player->effective_action & MV_VERTICAL;
+    boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
+    int jx = player->jx, jy = player->jy;
+    boolean player_is_moving_to_valid_field =
+      (!player_is_snapping &&
+       (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
+       canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
+    boolean player_can_fall_down = canFallDown(player);
+
+    if (player_can_fall_down &&
+       !player_is_moving_to_valid_field)
+      player->programmed_action = MV_DOWN;
   }
+}
 
-  if (!player->active)
-    RemoveHero(player);
+static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
+{
+  return CheckGravityMovement(player);
 
-  return moved;
+  if (player->gravity && !player->programmed_action)
+  {
+    int jx = player->jx, jy = player->jy;
+    boolean field_under_player_is_free =
+      (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
+    boolean player_is_standing_on_valid_field =
+      (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
+       (IS_WALKABLE(Feld[jx][jy]) &&
+       !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
+
+    if (field_under_player_is_free && !player_is_standing_on_valid_field)
+      player->programmed_action = MV_DOWN;
+  }
 }
 
-void ScrollPlayer(struct PlayerInfo *player, int mode)
+/*
+  MovePlayerOneStep()
+  -----------------------------------------------------------------------------
+  dx, dy:              direction (non-diagonal) to try to move the player to
+  real_dx, real_dy:    direction as read from input device (can be diagonal)
+*/
+
+boolean MovePlayerOneStep(struct PlayerInfo *player,
+                         int dx, int dy, int real_dx, int real_dy)
 {
   int jx = player->jx, jy = player->jy;
-  int last_jx = player->last_jx, last_jy = player->last_jy;
-  int move_stepsize = TILEX / player->move_delay_value;
+  int new_jx = jx + dx, new_jy = jy + dy;
+  int can_move;
+  boolean player_can_move = !player->cannot_move;
 
-  if (!player->active || !player->MovPos)
-    return;
+  if (!player->active || (!dx && !dy))
+    return MP_NO_ACTION;
 
-  if (mode == SCROLL_INIT)
+  player->MovDir = (dx < 0 ? MV_LEFT :
+                   dx > 0 ? MV_RIGHT :
+                   dy < 0 ? MV_UP :
+                   dy > 0 ? MV_DOWN :  MV_NONE);
+
+  if (!IN_LEV_FIELD(new_jx, new_jy))
+    return MP_NO_ACTION;
+
+  if (!player_can_move)
   {
-    player->actual_frame_counter = FrameCounter;
-    player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
+    if (player->MovPos == 0)
+    {
+      player->is_moving = FALSE;
+      player->is_digging = FALSE;
+      player->is_collecting = FALSE;
+      player->is_snapping = FALSE;
+      player->is_pushing = FALSE;
+    }
+  }
 
-#if 0
-    printf("::: %06d: %d,%d: %d (%d) [%d]\n",
-          FrameCounter,
-          last_jx, last_jy, Feld[last_jx][last_jy], EL_EXPLOSION,
-          player->block_delay);
-#endif
+  if (!options.network && game.centered_player_nr == -1 &&
+      !AllPlayersInSight(player, new_jx, new_jy))
+    return MP_NO_ACTION;
 
-#if USE_NEW_BLOCK_STYLE
+  can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
+  if (can_move != MP_MOVING)
+    return can_move;
 
-#if 0
-    if (player->block_delay <= 0)
-      printf("::: ALERT! block_delay == %d\n", player->block_delay);
-#endif
+  /* check if DigField() has caused relocation of the player */
+  if (player->jx != jx || player->jy != jy)
+    return MP_NO_ACTION;       /* <-- !!! CHECK THIS [-> MP_ACTION ?] !!! */
 
-    if ((player->block_last_field || player->block_delay_adjustment > 0) &&
-       Feld[last_jx][last_jy] == EL_EMPTY)
-    {
-      int last_field_block_delay = 0;  /* start with no blocking at all */
-      int block_delay_adjustment = player->block_delay_adjustment;
+  StorePlayer[jx][jy] = 0;
+  player->last_jx = jx;
+  player->last_jy = jy;
+  player->jx = new_jx;
+  player->jy = new_jy;
+  StorePlayer[new_jx][new_jy] = player->element_nr;
 
-      /* if player blocks last field, add delay for exactly one move */
-      if (player->block_last_field)
-      {
-       last_field_block_delay += player->move_delay_value;
+  if (player->move_delay_value_next != -1)
+  {
+    player->move_delay_value = player->move_delay_value_next;
+    player->move_delay_value_next = -1;
+  }
 
-#if USE_GRAVITY_BUGFIX_NEW
-       /* when blocking enabled, prevent moving up despite gravity */
-       if (game.gravity && player->MovDir == MV_UP)
-         block_delay_adjustment = -1;
-#endif
-      }
+  player->MovPos =
+    (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
 
-      /* add block delay adjustment (also possible when not blocking) */
-      last_field_block_delay += block_delay_adjustment;
+  player->step_counter++;
 
-#if 0
-#if USE_BLOCK_DELAY_BUGFIX
-      /* when blocking enabled, correct block delay for fast movement */
-      if (player->block_last_field &&
-         player->move_delay_value < MOVE_DELAY_NORMAL_SPEED)
-       last_field_block_delay =
-         player->move_delay_value + player->block_delay_adjustment;
-#endif
-#endif
+  PlayerVisit[jx][jy] = FrameCounter;
 
-#if 0
-#if USE_GRAVITY_BUGFIX_NEW
-      /* when blocking enabled, correct block delay for gravity movement */
-      if (player->block_last_field &&
-         game.gravity && player->MovDir == MV_UP)
-       last_field_block_delay = player->move_delay_value - 1;
-#endif
-#endif
+  player->is_moving = TRUE;
 
-      Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
-      MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
-    }
-#else
-#if USE_NEW_MOVE_STYLE
-    if ((game.engine_version < VERSION_IDENT(3,1,1,0) ||
-        player->block_last_field) &&
-       Feld[last_jx][last_jy] == EL_EMPTY)
-      Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
-#else
-    if (Feld[last_jx][last_jy] == EL_EMPTY)
-      Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
-#endif
+#if 1
+  /* should better be called in MovePlayer(), but this breaks some tapes */
+  ScrollPlayer(player, SCROLL_INIT);
 #endif
 
-#if 0
-    DrawPlayer(player);
-#endif
+  return MP_MOVING;
+}
 
-    return;
-  }
-  else if (!FrameReached(&player->actual_frame_counter, 1))
-    return;
+boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
+{
+  int jx = player->jx, jy = player->jy;
+  int old_jx = jx, old_jy = jy;
+  int moved = MP_NO_ACTION;
 
-  player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
-  player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
+  if (!player->active)
+    return FALSE;
 
-#if USE_NEW_BLOCK_STYLE
-#else
-  if (!player->block_last_field &&
-      Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
-#if 1
-    RemoveField(last_jx, last_jy);
-#else
-    Feld[last_jx][last_jy] = EL_EMPTY;
-#endif
-#endif
+  if (!dx && !dy)
+  {
+    if (player->MovPos == 0)
+    {
+      player->is_moving = FALSE;
+      player->is_digging = FALSE;
+      player->is_collecting = FALSE;
+      player->is_snapping = FALSE;
+      player->is_pushing = FALSE;
+    }
 
-  /* before DrawPlayer() to draw correct player graphic for this case */
-  if (player->MovPos == 0)
-    CheckGravityMovement(player);
+    return FALSE;
+  }
 
-#if 0
-  DrawPlayer(player);  /* needed here only to cleanup last field */
-#endif
+  if (player->move_delay > 0)
+    return FALSE;
 
-  if (player->MovPos == 0)     /* player reached destination field */
-  {
-#if 1
-    if (player->move_delay_reset_counter > 0)
-    {
-      player->move_delay_reset_counter--;
+  player->move_delay = -1;             /* set to "uninitialized" value */
 
-      if (player->move_delay_reset_counter == 0)
-      {
-       /* continue with normal speed after quickly moving through gate */
-       HALVE_PLAYER_SPEED(player);
+  /* store if player is automatically moved to next field */
+  player->is_auto_moving = (player->programmed_action != MV_NONE);
 
-       /* be able to make the next move without delay */
-       player->move_delay = 0;
-      }
-    }
-#else
-    if (IS_PASSABLE(Feld[last_jx][last_jy]))
-    {
-      /* continue with normal speed after quickly moving through gate */
-      HALVE_PLAYER_SPEED(player);
+  /* remove the last programmed player action */
+  player->programmed_action = 0;
 
-      /* be able to make the next move without delay */
-      player->move_delay = 0;
-    }
-#endif
+  if (player->MovPos)
+  {
+    /* should only happen if pre-1.2 tape recordings are played */
+    /* this is only for backward compatibility */
 
-#if USE_NEW_BLOCK_STYLE
-#else
-    if (player->block_last_field &&
-       Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
-#if 1
-      RemoveField(last_jx, last_jy);
-#else
-      Feld[last_jx][last_jy] = EL_EMPTY;
-#endif
+    int original_move_delay_value = player->move_delay_value;
+
+#if DEBUG
+    printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
+          tape.counter);
 #endif
 
-    player->last_jx = jx;
-    player->last_jy = jy;
+    /* scroll remaining steps with finest movement resolution */
+    player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
 
-    if (Feld[jx][jy] == EL_EXIT_OPEN ||
-       Feld[jx][jy] == EL_SP_EXIT_OPEN ||
-       Feld[jx][jy] == EL_SP_EXIT_OPENING)     /* <-- special case */
+    while (player->MovPos)
     {
-      DrawPlayer(player);      /* needed here only to cleanup last field */
-      RemoveHero(player);
+      ScrollPlayer(player, SCROLL_GO_ON);
+      ScrollScreen(NULL, SCROLL_GO_ON);
 
-      if (local_player->friends_still_needed == 0 ||
-         IS_SP_ELEMENT(Feld[jx][jy]))
-       player->LevelSolved = player->GameOver = TRUE;
+      AdvanceFrameAndPlayerCounters(player->index_nr);
+
+      DrawAllPlayers();
+      BackToFront_WithFrameDelay(0);
     }
 
-#if 1
-    /* !!! ENABLE THIS FOR NEW VERSIONS !!! */
-    /* this breaks one level: "machine", level 000 */
-#if 0
-    if (game.engine_version >= VERSION_IDENT(3,1,0,0))
-#endif
-    {
-      int move_direction = player->MovDir;
-#if 1
-      int enter_side = MV_DIR_OPPOSITE(move_direction);
-      int leave_side = move_direction;
-#else
-      static int trigger_sides[4][2] =
-      {
-       /* enter side           leave side */
-       { CH_SIDE_RIGHT,        CH_SIDE_LEFT    },      /* moving left  */
-       { CH_SIDE_LEFT,         CH_SIDE_RIGHT   },      /* moving right */
-       { CH_SIDE_BOTTOM,       CH_SIDE_TOP     },      /* moving up    */
-       { CH_SIDE_TOP,          CH_SIDE_BOTTOM  }       /* moving down  */
-      };
-      int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
-      int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
-#endif
-      int old_jx = last_jx;
-      int old_jy = last_jy;
-      int old_element = Feld[old_jx][old_jy];
-      int new_element = Feld[jx][jy];
+    player->move_delay_value = original_move_delay_value;
+  }
 
-#if 1
-      /* !!! TEST ONLY !!! */
-      if (IS_CUSTOM_ELEMENT(old_element))
-       CheckElementChangeByPlayer(old_jx, old_jy, old_element,
-                                  CE_LEFT_BY_PLAYER,
-                                  player->index_bit, leave_side);
+  player->is_active = FALSE;
 
-      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
-                                         CE_PLAYER_LEAVES_X,
-                                         player->index_bit, leave_side);
+  if (player->last_move_dir & MV_HORIZONTAL)
+  {
+    if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
+      moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
+  }
+  else
+  {
+    if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
+      moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
+  }
 
-      if (IS_CUSTOM_ELEMENT(new_element))
-       CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
-                                  player->index_bit, enter_side);
+  if (!moved && !player->is_active)
+  {
+    player->is_moving = FALSE;
+    player->is_digging = FALSE;
+    player->is_collecting = FALSE;
+    player->is_snapping = FALSE;
+    player->is_pushing = FALSE;
+  }
 
-      CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
-                                         CE_PLAYER_ENTERS_X,
-                                         player->index_bit, enter_side);
-#endif
+  jx = player->jx;
+  jy = player->jy;
 
-    }
-#endif
+  if (moved & MP_MOVING && !ScreenMovPos &&
+      (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;
 
-    if (game.engine_version >= VERSION_IDENT(3,0,7,0))
+    if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
     {
-      TestIfHeroTouchesBadThing(jx, jy);
-      TestIfPlayerTouchesCustomElement(jx, jy);
-#if 1
-#if 1
-      /* needed because pushed element has not yet reached its destination,
-        so it would trigger a change event at its previous field location */
-      if (!player->is_pushing)
-#endif
-       TestIfElementTouchesCustomElement(jx, jy);      /* for empty space */
-#endif
-
-      if (!player->active)
-       RemoveHero(player);
+      /* actual player has left the screen -- scroll in that direction */
+      if (jx != old_jx)                /* player has moved horizontally */
+       scroll_x += (jx - old_jx);
+      else                     /* player has moved vertically */
+       scroll_y += (jy - old_jy);
     }
-
-    if (level.use_step_counter)
+    else
     {
-      int i;
+      if (jx != old_jx)                /* player has moved horizontally */
+      {
+       if ((player->MovDir == MV_LEFT  && scroll_x > jx - MIDPOSX + offset) ||
+           (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
+         scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
 
-      TimePlayed++;
+       /* 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);
 
-      if (TimeLeft > 0)
+       /* don't scroll more than one field at a time */
+       scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
+
+       /* don't scroll against the player's moving direction */
+       if ((player->MovDir == MV_LEFT  && scroll_x > old_scroll_x) ||
+           (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
+         scroll_x = old_scroll_x;
+      }
+      else                     /* player has moved vertically */
       {
-       TimeLeft--;
+       if ((player->MovDir == MV_UP   && scroll_y > jy - MIDPOSY + offset) ||
+           (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
+         scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
 
-       if (TimeLeft <= 10 && setup.time_limit)
-         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+       /* 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);
 
-       DrawGameValue_Time(TimeLeft);
+       /* don't scroll more than one field at a time */
+       scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
 
-       if (!TimeLeft && setup.time_limit)
-         for (i = 0; i < MAX_PLAYERS; i++)
-           KillHero(&stored_player[i]);
+       /* don't scroll against the player's moving direction */
+       if ((player->MovDir == MV_UP   && scroll_y > old_scroll_y) ||
+           (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
+         scroll_y = old_scroll_y;
       }
-      else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
-       DrawGameValue_Time(TimePlayed);
     }
 
-    if (tape.single_step && tape.recording && !tape.pausing &&
-       !player->programmed_action)
-      TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+    if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
+    {
+      if (!options.network && game.centered_player_nr == -1 &&
+         !AllPlayersInVisibleScreen())
+      {
+       scroll_x = old_scroll_x;
+       scroll_y = old_scroll_y;
+      }
+      else
+      {
+       ScrollScreen(player, SCROLL_INIT);
+       ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
+      }
+    }
   }
-}
 
-void ScrollScreen(struct PlayerInfo *player, int mode)
-{
-  static unsigned long screen_frame_counter = 0;
+  player->StepFrame = 0;
 
-  if (mode == SCROLL_INIT)
+  if (moved & MP_MOVING)
   {
-    /* set scrolling step size according to actual player's moving speed */
-    ScrollStepSize = TILEX / player->move_delay_value;
+    if (old_jx != jx && old_jy == jy)
+      player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
+    else if (old_jx == jx && old_jy != jy)
+      player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
 
-    screen_frame_counter = FrameCounter;
-    ScreenMovDir = player->MovDir;
-    ScreenMovPos = player->MovPos;
-    ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
-    return;
-  }
-  else if (!FrameReached(&screen_frame_counter, 1))
-    return;
+    TEST_DrawLevelField(jx, jy);       /* for "crumbled sand" */
 
-  if (ScreenMovPos)
-  {
-    ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
-    ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
-    redraw_mask |= REDRAW_FIELD;
+    player->last_move_dir = player->MovDir;
+    player->is_moving = TRUE;
+    player->is_snapping = FALSE;
+    player->is_switching = FALSE;
+    player->is_dropping = FALSE;
+    player->is_dropping_pressed = FALSE;
+    player->drop_pressed_delay = 0;
+
+#if 0
+    /* should better be called here than above, but this breaks some tapes */
+    ScrollPlayer(player, SCROLL_INIT);
+#endif
   }
   else
-    ScreenMovDir = MV_NO_MOVING;
-}
-
-void TestIfPlayerTouchesCustomElement(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 */
+    CheckGravityMovementWhenNotMoving(player);
+
+    player->is_moving = FALSE;
+
+    /* at this point, the player is allowed to move, but cannot move right now
+       (e.g. because of something blocking the way) -- ensure that the player
+       is also allowed to move in the next frame (in old versions before 3.1.1,
+       the player was forced to wait again for eight frames before next try) */
+
+    if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+      player->move_delay = 0;  /* allow direct movement in the next frame */
+  }
+
+  if (player->move_delay == -1)                /* not yet initialized by DigField() */
+    player->move_delay = player->move_delay_value;
+
+  if (game.engine_version < VERSION_IDENT(3,0,7,0))
+  {
+    TestIfPlayerTouchesBadThing(jx, jy);
+    TestIfPlayerTouchesCustomElement(jx, jy);
+  }
+
+  if (!player->active)
+    RemovePlayer(player);
+
+  return moved;
+}
+
+void ScrollPlayer(struct PlayerInfo *player, int mode)
+{
+  int jx = player->jx, jy = player->jy;
+  int last_jx = player->last_jx, last_jy = player->last_jy;
+  int move_stepsize = TILEX / player->move_delay_value;
+
+  if (!player->active)
+    return;
+
+  if (player->MovPos == 0 && mode == SCROLL_GO_ON)     /* player not moving */
+    return;
+
+  if (mode == SCROLL_INIT)
+  {
+    player->actual_frame_counter = FrameCounter;
+    player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
+
+    if ((player->block_last_field || player->block_delay_adjustment > 0) &&
+       Feld[last_jx][last_jy] == EL_EMPTY)
+    {
+      int last_field_block_delay = 0;  /* start with no blocking at all */
+      int block_delay_adjustment = player->block_delay_adjustment;
+
+      /* if player blocks last field, add delay for exactly one move */
+      if (player->block_last_field)
+      {
+       last_field_block_delay += player->move_delay_value;
+
+       /* when blocking enabled, prevent moving up despite gravity */
+       if (player->gravity && player->MovDir == MV_UP)
+         block_delay_adjustment = -1;
+      }
+
+      /* add block delay adjustment (also possible when not blocking) */
+      last_field_block_delay += block_delay_adjustment;
+
+      Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
+      MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
+    }
+
+    if (player->MovPos != 0)   /* player has not yet reached destination */
+      return;
+  }
+  else if (!FrameReached(&player->actual_frame_counter, 1))
+    return;
+
+  if (player->MovPos != 0)
+  {
+    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);
+  }
+
+  if (player->MovPos == 0)     /* player reached destination field */
+  {
+    if (player->move_delay_reset_counter > 0)
+    {
+      player->move_delay_reset_counter--;
+
+      if (player->move_delay_reset_counter == 0)
+      {
+       /* continue with normal speed after quickly moving through gate */
+       HALVE_PLAYER_SPEED(player);
+
+       /* be able to make the next move without delay */
+       player->move_delay = 0;
+      }
+    }
+
+    player->last_jx = jx;
+    player->last_jy = jy;
+
+    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 */
+    {
+      DrawPlayer(player);      /* needed here only to cleanup last field */
+      RemovePlayer(player);
+
+      if (local_player->friends_still_needed == 0 ||
+         IS_SP_ELEMENT(Feld[jx][jy]))
+       PlayerWins(player);
+    }
+
+    /* this breaks one level: "machine", level 000 */
+    {
+      int move_direction = player->MovDir;
+      int enter_side = MV_DIR_OPPOSITE(move_direction);
+      int leave_side = move_direction;
+      int old_jx = last_jx;
+      int old_jy = last_jy;
+      int old_element = Feld[old_jx][old_jy];
+      int new_element = Feld[jx][jy];
+
+      if (IS_CUSTOM_ELEMENT(old_element))
+       CheckElementChangeByPlayer(old_jx, old_jy, old_element,
+                                  CE_LEFT_BY_PLAYER,
+                                  player->index_bit, leave_side);
+
+      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
+                                         CE_PLAYER_LEAVES_X,
+                                         player->index_bit, leave_side);
+
+      if (IS_CUSTOM_ELEMENT(new_element))
+       CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
+                                  player->index_bit, enter_side);
+
+      CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
+                                         CE_PLAYER_ENTERS_X,
+                                         player->index_bit, enter_side);
+
+      CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
+                                       CE_MOVE_OF_X, move_direction);
+    }
+
+    if (game.engine_version >= VERSION_IDENT(3,0,7,0))
+    {
+      TestIfPlayerTouchesBadThing(jx, jy);
+      TestIfPlayerTouchesCustomElement(jx, jy);
+
+      /* needed because pushed element has not yet reached its destination,
+        so it would trigger a change event at its previous field location */
+      if (!player->is_pushing)
+       TestIfElementTouchesCustomElement(jx, jy);      /* for empty space */
+
+      if (!player->active)
+       RemovePlayer(player);
+    }
+
+    if (!local_player->LevelSolved && level.use_step_counter)
+    {
+      int i;
+
+      TimePlayed++;
+
+      if (TimeLeft > 0)
+      {
+       TimeLeft--;
+
+       if (TimeLeft <= 10 && setup.time_limit)
+         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+       DisplayGameControlValues();
+
+       if (!TimeLeft && setup.time_limit)
+         for (i = 0; i < MAX_PLAYERS; i++)
+           KillPlayer(&stored_player[i]);
+      }
+      else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
+      {
+       game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
+
+       DisplayGameControlValues();
+      }
+    }
+
+    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 int screen_frame_counter = 0;
+
+  if (mode == SCROLL_INIT)
+  {
+    /* set scrolling step size according to actual player's moving speed */
+    ScrollStepSize = TILEX / player->move_delay_value;
+
+    screen_frame_counter = FrameCounter;
+    ScreenMovDir = player->MovDir;
+    ScreenMovPos = player->MovPos;
+    ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
+    return;
+  }
+  else if (!FrameReached(&screen_frame_counter, 1))
+    return;
+
+  if (ScreenMovPos)
+  {
+    ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
+    ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
+    redraw_mask |= REDRAW_FIELD;
+  }
+  else
+    ScreenMovDir = MV_NONE;
+}
+
+void TestIfPlayerTouchesCustomElement(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  */
@@ -10603,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);
 
@@ -10616,22 +12508,24 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
       else
        continue;               /* center and border element do not touch */
 
-#if 1
-      /* !!! TEST ONLY !!! */
       CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
                                 player->index_bit, border_side);
       CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
                                          CE_PLAYER_TOUCHES_X,
                                          player->index_bit, border_side);
-#else
-      CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
-                                         CE_PLAYER_TOUCHES_X,
-                                         player->index_bit, border_side);
-      CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
-                                player->index_bit, border_side);
-#endif
+
+      {
+       /* 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);
 
@@ -10641,20 +12535,22 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
          continue;             /* center and border element do not touch */
       }
 
-#if 1
-      /* !!! TEST ONLY !!! */
       CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
                                 player->index_bit, center_side);
       CheckTriggeredElementChangeByPlayer(x, y, center_element,
                                          CE_PLAYER_TOUCHES_X,
                                          player->index_bit, center_side);
-#else
-      CheckTriggeredElementChangeByPlayer(x, y, center_element,
-                                         CE_PLAYER_TOUCHES_X,
-                                         player->index_bit, center_side);
-      CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
-                                player->index_bit, center_side);
-#endif
+
+      {
+       /* 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;
     }
@@ -10686,19 +12582,18 @@ void TestIfElementTouchesCustomElement(int x, int y)
     MV_LEFT | MV_RIGHT
   };
   boolean change_center_element = FALSE;
-  int center_element_change_page = 0;
   int center_element = Feld[x][y];     /* should always be non-moving! */
-  int border_trigger_element = EL_UNDEFINED;
-  int i, j;
+  int border_element_old[NUM_DIRECTIONS];
+  int i;
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
     int xx = x + xy[i][0];
     int yy = y + xy[i][1];
-    int center_side = trigger_sides[i][0];
-    int border_side = trigger_sides[i][1];
     int border_element;
 
+    border_element_old[i] = -1;
+
     if (!IN_LEV_FIELD(xx, yy))
       continue;
 
@@ -10711,310 +12606,115 @@ void TestIfElementTouchesCustomElement(int x, int y)
     else
       continue;                        /* center and border element do not touch */
 
-    /* check for change of center element (but change it only once) */
-    if (IS_CUSTOM_ELEMENT(center_element) &&
-       HAS_ANY_CHANGE_EVENT(center_element, CE_TOUCHING_X) &&
-       !change_center_element)
-    {
-      for (j = 0; j < element_info[center_element].num_change_pages; j++)
-      {
-       struct ElementChangeInfo *change =
-         &element_info[center_element].change_page[j];
+    border_element_old[i] = border_element;
+  }
 
-       if (change->can_change &&
-           change->has_event[CE_TOUCHING_X] &&
-           change->trigger_side & border_side &&
-#if 1
-           IS_EQUAL_OR_IN_GROUP(border_element, change->trigger_element)
-#else
-           change->trigger_element == border_element
-#endif
-           )
-       {
-         change_center_element = TRUE;
-         center_element_change_page = j;
-         border_trigger_element = border_element;
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+  {
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
+    int center_side = trigger_sides[i][0];
+    int border_element = border_element_old[i];
 
-         break;
-       }
-      }
-    }
+    if (border_element == -1)
+      continue;
 
     /* check for change of border element */
-    if (IS_CUSTOM_ELEMENT(border_element) &&
-       HAS_ANY_CHANGE_EVENT(border_element, CE_TOUCHING_X))
-    {
-      for (j = 0; j < element_info[border_element].num_change_pages; j++)
-      {
-       struct ElementChangeInfo *change =
-         &element_info[border_element].change_page[j];
-
-       if (change->can_change &&
-           change->has_event[CE_TOUCHING_X] &&
-           change->trigger_side & center_side &&
-#if 1
-           IS_EQUAL_OR_IN_GROUP(center_element, change->trigger_element)
-#else
-           change->trigger_element == center_element
-#endif
-           )
-       {
-#if 0
-         printf("::: border_element %d, %d\n", x, y);
-#endif
+    CheckElementChangeBySide(xx, yy, border_element, center_element,
+                            CE_TOUCHING_X, center_side);
 
-         CheckElementChangeByPage(xx, yy, border_element, center_element,
-                                  CE_TOUCHING_X, j);
-         break;
-       }
-      }
-    }
+    /* (center element cannot be player, so we dont have to check this here) */
   }
 
-  if (change_center_element)
+  for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-#if 0
-    printf("::: center_element %d, %d\n", x, y);
-#endif
-
-    CheckElementChangeByPage(x, y, center_element, border_trigger_element,
-                            CE_TOUCHING_X, center_element_change_page);
-  }
-}
-
-void TestIfElementHitsCustomElement(int x, int y, int direction)
-{
-  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-  int hitx = x + dx, hity = y + dy;
-  int hitting_element = Feld[x][y];
-  int touched_element;
-#if 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);
-
-#if !USE_HITTING_SOMETHING_BUGFIX
-  /* "hitting something" is also true when hitting the playfield border */
-  CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                          CE_HITTING_SOMETHING, direction);
-#endif
+    int xx = x + xy[i][0];
+    int yy = y + xy[i][1];
+    int border_side = trigger_sides[i][1];
+    int border_element = border_element_old[i];
 
-  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);
+    if (border_element == -1)
+      continue;
 
-    object_hit = TRUE;
-#endif
+    /* 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);
 
-    if (object_hit)
+    if (IS_PLAYER(xx, yy))
     {
-      int i;
-
-#if !USE_HIT_BY_SOMETHING_BUGFIX
-      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_HIT_BY_SOMETHING, opposite_direction);
-#endif
-
-      if (IS_CUSTOM_ELEMENT(hitting_element) &&
-         HAS_ANY_CHANGE_EVENT(hitting_element, CE_HITTING_X))
-      {
-       for (i = 0; i < element_info[hitting_element].num_change_pages; i++)
-       {
-         struct ElementChangeInfo *change =
-           &element_info[hitting_element].change_page[i];
-
-         if (change->can_change &&
-             change->has_event[CE_HITTING_X] &&
-             change->trigger_side & touched_side &&
-         
-#if 1
-             IS_EQUAL_OR_IN_GROUP(touched_element, change->trigger_element)
-#else
-             change->trigger_element == touched_element
-#endif
-             )
-         {
-           CheckElementChangeByPage(x, y, hitting_element, touched_element,
-                                    CE_HITTING_X, i);
-           break;
-         }
-       }
-      }
-
-      if (IS_CUSTOM_ELEMENT(touched_element) &&
-         HAS_ANY_CHANGE_EVENT(touched_element, CE_HIT_BY_X))
-      {
-       for (i = 0; i < element_info[touched_element].num_change_pages; i++)
-       {
-         struct ElementChangeInfo *change =
-           &element_info[touched_element].change_page[i];
-
-         if (change->can_change &&
-             change->has_event[CE_HIT_BY_X] &&
-             change->trigger_side & hitting_side &&
-#if 1
-             IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
-#else
-             change->trigger_element == hitting_element
-#endif
-             )
-         {
-           CheckElementChangeByPage(hitx, hity, touched_element,
-                                    hitting_element, CE_HIT_BY_X, i);
-           break;
-         }
-       }
-      }
+      /* 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;
 
-#if USE_HIT_BY_SOMETHING_BUGFIX
-      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_HIT_BY_SOMETHING, opposite_direction);
-#endif
+      CheckElementChangeBySide(x, y, center_element, player_element,
+                              CE_TOUCHING_X, border_side);
     }
   }
-
-#if USE_HITTING_SOMETHING_BUGFIX
-  /* "hitting something" is also true when hitting the playfield border */
-  CheckElementChangeBySide(x, y, hitting_element, touched_element,
-                          CE_HITTING_SOMETHING, direction);
-#endif
 }
 
-#if 0
-void TestIfElementSmashesCustomElement(int x, int y, int direction)
+void TestIfElementHitsCustomElement(int x, int y, int direction)
 {
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
   int hitx = x + dx, hity = y + dy;
   int hitting_element = Feld[x][y];
   int touched_element;
-#if 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(x, y, hitting_element, touched_element,
+                              CE_HITTING_X, touched_side);
 
       CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
-                              CE_SMASHED_BY_SOMETHING, opposite_direction);
+                              CE_HIT_BY_X, hitting_side);
 
-      if (IS_CUSTOM_ELEMENT(hitting_element) &&
-         HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_SMASHING))
-      {
-       for (i = 0; i < element_info[hitting_element].num_change_pages; i++)
-       {
-         struct ElementChangeInfo *change =
-           &element_info[hitting_element].change_page[i];
-
-         if (change->can_change &&
-             change->has_event[CE_OTHER_IS_SMASHING] &&
-             change->trigger_side & touched_side &&
-         
-#if 1
-             IS_EQUAL_OR_IN_GROUP(touched_element, change->trigger_element)
-#else
-             change->trigger_element == touched_element
-#endif
-             )
-         {
-           CheckElementChangeByPage(x, y, hitting_element, touched_element,
-                                    CE_OTHER_IS_SMASHING, i);
-           break;
-         }
-       }
-      }
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_HIT_BY_SOMETHING, opposite_direction);
 
-      if (IS_CUSTOM_ELEMENT(touched_element) &&
-         HAS_ANY_CHANGE_EVENT(touched_element, CE_OTHER_GETS_SMASHED))
+      if (IS_PLAYER(hitx, hity))
       {
-       for (i = 0; i < element_info[touched_element].num_change_pages; i++)
-       {
-         struct ElementChangeInfo *change =
-           &element_info[touched_element].change_page[i];
+       /* 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;
 
-         if (change->can_change &&
-             change->has_event[CE_OTHER_GETS_SMASHED] &&
-             change->trigger_side & hitting_side &&
-#if 1
-             IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
-#else
-             change->trigger_element == hitting_element
-#endif
-             )
-         {
-           CheckElementChangeByPage(hitx, hity, touched_element,
-                                    hitting_element, CE_OTHER_GETS_SMASHED,i);
-           break;
-         }
-       }
+       CheckElementChangeBySide(x, y, hitting_element, player_element,
+                                CE_HITTING_X, touched_side);
       }
     }
   }
+
+  /* "hitting something" is also true when hitting the playfield border */
+  CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                          CE_HITTING_SOMETHING, direction);
 }
-#endif
 
 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
 {
   int i, kill_x = -1, kill_y = -1;
+
   int bad_element = -1;
   static int test_xy[4][2] =
   {
@@ -11042,13 +12742,9 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
       continue;
 
     test_move_dir =
-      (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NO_MOVING);
+      (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
 
-#if 0
-    test_element = Feld[test_x][test_y];
-#else
     test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
-#endif
 
     /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
        2nd case: DONT_TOUCH style bad thing does not move away from good thing
@@ -11070,18 +12766,11 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
     {
       struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
 
-#if 1
       if (player->shield_deadly_time_left > 0 &&
          !IS_INDESTRUCTIBLE(bad_element))
        Bang(kill_x, kill_y);
       else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
-       KillHero(player);
-#else
-      if (player->shield_deadly_time_left > 0)
-       Bang(kill_x, kill_y);
-      else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
-       KillHero(player);
-#endif
+       KillPlayer(player);
     }
     else
       Bang(good_x, good_y);
@@ -11123,11 +12812,12 @@ 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;
 
     test_move_dir =
-      (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NO_MOVING);
+      (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
 
     test_element = Feld[test_x][test_y];
 
@@ -11153,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;
       }
     }
@@ -11170,52 +12862,102 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
     {
       struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
 
-#if 1
       if (player->shield_deadly_time_left > 0 &&
          !IS_INDESTRUCTIBLE(bad_element))
        Bang(bad_x, bad_y);
       else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
-       KillHero(player);
-#else
-      if (player->shield_deadly_time_left > 0)
+       KillPlayer(player);
+    }
+    else
+      Bang(kill_x, kill_y);
+  }
+}
+
+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))
-       KillHero(player);
-#endif
+       KillPlayer(player);
     }
     else
       Bang(kill_x, kill_y);
   }
 }
 
-void TestIfHeroTouchesBadThing(int x, int y)
+void TestIfPlayerTouchesBadThing(int x, int y)
 {
-  TestIfGoodThingHitsBadThing(x, y, MV_NO_MOVING);
+  TestIfGoodThingHitsBadThing(x, y, MV_NONE);
 }
 
-void TestIfHeroRunsIntoBadThing(int x, int y, int move_dir)
+void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
 {
   TestIfGoodThingHitsBadThing(x, y, move_dir);
 }
 
-void TestIfBadThingTouchesHero(int x, int y)
+void TestIfBadThingTouchesPlayer(int x, int y)
 {
-  TestIfBadThingHitsGoodThing(x, y, MV_NO_MOVING);
+  TestIfBadThingHitsGoodThing(x, y, MV_NONE);
 }
 
-void TestIfBadThingRunsIntoHero(int x, int y, int move_dir)
+void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
 {
   TestIfBadThingHitsGoodThing(x, y, move_dir);
 }
 
 void TestIfFriendTouchesBadThing(int x, int y)
 {
-  TestIfGoodThingHitsBadThing(x, y, MV_NO_MOVING);
+  TestIfGoodThingHitsBadThing(x, y, MV_NONE);
 }
 
 void TestIfBadThingTouchesFriend(int x, int y)
 {
-  TestIfBadThingHitsGoodThing(x, y, MV_NO_MOVING);
+  TestIfBadThingHitsGoodThing(x, y, MV_NONE);
 }
 
 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
@@ -11252,13 +12994,35 @@ void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
     Bang(bad_x, bad_y);
 }
 
-void KillHero(struct PlayerInfo *player)
+void KillPlayer(struct PlayerInfo *player)
 {
   int jx = player->jx, jy = player->jy;
 
   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()
+     -> ExecuteCustomElementAction()
+     -> KillPlayer()
+     -> (infinitely repeating the above sequence of function calls)
+     which occurs when killing the player while having a CE with the setting
+     "kill player X when explosion of <player X>"; the solution using a new
+     field "player->killed" was chosen for backwards compatibility, although
+     clever use of the fields "player->active" etc. would probably also work */
+#if 1
+  if (player->killed)
+    return;
+#endif
+
+  player->killed = TRUE;
+
   /* remove accessible field at the player's position */
   Feld[jx][jy] = EL_EMPTY;
 
@@ -11266,41 +13030,51 @@ void KillHero(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);
-  BuryHero(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 KillHeroUnlessEnemyProtected(int x, int y)
+static void KillPlayerUnlessEnemyProtected(int x, int y)
 {
   if (!PLAYER_ENEMY_PROTECTED(x, y))
-    KillHero(PLAYERINFO(x, y));
+    KillPlayer(PLAYERINFO(x, y));
 }
 
-static void KillHeroUnlessExplosionProtected(int x, int y)
+static void KillPlayerUnlessExplosionProtected(int x, int y)
 {
   if (!PLAYER_EXPLOSION_PROTECTED(x, y))
-    KillHero(PLAYERINFO(x, y));
+    KillPlayer(PLAYERINFO(x, y));
 }
 
-void BuryHero(struct PlayerInfo *player)
+void BuryPlayer(struct PlayerInfo *player)
 {
   int jx = player->jx, jy = player->jy;
 
   if (!player->active)
     return;
 
-#if 1
-  PlayLevelSoundElementAction(jx, jy, player->element_nr, ACTION_DYING);
-#else
-  PlayLevelSound(jx, jy, SND_CLASS_PLAYER_DYING);
-#endif
+  PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
   PlayLevelSound(jx, jy, SND_GAME_LOSING);
 
   player->GameOver = TRUE;
-  RemoveHero(player);
+  RemovePlayer(player);
 }
 
-void RemoveHero(struct PlayerInfo *player)
+void RemovePlayer(struct PlayerInfo *player)
 {
   int jx = player->jx, jy = player->jy;
   int i, found = FALSE;
@@ -11311,6 +13085,9 @@ void RemoveHero(struct PlayerInfo *player)
   if (!ExplodeField[jx][jy])
     StorePlayer[jx][jy] = 0;
 
+  if (player->is_moving)
+    TEST_DrawLevelField(player->last_jx, player->last_jy);
+
   for (i = 0; i < MAX_PLAYERS; i++)
     if (stored_player[i].active)
       found = TRUE;
@@ -11322,6 +13099,25 @@ void RemoveHero(struct PlayerInfo *player)
   ExitY = ZY = jy;
 }
 
+static void setFieldForSnapping(int x, int y, int element, int direction)
+{
+  struct ElementInfo *ei = &element_info[element];
+  int direction_bit = MV_DIR_TO_BIT(direction);
+  int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
+  int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
+               IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
+
+  Feld[x][y] = EL_ELEMENT_SNAPPING;
+  MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
+
+  ResetGfxAnimation(x, y);
+
+  GfxElement[x][y] = element;
+  GfxAction[x][y] = action;
+  GfxDir[x][y] = direction;
+  GfxFrame[x][y] = -1;
+}
+
 /*
   =============================================================================
   checkDiagonalPushing()
@@ -11359,37 +13155,26 @@ 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)
 {
-#if 0
-  boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0,0));
-#endif
   boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
   boolean player_was_pushing = player->is_pushing;
+  boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
+  boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
   int jx = oldx, jy = oldy;
   int dx = x - jx, dy = y - jy;
   int nextx = x + dx, nexty = y + dy;
-  int move_direction = (dx == -1 ? MV_LEFT :
+  int move_direction = (dx == -1 ? MV_LEFT  :
                        dx == +1 ? MV_RIGHT :
-                       dy == -1 ? MV_UP :
-                       dy == +1 ? MV_DOWN : MV_NO_MOVING);
+                       dy == -1 ? MV_UP    :
+                       dy == +1 ? MV_DOWN  : MV_NONE);
   int opposite_direction = MV_DIR_OPPOSITE(move_direction);
-#if 1
   int dig_side = MV_DIR_OPPOSITE(move_direction);
-#else
-  static int trigger_sides[4] =
-  {
-    CH_SIDE_RIGHT,     /* moving left  */
-    CH_SIDE_LEFT,      /* moving right */
-    CH_SIDE_BOTTOM,    /* moving up    */
-    CH_SIDE_TOP,       /* moving down  */
-  };
-  int dig_side = trigger_sides[MV_DIR_BIT(move_direction)];
-#endif
   int old_element = Feld[jx][jy];
-  int element;
+  int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
+  int collect_count;
 
   if (is_player)               /* function can also be called by EL_PENGUIN */
   {
@@ -11405,897 +13190,739 @@ int DigField(struct PlayerInfo *player,
     if (mode == DF_NO_PUSH)    /* player just stopped pushing */
     {
       player->is_switching = FALSE;
-#if USE_NEW_PUSH_DELAY
       player->push_delay = -1;
-#else
-      player->push_delay = 0;
-#endif
-
-      return MF_NO_ACTION;
-    }
-  }
-
-  if (IS_MOVING(x, y) || IS_PLAYER(x, y))
-    return MF_NO_ACTION;
-
-#if 0
-
-#if 0
-  if (IS_TUBE(Feld[jx][jy]) || IS_TUBE(Back[jx][jy]))
-#else
-  if (IS_TUBE(Feld[jx][jy]) ||
-      (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0)))
-#endif
-  {
-    int i = 0;
-    int tube_element = (IS_TUBE(Feld[jx][jy]) ? Feld[jx][jy] : Back[jx][jy]);
-    int tube_leave_directions[][2] =
-    {
-      { EL_TUBE_ANY,                   MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
-      { EL_TUBE_VERTICAL,                                   MV_UP | MV_DOWN },
-      { EL_TUBE_HORIZONTAL,            MV_LEFT | MV_RIGHT                   },
-      { EL_TUBE_VERTICAL_LEFT,         MV_LEFT |            MV_UP | MV_DOWN },
-      { EL_TUBE_VERTICAL_RIGHT,                          MV_RIGHT | MV_UP | MV_DOWN },
-      { EL_TUBE_HORIZONTAL_UP,         MV_LEFT | MV_RIGHT | MV_UP           },
-      { EL_TUBE_HORIZONTAL_DOWN,       MV_LEFT | MV_RIGHT |         MV_DOWN },
-      { EL_TUBE_LEFT_UP,               MV_LEFT |            MV_UP           },
-      { EL_TUBE_LEFT_DOWN,             MV_LEFT |                    MV_DOWN },
-      { EL_TUBE_RIGHT_UP,                        MV_RIGHT | MV_UP           },
-      { EL_TUBE_RIGHT_DOWN,                      MV_RIGHT |         MV_DOWN },
-      { -1,                            MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN }
-    };
 
-    while (tube_leave_directions[i][0] != tube_element)
-    {
-      i++;
-      if (tube_leave_directions[i][0] == -1)   /* should not happen */
-       break;
+      return MP_NO_ACTION;
     }
-
-    if (!(tube_leave_directions[i][1] & move_direction))
-      return MF_NO_ACTION;     /* tube has no opening in this direction */
   }
 
-#else
-
   if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
 
-#endif
+  /* in case of element dropped at player position, check background */
+  else if (Back[jx][jy] != EL_EMPTY &&
+          game.engine_version >= VERSION_IDENT(2,2,0,0))
+    old_element = Back[jx][jy];
 
   if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
-    return MF_NO_ACTION;       /* field has no opening in this direction */
+    return MP_NO_ACTION;       /* field has no opening in this direction */
 
   if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
-    return MF_NO_ACTION;       /* field has no opening in this direction */
+    return MP_NO_ACTION;       /* field has no opening in this direction */
 
-  element = Feld[x][y];
+  if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
+  {
+    SplashAcid(x, y);
 
-  if (!is_player && !IS_COLLECTIBLE(element))  /* penguin cannot collect it */
-    return MF_NO_ACTION;
+    Feld[jx][jy] = player->artwork_element;
+    InitMovingField(jx, jy, MV_DOWN);
+    Store[jx][jy] = EL_ACID;
+    ContinueMoving(jx, jy);
+    BuryPlayer(player);
 
-  if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
-      game.engine_version >= VERSION_IDENT(2,2,0,0))
-    return MF_NO_ACTION;
+    return MP_DONT_RUN_INTO;
+  }
 
-#if 1
-  if (game.gravity && is_player && !player->is_auto_moving &&
+  if (player_can_move && DONT_RUN_INTO(element))
+  {
+    TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
+
+    return MP_DONT_RUN_INTO;
+  }
+
+  if (IS_MOVING(x, y) || IS_PLAYER(x, y))
+    return MP_NO_ACTION;
+
+  collect_count = element_info[element].collect_count_initial;
+
+  if (!is_player && !IS_COLLECTIBLE(element))  /* penguin cannot collect it */
+    return MP_NO_ACTION;
+
+  if (game.engine_version < VERSION_IDENT(2,2,0,0))
+    player_can_move = player_can_move_or_snap;
+
+  if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
+      game.engine_version >= VERSION_IDENT(2,2,0,0))
+  {
+    CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
+                              player->index_bit, dig_side);
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                       player->index_bit, dig_side);
+
+    if (element == EL_DC_LANDMINE)
+      Bang(x, y);
+
+    if (Feld[x][y] != element)         /* field changed by snapping */
+      return MP_ACTION;
+
+    return MP_NO_ACTION;
+  }
+
+  if (player->gravity && is_player && !player->is_auto_moving &&
       canFallDown(player) && move_direction != MV_DOWN &&
       !canMoveToValidFieldWithGravity(jx, jy, move_direction))
-    return MF_NO_ACTION;       /* player cannot walk here due to gravity */
-#endif
+    return MP_NO_ACTION;       /* player cannot walk here due to gravity */
 
-#if 0
-  if (element == EL_EMPTY_SPACE &&
-      game.gravity && !player->is_auto_moving &&
-      canFallDown(player) && move_direction != MV_DOWN)
-    return MF_NO_ACTION;       /* player cannot walk here due to gravity */
-#endif
+  if (player_can_move &&
+      IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
+  {
+    int sound_element = SND_ELEMENT(element);
+    int sound_action = ACTION_WALKING;
 
-  switch (element)
+    if (IS_RND_GATE(element))
+    {
+      if (!player->key[RND_GATE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_RND_GATE_GRAY(element))
+    {
+      if (!player->key[RND_GATE_GRAY_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_RND_GATE_GRAY_ACTIVE(element))
+    {
+      if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    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)
+    {
+      sound_action = ACTION_PASSING;   /* player is passing exit */
+    }
+    else if (element == EL_EMPTY)
+    {
+      sound_action = ACTION_MOVING;            /* nothing to walk on */
+    }
+
+    /* play sound from background or player, whatever is available */
+    if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
+      PlayLevelSoundElementAction(x, y, sound_element, sound_action);
+    else
+      PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
+  }
+  else if (player_can_move &&
+          IS_PASSABLE(element) && canPassField(x, y, move_direction))
   {
-#if 0
-    case EL_SP_PORT_LEFT:
-    case EL_SP_PORT_RIGHT:
-    case EL_SP_PORT_UP:
-    case EL_SP_PORT_DOWN:
-    case EL_SP_PORT_HORIZONTAL:
-    case EL_SP_PORT_VERTICAL:
-    case EL_SP_PORT_ANY:
-    case EL_SP_GRAVITY_PORT_LEFT:
-    case EL_SP_GRAVITY_PORT_RIGHT:
-    case EL_SP_GRAVITY_PORT_UP:
-    case EL_SP_GRAVITY_PORT_DOWN:
-#if 1
-      if (!canEnterSupaplexPort(x, y, dx, dy))
-       return MF_NO_ACTION;
-#else
-      if ((dx == -1 &&
-          element != EL_SP_PORT_LEFT &&
-          element != EL_SP_GRAVITY_PORT_LEFT &&
-          element != EL_SP_PORT_HORIZONTAL &&
-          element != EL_SP_PORT_ANY) ||
-         (dx == +1 &&
-          element != EL_SP_PORT_RIGHT &&
-          element != EL_SP_GRAVITY_PORT_RIGHT &&
-          element != EL_SP_PORT_HORIZONTAL &&
-          element != EL_SP_PORT_ANY) ||
-         (dy == -1 &&
-          element != EL_SP_PORT_UP &&
-          element != EL_SP_GRAVITY_PORT_UP &&
-          element != EL_SP_PORT_VERTICAL &&
-          element != EL_SP_PORT_ANY) ||
-         (dy == +1 &&
-          element != EL_SP_PORT_DOWN &&
-          element != EL_SP_GRAVITY_PORT_DOWN &&
-          element != EL_SP_PORT_VERTICAL &&
-          element != EL_SP_PORT_ANY) ||
-         !IN_LEV_FIELD(nextx, nexty) ||
-         !IS_FREE(nextx, nexty))
-       return MF_NO_ACTION;
-#endif
+    if (!ACCESS_FROM(element, opposite_direction))
+      return MP_NO_ACTION;     /* field not accessible from this direction */
+
+    if (CAN_MOVE(element))     /* only fixed elements can be passed! */
+      return MP_NO_ACTION;
+
+    if (IS_EM_GATE(element))
+    {
+      if (!player->key[EM_GATE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EM_GATE_GRAY(element))
+    {
+      if (!player->key[EM_GATE_GRAY_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EM_GATE_GRAY_ACTIVE(element))
+    {
+      if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE(element))
+    {
+      if (!player->key[EMC_GATE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE_GRAY(element))
+    {
+      if (!player->key[EMC_GATE_GRAY_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (IS_EMC_GATE_GRAY_ACTIVE(element))
+    {
+      if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
+       return MP_NO_ACTION;
+    }
+    else if (element == EL_DC_GATE_WHITE ||
+            element == EL_DC_GATE_WHITE_GRAY ||
+            element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
+    {
+      if (player->num_white_keys == 0)
+       return MP_NO_ACTION;
 
+      player->num_white_keys--;
+    }
+    else if (IS_SP_PORT(element))
+    {
       if (element == EL_SP_GRAVITY_PORT_LEFT ||
          element == EL_SP_GRAVITY_PORT_RIGHT ||
          element == EL_SP_GRAVITY_PORT_UP ||
          element == EL_SP_GRAVITY_PORT_DOWN)
-       game.gravity = !game.gravity;
+       player->gravity = !player->gravity;
+      else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
+              element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
+              element == EL_SP_GRAVITY_ON_PORT_UP ||
+              element == EL_SP_GRAVITY_ON_PORT_DOWN)
+       player->gravity = TRUE;
+      else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
+              element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
+              element == EL_SP_GRAVITY_OFF_PORT_UP ||
+              element == EL_SP_GRAVITY_OFF_PORT_DOWN)
+       player->gravity = FALSE;
+    }
 
-      /* automatically move to the next field with double speed */
-      player->programmed_action = move_direction;
-#if 1
-      if (player->move_delay_reset_counter == 0)
-      {
-       player->move_delay_reset_counter = 2;   /* two double speed steps */
+    /* automatically move to the next field with double speed */
+    player->programmed_action = move_direction;
 
-       DOUBLE_PLAYER_SPEED(player);
-      }
-#else
-      player->move_delay_reset_counter = 2;
+    if (player->move_delay_reset_counter == 0)
+    {
+      player->move_delay_reset_counter = 2;    /* two double speed steps */
 
       DOUBLE_PLAYER_SPEED(player);
-#endif
-
-#if 0
-      printf("::: passing port %d,%d [%d]\n", x, y, FrameCounter);
-#endif
-
-      PlayLevelSound(x, y, SND_CLASS_SP_PORT_PASSING);
-      break;
-#endif
+    }
 
-#if 0
-    case EL_TUBE_ANY:
-    case EL_TUBE_VERTICAL:
-    case EL_TUBE_HORIZONTAL:
-    case EL_TUBE_VERTICAL_LEFT:
-    case EL_TUBE_VERTICAL_RIGHT:
-    case EL_TUBE_HORIZONTAL_UP:
-    case EL_TUBE_HORIZONTAL_DOWN:
-    case EL_TUBE_LEFT_UP:
-    case EL_TUBE_LEFT_DOWN:
-    case EL_TUBE_RIGHT_UP:
-    case EL_TUBE_RIGHT_DOWN:
-      {
-       int i = 0;
-       int tube_enter_directions[][2] =
-       {
-         { EL_TUBE_ANY,                MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
-         { EL_TUBE_VERTICAL,                                MV_UP | MV_DOWN },
-         { EL_TUBE_HORIZONTAL,         MV_LEFT | MV_RIGHT                   },
-         { EL_TUBE_VERTICAL_LEFT,                MV_RIGHT | MV_UP | MV_DOWN },
-         { EL_TUBE_VERTICAL_RIGHT,     MV_LEFT            | MV_UP | MV_DOWN },
-         { EL_TUBE_HORIZONTAL_UP,      MV_LEFT | MV_RIGHT |         MV_DOWN },
-         { EL_TUBE_HORIZONTAL_DOWN,    MV_LEFT | MV_RIGHT | MV_UP           },
-         { EL_TUBE_LEFT_UP,                      MV_RIGHT |         MV_DOWN },
-         { EL_TUBE_LEFT_DOWN,                    MV_RIGHT | MV_UP           },
-         { EL_TUBE_RIGHT_UP,           MV_LEFT |                    MV_DOWN },
-         { EL_TUBE_RIGHT_DOWN,         MV_LEFT |            MV_UP           },
-         { -1,                         MV_NO_MOVING                         }
-       };
-
-       while (tube_enter_directions[i][0] != element)
-       {
-         i++;
-         if (tube_enter_directions[i][0] == -1)        /* should not happen */
-           break;
-       }
+    PlayLevelSoundAction(x, y, ACTION_PASSING);
+  }
+  else if (player_can_move_or_snap && IS_DIGGABLE(element))
+  {
+    RemoveField(x, y);
 
-       if (!(tube_enter_directions[i][1] & move_direction))
-         return MF_NO_ACTION;  /* tube has no opening in this direction */
+    if (mode != DF_SNAP)
+    {
+      GfxElement[x][y] = GFX_ELEMENT(element);
+      player->is_digging = TRUE;
+    }
 
-       PlayLevelSound(x, y, SND_CLASS_TUBE_WALKING);
-      }
-      break;
-#endif
+    PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
 
-    default:
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
+                                       player->index_bit, dig_side);
 
-#if 1
-      if (IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
-#else
-      if (IS_WALKABLE(element))
-#endif
-      {
-       int sound_element = SND_ELEMENT(element);
-       int sound_action = ACTION_WALKING;
+    if (mode == DF_SNAP)
+    {
+      if (level.block_snap_field)
+       setFieldForSnapping(x, y, element, move_direction);
+      else
+       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
 
-#if 0
-       if (!ACCESS_FROM(element, opposite_direction))
-         return MF_NO_ACTION;  /* field not accessible from this direction */
-#endif
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                         player->index_bit, dig_side);
+    }
+  }
+  else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
+  {
+    RemoveField(x, y);
 
-#if 0
-       if (element == EL_EMPTY_SPACE &&
-           game.gravity && !player->is_auto_moving &&
-           canFallDown(player) && move_direction != MV_DOWN)
-         return MF_NO_ACTION;  /* player cannot walk here due to gravity */
-#endif
+    if (is_player && mode != DF_SNAP)
+    {
+      GfxElement[x][y] = element;
+      player->is_collecting = TRUE;
+    }
 
-       if (IS_RND_GATE(element))
-       {
-         if (!player->key[RND_GATE_NR(element)])
-           return MF_NO_ACTION;
-       }
-       else if (IS_RND_GATE_GRAY(element))
-       {
-         if (!player->key[RND_GATE_GRAY_NR(element)])
-           return MF_NO_ACTION;
-       }
-       else if (element == EL_EXIT_OPEN ||
-                element == EL_SP_EXIT_OPEN ||
-                element == EL_SP_EXIT_OPENING)
-       {
-         sound_action = ACTION_PASSING;        /* player is passing exit */
-       }
-       else if (element == EL_EMPTY)
-       {
-         sound_action = ACTION_MOVING;         /* nothing to walk on */
-       }
+    if (element == EL_SPEED_PILL)
+    {
+      player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
+    }
+    else if (element == EL_EXTRA_TIME && level.time > 0)
+    {
+      TimeLeft += level.extra_time;
 
-       /* play sound from background or player, whatever is available */
-       if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
-         PlayLevelSoundElementAction(x, y, sound_element, sound_action);
-       else
-         PlayLevelSoundElementAction(x, y, player->element_nr, sound_action);
+      game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
-       break;
-      }
-#if 1
-      else if (IS_PASSABLE(element) && canPassField(x, y, move_direction))
-#else
-      else if (IS_PASSABLE(element))
-#endif
-      {
-#if 0
-       if (!canPassField(x, y, move_direction))
-         return MF_NO_ACTION;
-#else
+      DisplayGameControlValues();
+    }
+    else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
+    {
+      player->shield_normal_time_left += level.shield_normal_time;
+      if (element == EL_SHIELD_DEADLY)
+       player->shield_deadly_time_left += level.shield_deadly_time;
+    }
+    else if (element == EL_DYNAMITE ||
+            element == EL_EM_DYNAMITE ||
+            element == EL_SP_DISK_RED)
+    {
+      if (player->inventory_size < MAX_INVENTORY_SIZE)
+       player->inventory_element[player->inventory_size++] = element;
 
-#if 0
-#if 1
-       if (!IN_LEV_FIELD(nextx, nexty) || IS_PLAYER(nextx, nexty) ||
-           !IS_WALKABLE_FROM(Feld[nextx][nexty], move_direction) ||
-           (!level.can_pass_to_walkable && !IS_FREE(nextx, nexty)))
-         return MF_NO_ACTION;
-#else
-       if (!IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty))
-         return MF_NO_ACTION;
-#endif
-#endif
+      DrawGameDoorValues();
+    }
+    else if (element == EL_DYNABOMB_INCREASE_NUMBER)
+    {
+      player->dynabomb_count++;
+      player->dynabombs_left++;
+    }
+    else if (element == EL_DYNABOMB_INCREASE_SIZE)
+    {
+      player->dynabomb_size++;
+    }
+    else if (element == EL_DYNABOMB_INCREASE_POWER)
+    {
+      player->dynabomb_xl = TRUE;
+    }
+    else if (IS_KEY(element))
+    {
+      player->key[KEY_NR(element)] = TRUE;
 
-#if 1
-       if (!ACCESS_FROM(element, opposite_direction))
-         return MF_NO_ACTION;  /* field not accessible from this direction */
-#else
-       if (IS_CUSTOM_ELEMENT(element) &&
-           !ACCESS_FROM(element, opposite_direction))
-         return MF_NO_ACTION;  /* field not accessible from this direction */
-#endif
+      DrawGameDoorValues();
+    }
+    else if (element == EL_DC_KEY_WHITE)
+    {
+      player->num_white_keys++;
 
-#if 1
-       if (CAN_MOVE(element))  /* only fixed elements can be passed! */
-         return MF_NO_ACTION;
-#endif
+      /* display white keys? */
+      /* DrawGameDoorValues(); */
+    }
+    else if (IS_ENVELOPE(element))
+    {
+      player->show_envelope = element;
+    }
+    else if (element == EL_EMC_LENSES)
+    {
+      game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
 
-#endif
+      RedrawAllInvisibleElementsForLenses();
+    }
+    else if (element == EL_EMC_MAGNIFIER)
+    {
+      game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
 
-       if (IS_EM_GATE(element))
-       {
-         if (!player->key[EM_GATE_NR(element)])
-           return MF_NO_ACTION;
-       }
-       else if (IS_EM_GATE_GRAY(element))
-       {
-         if (!player->key[EM_GATE_GRAY_NR(element)])
-           return MF_NO_ACTION;
-       }
-       else if (IS_SP_PORT(element))
-       {
-         if (element == EL_SP_GRAVITY_PORT_LEFT ||
-             element == EL_SP_GRAVITY_PORT_RIGHT ||
-             element == EL_SP_GRAVITY_PORT_UP ||
-             element == EL_SP_GRAVITY_PORT_DOWN)
-           game.gravity = !game.gravity;
-         else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
-                  element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
-                  element == EL_SP_GRAVITY_ON_PORT_UP ||
-                  element == EL_SP_GRAVITY_ON_PORT_DOWN)
-           game.gravity = TRUE;
-         else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
-                  element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
-                  element == EL_SP_GRAVITY_OFF_PORT_UP ||
-                  element == EL_SP_GRAVITY_OFF_PORT_DOWN)
-           game.gravity = FALSE;
-       }
+      RedrawAllInvisibleElementsForMagnifier();
+    }
+    else if (IS_DROPPABLE(element) ||
+            IS_THROWABLE(element))     /* can be collected and dropped */
+    {
+      int i;
 
-       /* automatically move to the next field with double speed */
-       player->programmed_action = move_direction;
-#if 1
-       if (player->move_delay_reset_counter == 0)
-       {
-         player->move_delay_reset_counter = 2; /* two double speed steps */
+      if (collect_count == 0)
+       player->inventory_infinite_element = element;
+      else
+       for (i = 0; i < collect_count; i++)
+         if (player->inventory_size < MAX_INVENTORY_SIZE)
+           player->inventory_element[player->inventory_size++] = element;
 
-         DOUBLE_PLAYER_SPEED(player);
-       }
-#else
-       player->move_delay_reset_counter = 2;
+      DrawGameDoorValues();
+    }
+    else if (collect_count > 0)
+    {
+      local_player->gems_still_needed -= collect_count;
+      if (local_player->gems_still_needed < 0)
+       local_player->gems_still_needed = 0;
 
-       DOUBLE_PLAYER_SPEED(player);
-#endif
+      game.snapshot.collected_item = TRUE;
 
-       PlayLevelSoundAction(x, y, ACTION_PASSING);
+      game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
 
-       break;
-      }
-      else if (IS_DIGGABLE(element))
-      {
-       RemoveField(x, y);
+      DisplayGameControlValues();
+    }
 
-       if (mode != DF_SNAP)
-       {
-#if 1
-         GfxElement[x][y] = GFX_ELEMENT(element);
-#else
-         GfxElement[x][y] =
-           (GFX_CRUMBLED(element) ? EL_SAND : GFX_ELEMENT(element));
-#endif
-         player->is_digging = TRUE;
-       }
+    RaiseScoreElement(element);
+    PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
 
-       PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+    if (is_player)
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
+                                         player->index_bit, dig_side);
 
-       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
-                                           player->index_bit, dig_side);
+    if (mode == DF_SNAP)
+    {
+      if (level.block_snap_field)
+       setFieldForSnapping(x, y, element, move_direction);
+      else
+       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
 
-#if 1
-       if (mode == DF_SNAP)
-         TestIfElementTouchesCustomElement(x, y);      /* for empty space */
-#endif
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                         player->index_bit, dig_side);
+    }
+  }
+  else if (player_can_move_or_snap && IS_PUSHABLE(element))
+  {
+    if (mode == DF_SNAP && element != EL_BD_ROCK)
+      return MP_NO_ACTION;
 
-       break;
-      }
-      else if (IS_COLLECTIBLE(element))
-      {
-       RemoveField(x, y);
+    if (CAN_FALL(element) && dy)
+      return MP_NO_ACTION;
 
-       if (is_player && mode != DF_SNAP)
-       {
-         GfxElement[x][y] = element;
-         player->is_collecting = TRUE;
-       }
+    if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
+       !(element == EL_SPRING && level.use_spring_bug))
+      return MP_NO_ACTION;
 
-       if (element == EL_SPEED_PILL)
-         player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
-       else if (element == EL_EXTRA_TIME && level.time > 0)
-       {
-         TimeLeft += 10;
-         DrawGameValue_Time(TimeLeft);
-       }
-       else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
-       {
-         player->shield_normal_time_left += 10;
-         if (element == EL_SHIELD_DEADLY)
-           player->shield_deadly_time_left += 10;
-       }
-       else if (element == EL_DYNAMITE || element == EL_SP_DISK_RED)
-       {
-         if (player->inventory_size < MAX_INVENTORY_SIZE)
-           player->inventory_element[player->inventory_size++] = element;
+    if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
+       ((move_direction & MV_VERTICAL &&
+         ((element_info[element].move_pattern & MV_LEFT &&
+           IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
+          (element_info[element].move_pattern & MV_RIGHT &&
+           IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
+        (move_direction & MV_HORIZONTAL &&
+         ((element_info[element].move_pattern & MV_UP &&
+           IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
+          (element_info[element].move_pattern & MV_DOWN &&
+           IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
+      return MP_NO_ACTION;
 
-         DrawGameValue_Dynamite(local_player->inventory_size);
-       }
-       else if (element == EL_DYNABOMB_INCREASE_NUMBER)
-       {
-         player->dynabomb_count++;
-         player->dynabombs_left++;
-       }
-       else if (element == EL_DYNABOMB_INCREASE_SIZE)
-       {
-         player->dynabomb_size++;
-       }
-       else if (element == EL_DYNABOMB_INCREASE_POWER)
-       {
-         player->dynabomb_xl = TRUE;
-       }
-       else if (IS_KEY(element))
-       {
-         player->key[KEY_NR(element)] = TRUE;
+    /* do not push elements already moving away faster than player */
+    if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
+       ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
+      return MP_NO_ACTION;
 
-         DrawGameValue_Keys(player->key);
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+    {
+      if (player->push_delay_value == -1 || !player_was_pushing)
+       player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    }
+    else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+    {
+      if (player->push_delay_value == -1)
+       player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    }
+    else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
+    {
+      if (!player->is_pushing)
+       player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    }
 
-         redraw_mask |= REDRAW_DOOR_1;
-       }
-       else if (IS_ENVELOPE(element))
-       {
-#if 1
-         player->show_envelope = element;
-#else
-         ShowEnvelope(element - EL_ENVELOPE_1);
-#endif
-       }
-       else if (IS_DROPPABLE(element) ||
-                IS_THROWABLE(element)) /* can be collected and dropped */
-       {
-         int i;
+    player->is_pushing = TRUE;
+    player->is_active = TRUE;
 
-         if (element_info[element].collect_count == 0)
-           player->inventory_infinite_element = element;
-         else
-           for (i = 0; i < element_info[element].collect_count; i++)
-             if (player->inventory_size < MAX_INVENTORY_SIZE)
-               player->inventory_element[player->inventory_size++] = element;
+    if (!(IN_LEV_FIELD(nextx, nexty) &&
+         (IS_FREE(nextx, nexty) ||
+          (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;
 
-         DrawGameValue_Dynamite(local_player->inventory_size);
-       }
-       else if (element_info[element].collect_count > 0)
-       {
-         local_player->gems_still_needed -=
-           element_info[element].collect_count;
-         if (local_player->gems_still_needed < 0)
-           local_player->gems_still_needed = 0;
+    if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
+      return MP_NO_ACTION;
 
-         DrawGameValue_Emeralds(local_player->gems_still_needed);
-       }
+    if (player->push_delay == -1)      /* new pushing; restart delay */
+      player->push_delay = 0;
 
-       RaiseScoreElement(element);
-       PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
+    if (player->push_delay < player->push_delay_value &&
+       !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
+       element != EL_SPRING && element != EL_BALLOON)
+    {
+      /* make sure that there is no move delay before next try to push */
+      if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+       player->move_delay = 0;
 
-       if (is_player)
-         CheckTriggeredElementChangeByPlayer(x, y, element,
-                                             CE_PLAYER_COLLECTS_X,
-                                             player->index_bit, dig_side);
+      return MP_NO_ACTION;
+    }
 
-#if 1
-       if (mode == DF_SNAP)
-         TestIfElementTouchesCustomElement(x, y);      /* for empty space */
-#endif
+    if (IS_CUSTOM_ELEMENT(element) &&
+       CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
+    {
+      if (!DigFieldByCE(nextx, nexty, element))
+       return MP_NO_ACTION;
+    }
 
-       break;
-      }
-      else if (IS_PUSHABLE(element))
+    if (IS_SB_ELEMENT(element))
+    {
+      if (element == EL_SOKOBAN_FIELD_FULL)
       {
-       if (mode == DF_SNAP && element != EL_BD_ROCK)
-         return MF_NO_ACTION;
+       Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
+       local_player->sokobanfields_still_needed++;
+      }
 
-       if (CAN_FALL(element) && dy)
-         return MF_NO_ACTION;
+      if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
+      {
+       Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
+       local_player->sokobanfields_still_needed--;
+      }
 
-       if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
-           !(element == EL_SPRING && level.use_spring_bug))
-         return MF_NO_ACTION;
+      Feld[x][y] = EL_SOKOBAN_OBJECT;
 
-#if 1
-       if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
-           ((move_direction & MV_VERTICAL &&
-             ((element_info[element].move_pattern & MV_LEFT &&
-               IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
-              (element_info[element].move_pattern & MV_RIGHT &&
-               IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
-            (move_direction & MV_HORIZONTAL &&
-             ((element_info[element].move_pattern & MV_UP &&
-               IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
-              (element_info[element].move_pattern & MV_DOWN &&
-               IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
-         return MF_NO_ACTION;
-#endif
+      if (Back[x][y] == Back[nextx][nexty])
+       PlayLevelSoundAction(x, y, ACTION_PUSHING);
+      else if (Back[x][y] != 0)
+       PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
+                                   ACTION_EMPTYING);
+      else
+       PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
+                                   ACTION_FILLING);
 
-#if 1
-       /* do not push elements already moving away faster than player */
-       if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
-           ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
-         return MF_NO_ACTION;
-#else
-       if (element == EL_SPRING && MovDir[x][y] != MV_NO_MOVING)
-         return MF_NO_ACTION;
-#endif
+      if (local_player->sokobanfields_still_needed == 0 &&
+         (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
+      {
+       PlayerWins(player);
 
-#if 1
+       PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
+      }
+    }
+    else
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
 
-#if 1
-       if (game.engine_version >= VERSION_IDENT(3,1,0,0))
-       {
-         if (player->push_delay_value == -1 || !player_was_pushing)
-           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       }
-       else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
-       {
-         if (player->push_delay_value == -1)
-           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       }
-#else
-       if (game.engine_version >= VERSION_IDENT(3,0,7,1))
-       {
-         if (player->push_delay_value == -1 || !player_was_pushing)
-           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       }
-#endif
-       else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
-       {
-         if (!player->is_pushing)
-           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       }
+    InitMovingField(x, y, move_direction);
+    GfxAction[x][y] = ACTION_PUSHING;
 
-       /*
-       if (game.engine_version >= VERSION_IDENT(2,2,0,7) &&
-           (game.engine_version < VERSION_IDENT(3,0,7,1) ||
-            !player_is_pushing))
-         player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       */
-#else
-       if (!player->is_pushing &&
-           game.engine_version >= VERSION_IDENT(2,2,0,7))
-         player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-#endif
+    if (mode == DF_SNAP)
+      ContinueMoving(x, y);
+    else
+      MovPos[x][y] = (dx != 0 ? dx : dy);
 
-#if 0
-       printf("::: push delay: %ld -> %ld [%d, %d] [%d / %d] [%d '%s': %d]\n",
-              player->push_delay, player->push_delay_value,
-              FrameCounter, game.engine_version,
-              player_was_pushing, player->is_pushing,
-              element, element_info[element].token_name,
-              GET_NEW_PUSH_DELAY(element));
-#endif
+    Pushed[x][y] = TRUE;
+    Pushed[nextx][nexty] = TRUE;
 
-       player->is_pushing = TRUE;
+    if (game.engine_version < VERSION_IDENT(2,2,0,7))
+      player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+    else
+      player->push_delay_value = -1;   /* get new value later */
 
-       if (!(IN_LEV_FIELD(nextx, nexty) &&
-             (IS_FREE(nextx, nexty) ||
-              (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY &&
-               IS_SB_ELEMENT(element)))))
-         return MF_NO_ACTION;
+    /* check for element change _after_ element has been pushed */
+    if (game.use_change_when_pushing_bug)
+    {
+      CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
+                                player->index_bit, dig_side);
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
+                                         player->index_bit, dig_side);
+    }
+  }
+  else if (IS_SWITCHABLE(element))
+  {
+    if (PLAYER_SWITCHING(player, x, y))
+    {
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
+                                         player->index_bit, dig_side);
 
-       if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
-         return MF_NO_ACTION;
+      return MP_ACTION;
+    }
 
-#if USE_NEW_PUSH_DELAY
+    player->is_switching = TRUE;
+    player->switch_x = x;
+    player->switch_y = y;
 
-#if 0
-       if ( (player->push_delay == -1) != (player->push_delay2 == 0) )
-         printf("::: ALERT: %d, %d [%d / %d]\n",
-                player->push_delay, player->push_delay2,
-                FrameCounter, FrameCounter / 50);
-#endif
+    PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
 
-       if (player->push_delay == -1)   /* new pushing; restart delay */
-         player->push_delay = 0;
-#else
-       if (player->push_delay == 0)    /* new pushing; restart delay */
-         player->push_delay = FrameCounter;
-#endif
+    if (element == EL_ROBOT_WHEEL)
+    {
+      Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
+      ZX = x;
+      ZY = y;
 
-#if USE_NEW_PUSH_DELAY
-#if 0
-       if ( (player->push_delay > 0) != (!xxx_fr) )
-         printf("::: PUSH BUG! %d, (%d -> %d) %d [%d / %d]\n",
-                player->push_delay,
-                xxx_pdv2, player->push_delay2, player->push_delay_value,
-                FrameCounter, FrameCounter / 50);
-#endif
+      game.robot_wheel_active = TRUE;
 
-#if 0
-       if (player->push_delay > 0 &&
-           !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
-           element != EL_SPRING && element != EL_BALLOON)
-#else
-       /* !!! */
-       if (player->push_delay < player->push_delay_value &&
-           !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
-           element != EL_SPRING && element != EL_BALLOON)
-#endif
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_SP_TERMINAL)
+    {
+      int xx, yy;
 
-#else
-       if (!FrameReached(&player->push_delay, player->push_delay_value) &&
-           !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
-           element != EL_SPRING && element != EL_BALLOON)
-#endif
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
        {
-         /* make sure that there is no move delay before next try to push */
-#if USE_NEW_MOVE_DELAY
-         if (game.engine_version >= VERSION_IDENT(3,0,7,1))
-           player->move_delay = 0;
-#else
-         if (game.engine_version >= VERSION_IDENT(3,0,7,1))
-           player->move_delay = INITIAL_MOVE_DELAY_OFF;
-#endif
-
-         return MF_NO_ACTION;
+         Bang(xx, yy);
        }
-
-#if 0
-       printf("::: NOW PUSHING... [%d]\n", FrameCounter);
-#endif
-
-       if (IS_SB_ELEMENT(element))
+       else if (Feld[xx][yy] == EL_SP_TERMINAL)
        {
-         if (element == EL_SOKOBAN_FIELD_FULL)
-         {
-           Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
-           local_player->sokobanfields_still_needed++;
-         }
-
-         if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
-         {
-           Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
-           local_player->sokobanfields_still_needed--;
-         }
-
-         Feld[x][y] = EL_SOKOBAN_OBJECT;
-
-         if (Back[x][y] == Back[nextx][nexty])
-           PlayLevelSoundAction(x, y, ACTION_PUSHING);
-         else if (Back[x][y] != 0)
-           PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
-                                       ACTION_EMPTYING);
-         else
-           PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
-                                       ACTION_FILLING);
-
-         if (local_player->sokobanfields_still_needed == 0 &&
-             game.emulation == EMU_SOKOBAN)
-         {
-           player->LevelSolved = player->GameOver = TRUE;
-           PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
-         }
-       }
-       else
-         PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
-
-       InitMovingField(x, y, move_direction);
-       GfxAction[x][y] = ACTION_PUSHING;
-
-       if (mode == DF_SNAP)
-         ContinueMoving(x, y);
-       else
-         MovPos[x][y] = (dx != 0 ? dx : dy);
-
-       Pushed[x][y] = TRUE;
-       Pushed[nextx][nexty] = TRUE;
-
-       if (game.engine_version < VERSION_IDENT(2,2,0,7))
-         player->push_delay_value = GET_NEW_PUSH_DELAY(element);
-       else
-         player->push_delay_value = -1;        /* get new value later */
+         Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
 
-#if USE_PUSH_BUGFIX
-       /* now: check for element change _after_ element has been pushed! */
-#if 1
-       if (game.use_change_when_pushing_bug)
-#else
-       if (game.engine_version < VERSION_IDENT(3,1,0,0))
-#endif
-       {
-         CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
-                                    player->index_bit, dig_side);
-         CheckTriggeredElementChangeByPlayer(x,y, element, CE_PLAYER_PUSHES_X,
-                                             player->index_bit, dig_side);
+         ResetGfxAnimation(xx, yy);
+         TEST_DrawLevelField(xx, yy);
        }
+      }
+    }
+    else if (IS_BELT_SWITCH(element))
+    {
+      ToggleBeltSwitch(x, y);
+    }
+    else if (element == EL_SWITCHGATE_SWITCH_UP ||
+            element == EL_SWITCHGATE_SWITCH_DOWN ||
+            element == EL_DC_SWITCHGATE_SWITCH_UP ||
+            element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+    {
+      ToggleSwitchgateSwitch(x, y);
+    }
+    else if (element == EL_LIGHT_SWITCH ||
+            element == EL_LIGHT_SWITCH_ACTIVE)
+    {
+      ToggleLightSwitch(x, y);
+    }
+    else if (element == EL_TIMEGATE_SWITCH ||
+            element == EL_DC_TIMEGATE_SWITCH)
+    {
+      ActivateTimegateSwitch(x, y);
+    }
+    else if (element == EL_BALLOON_SWITCH_LEFT  ||
+            element == EL_BALLOON_SWITCH_RIGHT ||
+            element == EL_BALLOON_SWITCH_UP    ||
+            element == EL_BALLOON_SWITCH_DOWN  ||
+            element == EL_BALLOON_SWITCH_NONE  ||
+            element == EL_BALLOON_SWITCH_ANY)
+    {
+      game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT  ? MV_LEFT  :
+                            element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
+                            element == EL_BALLOON_SWITCH_UP    ? MV_UP    :
+                            element == EL_BALLOON_SWITCH_DOWN  ? MV_DOWN  :
+                            element == EL_BALLOON_SWITCH_NONE  ? MV_NONE  :
+                            move_direction);
+    }
+    else if (element == EL_LAMP)
+    {
+      Feld[x][y] = EL_LAMP_ACTIVE;
+      local_player->lights_still_needed--;
 
-#else
-
-#if 1
-       /* check for element change _after_ element has been pushed! */
-#else
-
-#if 1
-       /* !!! TEST ONLY !!! */
-       CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
-                                  player->index_bit, dig_side);
-       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
-                                           player->index_bit, dig_side);
-#else
-       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
-                                           player->index_bit, dig_side);
-       CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
-                                  player->index_bit, dig_side);
-#endif
-#endif
-
-#endif
+      ResetGfxAnimation(x, y);
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_TIME_ORB_FULL)
+    {
+      Feld[x][y] = EL_TIME_ORB_EMPTY;
 
-       break;
-      }
-      else if (IS_SWITCHABLE(element))
+      if (level.time > 0 || level.use_time_orb_bug)
       {
-       if (PLAYER_SWITCHING(player, x, y))
-       {
-         CheckTriggeredElementChangeByPlayer(x,y, element,
-                                             CE_PLAYER_PRESSES_X,
-                                             player->index_bit, dig_side);
-
-         return MF_ACTION;
-       }
+       TimeLeft += level.time_orb_time;
+       game.no_time_limit = FALSE;
 
-       player->is_switching = TRUE;
-       player->switch_x = x;
-       player->switch_y = y;
+       game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
-       PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
+       DisplayGameControlValues();
+      }
 
-       if (element == EL_ROBOT_WHEEL)
-       {
-         Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
-         ZX = x;
-         ZY = y;
+      ResetGfxAnimation(x, y);
+      TEST_DrawLevelField(x, y);
+    }
+    else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
+            element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
+    {
+      int xx, yy;
 
-         DrawLevelField(x, y);
-       }
-       else if (element == EL_SP_TERMINAL)
-       {
-         int xx, yy;
+      game.ball_state = !game.ball_state;
 
-         for (yy = 0; yy < lev_fieldy; yy++) for (xx=0; xx < lev_fieldx; xx++)
-         {
-           if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
-             Bang(xx, yy);
-           else if (Feld[xx][yy] == EL_SP_TERMINAL)
-             Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
-         }
-       }
-       else if (IS_BELT_SWITCH(element))
-       {
-         ToggleBeltSwitch(x, y);
-       }
-       else if (element == EL_SWITCHGATE_SWITCH_UP ||
-                element == EL_SWITCHGATE_SWITCH_DOWN)
-       {
-         ToggleSwitchgateSwitch(x, y);
-       }
-       else if (element == EL_LIGHT_SWITCH ||
-                element == EL_LIGHT_SWITCH_ACTIVE)
-       {
-         ToggleLightSwitch(x, y);
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       int e = Feld[xx][yy];
 
-#if 0
-         PlayLevelSound(x, y, element == EL_LIGHT_SWITCH ?
-                        SND_LIGHT_SWITCH_ACTIVATING :
-                        SND_LIGHT_SWITCH_DEACTIVATING);
-#endif
-       }
-       else if (element == EL_TIMEGATE_SWITCH)
-       {
-         ActivateTimegateSwitch(x, y);
-       }
-       else if (element == EL_BALLOON_SWITCH_LEFT ||
-                element == EL_BALLOON_SWITCH_RIGHT ||
-                element == EL_BALLOON_SWITCH_UP ||
-                element == EL_BALLOON_SWITCH_DOWN ||
-                element == EL_BALLOON_SWITCH_ANY)
+       if (game.ball_state)
        {
-         if (element == EL_BALLOON_SWITCH_ANY)
-           game.balloon_dir = move_direction;
-         else
-           game.balloon_dir = (element == EL_BALLOON_SWITCH_LEFT  ? MV_LEFT :
-                               element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
-                               element == EL_BALLOON_SWITCH_UP    ? MV_UP :
-                               element == EL_BALLOON_SWITCH_DOWN  ? MV_DOWN :
-                               MV_NO_MOVING);
+         if (e == EL_EMC_MAGIC_BALL)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
+         else if (e == EL_EMC_MAGIC_BALL_SWITCH)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
        }
-       else if (element == EL_LAMP)
+       else
        {
-         Feld[x][y] = EL_LAMP_ACTIVE;
-         local_player->lights_still_needed--;
-
-         ResetGfxAnimation(x, y);
-         DrawLevelField(x, y);
+         if (e == EL_EMC_MAGIC_BALL_ACTIVE)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL);
+         else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
+           CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
        }
-       else if (element == EL_TIME_ORB_FULL)
-       {
-         Feld[x][y] = EL_TIME_ORB_EMPTY;
-         TimeLeft += 10;
-         DrawGameValue_Time(TimeLeft);
+      }
+    }
 
-         ResetGfxAnimation(x, y);
-         DrawLevelField(x, y);
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
+                                       player->index_bit, dig_side);
 
-#if 0
-         PlaySoundStereo(SND_TIME_ORB_FULL_COLLECTING, SOUND_MIDDLE);
-#endif
-       }
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
+                                       player->index_bit, dig_side);
 
-       CheckTriggeredElementChangeByPlayer(x, y, element,
-                                           CE_SWITCH_OF_X,
-                                           player->index_bit, dig_side);
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
+                                       player->index_bit, dig_side);
 
-       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
-                                           player->index_bit, dig_side);
+    return MP_ACTION;
+  }
+  else
+  {
+    if (!PLAYER_SWITCHING(player, x, y))
+    {
+      player->is_switching = TRUE;
+      player->switch_x = x;
+      player->switch_y = y;
 
-       return MF_ACTION;
-      }
-      else
-      {
-       if (!PLAYER_SWITCHING(player, x, y))
-       {
-         player->is_switching = TRUE;
-         player->switch_x = x;
-         player->switch_y = y;
+      CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
+                                player->index_bit, dig_side);
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
+                                         player->index_bit, dig_side);
 
-#if 1
-         /* !!! TEST ONLY !!! */
-         CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
-                                    player->index_bit, dig_side);
-         CheckTriggeredElementChangeByPlayer(x, y, element,
-                                             CE_SWITCH_OF_X,
-                                             player->index_bit, dig_side);
-#else
-         CheckTriggeredElementChangeByPlayer(x, y, element,
-                                             CE_SWITCH_OF_X,
-                                             player->index_bit, dig_side);
-         CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
-                                    player->index_bit, dig_side);
-#endif
-       }
+      CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
+                                player->index_bit, dig_side);
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
+                                         player->index_bit, dig_side);
+    }
 
-#if 1
-       /* !!! TEST ONLY !!! (this breaks "machine", level 000) */
-       CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
-                                  player->index_bit, dig_side);
-       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
-                                           player->index_bit, dig_side);
-#else
-       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
-                                           player->index_bit, dig_side);
-       CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
-                                  player->index_bit, dig_side);
-#endif
-      }
+    CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
+                              player->index_bit, dig_side);
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
+                                       player->index_bit, dig_side);
 
-      return MF_NO_ACTION;
+    return MP_NO_ACTION;
   }
 
-#if USE_NEW_PUSH_DELAY
   player->push_delay = -1;
-#else
-  player->push_delay = 0;
-#endif
 
-#if USE_PENGUIN_COLLECT_BUGFIX
   if (is_player)               /* function can also be called by EL_PENGUIN */
-#endif
   {
     if (Feld[x][y] != element)         /* really digged/collected something */
+    {
       player->is_collecting = !player->is_digging;
+      player->is_active = TRUE;
+    }
+  }
+
+  return MP_MOVING;
+}
+
+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);
   }
 
-  return MF_MOVING;
+  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;
 }
 
-boolean SnapField(struct PlayerInfo *player, int dx, int dy)
+static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 {
   int jx = player->jx, jy = player->jy;
   int x = jx + dx, y = jy + dy;
-  int snap_direction = (dx == -1 ? MV_LEFT :
+  int snap_direction = (dx == -1 ? MV_LEFT  :
                        dx == +1 ? MV_RIGHT :
-                       dy == -1 ? MV_UP :
-                       dy == +1 ? MV_DOWN : MV_NO_MOVING);
+                       dy == -1 ? MV_UP    :
+                       dy == +1 ? MV_DOWN  : MV_NONE);
+  boolean can_continue_snapping = (level.continuous_snapping &&
+                                  WasJustFalling[x][y] < CHECK_DELAY_FALLING);
 
-#if 0
-  if (player->MovPos != 0)
-    return FALSE;
-#else
   if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
     return FALSE;
-#endif
 
   if (!player->active || !IN_LEV_FIELD(x, y))
     return FALSE;
@@ -12320,14 +13947,13 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
     return FALSE;
   }
 
-  if (player->is_snapping)
+  /* prevent snapping with already pressed snap key when not allowed */
+  if (player->is_snapping && !can_continue_snapping)
     return FALSE;
 
   player->MovDir = snap_direction;
 
-#if 1
   if (player->MovPos == 0)
-#endif
   {
     player->is_moving = FALSE;
     player->is_digging = FALSE;
@@ -12335,68 +13961,46 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
   }
 
   player->is_dropping = FALSE;
+  player->is_dropping_pressed = FALSE;
+  player->drop_pressed_delay = 0;
 
-  if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MF_NO_ACTION)
+  if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
     return FALSE;
 
   player->is_snapping = TRUE;
+  player->is_active = TRUE;
 
-#if 1
   if (player->MovPos == 0)
-#endif
   {
     player->is_moving = FALSE;
     player->is_digging = FALSE;
     player->is_collecting = FALSE;
   }
 
-#if 1
   if (player->MovPos != 0)     /* prevent graphic bugs in versions < 2.2.0 */
-    DrawLevelField(player->last_jx, player->last_jy);
-#endif
-
-  DrawLevelField(x, y);
+    TEST_DrawLevelField(player->last_jx, player->last_jy);
 
-#if 0
-  BackToFront();
-#endif
+  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;
-#if 1
   int drop_side = drop_direction;
-#else
-  static int trigger_sides[4] =
-  {
-    CH_SIDE_LEFT,      /* dropping left  */
-    CH_SIDE_RIGHT,     /* dropping right */
-    CH_SIDE_TOP,       /* dropping up    */
-    CH_SIDE_BOTTOM,    /* dropping down  */
-  };
-  int drop_side = trigger_sides[MV_DIR_BIT(drop_direction)];
-#endif
-  int drop_element = (player->inventory_size > 0 ?
-                     player->inventory_element[player->inventory_size - 1] :
-                     player->inventory_infinite_element != EL_UNDEFINED ?
-                     player->inventory_infinite_element :
-                     player->dynabombs_left > 0 ?
-                     EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
-                     EL_UNDEFINED);
-
-#if USE_DROP_BUGFIX
+  int drop_element = get_next_dropped_element(player);
+
+  player->is_dropping_pressed = TRUE;
+
   /* do not drop an element on top of another element; when holding drop key
      pressed without moving, dropped element must move away before the next
      element can be dropped (this is especially important if the next element
      is dynamite, which can be placed on background for historical reasons) */
   if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
-    return MF_ACTION;
-#endif
+    return MP_ACTION;
 
   if (IS_THROWABLE(drop_element))
   {
@@ -12415,30 +14019,20 @@ boolean DropElement(struct PlayerInfo *player)
     return FALSE;
 
   /* check if player has anything that can be dropped */
-#if 1
   if (new_element == EL_UNDEFINED)
     return FALSE;
-#else
-  if (player->inventory_size == 0 &&
-      player->inventory_infinite_element == EL_UNDEFINED &&
-      player->dynabombs_left == 0)
+
+  /* check if drop key was pressed long enough for EM style dynamite */
+  if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
     return FALSE;
-#endif
 
   /* check if anything can be dropped at the current position */
   if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
     return FALSE;
 
   /* collected custom elements can only be dropped on empty fields */
-#if 1
   if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
     return FALSE;
-#else
-  if (player->inventory_size > 0 &&
-      IS_CUSTOM_ELEMENT(player->inventory_element[player->inventory_size - 1])
-      && old_element != EL_EMPTY)
-    return FALSE;
-#endif
 
   if (old_element != EL_EMPTY)
     Back[dropx][dropy] = old_element;  /* store old element on this field */
@@ -12453,14 +14047,12 @@ boolean DropElement(struct PlayerInfo *player)
     {
       player->inventory_size--;
 
-#if 0
-      new_element = player->inventory_element[player->inventory_size];
-#endif
-
-      DrawGameValue_Dynamite(local_player->inventory_size);
+      DrawGameDoorValues();
 
       if (new_element == EL_DYNAMITE)
        new_element = EL_DYNAMITE_ACTIVE;
+      else if (new_element == EL_EM_DYNAMITE)
+       new_element = EL_EM_DYNAMITE_ACTIVE;
       else if (new_element == EL_SP_DISK_RED)
        new_element = EL_SP_DISK_RED_ACTIVE;
     }
@@ -12473,25 +14065,14 @@ boolean DropElement(struct PlayerInfo *player)
 
     PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
 
-#if 1
     /* needed if previous element just changed to "empty" in the last frame */
-    Changed[dropx][dropy] = FALSE;             /* allow another change */
-#endif
+    ChangeCount[dropx][dropy] = 0;     /* allow at least one more change */
 
-#if 1
-    /* !!! TEST ONLY !!! */
     CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
                               player->index_bit, drop_side);
     CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
                                        CE_PLAYER_DROPS_X,
                                        player->index_bit, drop_side);
-#else
-    CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
-                                       CE_PLAYER_DROPS_X,
-                                       player->index_bit, drop_side);
-    CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
-                              player->index_bit, drop_side);
-#endif
 
     TestIfElementTouchesCustomElement(dropx, dropy);
   }
@@ -12499,10 +14080,6 @@ boolean DropElement(struct PlayerInfo *player)
   {
     player->dynabombs_left--;
 
-#if 0
-    new_element = EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr;
-#endif
-
     Feld[dropx][dropy] = new_element;
 
     if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
@@ -12512,551 +14089,850 @@ boolean DropElement(struct PlayerInfo *player)
     PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
   }
 
-
-
-#if 1
-
   if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
-  {
-#if 1
     InitField_WithBug1(dropx, dropy, FALSE);
-#else
-    InitField(dropx, dropy, FALSE);
-    if (CAN_MOVE(Feld[dropx][dropy]))
-      InitMovDir(dropx, dropy);
-#endif
-  }
 
   new_element = Feld[dropx][dropy];    /* element might have changed */
 
   if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
       element_info[new_element].move_pattern == MV_WHEN_DROPPED)
   {
-#if 0
-    int move_stepsize = element_info[new_element].move_stepsize;
-#endif
-    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 1
-    Changed[dropx][dropy] = FALSE;             /* allow another change */
-    CheckCollision[dropx][dropy] = 2;
-#else
+    /* do not cause impact style collision by dropping elements that can fall */
+    CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
+  }
 
-    if (IN_LEV_FIELD_AND_IS_FREE(nextx, nexty))
-    {
-#if 0
-      WasJustMoving[dropx][dropy] = 3;
-#else
-#if 1
-      InitMovingField(dropx, dropy, move_direction);
-      ContinueMoving(dropx, dropy);
-#endif
-#endif
-    }
-#if 0
-    /* !!! commented out from 3.1.0-4 to 3.1.0-5 !!! */
-    else
-    {
-      Changed[dropx][dropy] = FALSE;   /* allow another change */
+  player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
+  player->is_dropping = TRUE;
+
+  player->drop_pressed_delay = 0;
+  player->is_dropping_pressed = FALSE;
+
+  player->drop_x = dropx;
+  player->drop_y = dropy;
+
+  return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+/* game sound playing functions                                              */
+/* ------------------------------------------------------------------------- */
+
+static int *loop_sound_frame = NULL;
+static int *loop_sound_volume = NULL;
+
+void InitPlayLevelSound()
+{
+  int num_sounds = getSoundListSize();
+
+  checked_free(loop_sound_frame);
+  checked_free(loop_sound_volume);
+
+  loop_sound_frame  = checked_calloc(num_sounds * sizeof(int));
+  loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
+}
+
+static void PlayLevelSound(int x, int y, int nr)
+{
+  int sx = SCREENX(x), sy = SCREENY(y);
+  int volume, stereo_position;
+  int max_distance = 8;
+  int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
+
+  if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
+      (!setup.sound_loops && IS_LOOP_SOUND(nr)))
+    return;
+
+  if (!IN_LEV_FIELD(x, y) ||
+      sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
+      sy < -max_distance || sy >= SCR_FIELDY + max_distance)
+    return;
+
+  volume = SOUND_MAX_VOLUME;
+
+  if (!IN_SCR_FIELD(sx, sy))
+  {
+    int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
+    int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
+
+    volume -= volume * (dx > dy ? dx : dy) / max_distance;
+  }
+
+  stereo_position = (SOUND_MAX_LEFT +
+                    (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
+                    (SCR_FIELDX + 2 * max_distance));
+
+  if (IS_LOOP_SOUND(nr))
+  {
+    /* This assures that quieter loop sounds do not overwrite louder ones,
+       while restarting sound volume comparison with each new game frame. */
+
+    if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
+      return;
+
+    loop_sound_volume[nr] = volume;
+    loop_sound_frame[nr] = FrameCounter;
+  }
+
+  PlaySoundExt(nr, volume, stereo_position, type);
+}
+
+static void PlayLevelSoundNearest(int x, int y, int sound_action)
+{
+  PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
+                x > LEVELX(BX2) ? LEVELX(BX2) : x,
+                y < LEVELY(BY1) ? LEVELY(BY1) :
+                y > LEVELY(BY2) ? LEVELY(BY2) : y,
+                sound_action);
+}
+
+static void PlayLevelSoundAction(int x, int y, int action)
+{
+  PlayLevelSoundElementAction(x, y, Feld[x][y], action);
+}
+
+static void PlayLevelSoundElementAction(int x, int y, int element, int action)
+{
+  int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
+
+  if (sound_effect != SND_UNDEFINED)
+    PlayLevelSound(x, y, sound_effect);
+}
+
+static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
+                                             int action)
+{
+  int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
+
+  if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
+    PlayLevelSound(x, y, sound_effect);
+}
+
+static void PlayLevelSoundActionIfLoop(int x, int y, int action)
+{
+  int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
 
+  if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
+    PlayLevelSound(x, y, sound_effect);
+}
+
+static void StopLevelSoundActionIfLoop(int x, int y, int action)
+{
+  int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
+
+  if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
+    StopSound(sound_effect);
+}
+
+static void PlayLevelMusic()
+{
+  if (levelset.music[level_nr] != MUS_UNDEFINED)
+    PlayMusic(levelset.music[level_nr]);       /* from config file */
+  else
+    PlayMusic(MAP_NOCONF_MUSIC(level_nr));     /* from music dir */
+}
+
+void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
+{
+  int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
+  int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
+  int x = xx - 1 - offset;
+  int y = yy - 1 - offset;
+
+  switch (sample)
+  {
+    case SAMPLE_blank:
+      PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
+      break;
+
+    case SAMPLE_roll:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
+      break;
+
+    case SAMPLE_stone:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_nut:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_crack:
+      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
+      break;
+
+    case SAMPLE_bug:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_tank:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_android_clone:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_android_move:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_spring:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_slurp:
+      PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
+      break;
+
+    case SAMPLE_eater:
+      PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
+      break;
+
+    case SAMPLE_eater_eat:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+      break;
+
+    case SAMPLE_alien:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_collect:
+      PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
+      break;
+
+    case SAMPLE_diamond:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_squash:
+      /* !!! CHECK THIS !!! */
 #if 1
-      TestIfElementHitsCustomElement(dropx, dropy, move_direction);
+      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
 #else
-      CheckElementChangeBySide(dropx, dropy, new_element, touched_element,
-                              CE_HITTING_SOMETHING, move_direction);
-#endif
-    }
+      PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
 #endif
+      break;
+
+    case SAMPLE_wonderfall:
+      PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
+      break;
+
+    case SAMPLE_drip:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_push:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
+      break;
+
+    case SAMPLE_dirt:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+      break;
+
+    case SAMPLE_acid:
+      PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
+      break;
+
+    case SAMPLE_ball:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_grow:
+      PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
+      break;
+
+    case SAMPLE_wonder:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_door:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
+      break;
+
+    case SAMPLE_exit_open:
+      PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
+      break;
+
+    case SAMPLE_exit_leave:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
+      break;
+
+    case SAMPLE_dynamite:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_tick:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_press:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
+      break;
+
+    case SAMPLE_wheel:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_boom:
+      PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
+      break;
+
+    case SAMPLE_die:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
+      break;
+
+    case SAMPLE_time:
+      PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+      break;
+
+    default:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
+      break;
+  }
+}
+
+void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
+{
+  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;
+
+  PlayLevelSoundElementAction(x, y, element, action);
+}
+
+void RaiseScore(int value)
+{
+  local_player->score += value;
+
+  game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
+
+  DisplayGameControlValues();
+}
+
+void RaiseScoreElement(int element)
+{
+  switch (element)
+  {
+    case EL_EMERALD:
+    case EL_BD_DIAMOND:
+    case EL_EMERALD_YELLOW:
+    case EL_EMERALD_RED:
+    case EL_EMERALD_PURPLE:
+    case EL_SP_INFOTRON:
+      RaiseScore(level.score[SC_EMERALD]);
+      break;
+    case EL_DIAMOND:
+      RaiseScore(level.score[SC_DIAMOND]);
+      break;
+    case EL_CRYSTAL:
+      RaiseScore(level.score[SC_CRYSTAL]);
+      break;
+    case EL_PEARL:
+      RaiseScore(level.score[SC_PEARL]);
+      break;
+    case EL_BUG:
+    case EL_BD_BUTTERFLY:
+    case EL_SP_ELECTRON:
+      RaiseScore(level.score[SC_BUG]);
+      break;
+    case EL_SPACESHIP:
+    case EL_BD_FIREFLY:
+    case EL_SP_SNIKSNAK:
+      RaiseScore(level.score[SC_SPACESHIP]);
+      break;
+    case EL_YAMYAM:
+    case EL_DARK_YAMYAM:
+      RaiseScore(level.score[SC_YAMYAM]);
+      break;
+    case EL_ROBOT:
+      RaiseScore(level.score[SC_ROBOT]);
+      break;
+    case EL_PACMAN:
+      RaiseScore(level.score[SC_PACMAN]);
+      break;
+    case EL_NUT:
+      RaiseScore(level.score[SC_NUT]);
+      break;
+    case EL_DYNAMITE:
+    case EL_EM_DYNAMITE:
+    case EL_SP_DISK_RED:
+    case EL_DYNABOMB_INCREASE_NUMBER:
+    case EL_DYNABOMB_INCREASE_SIZE:
+    case EL_DYNABOMB_INCREASE_POWER:
+      RaiseScore(level.score[SC_DYNAMITE]);
+      break;
+    case EL_SHIELD_NORMAL:
+    case EL_SHIELD_DEADLY:
+      RaiseScore(level.score[SC_SHIELD]);
+      break;
+    case EL_EXTRA_TIME:
+      RaiseScore(level.extra_time_score);
+      break;
+    case EL_KEY_1:
+    case EL_KEY_2:
+    case EL_KEY_3:
+    case EL_KEY_4:
+    case EL_EM_KEY_1:
+    case EL_EM_KEY_2:
+    case EL_EM_KEY_3:
+    case EL_EM_KEY_4:
+    case EL_EMC_KEY_5:
+    case EL_EMC_KEY_6:
+    case EL_EMC_KEY_7:
+    case EL_EMC_KEY_8:
+    case EL_DC_KEY_WHITE:
+      RaiseScore(level.score[SC_KEY]);
+      break;
+    default:
+      RaiseScore(element_info[element].collect_score);
+      break;
+  }
+}
 
-#endif
+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 0
-    player->drop_delay = 2 * TILEX / move_stepsize + 1;
+#if defined(NETWORK_AVALIABLE)
+    if (options.network)
+      SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
+    else
 #endif
-  }
+    {
+      if (quick_quit)
+       FadeSkipNextFadeIn();
 
-#if 0
-  player->drop_delay = 8 + 8 + 8;
-#endif
+      SetGameStatus(GAME_MODE_MAIN);
 
-#if 1
-  player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
-#endif
+      DrawMainMenu();
+    }
+  }
+  else         /* continue playing the game */
+  {
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOff(TRUE);
 
-#endif
+    OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
 
-  player->is_dropping = TRUE;
+    if (tape.playing && tape.deactivate_display)
+      TapeDeactivateDisplayOn();
+  }
+}
 
-#if USE_DROP_BUGFIX
-  player->drop_x = dropx;
-  player->drop_y = dropy;
-#endif
+void RequestQuitGame(boolean ask_if_really_quit)
+{
+  boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
+  boolean skip_request = AllPlayersGone || quick_quit;
 
-  return TRUE;
+  RequestQuitGameExt(skip_request, quick_quit,
+                    "Do you really want to quit the game?");
 }
 
+
 /* ------------------------------------------------------------------------- */
-/* game sound playing functions                                              */
+/* random generator functions                                                */
 /* ------------------------------------------------------------------------- */
 
-static int *loop_sound_frame = NULL;
-static int *loop_sound_volume = NULL;
+unsigned int InitEngineRandom_RND(int seed)
+{
+  game.num_random_calls = 0;
 
-void InitPlayLevelSound()
+  return InitEngineRandom(seed);
+}
+
+unsigned int RND(int max)
 {
-  int num_sounds = getSoundListSize();
+  if (max > 0)
+  {
+    game.num_random_calls++;
 
-  checked_free(loop_sound_frame);
-  checked_free(loop_sound_volume);
+    return GetEngineRandom(max);
+  }
 
-  loop_sound_frame  = checked_calloc(num_sounds * sizeof(int));
-  loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
+  return 0;
 }
 
-static void PlayLevelSound(int x, int y, int nr)
+
+/* ------------------------------------------------------------------------- */
+/* game engine snapshot handling functions                                   */
+/* ------------------------------------------------------------------------- */
+
+struct EngineSnapshotInfo
 {
-  int sx = SCREENX(x), sy = SCREENY(y);
-  int volume, stereo_position;
-  int max_distance = 8;
-  int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
+  /* runtime values for custom element collect score */
+  int collect_score[NUM_CUSTOM_ELEMENTS];
 
-  if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
-      (!setup.sound_loops && IS_LOOP_SOUND(nr)))
-    return;
+  /* runtime values for group element choice position */
+  int choice_pos[NUM_GROUP_ELEMENTS];
 
-  if (!IN_LEV_FIELD(x, y) ||
-      sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
-      sy < -max_distance || sy >= SCR_FIELDY + max_distance)
-    return;
+  /* runtime values for belt position animations */
+  int belt_graphic[4][NUM_BELT_PARTS];
+  int belt_anim_mode[4][NUM_BELT_PARTS];
+};
 
-  volume = SOUND_MAX_VOLUME;
+static struct EngineSnapshotInfo engine_snapshot_rnd;
+static char *snapshot_level_identifier = NULL;
+static int snapshot_level_nr = -1;
 
-  if (!IN_SCR_FIELD(sx, sy))
+static void SaveEngineSnapshotValues_RND()
+{
+  static int belt_base_active_element[4] =
   {
-    int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
-    int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
+    EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+    EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+  };
+  int i, j;
 
-    volume -= volume * (dx > dy ? dx : dy) / max_distance;
-  }
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
 
-  stereo_position = (SOUND_MAX_LEFT +
-                    (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
-                    (SCR_FIELDX + 2 * max_distance));
+    engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
+  }
 
-  if (IS_LOOP_SOUND(nr))
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
   {
-    /* This assures that quieter loop sounds do not overwrite louder ones,
-       while restarting sound volume comparison with each new game frame. */
-
-    if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
-      return;
+    int element = EL_GROUP_START + i;
 
-    loop_sound_volume[nr] = volume;
-    loop_sound_frame[nr] = FrameCounter;
+    engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
   }
 
-  PlaySoundExt(nr, volume, stereo_position, type);
-}
+  for (i = 0; i < 4; i++)
+  {
+    for (j = 0; j < NUM_BELT_PARTS; j++)
+    {
+      int element = belt_base_active_element[i] + j;
+      int graphic = el2img(element);
+      int anim_mode = graphic_info[graphic].anim_mode;
 
-static void PlayLevelSoundNearest(int x, int y, int sound_action)
-{
-  PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
-                x > LEVELX(BX2) ? LEVELX(BX2) : x,
-                y < LEVELY(BY1) ? LEVELY(BY1) :
-                y > LEVELY(BY2) ? LEVELY(BY2) : y,
-                sound_action);
+      engine_snapshot_rnd.belt_graphic[i][j] = graphic;
+      engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
+    }
+  }
 }
 
-static void PlayLevelSoundAction(int x, int y, int action)
+static void LoadEngineSnapshotValues_RND()
 {
-  PlayLevelSoundElementAction(x, y, Feld[x][y], action);
-}
+  unsigned int num_random_calls = game.num_random_calls;
+  int i, j;
 
-static void PlayLevelSoundElementAction(int x, int y, int element, int action)
-{
-  int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
 
-  if (sound_effect != SND_UNDEFINED)
-    PlayLevelSound(x, y, sound_effect);
-}
+    element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
+  }
 
-static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
-                                             int action)
-{
-  int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+  {
+    int element = EL_GROUP_START + i;
 
-  if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
-    PlayLevelSound(x, y, sound_effect);
-}
+    element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
+  }
 
-static void PlayLevelSoundActionIfLoop(int x, int y, int action)
-{
-  int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
+  for (i = 0; i < 4; i++)
+  {
+    for (j = 0; j < NUM_BELT_PARTS; j++)
+    {
+      int graphic = engine_snapshot_rnd.belt_graphic[i][j];
+      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
 
-  if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
-    PlayLevelSound(x, y, sound_effect);
+      graphic_info[graphic].anim_mode = anim_mode;
+    }
+  }
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+  {
+    InitRND(tape.random_seed);
+    for (i = 0; i < num_random_calls; i++)
+      RND(1);
+  }
+
+  if (game.num_random_calls != num_random_calls)
+  {
+    Error(ERR_INFO, "number of random calls out of sync");
+    Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
+    Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
+    Error(ERR_EXIT, "this should not happen -- please debug");
+  }
 }
 
-static void StopLevelSoundActionIfLoop(int x, int y, int action)
+void FreeEngineSnapshotSingle()
 {
-  int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
+  FreeSnapshotSingle();
 
-  if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
-    StopSound(sound_effect);
+  setString(&snapshot_level_identifier, NULL);
+  snapshot_level_nr = -1;
 }
 
-static void PlayLevelMusic()
+void FreeEngineSnapshotList()
 {
-  if (levelset.music[level_nr] != MUS_UNDEFINED)
-    PlayMusic(levelset.music[level_nr]);       /* from config file */
-  else
-    PlayMusic(MAP_NOCONF_MUSIC(level_nr));     /* from music dir */
+  FreeSnapshotList();
 }
 
-void PlayLevelSound_EM(int x, int y, int element_em, int sample)
+ListNode *SaveEngineSnapshotBuffers()
 {
-  int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
+  ListNode *buffers = NULL;
 
-#if 0
-  if (sample == SAMPLE_bug)
-    printf("::: PlayLevelSound_EM: %d, %d: %d\n", x, y, sample);
-#endif
-
-  switch (sample)
-  {
-    case SAMPLE_blank:
-      PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
-      break;
-
-    case SAMPLE_roll:
-      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
-      break;
-
-    case SAMPLE_stone:
-      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
-      break;
+  /* copy some special values to a structure better suited for the snapshot */
 
-    case SAMPLE_nut:
-      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
-      break;
+  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);
 
-    case SAMPLE_crack:
-      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
-      break;
+  /* save values stored in special snapshot structure */
 
-    case SAMPLE_bug:
-      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
-      break;
+  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));
 
-    case SAMPLE_tank:
-      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
-      break;
+  /* save further RND engine values */
 
-    case SAMPLE_android_clone:
-      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
 
-    case SAMPLE_android_move:
-      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
-      break;
+  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));
 
-    case SAMPLE_spring:
-      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
-      break;
+  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));
 
-    case SAMPLE_slurp:
-      PlayLevelSoundElementAction(x, y, element, ACTION_SLURPED_BY_SPRING);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
 
-    case SAMPLE_eater:
-      PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
 
-    case SAMPLE_eater_eat:
-      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
 
-    case SAMPLE_alien:
-      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
 
-    case SAMPLE_collect:
-      PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
-      break;
+  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));
 
-    case SAMPLE_diamond:
-      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
 
-    case SAMPLE_squash:
-      /* !!! CHECK THIS !!! */
-#if 1
-      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
-#else
-      PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
-#endif
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
 
-    case SAMPLE_wonderfall:
-      PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
 
-    case SAMPLE_drip:
-      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
-      break;
+  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));
 
-    case SAMPLE_push:
-      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
-      break;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
 
-    case SAMPLE_dirt:
-      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
-      break;
+#if 0
+  ListNode *node = engine_snapshot_list_rnd;
+  int num_bytes = 0;
 
-    case SAMPLE_acid:
-      PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
-      break;
+  while (node != NULL)
+  {
+    num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
 
-    case SAMPLE_ball:
-      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
-      break;
+    node = node->next;
+  }
 
-    case SAMPLE_grow:
-      PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
-      break;
+  printf("::: size of engine snapshot: %d bytes\n", num_bytes);
+#endif
 
-    case SAMPLE_wonder:
-      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
-      break;
+  return buffers;
+}
 
-    case SAMPLE_door:
-      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
-      break;
+void SaveEngineSnapshotSingle()
+{
+  ListNode *buffers = SaveEngineSnapshotBuffers();
 
-    case SAMPLE_exit_open:
-      PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
-      break;
+  /* finally save all snapshot buffers to single snapshot */
+  SaveSnapshotSingle(buffers);
 
-    case SAMPLE_exit_leave:
-      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
-      break;
+  /* save level identification information */
+  setString(&snapshot_level_identifier, leveldir_current->identifier);
+  snapshot_level_nr = level_nr;
+}
 
-    case SAMPLE_dynamite:
-      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
-      break;
+boolean CheckSaveEngineSnapshotToList()
+{
+  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));
 
-    case SAMPLE_tick:
-      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
-      break;
+  game.snapshot.changed_action = FALSE;
+  game.snapshot.collected_item = FALSE;
+  game.snapshot.save_snapshot = save_snapshot;
 
-    case SAMPLE_press:
-      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
-      break;
+  return save_snapshot;
+}
 
-    case SAMPLE_wheel:
-      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
-      break;
+void SaveEngineSnapshotToList()
+{
+  if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
+      tape.quick_resume)
+    return;
 
-    case SAMPLE_boom:
-      PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
-      break;
+  ListNode *buffers = SaveEngineSnapshotBuffers();
 
-    case SAMPLE_die:
-      PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
-      break;
+  /* finally save all snapshot buffers to snapshot list */
+  SaveSnapshotToList(buffers);
+}
 
-    case SAMPLE_time:
-      PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
-      break;
+void SaveEngineSnapshotToListInitial()
+{
+  FreeEngineSnapshotList();
 
-    default:
-      PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
-      break;
-  }
+  SaveEngineSnapshotToList();
 }
 
-void RaiseScore(int value)
+void LoadEngineSnapshotValues()
 {
-  local_player->score += value;
+  /* restore special values from snapshot structure */
 
-  DrawGameValue_Score(local_player->score);
+  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 RaiseScoreElement(int element)
+void LoadEngineSnapshotSingle()
 {
-  switch(element)
-  {
-    case EL_EMERALD:
-    case EL_BD_DIAMOND:
-    case EL_EMERALD_YELLOW:
-    case EL_EMERALD_RED:
-    case EL_EMERALD_PURPLE:
-    case EL_SP_INFOTRON:
-      RaiseScore(level.score[SC_EMERALD]);
-      break;
-    case EL_DIAMOND:
-      RaiseScore(level.score[SC_DIAMOND]);
-      break;
-    case EL_CRYSTAL:
-      RaiseScore(level.score[SC_CRYSTAL]);
-      break;
-    case EL_PEARL:
-      RaiseScore(level.score[SC_PEARL]);
-      break;
-    case EL_BUG:
-    case EL_BD_BUTTERFLY:
-    case EL_SP_ELECTRON:
-      RaiseScore(level.score[SC_BUG]);
-      break;
-    case EL_SPACESHIP:
-    case EL_BD_FIREFLY:
-    case EL_SP_SNIKSNAK:
-      RaiseScore(level.score[SC_SPACESHIP]);
-      break;
-    case EL_YAMYAM:
-    case EL_DARK_YAMYAM:
-      RaiseScore(level.score[SC_YAMYAM]);
-      break;
-    case EL_ROBOT:
-      RaiseScore(level.score[SC_ROBOT]);
-      break;
-    case EL_PACMAN:
-      RaiseScore(level.score[SC_PACMAN]);
-      break;
-    case EL_NUT:
-      RaiseScore(level.score[SC_NUT]);
-      break;
-    case EL_DYNAMITE:
-    case EL_SP_DISK_RED:
-    case EL_DYNABOMB_INCREASE_NUMBER:
-    case EL_DYNABOMB_INCREASE_SIZE:
-    case EL_DYNABOMB_INCREASE_POWER:
-      RaiseScore(level.score[SC_DYNAMITE]);
-      break;
-    case EL_SHIELD_NORMAL:
-    case EL_SHIELD_DEADLY:
-      RaiseScore(level.score[SC_SHIELD]);
-      break;
-    case EL_EXTRA_TIME:
-      RaiseScore(level.score[SC_TIME_BONUS]);
-      break;
-    case EL_KEY_1:
-    case EL_KEY_2:
-    case EL_KEY_3:
-    case EL_KEY_4:
-    case EL_EM_KEY_1:
-    case EL_EM_KEY_2:
-    case EL_EM_KEY_3:
-    case EL_EM_KEY_4:
-    case EL_EMC_KEY_5:
-    case EL_EMC_KEY_6:
-    case EL_EMC_KEY_7:
-    case EL_EMC_KEY_8:
-      RaiseScore(level.score[SC_KEY]);
-      break;
-    default:
-      RaiseScore(element_info[element].collect_score);
-      break;
-  }
+  LoadSnapshotSingle();
+
+  LoadEngineSnapshotValues();
 }
 
-void RequestQuitGame(boolean ask_if_really_quit)
+void LoadEngineSnapshot_Undo(int steps)
 {
-  if (AllPlayersGone ||
-      !ask_if_really_quit ||
-      level_editor_test_game ||
-      Request("Do you really want to quit the game ?",
-             REQ_ASK | REQ_STAY_CLOSED))
-  {
-#if defined(NETWORK_AVALIABLE)
-    if (options.network)
-      SendToServer_StopPlaying();
-    else
-#endif
-    {
-      game_status = GAME_MODE_MAIN;
-      DrawMainMenu();
-    }
-  }
-  else
-  {
+  LoadSnapshotFromList_Older(steps);
 
-#if 1
-    if (tape.playing && tape.deactivate_display)
-      TapeDeactivateDisplayOff(TRUE);
-#endif
+  LoadEngineSnapshotValues();
+}
 
-    OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
+void LoadEngineSnapshot_Redo(int steps)
+{
+  LoadSnapshotFromList_Newer(steps);
 
-#if 1
-    if (tape.playing && tape.deactivate_display)
-      TapeDeactivateDisplayOn();
-#endif
+  LoadEngineSnapshotValues();
+}
 
-  }
+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 graphic;
+  struct XY *pos;
   int gadget_id;
   char *infotext;
 } gamebutton_info[NUM_GAME_BUTTONS] =
 {
   {
-    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"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PAUSE,         &game.button.pause,
+    GAME_CTRL_ID_PAUSE,                        "pause game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_PLAY,          &game.button.play,
+    GAME_CTRL_ID_PLAY,                 "play game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_UNDO,          &game.button.undo,
+    GAME_CTRL_ID_UNDO,                 "undo step"
   },
   {
-    GAME_BUTTON_PAUSE_XPOS,    GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PAUSE,
-    "pause game"
+    IMG_GFX_GAME_BUTTON_REDO,          &game.button.redo,
+    GAME_CTRL_ID_REDO,                 "redo step"
   },
   {
-    GAME_BUTTON_PLAY_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PLAY,
-    "play game"
+    IMG_GFX_GAME_BUTTON_SAVE,          &game.button.save,
+    GAME_CTRL_ID_SAVE,                 "save game"
   },
   {
-    SOUND_BUTTON_MUSIC_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_MUSIC,
-    "background music on/off"
+    IMG_GFX_GAME_BUTTON_PAUSE2,                &game.button.pause2,
+    GAME_CTRL_ID_PAUSE2,               "pause game"
   },
   {
-    SOUND_BUTTON_LOOPS_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_LOOPS,
-    "sound loops on/off"
+    IMG_GFX_GAME_BUTTON_LOAD,          &game.button.load,
+    GAME_CTRL_ID_LOAD,                 "load game"
   },
   {
-    SOUND_BUTTON_SIMPLE_XPOS,  SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_SIMPLE,
-    "normal sounds on/off"
+    IMG_GFX_GAME_BUTTON_SOUND_MUSIC,   &game.button.sound_music,
+    SOUND_CTRL_ID_MUSIC,               "background music on/off"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_SOUND_LOOPS,   &game.button.sound_loops,
+    SOUND_CTRL_ID_LOOPS,               "sound loops on/off"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_SOUND_SIMPLE,  &game.button.sound_simple,
+    SOUND_CTRL_ID_SIMPLE,              "normal sounds on/off"
   }
 };
 
@@ -13066,29 +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 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;
 
-    gd_xoffset = gamebutton_info[i].x;
-    gd_yoffset = gamebutton_info[i].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
     {
@@ -13098,23 +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,
-                     GDI_X, DX + gd_xoffset,
-                     GDI_Y, DY + gd_yoffset,
-                     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);
@@ -13134,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()
@@ -13150,21 +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:
-      RequestQuitGame(TRUE);
+      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)
@@ -13175,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)
@@ -13214,6 +15266,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       else if (audio.loops_available)
       {
        setup.sound = setup.sound_loops = TRUE;
+
        SetAudioMode(setup.sound);
       }
       break;
@@ -13224,6 +15277,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       else if (audio.sound_available)
       {
        setup.sound = setup.sound_simple = TRUE;
+
        SetAudioMode(setup.sound);
       }
       break;
@@ -13232,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);
+}