rnd-20070302-1-src
[rocksndiamonds.git] / src / game.c
index 73c01c7b6b159f39230468ee2af55e80f232f52e..afa0f24f923f9cbcccfbe3ebcac1872545fe2b55 100644 (file)
@@ -1,7 +1,7 @@
 /***********************************************************
 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
 *----------------------------------------------------------*
-* (c) 1995-2002 Artsoft Entertainment                      *
+* (c) 1995-2006 Artsoft Entertainment                      *
 *               Holger Schemel                             *
 *               Detmolder Strasse 189                      *
 *               33604 Bielefeld                            *
 #define USE_CODE_THAT_BREAKS_SNAKE_BITE        (USE_NEW_STUFF          * 1)
 
 #define USE_UFAST_PLAYER_EXIT_BUGFIX   (USE_NEW_STUFF          * 1)
-#define USE_NEW_GAME_WON               (USE_NEW_STUFF          * 1)
+
+#define USE_GFX_RESET_ONLY_WHEN_MOVING (USE_NEW_STUFF          * 1)
+#define USE_GFX_RESET_PLAYER_ARTWORK   (USE_NEW_STUFF          * 1)
+
+#define USE_FIX_KILLED_BY_NON_WALKABLE (USE_NEW_STUFF          * 1)
+#define USE_FIX_IMPACT_COLLISION       (USE_NEW_STUFF          * 1)
+
+#define USE_GFX_RESET_WHEN_NOT_MOVING  (USE_NEW_STUFF          * 1)
 
 
 /* for DigField() */
 #define EX_TYPE_DYNA           (1 << 4)
 #define EX_TYPE_SINGLE_TILE    (EX_TYPE_CENTER | EX_TYPE_BORDER)
 
+#if 1
+#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_MENU_XPOS(p))
+#define PANEL_YPOS(p)          (DY + ALIGNED_MENU_YPOS(p))
+#else
 #define        PANEL_DEACTIVATED(p)    ((p).x < 0 || (p).y < 0)
+#define PANEL_XPOS(p)          (ALIGNED_XPOS((p).x, (p).width, (p).align))
+#define PANEL_YPOS(p)          ((p).y)
+#endif
 
 /* special positions in the game control window (relative to control window) */
-#define XX_LEVEL1              (game.panel.level.x)
-#define XX_LEVEL2              (game.panel.level.x - 1)
-#define YY_LEVEL               (game.panel.level.y)
-#define XX_EMERALDS            (game.panel.gems.x)
-#define YY_EMERALDS            (game.panel.gems.y)
-#define XX_DYNAMITE            (game.panel.inventory.x)
-#define YY_DYNAMITE            (game.panel.inventory.y)
-#define XX_KEYS                        (game.panel.keys.x)
-#define YY_KEYS                        (game.panel.keys.y)
-#define XX_SCORE               (game.panel.score.x)
-#define YY_SCORE               (game.panel.score.y)
-#define XX_TIME1               (game.panel.time.x)
-#define XX_TIME2               (game.panel.time.x + 1)
-#define YY_TIME                        (game.panel.time.y)
+#define XX_LEVEL1              (PANEL_XPOS(game.panel.level))
+#define XX_LEVEL2              (PANEL_XPOS(game.panel.level) - 1)
+#define XX_LEVEL               (PANEL_XPOS(game.panel.level))
+#define YY_LEVEL               (PANEL_YPOS(game.panel.level))
+#define XX_EMERALDS            (PANEL_XPOS(game.panel.gems))
+#define YY_EMERALDS            (PANEL_YPOS(game.panel.gems))
+#define XX_DYNAMITE            (PANEL_XPOS(game.panel.inventory))
+#define YY_DYNAMITE            (PANEL_YPOS(game.panel.inventory))
+#define XX_KEYS                        (PANEL_XPOS(game.panel.keys))
+#define YY_KEYS                        (PANEL_YPOS(game.panel.keys))
+#define XX_SCORE               (PANEL_XPOS(game.panel.score))
+#define YY_SCORE               (PANEL_YPOS(game.panel.score))
+#define XX_TIME1               (PANEL_XPOS(game.panel.time))
+#define XX_TIME2               (PANEL_XPOS(game.panel.time) + 1)
+#define XX_TIME                        (PANEL_XPOS(game.panel.time))
+#define YY_TIME                        (PANEL_YPOS(game.panel.time))
 
 /* special positions in the game control window (relative to main window) */
 #define DX_LEVEL1              (DX + XX_LEVEL1)
 #define DX_LEVEL2              (DX + XX_LEVEL2)
+#define DX_LEVEL               (DX + XX_LEVEL)
 #define DY_LEVEL               (DY + YY_LEVEL)
 #define DX_EMERALDS            (DX + XX_EMERALDS)
 #define DY_EMERALDS            (DY + YY_EMERALDS)
 #define DY_SCORE               (DY + YY_SCORE)
 #define DX_TIME1               (DX + XX_TIME1)
 #define DX_TIME2               (DX + XX_TIME2)
+#define DX_TIME                        (DX + XX_TIME)
 #define DY_TIME                        (DY + YY_TIME)
 
 /* values for delayed check of falling and moving elements and for collision */
 #define CHECK_DELAY_MOVING     3
-#define CHECK_DELAY_FALLING    3
+#define CHECK_DELAY_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 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))
         (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score  :  \
         (e) == EL_CURRENT_CE_VALUE ? (cv) :                            \
         (e) == EL_CURRENT_CE_SCORE ? (cs) :                            \
-        (e) >= EL_LAST_CE_8 && (e) <= EL_NEXT_CE_8 ?                   \
+        (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ?                   \
         RESOLVED_REFERENCE_ELEMENT(be, e) :                            \
         (e))
 
        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)
@@ -373,6 +397,33 @@ 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;
+
 
 /* ------------------------------------------------------------------------- */
 /* definition of elements that automatically change to other elements after  */
@@ -441,6 +492,62 @@ 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,
+#if 1
+    EL_EMPTY,
+#else
+    EL_EM_EXIT_CLOSED,
+#endif
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_STEEL_EXIT_OPENING,
+    EL_EM_STEEL_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EM_STEEL_EXIT_CLOSING,
+#if 1
+    EL_STEELWALL,
+#else
+    EL_EM_STEEL_EXIT_CLOSED,
+#endif
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
   {
     EL_SP_EXIT_OPENING,
     EL_SP_EXIT_OPEN,
@@ -562,6 +669,14 @@ 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,
@@ -634,10 +749,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 },
 };
@@ -803,6 +922,8 @@ static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
 
 void GetPlayerConfig()
 {
+  GameFrameDelay = setup.game_frame_delay;
+
   if (!audio.sound_available)
     setup.sound_simple = FALSE;
 
@@ -821,57 +942,6 @@ 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)
-{
-  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] =
-  {
-    EL_CONVEYOR_BELT_1_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_2_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_3_SWITCH_LEFT,
-    EL_CONVEYOR_BELT_4_SWITCH_LEFT
-  };
-
-  int belt_nr = getBeltNrFromBeltSwitchElement(element);
-  int belt_dir_nr = element - belt_base_element[belt_nr];
-
-  return (belt_dir_nr % 3);
-}
-
-static int getBeltDirFromBeltSwitchElement(int element)
-{
-  static int belt_move_dir[3] =
-  {
-    MV_LEFT,
-    MV_NONE,
-    MV_RIGHT
-  };
-
-  int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(element);
-
-  return belt_move_dir[belt_dir_nr];
-}
-
 static int get_element_from_group_element(int element)
 {
   if (IS_GROUP_ELEMENT(element))
@@ -1127,6 +1197,11 @@ static void InitField(int x, int y, boolean init_game)
       if (init_game)
        Feld[x][y] = EL_SWITCHGATE_SWITCH_UP;
       break;
+
+    case EL_DC_SWITCHGATE_SWITCH_DOWN: /* always start with same switch pos */
+      if (init_game)
+       Feld[x][y] = EL_DC_SWITCHGATE_SWITCH_UP;
+      break;
 #endif
 
     case EL_LIGHT_SWITCH_ACTIVE:
@@ -1208,65 +1283,195 @@ static inline void InitField_WithBug2(int x, int y, boolean init_game)
   */
 }
 
-inline void DrawGameValue_Emeralds(int value)
+#if 1
+
+void DrawGameValue_Emeralds(int value)
+{
+  struct TextPosInfo *pos = &game.panel.gems;
+  int font_nr = FONT_TEXT_2;
+  int font_width = getFontWidth(font_nr);
+  int digits = pos->chars;
+
+  if (PANEL_DEACTIVATED(pos))
+    return;
+
+  pos->width = digits * font_width;
+
+  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, digits), font_nr);
+}
+
+void DrawGameValue_Dynamite(int value)
 {
-  int xpos = (3 * 14 - 3 * getFontWidth(FONT_TEXT_2)) / 2;
+  struct TextPosInfo *pos = &game.panel.inventory;
+  int font_nr = FONT_TEXT_2;
+  int font_width = getFontWidth(font_nr);
+  int digits = pos->chars;
 
-  if (PANEL_DEACTIVATED(game.panel.gems))
+  if (PANEL_DEACTIVATED(pos))
     return;
 
-  DrawText(DX_EMERALDS + xpos, DY_EMERALDS, int2str(value, 3), FONT_TEXT_2);
+  pos->width = digits * font_width;
+
+  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, digits), font_nr);
 }
 
-inline void DrawGameValue_Dynamite(int value)
+void DrawGameValue_Score(int value)
 {
-  int xpos = (3 * 14 - 3 * getFontWidth(FONT_TEXT_2)) / 2;
+  struct TextPosInfo *pos = &game.panel.score;
+  int font_nr = FONT_TEXT_2;
+  int font_width = getFontWidth(font_nr);
+  int digits = pos->chars;
 
-  if (PANEL_DEACTIVATED(game.panel.inventory))
+  if (PANEL_DEACTIVATED(pos))
+    return;
+
+  pos->width = digits * font_width;
+
+  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, digits), font_nr);
+}
+
+void DrawGameValue_Time(int value)
+{
+  struct TextPosInfo *pos = &game.panel.time;
+  static int last_value = -1;
+  int digits1 = 3;
+  int digits2 = 4;
+  int digits = pos->chars;
+  int font1_nr = FONT_TEXT_2;
+  int font2_nr = FONT_TEXT_1;
+  int font_nr = font1_nr;
+  boolean use_dynamic_digits = (digits == -1 ? TRUE : FALSE);
+
+  if (PANEL_DEACTIVATED(pos))
     return;
 
-  DrawText(DX_DYNAMITE + xpos, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2);
+  if (use_dynamic_digits)              /* use dynamic number of digits */
+  {
+    digits  = (value < 1000 ? digits1  : digits2);
+    font_nr = (value < 1000 ? font1_nr : font2_nr);
+  }
+
+  /* clear background if value just changed its size (dynamic digits only) */
+  if (use_dynamic_digits && (last_value < 1000) != (value < 1000))
+  {
+    int width1 = digits1 * getFontWidth(font1_nr);
+    int width2 = digits2 * getFontWidth(font2_nr);
+    int max_width = MAX(width1, width2);
+    int max_height = MAX(getFontHeight(font1_nr), getFontHeight(font2_nr));
+
+    pos->width = max_width;
+
+    ClearRectangleOnBackground(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+                              max_width, max_height);
+  }
+
+  pos->width = digits * getFontWidth(font_nr);
+
+  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, digits), font_nr);
+
+  last_value = value;
 }
 
-inline void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
+void DrawGameValue_Level(int value)
 {
+  struct TextPosInfo *pos = &game.panel.level;
+  int digits1 = 2;
+  int digits2 = 3;
+  int digits = pos->chars;
+  int font1_nr = FONT_TEXT_2;
+  int font2_nr = FONT_TEXT_1;
+  int font_nr = font1_nr;
+  boolean use_dynamic_digits = (digits == -1 ? TRUE : FALSE);
+
+  if (PANEL_DEACTIVATED(pos))
+    return;
+
+  if (use_dynamic_digits)              /* use dynamic number of digits */
+  {
+    digits  = (level_nr < 100 ? digits1  : digits2);
+    font_nr = (level_nr < 100 ? font1_nr : font2_nr);
+  }
+
+  pos->width = digits * getFontWidth(font_nr);
+
+  DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, digits), font_nr);
+}
+
+void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
+{
+  struct TextPosInfo *pos = &game.panel.keys;
   int base_key_graphic = EL_KEY_1;
   int i;
 
-  if (PANEL_DEACTIVATED(game.panel.keys))
+  if (PANEL_DEACTIVATED(pos))
     return;
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
     base_key_graphic = EL_EM_KEY_1;
 
+  pos->width = 4 * MINI_TILEX;
+
   /* currently only 4 of 8 possible keys are displayed */
   for (i = 0; i < STD_NUM_KEYS; i++)
   {
-    int x = XX_KEYS + i * MINI_TILEX;
-    int y = YY_KEYS;
+    int src_x = DOOR_GFX_PAGEX5 + 18;
+    int src_y = DOOR_GFX_PAGEY1 + 123;
+    int dst_x = PANEL_XPOS(pos) + i * MINI_TILEX;
+    int dst_y = PANEL_YPOS(pos);
 
     if (key[i])
-      DrawMiniGraphicExt(drawto, DX + x,DY + y, el2edimg(base_key_graphic + i));
+      DrawMiniGraphicExt(drawto, dst_x, dst_y, el2edimg(base_key_graphic + i));
     else
-      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
-                DOOR_GFX_PAGEX5 + x, y, MINI_TILEX, MINI_TILEY, DX + x,DY + y);
+      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y,
+                MINI_TILEX, MINI_TILEY, dst_x, dst_y);
   }
 }
 
-inline void DrawGameValue_Score(int value)
+#else
+
+void DrawGameValue_Emeralds(int value)
+{
+  int font_nr = FONT_TEXT_2;
+  int xpos = (3 * 14 - 3 * getFontWidth(font_nr)) / 2;
+
+  if (PANEL_DEACTIVATED(game.panel.gems))
+    return;
+
+  DrawText(DX_EMERALDS + xpos, DY_EMERALDS, int2str(value, 3), font_nr);
+}
+
+void DrawGameValue_Dynamite(int value)
+{
+  int font_nr = FONT_TEXT_2;
+  int xpos = (3 * 14 - 3 * getFontWidth(font_nr)) / 2;
+
+  if (PANEL_DEACTIVATED(game.panel.inventory))
+    return;
+
+  DrawText(DX_DYNAMITE + xpos, DY_DYNAMITE, int2str(value, 3), font_nr);
+}
+
+void DrawGameValue_Score(int value)
 {
-  int xpos = (5 * 14 - 5 * getFontWidth(FONT_TEXT_2)) / 2;
+  int font_nr = FONT_TEXT_2;
+  int xpos = (5 * 14 - 5 * getFontWidth(font_nr)) / 2;
 
   if (PANEL_DEACTIVATED(game.panel.score))
     return;
 
-  DrawText(DX_SCORE + xpos, DY_SCORE, int2str(value, 5), FONT_TEXT_2);
+  DrawText(DX_SCORE + xpos, DY_SCORE, int2str(value, 5), font_nr);
 }
 
-inline void DrawGameValue_Time(int value)
+void DrawGameValue_Time(int value)
 {
-  int xpos3 = (3 * 14 - 3 * getFontWidth(FONT_TEXT_2)) / 2;
-  int xpos4 = (4 * 10 - 4 * getFontWidth(FONT_LEVEL_NUMBER)) / 2;
+  int font1_nr = FONT_TEXT_2;
+#if 1
+  int font2_nr = FONT_TEXT_1;
+#else
+  int font2_nr = FONT_LEVEL_NUMBER;
+#endif
+  int xpos3 = (3 * 14 - 3 * getFontWidth(font1_nr)) / 2;
+  int xpos4 = (4 * 10 - 4 * getFontWidth(font2_nr)) / 2;
 
   if (PANEL_DEACTIVATED(game.panel.time))
     return;
@@ -1276,28 +1481,67 @@ inline void DrawGameValue_Time(int value)
     ClearRectangleOnBackground(drawto, DX_TIME1, DY_TIME, 14 * 3, 14);
 
   if (value < 1000)
-    DrawText(DX_TIME1 + xpos3, DY_TIME, int2str(value, 3), FONT_TEXT_2);
+    DrawText(DX_TIME1 + xpos3, DY_TIME, int2str(value, 3), font1_nr);
   else
-    DrawText(DX_TIME2 + xpos4, DY_TIME, int2str(value, 4), FONT_LEVEL_NUMBER);
+    DrawText(DX_TIME2 + xpos4, DY_TIME, int2str(value, 4), font2_nr);
 }
 
-inline void DrawGameValue_Level(int value)
+void DrawGameValue_Level(int value)
 {
+  int font1_nr = FONT_TEXT_2;
+#if 1
+  int font2_nr = FONT_TEXT_1;
+#else
+  int font2_nr = FONT_LEVEL_NUMBER;
+#endif
+
   if (PANEL_DEACTIVATED(game.panel.level))
     return;
 
   if (level_nr < 100)
-    DrawText(DX_LEVEL1, DY_LEVEL, int2str(value, 2), FONT_TEXT_2);
+    DrawText(DX_LEVEL1, DY_LEVEL, int2str(value, 2), font1_nr);
   else
-    DrawText(DX_LEVEL2, DY_LEVEL, int2str(value, 3), FONT_LEVEL_NUMBER);
+    DrawText(DX_LEVEL2, DY_LEVEL, int2str(value, 3), font2_nr);
 }
 
+void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
+{
+  int base_key_graphic = EL_KEY_1;
+  int i;
+
+  if (PANEL_DEACTIVATED(game.panel.keys))
+    return;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    base_key_graphic = EL_EM_KEY_1;
+
+  /* currently only 4 of 8 possible keys are displayed */
+  for (i = 0; i < STD_NUM_KEYS; i++)
+  {
+    int x = XX_KEYS + i * MINI_TILEX;
+    int y = YY_KEYS;
+
+    if (key[i])
+      DrawMiniGraphicExt(drawto, DX + x,DY + y, el2edimg(base_key_graphic + i));
+    else
+      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
+                DOOR_GFX_PAGEX5 + x, y, MINI_TILEX, MINI_TILEY, DX + x,DY + y);
+  }
+}
+
+#endif
+
 void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
                       int key_bits)
 {
   int key[MAX_NUM_KEYS];
   int i;
 
+  /* prevent EM engine from updating time/score values parallel to GameWon() */
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM &&
+      local_player->LevelSolved)
+    return;
+
   for (i = 0; i < MAX_NUM_KEYS; i++)
     key[i] = key_bits & (1 << i);
 
@@ -1309,14 +1553,15 @@ void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
   DrawGameValue_Time(time);
 
   DrawGameValue_Keys(key);
-
-  redraw_mask |= REDRAW_DOOR_1;
 }
 
 void DrawGameDoorValues()
 {
   int time_value = (level.time == 0 ? TimePlayed : TimeLeft);
-  int dynamite_state = 0;
+  int dynamite_value = 0;
+  int score_value = (local_player->LevelSolved ? local_player->score_final :
+                    local_player->score);
+  int gems_value = local_player->gems_still_needed;
   int key_bits = 0;
   int i, j;
 
@@ -1335,7 +1580,7 @@ void DrawGameDoorValues()
        if (stored_player[i].key[j])
          key_bits |= (1 << j);
 
-      dynamite_state += stored_player[i].inventory_size;
+      dynamite_value += stored_player[i].inventory_size;
     }
   }
   else
@@ -1346,11 +1591,11 @@ void DrawGameDoorValues()
       if (stored_player[player_nr].key[i])
        key_bits |= (1 << i);
 
-    dynamite_state = stored_player[player_nr].inventory_size;
+    dynamite_value = stored_player[player_nr].inventory_size;
   }
 
-  DrawAllGameValues(local_player->gems_still_needed, dynamite_state,
-                   local_player->score, time_value, key_bits);
+  DrawAllGameValues(gems_value, dynamite_value, score_value, time_value,
+                   key_bits);
 }
 
 
@@ -1774,6 +2019,11 @@ static void InitGameEngine()
         EL_EMPTY);
     }
   }
+
+  /* ---------- initialize recursion detection ------------------------------ */
+  recursion_loop_depth = 0;
+  recursion_loop_detected = FALSE;
+  recursion_loop_element = EL_UNDEFINED;
 }
 
 int get_num_special_action(int element, int action_first, int action_last)
@@ -1833,12 +2083,15 @@ void InitGame()
 
     player->present = FALSE;
     player->active = FALSE;
+    player->killed = 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;
@@ -1847,6 +2100,8 @@ 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;
@@ -1922,8 +2177,10 @@ void InitGame()
     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;
@@ -1937,7 +2194,9 @@ void InitGame()
     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;
   }
@@ -2023,6 +2282,7 @@ void InitGame()
     WasJustMoving[x][y] = 0;
     WasJustFalling[x][y] = 0;
     CheckCollision[x][y] = 0;
+    CheckImpact[x][y] = 0;
     Stop[x][y] = FALSE;
     Pushed[x][y] = FALSE;
 
@@ -2423,7 +2683,7 @@ void InitGame()
 
     OpenDoor(DOOR_OPEN_ALL);
 
-    PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
+    PlaySound(SND_GAME_STARTING);
 
     if (setup.sound_music)
       PlayLevelMusic();
@@ -2438,6 +2698,13 @@ void InitGame()
     }
   }
 
+#if 1
+  UnmapAllGadgets();
+
+  MapGameButtons();
+  MapTapeButtons();
+#endif
+
   game.restart_level = FALSE;
 }
 
@@ -2467,7 +2734,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:
@@ -2648,119 +2915,167 @@ void InitAmoebaNr(int x, int y)
   AmoebaCnt2[group_nr]++;
 }
 
-#if USE_NEW_GAME_WON
+static void PlayerWins(struct PlayerInfo *player)
+{
+  player->LevelSolved = TRUE;
+  player->GameOver = TRUE;
+
+  player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                        level.native_em_level->lev->score : player->score);
+}
 
 void GameWon()
 {
-  static boolean score_done = FALSE;
-  static boolean player_done = FALSE;
-  static int game_over_delay = 0;
-  int game_over_delay_value = 50;
+  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;
 
-  /* do not start end game actions before the player stops moving (to exit) */
-  if (local_player->MovPos)
-    return;
+  if (!local_player->LevelSolved_GameWon)
+  {
+    int i;
 
-  if (tape.auto_play)          /* tape might already be stopped here */
-    tape.auto_play_level_solved = TRUE;
+    /* do not start end game actions before the player stops moving (to exit) */
+    if (local_player->MovPos)
+      return;
 
-  if (!local_player->LevelSolved_GameEnd)
-  {
-    local_player->LevelSolved_GameEnd = TRUE;
+    local_player->LevelSolved_GameWon = TRUE;
     local_player->LevelSolved_SaveTape = tape.recording;
     local_player->LevelSolved_SaveScore = !tape.playing;
 
-    score_done = FALSE;
-    player_done = FALSE;
-    game_over_delay = 0;
-  }
+    if (tape.auto_play)                /* tape might already be stopped here */
+      tape.auto_play_level_solved = TRUE;
 
-  PlaySoundStereo(SND_GAME_WINNING, SOUND_MIDDLE);
+#if 1
+    TapeStop();
+#endif
 
-  if (TimeLeft > 0)
-  {
-    if (!tape.playing)
-    {
-      if (setup.sound_loops)
-       PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
-                    SND_CTRL_PLAY_LOOP);
-      else
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
-    }
+    game_over_delay_1 = game_over_delay_value_1;
+    game_over_delay_2 = game_over_delay_value_2;
+
+    time = time_final = (level.time == 0 ? TimePlayed : TimeLeft);
+    score = score_final = local_player->score_final;
 
-    if (TimeLeft > 100 && TimeLeft % 10 == 0)
+    if (TimeLeft > 0)
     {
-      TimeLeft -= 10;
-      RaiseScore(level.score[SC_TIME_BONUS] * 10);
+      time_final = 0;
+      score_final += TimeLeft * level.score[SC_TIME_BONUS];
     }
-    else
+    else if (level.time == 0 && TimePlayed < 999)
     {
-      TimeLeft--;
-      RaiseScore(level.score[SC_TIME_BONUS]);
+      time_final = 999;
+      score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
     }
 
-    DrawGameValue_Time(TimeLeft);
+    local_player->score_final = score_final;
 
-    if (TimeLeft <= 0 && !tape.playing && setup.sound_loops)
-      StopSound(SND_GAME_LEVELTIME_BONUS);
-  }
-  else if (level.time == 0 && TimePlayed < 999)        /* level without time limit */
-  {
-    if (!tape.playing)
+    if (level_editor_test_game)
     {
-      if (setup.sound_loops)
-       PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
-                    SND_CTRL_PLAY_LOOP);
-      else
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
-    }
+      time = time_final;
+      score = score_final;
 
-    if (TimePlayed < 900 && TimePlayed % 10 == 0)
-    {
-      TimePlayed += 10;
-      RaiseScore(level.score[SC_TIME_BONUS] * 10);
+      DrawGameValue_Time(time);
+      DrawGameValue_Score(score);
     }
-    else
+
+    if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
     {
-      TimePlayed++;
-      RaiseScore(level.score[SC_TIME_BONUS]);
-    }
+      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];
 
-    DrawGameValue_Time(TimePlayed);
+#if 0
+         if (element == EL_EM_EXIT_OPEN ||
+             element == EL_EM_STEEL_EXIT_OPEN)
+         {
+           Bang(ExitX, ExitY);
+         }
+         else
+#endif
+         {
+           Feld[ExitX][ExitY] =
+             (element == EL_EXIT_OPEN          ? EL_EXIT_CLOSING :
+              element == EL_EM_EXIT_OPEN       ? EL_EM_EXIT_CLOSING :
+              element == EL_SP_EXIT_OPEN       ? EL_SP_EXIT_CLOSING:
+              element == EL_STEEL_EXIT_OPEN    ? EL_STEEL_EXIT_CLOSING:
+              EL_EM_STEEL_EXIT_CLOSING);
+
+           PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
+         }
+       }
 
-    if (TimePlayed >= 999 && !tape.playing && setup.sound_loops)
-      StopSound(SND_GAME_LEVELTIME_BONUS);
+       /* player disappears */
+       DrawLevelField(ExitX, ExitY);
+      }
+
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+
+       if (player->present)
+       {
+         RemovePlayer(player);
+
+         /* player disappears */
+         DrawLevelField(player->jx, player->jy);
+       }
+      }
+    }
+
+    PlaySound(SND_GAME_WINNING);
   }
-  else
+
+  if (game_over_delay_1 > 0)
   {
-    score_done = TRUE;
+    game_over_delay_1--;
+
+    return;
   }
 
-  /* 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))
+  if (time != time_final)
   {
-    int element = Feld[ExitX][ExitY];
+    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);
 
-    Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
-                         EL_SP_EXIT_CLOSING);
+    time  += time_count_steps * time_count_dir;
+    score += time_count_steps * level.score[SC_TIME_BONUS];
 
-    PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
-  }
+    DrawGameValue_Time(time);
+    DrawGameValue_Score(score);
 
-  /* player disappears */
-  if (ExitX >= 0 && ExitY >= 0 && !player_done)
-  {
-    DrawLevelField(ExitX, ExitY);
+    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);
 
-    player_done = TRUE;
+    return;
   }
 
-  game_over_delay++;
+  local_player->LevelSolved_PanelOff = TRUE;
+
+  if (game_over_delay_2 > 0)
+  {
+    game_over_delay_2--;
 
-  if (game_over_delay < game_over_delay_value || !score_done)
     return;
+  }
+
+#if 1
+  GameEnd();
+#endif
 }
 
 void GameEnd()
@@ -2768,13 +3083,30 @@ void GameEnd()
   int hi_pos;
   boolean raise_level = FALSE;
 
+  local_player->LevelSolved_GameEnd = TRUE;
+
   CloseDoor(DOOR_CLOSE_1);
 
   if (local_player->LevelSolved_SaveTape)
   {
+#if 0
     TapeStop();
+#endif
 
-    SaveTape(tape.level_nr);           /* Ask to save tape */
+#if 1
+    SaveTapeChecked(tape.level_nr);    /* ask to save tape */
+#else
+    SaveTape(tape.level_nr);           /* ask to save tape */
+#endif
+  }
+
+  if (level_editor_test_game)
+  {
+    game_status = GAME_MODE_MAIN;
+
+    DrawMainMenu();
+
+    return;
   }
 
   if (!local_player->LevelSolved_SaveScore)
@@ -2794,10 +3126,8 @@ void GameEnd()
     SaveLevelSetup_SeriesInfo();
   }
 
-  if (level_editor_test_game)
-    local_player->score = -1;  /* no highscore when playing from editor */
-  else if (level_nr < leveldir_current->last_level)
-    raise_level = TRUE;                /* advance to next level */
+  if (level_nr < leveldir_current->last_level)
+    raise_level = TRUE;                        /* advance to next level */
 
   if ((hi_pos = NewHiScore()) >= 0) 
   {
@@ -2825,161 +3155,8 @@ void GameEnd()
 
     DrawAndFadeInMainMenu(REDRAW_FIELD);
   }
-
-  local_player->LevelSolved_SaveScore = FALSE;
-}
-
-#else
-
-void GameWon()
-{
-  int hi_pos;
-  boolean raise_level = FALSE;
-
-  if (local_player->MovPos)
-    return;
-
-  if (tape.auto_play)          /* tape might already be stopped here */
-    tape.auto_play_level_solved = TRUE;
-
-  local_player->LevelSolved = FALSE;
-
-  PlaySoundStereo(SND_GAME_WINNING, SOUND_MIDDLE);
-
-  if (TimeLeft)
-  {
-    if (!tape.playing && setup.sound_loops)
-      PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
-                  SND_CTRL_PLAY_LOOP);
-
-    while (TimeLeft > 0)
-    {
-      if (!tape.playing && !setup.sound_loops)
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
-
-      if (TimeLeft > 100 && TimeLeft % 10 == 0)
-      {
-       TimeLeft -= 10;
-       RaiseScore(level.score[SC_TIME_BONUS] * 10);
-      }
-      else
-      {
-       TimeLeft--;
-       RaiseScore(level.score[SC_TIME_BONUS]);
-      }
-
-      DrawGameValue_Time(TimeLeft);
-
-      BackToFront();
-
-      if (!tape.playing)
-       Delay(10);
-    }
-
-    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);
-
-    while (TimePlayed < 999)
-    {
-      if (!tape.playing && !setup.sound_loops)
-       PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
-
-      if (TimePlayed < 900 && TimePlayed % 10 == 0)
-      {
-       TimePlayed += 10;
-       RaiseScore(level.score[SC_TIME_BONUS] * 10);
-      }
-      else
-      {
-       TimePlayed++;
-       RaiseScore(level.score[SC_TIME_BONUS]);
-      }
-
-      DrawGameValue_Time(TimePlayed);
-
-      BackToFront();
-
-      if (!tape.playing)
-       Delay(10);
-    }
-
-    if (!tape.playing && setup.sound_loops)
-      StopSound(SND_GAME_LEVELTIME_BONUS);
-  }
-
-  /* close exit door after last player */
-  if (AllPlayersGone && ExitX >= 0 && ExitY >= 0 &&
-      (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
-       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN))
-  {
-    int element = Feld[ExitX][ExitY];
-
-    Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
-                         EL_SP_EXIT_CLOSING);
-
-    PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
-  }
-
-  /* player disappears */
-  if (ExitX >= 0 && ExitY >= 0)
-    DrawLevelField(ExitX, ExitY);
-
-  BackToFront();
-
-  if (tape.playing)
-    return;
-
-  CloseDoor(DOOR_CLOSE_1);
-
-  if (tape.recording)
-  {
-    TapeStop();
-    SaveTape(tape.level_nr);           /* Ask to save tape */
-  }
-
-  if (level_nr == leveldir_current->handicap_level)
-  {
-    leveldir_current->handicap_level++;
-    SaveLevelSetup_SeriesInfo();
-  }
-
-  if (level_editor_test_game)
-    local_player->score = -1;  /* no highscore when playing from editor */
-  else if (level_nr < leveldir_current->last_level)
-    raise_level = TRUE;                /* advance to next level */
-
-  if ((hi_pos = NewHiScore()) >= 0) 
-  {
-    game_status = GAME_MODE_SCORES;
-    DrawHallOfFame(hi_pos);
-    if (raise_level)
-    {
-      level_nr++;
-      TapeErase();
-    }
-  }
-  else
-  {
-    game_status = GAME_MODE_MAIN;
-    if (raise_level)
-    {
-      level_nr++;
-      TapeErase();
-    }
-    DrawMainMenu();
-  }
-
-  BackToFront();
 }
 
-#endif
-
 int NewHiScore()
 {
   int k, l;
@@ -2988,12 +3165,12 @@ int NewHiScore()
   LoadScore(level_nr);
 
   if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
-      local_player->score < highscore[MAX_SCORE_ENTRIES - 1].Score) 
+      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 */
 
@@ -3021,7 +3198,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;
     }
@@ -3040,10 +3217,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);
@@ -3063,6 +3239,11 @@ inline static int getElementMoveStepsize(int x, int y)
   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)
@@ -3123,18 +3304,56 @@ 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 0
+  boolean continues_moving = (WasJustMoving[x][y] && direction == MovDir[x][y]);
+#endif
+
+  /* check if element was/is moving or being moved before/after mode change */
+#if 1
+#if 1
+  is_moving_before = (WasJustMoving[x][y] != 0);
+#else
+  /* (!!! this does not work -- WasJustMoving is NOT a boolean value !!!) */
+  is_moving_before = WasJustMoving[x][y];
+#endif
+#else
+  is_moving_before = (getElementMoveStepsizeExt(x, y, MovDir[x][y]) != 0);
+#endif
+  is_moving_after  = (getElementMoveStepsizeExt(x, y, direction)    != 0);
 
-  if (!WasJustMoving[x][y] || direction != MovDir[x][y])
+  /* reset animation only for moving elements which change direction of moving
+     or which just started or stopped moving
+     (else CEs with property "can move" / "not moving" are reset each frame) */
+#if USE_GFX_RESET_ONLY_WHEN_MOVING
+#if 1
+  if (is_moving_before != is_moving_after ||
+      direction != MovDir[x][y])
+    ResetGfxAnimation(x, y);
+#else
+  if ((is_moving_before || is_moving_after) && !continues_moving)
+    ResetGfxAnimation(x, y);
+#endif
+#else
+  if (!continues_moving)
     ResetGfxAnimation(x, y);
+#endif
 
   MovDir[x][y] = direction;
   GfxDir[x][y] = direction;
+
+#if USE_GFX_RESET_ONLY_WHEN_MOVING
+  GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
+                    direction == MV_DOWN && CAN_FALL(element) ?
+                    ACTION_FALLING : ACTION_MOVING);
+#else
   GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
                     ACTION_FALLING : ACTION_MOVING);
+#endif
 
   /* this is needed for CEs with property "can move" / "not moving" */
 
-  if (getElementMoveStepsize(x, y) != 0)       /* moving or being moved */
+  if (is_moving_after)
   {
     if (Feld[newx][newy] == EL_EMPTY)
       Feld[newx][newy] = EL_BLOCKED;
@@ -3281,8 +3500,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]);
 
@@ -3393,8 +3614,8 @@ static void setScreenCenteredToAllPlayers(int *sx, int *sy)
   *sy = (sy1 + sy2) / 2;
 }
 
-void DrawRelocateScreen(int x, int y, int move_dir, boolean center_screen,
-                       boolean quick_relocation)
+void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
+                       boolean center_screen, boolean quick_relocation)
 {
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
@@ -3407,13 +3628,39 @@ void DrawRelocateScreen(int x, int y, int move_dir, boolean center_screen,
 
     if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
     {
-      scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
-                 x > SBX_Right + MIDPOSX ? SBX_Right :
-                 x - MIDPOSX);
+      if (center_screen)
+      {
+       scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+                   x > SBX_Right + MIDPOSX ? SBX_Right :
+                   x - MIDPOSX);
+
+       scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                   y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                   y - MIDPOSY);
+      }
+      else
+      {
+       /* quick relocation (without scrolling), but do not center screen */
+
+       int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                              old_x > SBX_Right + MIDPOSX ? SBX_Right :
+                              old_x - MIDPOSX);
+
+       int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                              old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                              old_y - MIDPOSY);
 
-      scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                 y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                 y - MIDPOSY);
+       int offset_x = x + (scroll_x - center_scroll_x);
+       int offset_y = y + (scroll_y - center_scroll_y);
+
+       scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                   offset_x > SBX_Right + MIDPOSX ? SBX_Right :
+                   offset_x - MIDPOSX);
+
+       scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                   offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                   offset_y - MIDPOSY);
+      }
     }
     else
     {
@@ -3560,8 +3807,8 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
   }
 
   /* only visually relocate centered player */
-  DrawRelocateScreen(player->jx, player->jy, player->MovDir, FALSE,
-                    level.instant_relocation);
+  DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
+                    FALSE, level.instant_relocation);
 
   TestIfPlayerTouchesBadThing(jx, jy);
   TestIfPlayerTouchesCustomElement(jx, jy);
@@ -3994,7 +4241,7 @@ void Bang(int x, int y)
     }
   }
 
-  switch(element)
+  switch (element)
   {
     case EL_BUG:
     case EL_SPACESHIP:
@@ -4018,6 +4265,14 @@ void Bang(int x, int y)
       explosion_type = EX_TYPE_DYNA;
       break;
 
+    case EL_DC_LANDMINE:
+#if 0
+    case EL_EM_EXIT_OPEN:
+    case EL_EM_STEEL_EXIT_OPEN:
+#endif
+      explosion_type = EX_TYPE_CENTER;
+      break;
+
     case EL_PENGUIN:
     case EL_LAMP:
     case EL_LAMP_ACTIVE:
@@ -4231,6 +4486,12 @@ static void ToggleSwitchgateSwitch(int x, int y)
       Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
       DrawLevelField(xx, yy);
     }
+    else if (element == EL_DC_SWITCHGATE_SWITCH_UP ||
+            element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+    {
+      Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
+      DrawLevelField(xx, yy);
+    }
 #else
     if (element == EL_SWITCHGATE_SWITCH_UP)
     {
@@ -4242,6 +4503,16 @@ static void ToggleSwitchgateSwitch(int x, int y)
       Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
       DrawLevelField(xx, yy);
     }
+    else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
+    {
+      Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
+      DrawLevelField(xx, yy);
+    }
+    else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+    {
+      Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
+      DrawLevelField(xx, yy);
+    }
 #endif
     else if (element == EL_SWITCHGATE_OPEN ||
             element == EL_SWITCHGATE_OPENING)
@@ -4458,7 +4729,7 @@ static void ActivateTimegateSwitch(int x, int y)
        element == EL_TIMEGATE_CLOSING)
     {
       Feld[xx][yy] = EL_TIMEGATE_OPENING;
-      PlayLevelSound(xx, yy, SND_TIMEGATE_OPENING);
+      PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
     }
 
     /*
@@ -4471,7 +4742,12 @@ static void ActivateTimegateSwitch(int x, int y)
 
   }
 
+#if 1
+  Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
+               EL_DC_TIMEGATE_SWITCH_ACTIVE);
+#else
   Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE;
+#endif
 }
 
 void Impact(int x, int y)
@@ -4506,6 +4782,16 @@ void Impact(int x, int y)
 
       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;
+      DrawLevelField(x, y + 2);
+
+      object_hit = TRUE;
+    }
 #endif
 
     if (object_hit)
@@ -4534,7 +4820,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);
 
@@ -4567,26 +4854,33 @@ 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 */
       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))
@@ -4666,7 +4960,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);
        }
@@ -4699,12 +4995,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;
   }
@@ -5061,7 +5360,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;
@@ -5618,6 +5920,43 @@ void StartMoving(int x, int y)
        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
+       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;
+
+       if (MovDelay[x][y])
+       {
+         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 ((element == EL_ROCK || element == EL_BD_ROCK) &&
             Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
     {
@@ -5629,6 +5968,17 @@ void StartMoving(int x, int y)
 
       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 if (element == EL_MAGIC_WALL_FULL)
     {
       if (IS_FREE(x, y + 1))
@@ -5665,7 +6015,7 @@ 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)
       {
@@ -5681,20 +6031,52 @@ 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;
     }
     else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
@@ -5706,9 +6088,14 @@ void StartMoving(int x, int y)
 
       Store[x][y] = EL_ACID;
     }
-    else if ((game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+    else if (
+#if USE_FIX_IMPACT_COLLISION
+            (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+             CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
+#else
+            (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
              CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
-
+#endif
             (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
              CAN_FALL(element) && WasJustFalling[x][y] &&
              (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
@@ -5728,6 +6115,7 @@ void StartMoving(int x, int y)
         simply not covered here... :-/ ) */
 
       CheckCollision[x][y] = 0;
+      CheckImpact[x][y] = 0;
 
       Impact(x, y);
     }
@@ -6052,7 +6440,10 @@ void StartMoving(int x, int y)
     }
     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)
       {
        RemoveField(x, y);
        DrawLevelField(x, y);
@@ -6064,7 +6455,7 @@ 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;
       }
@@ -6470,6 +6861,17 @@ void ContinueMoving(int x, int y)
 
   if (ABS(MovPos[x][y]) < TILEX)
   {
+#if 0
+    int ee = Feld[x][y];
+    int gg = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+    int ff = getGraphicAnimationFrame(gg, GfxFrame[x][y]);
+
+    printf("::: %d.%d: moving %d ... [%d, %d, %d] [%d, %d, %d]\n",
+          x, y, ABS(MovPos[x][y]),
+          ee, gg, ff,
+          GfxAction[x][y], GfxDir[x][y], GfxFrame[x][y]);
+#endif
+
     DrawLevelField(x, y);
 
     return;    /* element is still moving */
@@ -6501,6 +6903,16 @@ void ContinueMoving(int x, int y)
     Feld[x][y] = get_next_element(element);
     element = Feld[newx][newy] = Store[x][y];
   }
+  else if (element == EL_QUICKSAND_FAST_FILLING)
+  {
+    element = Feld[newx][newy] = get_next_element(element);
+    Store[newx][newy] = Store[x][y];
+  }
+  else if (element == EL_QUICKSAND_FAST_EMPTYING)
+  {
+    Feld[x][y] = get_next_element(element);
+    element = Feld[newx][newy] = Store[x][y];
+  }
   else if (element == EL_MAGIC_WALL_FILLING)
   {
     element = Feld[newx][newy] = get_next_element(element);
@@ -6533,6 +6945,24 @@ void ContinueMoving(int x, int y)
       Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
     element = Feld[newx][newy] = Store[x][y];
 
+#if USE_NEW_CUSTOM_VALUE
+    InitField(newx, newy, FALSE);
+#endif
+  }
+  else if (element == EL_DC_MAGIC_WALL_FILLING)
+  {
+    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];
+
 #if USE_NEW_CUSTOM_VALUE
     InitField(newx, newy, FALSE);
 #endif
@@ -6672,6 +7102,11 @@ void ContinueMoving(int x, int y)
 
     if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
       CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
+
+#if USE_FIX_IMPACT_COLLISION
+    if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
+      CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
+#endif
   }
 
   if (DONT_TOUCH(element))     /* object may be nasty to player or others */
@@ -6739,7 +7174,7 @@ void ContinueMoving(int x, int y)
   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));
+                            MV_DIR_OPPOSITE(direction));
 }
 
 int AmoebeNachbarNr(int ax, int ay)
@@ -7031,7 +7466,8 @@ void AmoebeAbleger(int ax, int ay)
 
     if (IS_FREE(x, y) ||
        CAN_GROW_INTO(Feld[x][y]) ||
-       Feld[x][y] == EL_QUICKSAND_EMPTY)
+       Feld[x][y] == EL_QUICKSAND_EMPTY ||
+       Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
     {
       newax = x;
       neway = y;
@@ -7056,7 +7492,8 @@ void AmoebeAbleger(int ax, int ay)
 
       if (IS_FREE(x, y) ||
          CAN_GROW_INTO(Feld[x][y]) ||
-         Feld[x][y] == EL_QUICKSAND_EMPTY)
+         Feld[x][y] == EL_QUICKSAND_EMPTY ||
+         Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
       {
        newax = x;
        neway = y;
@@ -7241,7 +7678,7 @@ static void InitTimegateWheel(int x, int y)
 
 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)
@@ -7308,6 +7745,75 @@ void CheckExit(int x, int y)
   PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
 }
 
+void CheckExitEM(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_EXIT_OPENING;
+
+  PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
+}
+
+void CheckExitSteel(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_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)
@@ -7346,7 +7852,7 @@ static void CloseAllOpenTimegates()
   }
 }
 
-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;
@@ -7355,7 +7861,7 @@ 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 */
   {
@@ -7546,6 +8052,108 @@ void MauerAbleger(int ax, int ay)
     PlayLevelSoundAction(ax, ay, ACTION_GROWING);
 }
 
+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_WALL;
+
+  if (new_wall)
+    PlayLevelSoundAction(ax, ay, ACTION_GROWING);
+}
+
 void CheckForDragon(int x, int y)
 {
   int i, j;
@@ -7789,7 +8397,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
   /* ---------- execute action  -------------------------------------------- */
 
-  switch(action_type)
+  switch (action_type)
   {
     case CA_NO_ACTION:
     {
@@ -7884,7 +8492,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     {
       for (i = 0; i < MAX_PLAYERS; i++)
        if (action_arg_player_bits & (1 << i))
-         stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE;
+         PlayerWins(&stored_player[i]);
 
       break;
     }
@@ -8017,6 +8625,11 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
              (level.use_artwork_element[i] ? level.artwork_element[i] :
               stored_player[i].element_nr);
 
+#if USE_GFX_RESET_PLAYER_ARTWORK
+         if (stored_player[i].artwork_element != artwork_element)
+           stored_player[i].Frame = 0;
+#endif
+
          stored_player[i].artwork_element = artwork_element;
 
          SetPlayerWaiting(&stored_player[i], FALSE);
@@ -8124,6 +8737,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
 #if USE_NEW_CUSTOM_VALUE
   int last_ce_value = CustomValue[x][y];
 #endif
+  boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
   boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
   boolean add_player_onto_element = (new_element_is_player &&
 #if USE_CODE_THAT_BREAKS_SNAKE_BITE
@@ -8187,6 +8801,15 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
   /* check if element under the player changes from accessible to unaccessible
      (needed for special case of dropping element which then changes) */
   /* (must be checked after creating new element for walkable group elements) */
+#if USE_FIX_KILLED_BY_NON_WALKABLE
+  if (IS_PLAYER(x, y) && !player_explosion_protected &&
+      IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
+  {
+    Bang(x, y);
+
+    return;
+  }
+#else
   if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
       IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
   {
@@ -8194,6 +8817,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
 
     return;
   }
+#endif
 #endif
 
   /* "ChangeCount" not set yet to allow "entered by player" change one time */
@@ -8439,9 +9063,19 @@ static void HandleElementChange(int x, int y, int page)
     ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
 
     if (change->can_change)
-    {
-      ResetGfxAnimation(x, y);
-      ResetRandomAnimationValue(x, y);
+    {
+#if 0
+      /* !!! not clear why graphic animation should be reset at all here !!! */
+#if USE_GFX_RESET_WHEN_NOT_MOVING
+      /* when a custom element is about to change (for example by change delay),
+        do not reset graphic animation when the custom element is moving */
+      if (IS_MOVING(x, y))
+#endif
+      {
+       ResetGfxAnimation(x, y);
+       ResetRandomAnimationValue(x, y);
+      }
+#endif
 
       if (change->pre_change_function)
        change->pre_change_function(x, y);
@@ -8590,6 +9224,14 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
   if (!(trigger_events[trigger_element][trigger_event]))
     return FALSE;
 
+#if 0
+  printf("::: CheckTriggeredElementChangeExt %d ... [%d, %d, %d, '%s']\n",
+        trigger_event, recursion_loop_depth, recursion_loop_detected,
+        recursion_loop_element, EL_NAME(recursion_loop_element));
+#endif
+
+  RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
@@ -8658,6 +9300,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
     }
   }
 
+  RECURSION_LOOP_DETECTION_END();
+
   return change_done_any;
 }
 
@@ -8696,14 +9340,30 @@ static boolean CheckElementChangeExt(int x, int y,
     return FALSE;
 #endif
 
+#if 0
+  printf("::: CheckElementChangeExt %d ... [%d, %d, %d, '%s']\n",
+        trigger_event, recursion_loop_depth, recursion_loop_detected,
+        recursion_loop_element, EL_NAME(recursion_loop_element));
+#endif
+
+  RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
   for (p = 0; p < element_info[element].num_change_pages; p++)
   {
     struct ElementChangeInfo *change = &element_info[element].change_page[p];
 
+    /* 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_HIT_BY_X ||
+#if 1
+       /* this one was forgotten until 3.2.3 */
+       trigger_event == CE_DIGGING_X);
+#endif
 
     if (change->can_change_or_has_action &&
        change->has_event[trigger_event] &&
@@ -8767,6 +9427,8 @@ static boolean CheckElementChangeExt(int x, int y,
     }
   }
 
+  RECURSION_LOOP_DETECTION_END();
+
   return change_done;
 }
 
@@ -8820,11 +9482,11 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
       player->frame_counter_bored =
        FrameCounter +
        game.player_boring_delay_fixed +
-       SimpleRND(game.player_boring_delay_random);
+       GetSimpleRandom(game.player_boring_delay_random);
       player->frame_counter_sleeping =
        FrameCounter +
        game.player_sleeping_delay_fixed +
-       SimpleRND(game.player_sleeping_delay_random);
+       GetSimpleRandom(game.player_sleeping_delay_random);
 
       InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
     }
@@ -8880,10 +9542,10 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
 
          player->anim_delay_counter =
            graphic_info[special_graphic].anim_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].anim_delay_random);
+           GetSimpleRandom(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);
+           GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
 
          player->special_action_sleeping = special_action;
        }
@@ -8906,16 +9568,16 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
        if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
        {
          int special_action =
-           ACTION_BORING_1 + SimpleRND(player->num_special_action_bored);
+           ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
          int special_graphic =
            el_act_dir2img(player->artwork_element, special_action, move_dir);
 
          player->anim_delay_counter =
            graphic_info[special_graphic].anim_delay_fixed +
-           SimpleRND(graphic_info[special_graphic].anim_delay_random);
+           GetSimpleRandom(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);
+           GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
 
          player->special_action_bored = special_action;
        }
@@ -9022,7 +9684,8 @@ static void CheckLevelTime()
   {
     if (level.native_em_level->lev->home == 0) /* all players at home */
     {
-      local_player->LevelSolved = TRUE;
+      PlayerWins(local_player);
+
       AllPlayersGone = TRUE;
 
       level.native_em_level->lev->home = -1;
@@ -9053,7 +9716,7 @@ static void CheckLevelTime()
       }
     }
 
-    if (!level.use_step_counter)
+    if (!local_player->LevelSolved && !level.use_step_counter)
     {
       TimePlayed++;
 
@@ -9062,7 +9725,7 @@ static void CheckLevelTime()
        TimeLeft--;
 
        if (TimeLeft <= 10 && setup.time_limit)
-         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
 
        DrawGameValue_Time(TimeLeft);
 
@@ -9167,6 +9830,25 @@ void GameActions()
   byte tape_action[MAX_PLAYERS];
   int i;
 
+  /* detect endless loops, caused by custom element programming */
+  if (recursion_loop_detected && recursion_loop_depth == 0)
+  {
+    char *message = getStringCat3("Internal Error ! Element ",
+                                 EL_NAME(recursion_loop_element),
+                                 " caused endless loop ! Quit the game ?");
+
+    Error(ERR_WARN, "element '%s' caused endless loop in game engine",
+         EL_NAME(recursion_loop_element));
+
+    RequestQuitGameExt(FALSE, level_editor_test_game, message);
+
+    recursion_loop_detected = FALSE;   /* if game should be continued */
+
+    free(message);
+
+    return;
+  }
+
   if (game.restart_level)
     StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
 
@@ -9174,7 +9856,8 @@ void GameActions()
   {
     if (level.native_em_level->lev->home == 0) /* all players at home */
     {
-      local_player->LevelSolved = TRUE;
+      PlayerWins(local_player);
+
       AllPlayersGone = TRUE;
 
       level.native_em_level->lev->home = -1;
@@ -9187,7 +9870,7 @@ void GameActions()
       AllPlayersGone = TRUE;
   }
 
-  if (local_player->LevelSolved)
+  if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
     GameWon();
 
   if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
@@ -9369,7 +10052,7 @@ void GameActions_RND()
     game.centered_player_nr = game.centered_player_nr_next;
     game.set_centered_player = FALSE;
 
-    DrawRelocateScreen(sx, sy, MV_NONE, TRUE, setup.quick_switch);
+    DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
     DrawGameDoorValues();
   }
 
@@ -9475,6 +10158,8 @@ void GameActions_RND()
       WasJustFalling[x][y]--;
     if (CheckCollision[x][y] > 0)
       CheckCollision[x][y]--;
+    if (CheckImpact[x][y] > 0)
+      CheckImpact[x][y]--;
 
     GfxFrame[x][y]++;
 
@@ -9559,11 +10244,14 @@ void GameActions_RND()
        DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
       if (IS_GEM(element) || element == EL_SP_INFOTRON)
-       EdelsteinFunkeln(x, y);
+       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 ||
@@ -9589,9 +10277,16 @@ void GameActions_RND()
       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)
+    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 ||
@@ -9599,6 +10294,10 @@ void GameActions_RND()
             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)
@@ -9627,7 +10326,10 @@ void GameActions_RND()
           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_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;
@@ -9652,6 +10354,7 @@ void GameActions_RND()
          (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))
       {
@@ -9698,6 +10401,10 @@ void GameActions_RND()
          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);
     }
@@ -9723,6 +10430,12 @@ void GameActions_RND()
            Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
            DrawLevelField(x, y);
          }
+         else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
+                  element == EL_DC_MAGIC_WALL_FULL)
+         {
+           Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
+           DrawLevelField(x, y);
+         }
        }
 
        game.magic_wall_active = FALSE;
@@ -9854,25 +10567,79 @@ static boolean AllPlayersInVisibleScreen()
 
 void ScrollLevel(int dx, int dy)
 {
+#if 1
+  static Bitmap *bitmap_db_field2 = NULL;
   int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
   int x, y;
+#else
+  int i, x, y;
+#endif
+
+  /* only horizontal XOR vertical scroll direction allowed */
+  if ((dx == 0 && dy == 0) || (dx != 0 && dy != 0))
+    return;
+
+#if 1
+  if (bitmap_db_field2 == NULL)
+    bitmap_db_field2 = CreateBitmap(FXSIZE, FYSIZE, DEFAULT_DEPTH);
+
+  BlitBitmap(drawto_field, bitmap_db_field2,
+            FX + TILEX * (dx == -1) - softscroll_offset,
+            FY + TILEY * (dy == -1) - softscroll_offset,
+            SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset,
+            SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset,
+            FX + TILEX * (dx == 1) - softscroll_offset,
+            FY + TILEY * (dy == 1) - softscroll_offset);
+  BlitBitmap(bitmap_db_field2, drawto_field,
+            FX + TILEX * (dx == 1) - softscroll_offset,
+            FY + TILEY * (dy == 1) - softscroll_offset,
+            SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset,
+            SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset,
+            FX + TILEX * (dx == 1) - softscroll_offset,
+            FY + TILEY * (dy == 1) - softscroll_offset);
+
+#else
+
+#if 1
+  int xsize = (BX2 - BX1 + 1);
+  int ysize = (BY2 - BY1 + 1);
+  int start = (dx != 0 ? (dx == -1 ? BX1 : BX2) : (dy == -1 ? BY1 : BY2));
+  int end   = (dx != 0 ? (dx == -1 ? BX2 : BX1) : (dy == -1 ? BY2 : BY1));
+  int step  = (start < end ? +1 : -1);
+
+  for (i = start; i != end; i += step)
+  {
+    BlitBitmap(drawto_field, drawto_field,
+              FX + TILEX * (dx != 0 ? i + step : 0),
+              FY + TILEY * (dy != 0 ? i + step : 0),
+              TILEX * (dx != 0 ? 1 : xsize),
+              TILEY * (dy != 0 ? 1 : ysize),
+              FX + TILEX * (dx != 0 ? i : 0),
+              FY + TILEY * (dy != 0 ? i : 0));
+  }
+
+#else
+
+  int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
 
   BlitBitmap(drawto_field, drawto_field,
             FX + TILEX * (dx == -1) - softscroll_offset,
             FY + TILEY * (dy == -1) - softscroll_offset,
-            SXSIZE - TILEX * (dx!=0) + 2 * softscroll_offset,
-            SYSIZE - TILEY * (dy!=0) + 2 * softscroll_offset,
+            SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset,
+            SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset,
             FX + TILEX * (dx == 1) - softscroll_offset,
             FY + TILEY * (dy == 1) - softscroll_offset);
+#endif
+#endif
 
-  if (dx)
+  if (dx != 0)
   {
     x = (dx == 1 ? BX1 : BX2);
     for (y = BY1; y <= BY2; y++)
       DrawScreenField(x, y);
   }
 
-  if (dy)
+  if (dy != 0)
   {
     y = (dy == 1 ? BY1 : BY2);
     for (x = BX1; x <= BX2; x++)
@@ -10411,6 +11178,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     player->last_jy = jy;
 
     if (Feld[jx][jy] == EL_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_EXIT_OPEN ||
+       Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
+       Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
        Feld[jx][jy] == EL_SP_EXIT_OPEN ||
        Feld[jx][jy] == EL_SP_EXIT_OPENING)     /* <-- special case */
     {
@@ -10419,7 +11189,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
 
       if (local_player->friends_still_needed == 0 ||
          IS_SP_ELEMENT(Feld[jx][jy]))
-       player->LevelSolved = player->GameOver = TRUE;
+       PlayerWins(player);
     }
 
     /* this breaks one level: "machine", level 000 */
@@ -10467,7 +11237,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        RemovePlayer(player);
     }
 
-    if (level.use_step_counter)
+    if (!local_player->LevelSolved && level.use_step_counter)
     {
       int i;
 
@@ -10478,7 +11248,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        TimeLeft--;
 
        if (TimeLeft <= 10 && setup.time_limit)
-         PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
 
        DrawGameValue_Time(TimeLeft);
 
@@ -11086,6 +11856,23 @@ void KillPlayer(struct PlayerInfo *player)
   if (!player->active)
     return;
 
+  /* 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;
 
@@ -11319,6 +12106,9 @@ int DigField(struct PlayerInfo *player,
     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;
 
@@ -11359,6 +12149,9 @@ int DigField(struct PlayerInfo *player,
        return MP_NO_ACTION;
     }
     else if (element == EL_EXIT_OPEN ||
+            element == EL_EM_EXIT_OPEN ||
+            element == EL_STEEL_EXIT_OPEN ||
+            element == EL_EM_STEEL_EXIT_OPEN ||
             element == EL_SP_EXIT_OPEN ||
             element == EL_SP_EXIT_OPENING)
     {
@@ -11414,6 +12207,15 @@ int DigField(struct PlayerInfo *player,
       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 ||
@@ -11540,6 +12342,13 @@ int DigField(struct PlayerInfo *player,
 
       DrawGameDoorValues();
     }
+    else if (element == EL_DC_KEY_WHITE)
+    {
+      player->num_white_keys++;
+
+      /* display white keys? */
+      /* DrawGameDoorValues(); */
+    }
     else if (IS_ENVELOPE(element))
     {
       player->show_envelope = element;
@@ -11701,7 +12510,8 @@ int DigField(struct PlayerInfo *player,
       if (local_player->sokobanfields_still_needed == 0 &&
          game.emulation == EMU_SOKOBAN)
       {
-       player->LevelSolved = player->GameOver = TRUE;
+       PlayerWins(player);
+
        PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
       }
     }
@@ -11774,7 +12584,9 @@ int DigField(struct PlayerInfo *player,
       ToggleBeltSwitch(x, y);
     }
     else if (element == EL_SWITCHGATE_SWITCH_UP ||
-            element == EL_SWITCHGATE_SWITCH_DOWN)
+            element == EL_SWITCHGATE_SWITCH_DOWN ||
+            element == EL_DC_SWITCHGATE_SWITCH_UP ||
+            element == EL_DC_SWITCHGATE_SWITCH_DOWN)
     {
       ToggleSwitchgateSwitch(x, y);
     }
@@ -11783,7 +12595,8 @@ int DigField(struct PlayerInfo *player,
     {
       ToggleLightSwitch(x, y);
     }
-    else if (element == EL_TIMEGATE_SWITCH)
+    else if (element == EL_TIMEGATE_SWITCH ||
+            element == EL_DC_TIMEGATE_SWITCH)
     {
       ActivateTimegateSwitch(x, y);
     }
@@ -12110,7 +12923,13 @@ boolean DropElement(struct PlayerInfo *player)
     nexty = dropy + GET_DY_FROM_DIR(move_direction);
 
     ChangeCount[dropx][dropy] = 0;     /* allow at least one more change */
+
+#if USE_FIX_IMPACT_COLLISION
+    /* do not cause impact style collision by dropping elements that can fall */
+    CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
+#else
     CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
+#endif
   }
 
   player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
@@ -12243,9 +13062,12 @@ static void PlayLevelMusic()
     PlayMusic(MAP_NOCONF_MUSIC(level_nr));     /* from music dir */
 }
 
-void PlayLevelSound_EM(int x, int y, int element_em, int sample)
+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)
   {
@@ -12391,7 +13213,7 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
       break;
 
     case SAMPLE_time:
-      PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+      PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
       break;
 
     default:
@@ -12400,6 +13222,31 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample)
   }
 }
 
+#if 0
+void ChangeTime(int value)
+{
+  int *time = (level.time == 0 ? &TimePlayed : &TimeLeft);
+
+  *time += value;
+
+  /* EMC game engine uses value from time counter of RND game engine */
+  level.native_em_level->lev->time = *time;
+
+  DrawGameValue_Time(*time);
+}
+
+void RaiseScore(int value)
+{
+  /* EMC game engine and RND game engine have separate score counters */
+  int *score = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+               &level.native_em_level->lev->score : &local_player->score);
+
+  *score += value;
+
+  DrawGameValue_Score(*score);
+}
+#endif
+
 void RaiseScore(int value)
 {
   local_player->score += value;
@@ -12409,7 +13256,7 @@ void RaiseScore(int value)
 
 void RaiseScoreElement(int element)
 {
-  switch(element)
+  switch (element)
   {
     case EL_EMERALD:
     case EL_BD_DIAMOND:
@@ -12478,6 +13325,7 @@ void RaiseScoreElement(int element)
     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:
@@ -12486,13 +13334,9 @@ void RaiseScoreElement(int element)
   }
 }
 
-void RequestQuitGame(boolean ask_if_really_quit)
+void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 {
-  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 (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
   {
 #if defined(NETWORK_AVALIABLE)
     if (options.network)
@@ -12500,7 +13344,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
     else
 #endif
     {
-      if (!ask_if_really_quit || level_editor_test_game)
+      if (quick_quit)
       {
        game_status = GAME_MODE_MAIN;
 
@@ -12516,7 +13360,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
       }
     }
   }
-  else
+  else         /* continue playing the game */
   {
     if (tape.playing && tape.deactivate_display)
       TapeDeactivateDisplayOff(TRUE);
@@ -12528,6 +13372,325 @@ void RequestQuitGame(boolean ask_if_really_quit)
   }
 }
 
+void RequestQuitGame(boolean ask_if_really_quit)
+{
+  boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
+  boolean skip_request = AllPlayersGone || quick_quit;
+
+  RequestQuitGameExt(skip_request, quick_quit,
+                    "Do you really want to quit the game ?");
+}
+
+
+/* ------------------------------------------------------------------------- */
+/* random generator functions                                                */
+/* ------------------------------------------------------------------------- */
+
+unsigned int InitEngineRandom_RND(long seed)
+{
+  game.num_random_calls = 0;
+
+#if 0
+  unsigned int rnd_seed = InitEngineRandom(seed);
+
+  printf("::: START RND: %d\n", rnd_seed);
+
+  return rnd_seed;
+#else
+
+  return InitEngineRandom(seed);
+
+#endif
+
+}
+
+unsigned int RND(int max)
+{
+  if (max > 0)
+  {
+    game.num_random_calls++;
+
+    return GetEngineRandom(max);
+  }
+
+  return 0;
+}
+
+
+/* ------------------------------------------------------------------------- */
+/* game engine snapshot handling functions                                   */
+/* ------------------------------------------------------------------------- */
+
+#define ARGS_ADDRESS_AND_SIZEOF(x)             (&(x)), (sizeof(x))
+
+struct EngineSnapshotInfo
+{
+  /* runtime values for custom element collect score */
+  int collect_score[NUM_CUSTOM_ELEMENTS];
+
+  /* runtime values for group element choice position */
+  int choice_pos[NUM_GROUP_ELEMENTS];
+
+  /* runtime values for belt position animations */
+  int belt_graphic[4 * NUM_BELT_PARTS];
+  int belt_anim_mode[4 * NUM_BELT_PARTS];
+};
+
+struct EngineSnapshotNodeInfo
+{
+  void *buffer_orig;
+  void *buffer_copy;
+  int size;
+};
+
+static struct EngineSnapshotInfo engine_snapshot_rnd;
+static ListNode *engine_snapshot_list = NULL;
+static char *snapshot_level_identifier = NULL;
+static int snapshot_level_nr = -1;
+
+void FreeEngineSnapshot()
+{
+  while (engine_snapshot_list != NULL)
+    deleteNodeFromList(&engine_snapshot_list, engine_snapshot_list->key,
+                      checked_free);
+
+  setString(&snapshot_level_identifier, NULL);
+  snapshot_level_nr = -1;
+}
+
+static void SaveEngineSnapshotValues_RND()
+{
+  static int belt_base_active_element[4] =
+  {
+    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;
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+
+    engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
+  }
+
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+  {
+    int element = EL_GROUP_START + i;
+
+    engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
+  }
+
+  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;
+
+      engine_snapshot_rnd.belt_graphic[i * 4 + j] = graphic;
+      engine_snapshot_rnd.belt_anim_mode[i * 4 + j] = anim_mode;
+    }
+  }
+}
+
+static void LoadEngineSnapshotValues_RND()
+{
+  unsigned long num_random_calls = game.num_random_calls;
+  int i, j;
+
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+
+    element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
+  }
+
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+  {
+    int element = EL_GROUP_START + i;
+
+    element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
+  }
+
+  for (i = 0; i < 4; i++)
+  {
+    for (j = 0; j < NUM_BELT_PARTS; j++)
+    {
+      int graphic = engine_snapshot_rnd.belt_graphic[i * 4 + j];
+      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i * 4 + j];
+
+      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 SaveEngineSnapshotBuffer(void *buffer, int size)
+{
+  struct EngineSnapshotNodeInfo *bi =
+    checked_calloc(sizeof(struct EngineSnapshotNodeInfo));
+
+  bi->buffer_orig = buffer;
+  bi->buffer_copy = checked_malloc(size);
+  bi->size = size;
+
+  memcpy(bi->buffer_copy, buffer, size);
+
+  addNodeToList(&engine_snapshot_list, NULL, bi);
+}
+
+void SaveEngineSnapshot()
+{
+  FreeEngineSnapshot();                /* free previous snapshot, if needed */
+
+  if (level_editor_test_game)  /* do not save snapshots from editor */
+    return;
+
+  /* copy some special values to a structure better suited for the snapshot */
+
+  SaveEngineSnapshotValues_RND();
+  SaveEngineSnapshotValues_EM();
+
+  /* save values stored in special snapshot structure */
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
+
+  /* save further RND engine values */
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(stored_player));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(game));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(tape));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZX));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZY));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitX));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitY));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TapeTime));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Feld));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovPos));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDir));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDelay));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangePage));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CustomValue));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store2));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Back));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Stop));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Pushed));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxElement));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxAction));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxDir));
+
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_x));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_y));
+
+  /* save level identification information */
+
+  setString(&snapshot_level_identifier, leveldir_current->identifier);
+  snapshot_level_nr = level_nr;
+
+#if 0
+  ListNode *node = engine_snapshot_list;
+  int num_bytes = 0;
+
+  while (node != NULL)
+  {
+    num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
+
+    node = node->next;
+  }
+
+  printf("::: size of engine snapshot: %d bytes\n", num_bytes);
+#endif
+}
+
+static void LoadEngineSnapshotBuffer(struct EngineSnapshotNodeInfo *bi)
+{
+  memcpy(bi->buffer_orig, bi->buffer_copy, bi->size);
+}
+
+void LoadEngineSnapshot()
+{
+  ListNode *node = engine_snapshot_list;
+
+  if (engine_snapshot_list == NULL)
+    return;
+
+  while (node != NULL)
+  {
+    LoadEngineSnapshotBuffer((struct EngineSnapshotNodeInfo *)node->content);
+
+    node = node->next;
+  }
+
+  /* restore special values from snapshot structure */
+
+  LoadEngineSnapshotValues_RND();
+  LoadEngineSnapshotValues_EM();
+}
+
+boolean CheckEngineSnapshot()
+{
+  return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
+         snapshot_level_nr == level_nr);
+}
+
 
 /* ---------- new game button stuff ---------------------------------------- */