rnd-20070114-1-src
[rocksndiamonds.git] / src / game.c
index 05a0b9ab5f7a5ef41e25f6ba7e9465ffef91e2a7..843956362443cfc5e95da1e4f338e71620ef54cd 100644 (file)
 
 #define USE_UFAST_PLAYER_EXIT_BUGFIX   (USE_NEW_STUFF          * 1)
 
+#define USE_GFX_RESET_ONLY_WHEN_MOVING (USE_NEW_STUFF          * 1)
+#define USE_GFX_RESET_PLAYER_ARTWORK   (USE_NEW_STUFF          * 1)
+
+#define USE_FIX_KILLED_BY_NON_WALKABLE (USE_NEW_STUFF          * 1)
+#define USE_FIX_IMPACT_COLLISION       (USE_NEW_STUFF          * 1)
+
 
 /* for DigField() */
 #define DF_NO_PUSH             0
 
 /* 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
         (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_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)
@@ -372,6 +380,33 @@ static int getInvisibleFromInvisibleActiveElement(int);
 
 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 
+/* for detection of endless loops, caused by custom element programming */
+/* (using "MAX_PLAYFIELD_WIDTH" here is just a rough approximation...) */
+#define MAX_ELEMENT_CHANGE_RECURSION_DEPTH     (MAX_PLAYFIELD_WIDTH)
+
+#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  */
@@ -440,6 +475,22 @@ 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_SP_EXIT_OPENING,
     EL_SP_EXIT_OPEN,
@@ -561,6 +612,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,
@@ -1126,6 +1185,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:
@@ -1779,6 +1843,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)
@@ -1838,6 +1907,7 @@ void InitGame()
 
     player->present = FALSE;
     player->active = FALSE;
+    player->killed = FALSE;
 
     player->action = 0;
     player->effective_action = 0;
@@ -1929,8 +1999,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;
@@ -2030,6 +2102,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;
 
@@ -2445,6 +2518,13 @@ void InitGame()
     }
   }
 
+#if 1
+  UnmapAllGadgets();
+
+  MapGameButtons();
+  MapTapeButtons();
+#endif
+
   game.restart_level = FALSE;
 }
 
@@ -2474,7 +2554,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:
@@ -2717,35 +2797,40 @@ void GameWon()
       DrawGameValue_Score(score);
     }
 
-    if (ExitX >= 0 && ExitY >= 0)      /* local player has left the level */
+    if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
     {
-      /* close exit door after last player */
-      if (AllPlayersGone &&
-         (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
-          Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN))
+      if (ExitX >= 0 && ExitY >= 0)    /* local player has left the level */
       {
-       int element = Feld[ExitX][ExitY];
-
-       Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
-                             EL_SP_EXIT_CLOSING);
+       /* 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))
+       {
+         int element = Feld[ExitX][ExitY];
 
-       PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
-      }
+         Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
+                               element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
+                               EL_STEEL_EXIT_CLOSING);
 
-      /* player disappears */
-      DrawLevelField(ExitX, ExitY);
-    }
+         PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
+       }
 
-    for (i = 0; i < MAX_PLAYERS; i++)
-    {
-      struct PlayerInfo *player = &stored_player[i];
+       /* player disappears */
+       DrawLevelField(ExitX, ExitY);
+      }
 
-      if (player->present)
+      for (i = 0; i < MAX_PLAYERS; i++)
       {
-       RemovePlayer(player);
+       struct PlayerInfo *player = &stored_player[i];
 
-       /* player disappears */
-       DrawLevelField(player->jx, player->jy);
+       if (player->present)
+       {
+         RemovePlayer(player);
+
+         /* player disappears */
+         DrawLevelField(player->jx, player->jy);
+       }
       }
     }
 
@@ -2793,7 +2878,11 @@ void GameEnd()
     TapeStop();
 #endif
 
+#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)
@@ -2913,10 +3002,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);
@@ -2936,6 +3024,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)
@@ -2996,18 +3089,51 @@ 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
+  is_moving_before = WasJustMoving[x][y];
+#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;
@@ -3266,8 +3392,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);
@@ -3280,13 +3406,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);
+       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);
+
+       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
     {
@@ -3433,8 +3585,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);
@@ -3867,7 +4019,7 @@ void Bang(int x, int y)
     }
   }
 
-  switch(element)
+  switch (element)
   {
     case EL_BUG:
     case EL_SPACESHIP:
@@ -3891,6 +4043,10 @@ void Bang(int x, int y)
       explosion_type = EX_TYPE_DYNA;
       break;
 
+    case EL_DC_LANDMINE:
+      explosion_type = EX_TYPE_CENTER;
+      break;
+
     case EL_PENGUIN:
     case EL_LAMP:
     case EL_LAMP_ACTIVE:
@@ -4104,6 +4260,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)
     {
@@ -4115,6 +4277,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)
@@ -4331,7 +4503,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);
     }
 
     /*
@@ -4344,7 +4516,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)
@@ -4539,7 +4716,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);
        }
@@ -4934,7 +5113,8 @@ 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_STEEL_EXIT_OPEN))
        {
          attr_x = ex;
          attr_y = ey;
@@ -5579,9 +5759,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))) ||
@@ -5601,6 +5786,7 @@ void StartMoving(int x, int y)
         simply not covered here... :-/ ) */
 
       CheckCollision[x][y] = 0;
+      CheckImpact[x][y] = 0;
 
       Impact(x, y);
     }
@@ -5925,7 +6111,8 @@ 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_STEEL_EXIT_OPEN)
       {
        RemoveField(x, y);
        DrawLevelField(x, y);
@@ -6545,6 +6732,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 */
@@ -6612,7 +6804,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)
@@ -7114,7 +7306,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)
@@ -7181,6 +7373,29 @@ void CheckExit(int x, int y)
   PlayLevelSoundNearest(x, y, SND_CLASS_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 CheckExitSP(int x, int y)
 {
   if (local_player->gems_still_needed > 0)
@@ -7219,7 +7434,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;
@@ -7419,6 +7634,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;
@@ -7662,7 +7979,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
   /* ---------- execute action  -------------------------------------------- */
 
-  switch(action_type)
+  switch (action_type)
   {
     case CA_NO_ACTION:
     {
@@ -7890,6 +8207,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);
@@ -7997,6 +8319,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
@@ -8060,6 +8383,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))
   {
@@ -8067,6 +8399,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 */
@@ -8463,6 +8796,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;
@@ -8531,6 +8872,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
     }
   }
 
+  RECURSION_LOOP_DETECTION_END();
+
   return change_done_any;
 }
 
@@ -8569,14 +8912,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] &&
@@ -8640,6 +8999,8 @@ static boolean CheckElementChangeExt(int x, int y,
     }
   }
 
+  RECURSION_LOOP_DETECTION_END();
+
   return change_done;
 }
 
@@ -9041,6 +9402,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);
 
@@ -9244,7 +9624,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();
   }
 
@@ -9350,6 +9730,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]++;
 
@@ -9434,11 +9816,12 @@ 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_SP_EXIT_OPEN ||
+             element == EL_STEEL_EXIT_OPEN ||
              element == EL_SP_TERMINAL ||
              element == EL_SP_TERMINAL_ACTIVE ||
              element == EL_EXTRA_TIME ||
@@ -9464,9 +9847,12 @@ void GameActions_RND()
       Life(x, y);
     else if (element == EL_EXIT_CLOSED)
       CheckExit(x, y);
+    else if (element == EL_STEEL_EXIT_CLOSED)
+      CheckExitSteel(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 ||
@@ -9474,6 +9860,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)
@@ -10286,6 +10676,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     player->last_jy = jy;
 
     if (Feld[jx][jy] == EL_EXIT_OPEN ||
+       Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
        Feld[jx][jy] == EL_SP_EXIT_OPEN ||
        Feld[jx][jy] == EL_SP_EXIT_OPENING)     /* <-- special case */
     {
@@ -10961,6 +11352,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;
 
@@ -11194,6 +11602,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;
 
@@ -11234,6 +11645,7 @@ int DigField(struct PlayerInfo *player,
        return MP_NO_ACTION;
     }
     else if (element == EL_EXIT_OPEN ||
+            element == EL_STEEL_EXIT_OPEN ||
             element == EL_SP_EXIT_OPEN ||
             element == EL_SP_EXIT_OPENING)
     {
@@ -11650,7 +12062,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);
     }
@@ -11659,7 +12073,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);
     }
@@ -11986,7 +12401,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);
@@ -12313,7 +12734,7 @@ void RaiseScore(int value)
 
 void RaiseScoreElement(int element)
 {
-  switch(element)
+  switch (element)
   {
     case EL_EMERALD:
     case EL_BD_DIAMOND:
@@ -12390,13 +12811,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)
@@ -12404,7 +12821,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;
 
@@ -12420,7 +12837,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
       }
     }
   }
-  else
+  else         /* continue playing the game */
   {
     if (tape.playing && tape.deactivate_display)
       TapeDeactivateDisplayOff(TRUE);
@@ -12432,6 +12849,15 @@ 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                                                */
@@ -12546,10 +12972,6 @@ static void SaveEngineSnapshotValues_RND()
       engine_snapshot_rnd.belt_anim_mode[i * 4 + j] = anim_mode;
     }
   }
-
-#if 0
-  printf("::: SAVE: %d RNDs\n", game.num_random_calls);
-#endif
 }
 
 static void LoadEngineSnapshotValues_RND()
@@ -12582,13 +13004,12 @@ static void LoadEngineSnapshotValues_RND()
     }
   }
 
-#if 0
-  printf("::: LOADING ... : %d RNDs\n", num_random_calls);
-#endif
-
-  InitRND(tape.random_seed);
-  for (i = 0; i < num_random_calls; i++)
-    RND(1);
+  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)
   {
@@ -12597,10 +13018,6 @@ static void LoadEngineSnapshotValues_RND()
     Error(ERR_RETURN, "number of random calls is %d", game.num_random_calls);
     Error(ERR_EXIT, "this should not happen -- please debug");
   }
-
-#if 0
-  printf("::: LOADED: %d RNDs\n", num_random_calls);
-#endif
 }
 
 static void SaveEngineSnapshotBuffer(void *buffer, int size)
@@ -12621,6 +13038,9 @@ 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();
@@ -12674,6 +13094,7 @@ void SaveEngineSnapshot()
   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));