rnd-20040207-1-src
[rocksndiamonds.git] / src / game.c
index 20c68e41f7edfbfec10ecab5b223aa8f8e9fb799..4bd3f46cbf61cccdc10ef407d2b9578ab508d284 100644 (file)
 #define GET_MAX_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
                                    (element_info[e].move_delay_random))
 
+#if 1
+#define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
+               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
+                                       (condition) ||                  \
+                                       (DONT_COLLIDE_WITH(e) &&        \
+                                        IS_PLAYER(x, y) &&             \
+                                        !PLAYER_ENEMY_PROTECTED(x, y))))
+#else
 #define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        (condition) ||                  \
                                        (DONT_COLLIDE_WITH(e) &&        \
                                         IS_FREE_OR_PLAYER(x, y))))
+#endif
 
 #define ELEMENT_CAN_ENTER_FIELD_GENERIC_2(x, y, condition)             \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        Feld[x][y] == EL_EXIT_OPEN ||   \
                                        Feld[x][y] == EL_ACID))
 
+#if 0
 #if 1
 #define MAZE_RUNNER_CAN_ENTER_FIELD(x, y)                              \
                (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        IS_FOOD_DARK_YAMYAM(Feld[x][y])))
 #endif
+#endif
+
+#define GROUP_NR(e)            ((e) - EL_GROUP_START)
+#define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
+#define IS_IN_GROUP(e, nr)     (element_info[e].in_group[nr] == TRUE)
+#define IS_IN_GROUP_EL(e, ge)  (IS_IN_GROUP(e, (ge) - EL_GROUP_START))
+
+#define IS_EQUAL_OR_IN_GROUP(e, ge)                                    \
+       (IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge))
+
+#if 1
+#define CE_ENTER_FIELD_COND(e, x, y)                                   \
+               (!IS_PLAYER(x, y) &&                                    \
+                (Feld[x][y] == EL_ACID ||                              \
+                 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e))))
+#else
+#define CE_ENTER_FIELD_COND(e, x, y)                                   \
+               (!IS_PLAYER(x, y) &&                                    \
+                (Feld[x][y] == EL_ACID ||                              \
+                 Feld[x][y] == MOVE_ENTER_EL(e) ||                     \
+                 (IS_GROUP_ELEMENT(MOVE_ENTER_EL(e)) &&                \
+                  IS_IN_GROUP_EL(Feld[x][y], MOVE_ENTER_EL(e)))))
+#endif
+
+#define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y)                                \
+       ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
 
 #define MOLE_CAN_ENTER_FIELD(x, y, condition)                          \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || (condition)))
@@ -180,7 +216,8 @@ static void ScrollScreen(struct PlayerInfo *, int);
 static void InitBeltMovement(void);
 static void CloseAllOpenTimegates(void);
 static void CheckGravityMovement(struct PlayerInfo *);
-static void KillHeroUnlessProtected(int, int);
+static void KillHeroUnlessEnemyProtected(int, int);
+static void KillHeroUnlessExplosionProtected(int, int);
 
 static void TestIfPlayerTouchesCustomElement(int, int);
 static void TestIfElementTouchesCustomElement(int, int);
@@ -570,6 +607,10 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
 
     player->present = TRUE;
 
+    player->block_last_field = (element == EL_SP_MURPHY ?
+                               level.sp_block_last_field :
+                               level.block_last_field);
+
     if (!options.network || player->connected)
     {
       player->active = TRUE;
@@ -609,6 +650,18 @@ static void InitField(int x, int y, boolean init_game)
       InitPlayerField(x, y, element, init_game);
       break;
 
+    case EL_SOKOBAN_FIELD_PLAYER:
+      element = Feld[x][y] = EL_PLAYER_1;
+      InitField(x, y, init_game);
+
+      element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
+      InitField(x, y, init_game);
+      break;
+
+    case EL_SOKOBAN_FIELD_EMPTY:
+      local_player->sokobanfields_still_needed++;
+      break;
+
     case EL_STONEBLOCK:
       if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
        Feld[x][y] = EL_ACID_POOL_TOPLEFT;
@@ -686,10 +739,6 @@ static void InitField(int x, int y, boolean init_game)
       local_player->lights_still_needed++;
       break;
 
-    case EL_SOKOBAN_FIELD_EMPTY:
-      local_player->sokobanfields_still_needed++;
-      break;
-
     case EL_PENGUIN:
       local_player->friends_still_needed++;
       break;
@@ -763,6 +812,28 @@ static void InitField(int x, int y, boolean init_game)
     default:
       if (IS_CUSTOM_ELEMENT(element) && CAN_MOVE(element))
        InitMovDir(x, y);
+      else if (IS_GROUP_ELEMENT(element))
+      {
+       struct ElementGroupInfo *group = element_info[element].group;
+       int last_anim_random_frame = gfx.anim_random_frame;
+       int element_pos;
+
+       if (group->choice_mode == ANIM_RANDOM)
+         gfx.anim_random_frame = RND(group->num_elements_resolved);
+
+       element_pos = getAnimationFrame(group->num_elements_resolved, 1,
+                                       group->choice_mode, 0,
+                                       group->choice_pos);
+
+       if (group->choice_mode == ANIM_RANDOM)
+         gfx.anim_random_frame = last_anim_random_frame;
+
+       group->choice_pos++;
+
+       Feld[x][y] = group->element_resolved[element_pos];
+
+       InitField(x, y, init_game);
+      }
       break;
   }
 }
@@ -787,6 +858,61 @@ void DrawGameDoorValues()
           int2str(TimeLeft, 3), FONT_TEXT_2);
 }
 
+static void resolve_group_element(int group_element, int recursion_depth)
+{
+  static int group_nr;
+  static struct ElementGroupInfo *group;
+  struct ElementGroupInfo *actual_group = element_info[group_element].group;
+  int i;
+
+  if (recursion_depth > NUM_GROUP_ELEMENTS)    /* recursion too deep */
+  {
+    Error(ERR_WARN, "recursion too deep when resolving group element %d",
+         group_element - EL_GROUP_START + 1);
+
+    /* replace element which caused too deep recursion by question mark */
+    group->element_resolved[group->num_elements_resolved++] = EL_UNKNOWN;
+
+    return;
+  }
+
+  if (recursion_depth == 0)                    /* initialization */
+  {
+    group = element_info[group_element].group;
+    group_nr = group_element - EL_GROUP_START;
+
+    group->num_elements_resolved = 0;
+    group->choice_pos = 0;
+  }
+
+  for (i = 0; i < actual_group->num_elements; i++)
+  {
+    int element = actual_group->element[i];
+
+    if (group->num_elements_resolved == NUM_FILE_ELEMENTS)
+      break;
+
+    if (IS_GROUP_ELEMENT(element))
+      resolve_group_element(element, recursion_depth + 1);
+    else
+    {
+      group->element_resolved[group->num_elements_resolved++] = element;
+      element_info[element].in_group[group_nr] = TRUE;
+    }
+  }
+
+#if 0
+  if (recursion_depth == 0 && group_element <= EL_GROUP_4)
+  {
+    printf("::: group %d: %d resolved elements\n",
+          group_element - EL_GROUP_START, group->num_elements_resolved);
+    for (i = 0; i < group->num_elements_resolved; i++)
+      printf("::: - %d ['%s']\n", group->element_resolved[i],
+            element_info[group->element_resolved[i]].token_name);
+  }
+#endif
+}
+
 
 /*
   =============================================================================
@@ -815,6 +941,15 @@ static void InitGameEngine()
   printf("       => game.engine_version == %06d\n", game.engine_version);
 #endif
 
+  /* ---------- recursively resolve group elements ------------------------- */
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    for (j = 0; j < NUM_GROUP_ELEMENTS; j++)
+      element_info[i].in_group[j] = FALSE;
+
+  for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+    resolve_group_element(EL_GROUP_START + i, 0);
+
   /* ---------- initialize player's initial move delay --------------------- */
 
   /* dynamically adjust player properties according to game engine version */
@@ -933,7 +1068,16 @@ static void InitGameEngine()
       {
        int trigger_element = ei->change_page[j].trigger_element;
 
-       trigger_events[trigger_element] |= ei->change_page[j].events;
+       if (IS_GROUP_ELEMENT(trigger_element))
+       {
+         struct ElementGroupInfo *group = element_info[trigger_element].group;
+
+         for (k = 0; k < group->num_elements_resolved; k++)
+           trigger_events[group->element_resolved[k]]
+             |= ei->change_page[j].events;
+       }
+       else
+         trigger_events[trigger_element] |= ei->change_page[j].events;
       }
     }
   }
@@ -966,6 +1110,19 @@ static void InitGameEngine()
     element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
   }
 
+  /* set push delay value for Supaplex elements for newer engine versions */
+  if (game.engine_version >= VERSION_IDENT(3,0,9,0))
+  {
+    for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    {
+      if (IS_SP_ELEMENT(i))
+      {
+       element_info[i].push_delay_fixed  = 6;
+       element_info[i].push_delay_random = 0;
+      }
+    }
+  }
+
   /* ---------- initialize move stepsize ----------------------------------- */
 
   /* initialize move stepsize values to default */
@@ -981,6 +1138,14 @@ static void InitGameEngine()
     element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
   }
 
+  /* ---------- initialize move dig/leave ---------------------------------- */
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    element_info[i].can_leave_element = FALSE;
+    element_info[i].can_leave_element_last = FALSE;
+  }
+
   /* ---------- initialize gem count --------------------------------------- */
 
   /* initialize gem count values for each element */
@@ -1063,6 +1228,8 @@ void InitGame()
 
     player->use_murphy_graphic = FALSE;
 
+    player->block_last_field = FALSE;
+
     player->actual_frame_counter = 0;
 
     player->step_counter = 0;
@@ -1218,6 +1385,7 @@ void InitGame()
       ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
 
       ExplodePhase[x][y] = 0;
+      ExplodeDelay[x][y] = 0;
       ExplodeField[x][y] = EX_NO_EXPLOSION;
 
       RunnerVisit[x][y] = 0;
@@ -1252,6 +1420,30 @@ void InitGame()
                    emulate_sb ? EMU_SOKOBAN :
                    emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
 
+  /* initialize explosion and ignition delay */
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      int num_phase = 9;
+      int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+      int last_phase = num_phase * delay;
+      int half_phase = (num_phase / 2) * delay;
+
+      element_info[i].explosion_delay = last_phase;
+      element_info[i].ignition_delay = half_phase;
+
+      if (i == EL_BLACK_ORB)
+       element_info[i].ignition_delay = 1;
+    }
+
+    if (element_info[i].explosion_delay < 2)   /* !!! check again !!! */
+      element_info[i].explosion_delay = 2;
+
+    if (element_info[i].ignition_delay < 1)    /* !!! check again !!! */
+      element_info[i].ignition_delay = 1;
+  }
+
   /* correct non-moving belts to start moving left */
   for (i = 0; i < 4; i++)
     if (game.belt_dir[i] == MV_NO_MOVING)
@@ -1274,7 +1466,9 @@ void InitGame()
        {
          player->present = TRUE;
          player->active = TRUE;
+
          some_player->present = FALSE;
+         some_player->active = FALSE;
 
          StorePlayer[jx][jy] = player->element_nr;
          player->jx = player->last_jx = jx;
@@ -1288,7 +1482,7 @@ void InitGame()
 
   if (tape.playing)
   {
-    /* when playing a tape, eliminate all players who do not participate */
+    /* when playing a tape, eliminate all players which do not participate */
 
     for (i = 0; i < MAX_PLAYERS; i++)
     {
@@ -1319,6 +1513,8 @@ void InitGame()
            int jx = player->jx, jy = player->jy;
 
            player->active = FALSE;
+           player->present = FALSE;
+
            StorePlayer[jx][jy] = 0;
            Feld[jx][jy] = EL_EMPTY;
          }
@@ -1619,23 +1815,37 @@ void InitMovDir(int x, int y)
     default:
       if (IS_CUSTOM_ELEMENT(element))
       {
-       if (element_info[element].move_direction_initial != MV_NO_MOVING)
-         MovDir[x][y] = element_info[element].move_direction_initial;
-       else if (element_info[element].move_pattern == MV_ALL_DIRECTIONS ||
-                element_info[element].move_pattern == MV_TURNING_LEFT ||
-                element_info[element].move_pattern == MV_TURNING_RIGHT ||
-                element_info[element].move_pattern == MV_TURNING_LEFT_RIGHT ||
-                element_info[element].move_pattern == MV_TURNING_RIGHT_LEFT ||
-                element_info[element].move_pattern == MV_TURNING_RANDOM)
+       struct ElementInfo *ei = &element_info[element];
+       int move_direction_initial = ei->move_direction_initial;
+       int move_pattern = ei->move_pattern;
+
+       if (move_direction_initial == MV_START_PREVIOUS)
+       {
+         if (MovDir[x][y] != MV_NO_MOVING)
+           return;
+
+         move_direction_initial = MV_START_AUTOMATIC;
+       }
+
+       if (move_direction_initial == MV_START_RANDOM)
+         MovDir[x][y] = 1 << RND(4);
+       else if (move_direction_initial & MV_ANY_DIRECTION)
+         MovDir[x][y] = move_direction_initial;
+       else if (move_pattern == MV_ALL_DIRECTIONS ||
+                move_pattern == MV_TURNING_LEFT ||
+                move_pattern == MV_TURNING_RIGHT ||
+                move_pattern == MV_TURNING_LEFT_RIGHT ||
+                move_pattern == MV_TURNING_RIGHT_LEFT ||
+                move_pattern == MV_TURNING_RANDOM)
          MovDir[x][y] = 1 << RND(4);
-       else if (element_info[element].move_pattern == MV_HORIZONTAL)
+       else if (move_pattern == MV_HORIZONTAL)
          MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
-       else if (element_info[element].move_pattern == MV_VERTICAL)
+       else if (move_pattern == MV_VERTICAL)
          MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
-       else if (element_info[element].move_pattern & MV_ANY_DIRECTION)
+       else if (move_pattern & MV_ANY_DIRECTION)
          MovDir[x][y] = element_info[element].move_pattern;
-       else if (element_info[element].move_pattern == MV_ALONG_LEFT_SIDE ||
-                element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE)
+       else if (move_pattern == MV_ALONG_LEFT_SIDE ||
+                move_pattern == MV_ALONG_RIGHT_SIDE)
        {
          for (i = 0; i < 4; i++)
          {
@@ -1644,7 +1854,7 @@ void InitMovDir(int x, int y)
 
            if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
            {
-             if (element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE)
+             if (move_pattern == MV_ALONG_RIGHT_SIDE)
                MovDir[x][y] = direction[0][i];
              else
                MovDir[x][y] = direction[1][i];
@@ -2154,6 +2364,10 @@ void CheckDynamite(int x, int y)
 void RelocatePlayer(int x, int y, int element)
 {
   struct PlayerInfo *player = &stored_player[element - EL_PLAYER_1];
+  boolean ffwd_delay = (tape.playing && tape.fast_forward);
+  boolean no_delay = (tape.index_search);
+  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
 
   if (player->GameOver)                /* do not reanimate dead player */
     return;
@@ -2174,7 +2388,7 @@ void RelocatePlayer(int x, int y, int element)
       DrawPlayer(player);
 
       BackToFront();
-      Delay(GAME_FRAME_DELAY);
+      Delay(wait_delay_value);
     }
 
     DrawPlayer(player);                /* needed here only to cleanup last field */
@@ -2218,11 +2432,11 @@ void RelocatePlayer(int x, int y, int element)
       /* scroll in two steps of half tile size to make things smoother */
       BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
       FlushDisplay();
-      Delay(GAME_FRAME_DELAY);
+      Delay(wait_delay_value);
 
       /* scroll second step to align at full tile size */
       BackToFront();
-      Delay(GAME_FRAME_DELAY);
+      Delay(wait_delay_value);
     }
   }
 }
@@ -2235,6 +2449,9 @@ void Explode(int ex, int ey, int phase, int mode)
   int last_phase = num_phase * delay;
   int half_phase = (num_phase / 2) * delay;
   int first_phase_after_start = EX_PHASE_START + 1;
+  int border_element;
+
+  int last_phase_TEST = last_phase;
 
   if (game.explosions_delayed)
   {
@@ -2269,6 +2486,10 @@ void Explode(int ex, int ey, int phase, int mode)
       Feld[ex][ey] = center_element;
     }
 
+#if 1
+    last_phase = element_info[center_element].explosion_delay;
+#endif
+
     for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
     {
       int xx = x - ex + 1;
@@ -2348,22 +2569,22 @@ void Explode(int ex, int ey, int phase, int mode)
       RemoveField(x, y);
 #endif
 
-      if (IS_PLAYER(ex, ey) && !PLAYER_PROTECTED(ex, ey))
+      if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
       {
        switch(StorePlayer[ex][ey])
        {
          case EL_PLAYER_2:
-           Store[x][y] = EL_EMERALD_RED;
+           Store[x][y] = EL_PLAYER_IS_EXPLODING_2;
            break;
          case EL_PLAYER_3:
-           Store[x][y] = EL_EMERALD;
+           Store[x][y] = EL_PLAYER_IS_EXPLODING_3;
            break;
          case EL_PLAYER_4:
-           Store[x][y] = EL_EMERALD_PURPLE;
+           Store[x][y] = EL_PLAYER_IS_EXPLODING_4;
            break;
          case EL_PLAYER_1:
          default:
-           Store[x][y] = EL_EMERALD_YELLOW;
+           Store[x][y] = EL_PLAYER_IS_EXPLODING_1;
            break;
        }
 
@@ -2439,6 +2660,9 @@ void Explode(int ex, int ey, int phase, int mode)
 #endif
 
       ExplodePhase[x][y] = 1;
+#if 1
+      ExplodeDelay[x][y] = last_phase;
+#endif
       Stop[x][y] = TRUE;
     }
 
@@ -2455,6 +2679,10 @@ void Explode(int ex, int ey, int phase, int mode)
   x = ex;
   y = ey;
 
+#if 1
+  last_phase = ExplodeDelay[x][y];
+#endif
+
   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
 
 #ifdef DEBUG
@@ -2475,6 +2703,127 @@ void Explode(int ex, int ey, int phase, int mode)
   }
 #endif
 
+#if 1
+
+  border_element = Store2[x][y];
+  if (IS_PLAYER(x, y))
+    border_element = StorePlayer[x][y];
+
+  if (phase == element_info[border_element].ignition_delay ||
+      phase == last_phase)
+  {
+    if (IS_PLAYER(x, y))
+    {
+      if (phase == 2)
+       printf("::: IS_PLAYER\n");
+
+      KillHeroUnlessExplosionProtected(x, y);
+      return;
+    }
+    else if (CAN_EXPLODE_BY_FIRE(border_element))
+    {
+      if (phase == 2)
+       printf("::: CAN_EXPLODE_BY_FIRE\n");
+
+      Feld[x][y] = Store2[x][y];
+      Store2[x][y] = 0;
+      Bang(x, y);
+      return;
+    }
+    else if (border_element == EL_AMOEBA_TO_DIAMOND)
+    {
+      if (phase == 2)
+       printf("::: EL_AMOEBA_TO_DIAMOND\n");
+
+      AmoebeUmwandeln(x, y);
+      return;
+    }
+  }
+
+  if (phase == last_phase)
+  {
+    int element;
+
+    element = Feld[x][y] = Store[x][y];
+    Store[x][y] = Store2[x][y] = 0;
+    GfxElement[x][y] = EL_UNDEFINED;
+
+    /* player can escape from explosions and might therefore be still alive */
+    if (element >= EL_PLAYER_IS_EXPLODING_1 &&
+       element <= EL_PLAYER_IS_EXPLODING_4)
+      Feld[x][y] = (stored_player[element - EL_PLAYER_IS_EXPLODING_1].active ?
+                   EL_EMPTY :
+                   element == EL_PLAYER_IS_EXPLODING_1 ? EL_EMERALD_YELLOW :
+                   element == EL_PLAYER_IS_EXPLODING_2 ? EL_EMERALD_RED :
+                   element == EL_PLAYER_IS_EXPLODING_3 ? EL_EMERALD :
+                   EL_EMERALD_PURPLE);
+
+    /* restore probably existing indestructible background element */
+    if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
+      element = Feld[x][y] = Back[x][y];
+    Back[x][y] = 0;
+
+    MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
+    GfxDir[x][y] = MV_NO_MOVING;
+    ChangeDelay[x][y] = 0;
+    ChangePage[x][y] = -1;
+
+    InitField(x, y, FALSE);
+#if 1
+    /* !!! not needed !!! */
+    if (CAN_MOVE(element))
+      InitMovDir(x, y);
+#endif
+    DrawLevelField(x, y);
+
+    TestIfElementTouchesCustomElement(x, y);
+
+    if (GFX_CRUMBLED(element))
+      DrawLevelFieldCrumbledSandNeighbours(x, y);
+
+    if (IS_PLAYER(x, y) && !PLAYERINFO(x,y)->present)
+      StorePlayer[x][y] = 0;
+
+    if (ELEM_IS_PLAYER(element))
+      RelocatePlayer(x, y, element);
+  }
+  else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+  {
+#if 1
+    int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
+#else
+    int stored = Store[x][y];
+    int graphic = (game.emulation != EMU_SUPAPLEX ? IMG_EXPLOSION :
+                  stored == EL_SP_INFOTRON ? IMG_SP_EXPLOSION_INFOTRON :
+                  IMG_SP_EXPLOSION);
+#endif
+    int frame = getGraphicAnimationFrame(graphic, phase - delay);
+
+#if 0
+    printf("::: %d ['%s'] -> %d\n", GfxElement[x][y],
+          element_info[GfxElement[x][y]].token_name,
+          graphic);
+#endif
+
+    if (phase == delay)
+      DrawLevelFieldCrumbledSand(x, y);
+
+    if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
+    {
+      DrawLevelElement(x, y, Back[x][y]);
+      DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
+    }
+    else if (IS_WALKABLE_UNDER(Back[x][y]))
+    {
+      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+      DrawLevelElementThruMask(x, y, Back[x][y]);
+    }
+    else if (!IS_WALKABLE_INSIDE(Back[x][y]))
+      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+  }
+
+#else
+
   if (phase == first_phase_after_start)
   {
     int element = Store2[x][y];
@@ -2491,7 +2840,7 @@ void Explode(int ex, int ey, int phase, int mode)
     int element = Store2[x][y];
 
     if (IS_PLAYER(x, y))
-      KillHeroUnlessProtected(x, y);
+      KillHeroUnlessExplosionProtected(x, y);
     else if (CAN_EXPLODE_BY_FIRE(element))
     {
       Feld[x][y] = Store2[x][y];
@@ -2510,6 +2859,17 @@ void Explode(int ex, int ey, int phase, int mode)
     Store[x][y] = Store2[x][y] = 0;
     GfxElement[x][y] = EL_UNDEFINED;
 
+    /* player can escape from explosions and might therefore be still alive */
+    if (element >= EL_PLAYER_IS_EXPLODING_1 &&
+       element <= EL_PLAYER_IS_EXPLODING_4)
+      Feld[x][y] = (stored_player[element - EL_PLAYER_IS_EXPLODING_1].active ?
+                   EL_EMPTY :
+                   element == EL_PLAYER_IS_EXPLODING_1 ? EL_EMERALD_YELLOW :
+                   element == EL_PLAYER_IS_EXPLODING_2 ? EL_EMERALD_RED :
+                   element == EL_PLAYER_IS_EXPLODING_3 ? EL_EMERALD :
+                   EL_EMERALD_PURPLE);
+
+    /* restore probably existing indestructible background element */
     if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
       element = Feld[x][y] = Back[x][y];
     Back[x][y] = 0;
@@ -2520,8 +2880,11 @@ void Explode(int ex, int ey, int phase, int mode)
     ChangePage[x][y] = -1;
 
     InitField(x, y, FALSE);
+#if 1
+    /* !!! not needed !!! */
     if (CAN_MOVE(element))
       InitMovDir(x, y);
+#endif
     DrawLevelField(x, y);
 
     TestIfElementTouchesCustomElement(x, y);
@@ -2569,6 +2932,7 @@ void Explode(int ex, int ey, int phase, int mode)
     else if (!IS_WALKABLE_INSIDE(Back[x][y]))
       DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
   }
+#endif
 }
 
 void DynaExplode(int ex, int ey)
@@ -2634,7 +2998,7 @@ void Bang(int x, int y)
 #endif
 
 #if 1
-  if (IS_PLAYER(x, y) && !PLAYER_PROTECTED(x, y))
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
 #else
   if (IS_PLAYER(x, y))
 #endif
@@ -3125,7 +3489,7 @@ void Impact(int x, int y)
   if (impact && element == EL_AMOEBA_DROP)
   {
     if (object_hit && IS_PLAYER(x, y + 1))
-      KillHeroUnlessProtected(x, y + 1);
+      KillHeroUnlessEnemyProtected(x, y + 1);
     else if (object_hit && smashed == EL_PENGUIN)
       Bang(x, y + 1);
     else
@@ -3167,7 +3531,7 @@ void Impact(int x, int y)
     {
       if (CAN_SMASH_PLAYER(element))
       {
-       KillHeroUnlessProtected(x, y + 1);
+       KillHeroUnlessEnemyProtected(x, y + 1);
        return;
       }
     }
@@ -3187,11 +3551,13 @@ void Impact(int x, int y)
        return;
       }
     }
-    else if ((element == EL_SP_INFOTRON ||
-             element == EL_SP_ZONK) &&
-            (smashed == EL_SP_SNIKSNAK ||
-             smashed == EL_SP_ELECTRON ||
-             smashed == EL_SP_DISK_ORANGE))
+    else if (((element == EL_SP_INFOTRON ||
+              element == EL_SP_ZONK) &&
+             (smashed == EL_SP_SNIKSNAK ||
+              smashed == EL_SP_ELECTRON ||
+              smashed == EL_SP_DISK_ORANGE)) ||
+            (element == EL_SP_INFOTRON &&
+             smashed == EL_SP_DISK_YELLOW))
     {
       Bang(x, y + 1);
       return;
@@ -3722,8 +4088,10 @@ inline static void TurnRoundExt(int x, int y)
           move_pattern == MV_TURNING_RANDOM ||
           move_pattern == MV_ALL_DIRECTIONS)
   {
-    boolean can_turn_left  = ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
-    boolean can_turn_right = ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
+    boolean can_turn_left =
+      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
+    boolean can_turn_right =
+      CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
 
     if (move_pattern == MV_TURNING_LEFT)
       MovDir[x][y] = left_dir;
@@ -3767,9 +4135,9 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (move_pattern == MV_ALONG_LEFT_SIDE)
   {
-    if (ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
+    if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
       MovDir[x][y] = left_dir;
-    else if (!ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+    else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = right_dir;
 
     if (MovDir[x][y] != old_move_dir)
@@ -3777,9 +4145,9 @@ inline static void TurnRoundExt(int x, int y)
   }
   else if (move_pattern == MV_ALONG_RIGHT_SIDE)
   {
-    if (ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
+    if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
       MovDir[x][y] = right_dir;
-    else if (!ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+    else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = left_dir;
 
     if (MovDir[x][y] != old_move_dir)
@@ -3839,14 +4207,14 @@ inline static void TurnRoundExt(int x, int y)
        new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
       Moving2Blocked(x, y, &newx, &newy);
 
-      if (ELEMENT_CAN_ENTER_FIELD_OR_ACID(element, newx, newy))
+      if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
        return;
 
       MovDir[x][y] =
        new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
       Moving2Blocked(x, y, &newx, &newy);
 
-      if (ELEMENT_CAN_ENTER_FIELD_OR_ACID(element, newx, newy))
+      if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
        return;
 
       MovDir[x][y] = old_move_dir;
@@ -3860,8 +4228,7 @@ inline static void TurnRoundExt(int x, int y)
 
     MovDelay[x][y] = 0;
   }
-  else if (move_pattern & MV_MAZE_RUNNER_STYLE ||
-          element == EL_MAZE_RUNNER)
+  else if (move_pattern & MV_MAZE_RUNNER_STYLE)
   {
     static int test_xy[7][2] =
     {
@@ -3905,7 +4272,7 @@ inline static void TurnRoundExt(int x, int y)
        break;
       }
 
-      if (!MAZE_RUNNER_CAN_ENTER_FIELD(xx, yy))
+      if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
        continue;
 
       move_dir_preference = -1 * RunnerVisit[xx][yy];
@@ -4505,10 +4872,11 @@ void StartMoving(int x, int y)
 
     if (DONT_COLLIDE_WITH(element) &&
        IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
-       !PLAYER_PROTECTED(newx, newy))
+       !PLAYER_ENEMY_PROTECTED(newx, newy))
     {
 #if 1
       TestIfBadThingRunsIntoHero(x, y, MovDir[x][y]);
+
       return;
 #else
       /* player killed by element which is deadly when colliding with */
@@ -4567,6 +4935,7 @@ void StartMoving(int x, int y)
          DrawPlayerField(x, y);
        else
          DrawLevelField(x, y);
+
        return;
       }
     }
@@ -4590,42 +4959,80 @@ void StartMoving(int x, int y)
          DrawPlayerField(x, y);
        else
          DrawLevelField(x, y);
+
        return;
       }
     }
-    else if ((move_pattern & MV_MAZE_RUNNER_STYLE ||
-             element == EL_MAZE_RUNNER) && IN_LEV_FIELD(newx, newy))
-    {
+
 #if 1
-      if (IS_FREE(newx, newy))
-#else
-      if (IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
+
+    /*
+    else if (move_pattern & MV_MAZE_RUNNER_STYLE && IN_LEV_FIELD(newx, newy))
+    */
+
+    else if (IS_CUSTOM_ELEMENT(element) &&
+            CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy)
+
+#if 0
+ &&
+            !IS_FREE(newx, newy)
 #endif
+
+)
+    {
+      int new_element = Feld[newx][newy];
+
+#if 0
+      printf("::: '%s' digs '%s' [%d]\n",
+            element_info[element].token_name,
+            element_info[Feld[newx][newy]].token_name,
+            StorePlayer[newx][newy]);
+#endif
+
+      if (!IS_FREE(newx, newy))
       {
+       int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING :
+                     IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING :
+                     ACTION_BREAKING);
+
+       /* no element can dig solid indestructible elements */
+       if (IS_INDESTRUCTIBLE(new_element) &&
+           !IS_DIGGABLE(new_element) &&
+           !IS_COLLECTIBLE(new_element))
+         return;
+
+       if (AmoebaNr[newx][newy] &&
+           (new_element == EL_AMOEBA_FULL ||
+            new_element == EL_BD_AMOEBA ||
+            new_element == EL_AMOEBA_GROWING))
+       {
+         AmoebaCnt[AmoebaNr[newx][newy]]--;
+         AmoebaCnt2[AmoebaNr[newx][newy]]--;
+       }
+
        if (IS_MOVING(newx, newy))
          RemoveMovingField(newx, newy);
        else
        {
-         Feld[newx][newy] = EL_EMPTY;
+         RemoveField(newx, newy);
          DrawLevelField(newx, newy);
        }
 
-       PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
+       PlayLevelSoundAction(x, y, action);
       }
-      else if (!IS_FREE(newx, newy))
+
+      if (new_element == element_info[element].move_enter_element)
+       element_info[element].can_leave_element = TRUE;
+
+      if (move_pattern & MV_MAZE_RUNNER_STYLE)
       {
-#if 0
-       if (IS_PLAYER(x, y))
-         DrawPlayerField(x, y);
-       else
-         DrawLevelField(x, y);
-#endif
-       return;
+       RunnerVisit[x][y] = FrameCounter;
+       PlayerVisit[x][y] /= 8;         /* expire player visit path */
       }
-
-      RunnerVisit[x][y] = FrameCounter;
-      PlayerVisit[x][y] /= 8;          /* expire player visit path */
     }
+
+#endif
+
     else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
     {
       if (!IS_FREE(newx, newy))
@@ -4733,6 +5140,7 @@ void StartMoving(int x, int y)
        DrawLevelField(x, y);
 
        MovDelay[newx][newy] = 0;       /* start amoeba shrinking delay */
+
        return;                         /* wait for shrinking amoeba */
       }
       else     /* element == EL_PACMAN */
@@ -4796,6 +5204,7 @@ void StartMoving(int x, int y)
 void ContinueMoving(int x, int y)
 {
   int element = Feld[x][y];
+  struct ElementInfo *ei = &element_info[element];
   int direction = MovDir[x][y];
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
@@ -4912,6 +5321,24 @@ void ContinueMoving(int x, int y)
 
   ResetGfxAnimation(x, y);     /* reset animation values for old field */
 
+#if 1
+  /* some elements can leave other elements behind after moving */
+  if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
+      ei->move_leave_element != EL_EMPTY &&
+      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED ||
+       ei->can_leave_element_last))
+  {
+    Feld[x][y] = ei->move_leave_element;
+    InitField(x, y, FALSE);
+
+    if (GFX_CRUMBLED(Feld[x][y]))
+      DrawLevelFieldCrumbledSandNeighbours(x, y);
+  }
+
+  ei->can_leave_element_last = ei->can_leave_element;
+  ei->can_leave_element = FALSE;
+#endif
+
 #if 0
   /* 2.1.1 (does not work correctly for spring) */
   if (!CAN_MOVE(element))
@@ -4971,7 +5398,7 @@ void ContinueMoving(int x, int y)
     Impact(x, newy);
 
 #if 1
-  TestIfElementTouchesCustomElement(x, y);             /* for empty space */
+  TestIfElementTouchesCustomElement(x, y);     /* empty or new element */
 #endif
 
 #if 0
@@ -4996,15 +5423,7 @@ void ContinueMoving(int x, int y)
 #if 0
     if (IN_LEV_FIELD(nextx, nexty))
     {
-      static int opposite_directions[] =
-      {
-       MV_RIGHT,
-       MV_LEFT,
-       MV_DOWN,
-       MV_UP
-      };
-      int move_dir_bit = MV_DIR_BIT(direction);
-      int opposite_direction = opposite_directions[move_dir_bit];
+      int opposite_direction = MV_DIR_OPPOSITE(direction);
       int hitting_side = direction;
       int touched_side = opposite_direction;
       int touched_element = MovingOrBlocked2Element(nextx, nexty);
@@ -5963,9 +6382,11 @@ static void ChangeActiveTrap(int x, int y)
 
 static void ChangeElementNowExt(int x, int y, int target_element)
 {
+  int previous_move_direction = MovDir[x][y];
+
   /* check if element under player changes from accessible to unaccessible
      (needed for special case of dropping element which then changes) */
-  if (IS_PLAYER(x, y) && !PLAYER_PROTECTED(x, y) &&
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
       IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element))
   {
     Bang(x, y);
@@ -5980,6 +6401,9 @@ static void ChangeElementNowExt(int x, int y, int target_element)
   ResetGfxAnimation(x, y);
   ResetRandomAnimationValue(x, y);
 
+  if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+    MovDir[x][y] = previous_move_direction;
+
   InitField(x, y, FALSE);
   if (CAN_MOVE(Feld[x][y]))
     InitMovDir(x, y);
@@ -6219,7 +6643,12 @@ static boolean CheckTriggeredElementSideChange(int lx, int ly,
          change->events & CH_EVENT_BIT(trigger_event) &&
 #endif
          change->sides & trigger_side &&
-         change->trigger_element == trigger_element)
+#if 1
+         IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)
+#else
+         change->trigger_element == trigger_element
+#endif
+         )
       {
 #if 0
        if (!(change->events & CH_EVENT_BIT(trigger_event)))
@@ -6484,6 +6913,11 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
   if (!player->active || tape.pausing)
     return 0;
 
+#if 0
+  printf("::: [%d %d %d %d] [%d %d]\n",
+        left, right, up, down, button1, button2);
+#endif
+
   if (player_action)
   {
 #if 0
@@ -7293,6 +7727,38 @@ void ScrollLevel(int dx, int dy)
   redraw_mask |= REDRAW_FIELD;
 }
 
+static boolean canEnterSupaplexPort(int x, int y, int dx, int dy)
+{
+  int nextx = x + dx, nexty = y + dy;
+  int element = Feld[x][y];
+
+  if ((dx == -1 &&
+       element != EL_SP_PORT_LEFT &&
+       element != EL_SP_GRAVITY_PORT_LEFT &&
+       element != EL_SP_PORT_HORIZONTAL &&
+       element != EL_SP_PORT_ANY) ||
+      (dx == +1 &&
+       element != EL_SP_PORT_RIGHT &&
+       element != EL_SP_GRAVITY_PORT_RIGHT &&
+       element != EL_SP_PORT_HORIZONTAL &&
+       element != EL_SP_PORT_ANY) ||
+      (dy == -1 &&
+       element != EL_SP_PORT_UP &&
+       element != EL_SP_GRAVITY_PORT_UP &&
+       element != EL_SP_PORT_VERTICAL &&
+       element != EL_SP_PORT_ANY) ||
+      (dy == +1 &&
+       element != EL_SP_PORT_DOWN &&
+       element != EL_SP_GRAVITY_PORT_DOWN &&
+       element != EL_SP_PORT_VERTICAL &&
+       element != EL_SP_PORT_ANY) ||
+      !IN_LEV_FIELD(nextx, nexty) ||
+      !IS_FREE(nextx, nexty))
+    return FALSE;
+
+  return TRUE;
+}
+
 static void CheckGravityMovement(struct PlayerInfo *player)
 {
   if (game.gravity && !player->programmed_action)
@@ -7312,7 +7778,9 @@ static void CheckGravityMovement(struct PlayerInfo *player)
     boolean player_is_moving_to_valid_field =
       (IN_LEV_FIELD(new_jx, new_jy) &&
        (Feld[new_jx][new_jy] == EL_SP_BASE ||
-       Feld[new_jx][new_jy] == EL_SAND));
+       Feld[new_jx][new_jy] == EL_SAND ||
+       (IS_SP_PORT(Feld[new_jx][new_jy]) &&
+        canEnterSupaplexPort(new_jx, new_jy, dx, dy))));
     /* !!! extend EL_SAND to anything diggable !!! */
 
     if (field_under_player_is_free &&
@@ -7700,6 +8168,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
 #if 0
     DrawPlayer(player);
 #endif
+
     return;
   }
   else if (!FrameReached(&player->actual_frame_counter, 1))
@@ -7708,7 +8177,8 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
   player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
 
-  if (Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
+  if (!player->block_last_field &&
+      Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
     Feld[last_jx][last_jy] = EL_EMPTY;
 
   /* before DrawPlayer() to draw correct player graphic for this case */
@@ -7746,6 +8216,10 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     }
 #endif
 
+    if (player->block_last_field &&
+       Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
+      Feld[last_jx][last_jy] = EL_EMPTY;
+
     player->last_jx = jx;
     player->last_jy = jy;
 
@@ -7943,7 +8417,12 @@ void TestIfElementTouchesCustomElement(int x, int y)
        if (change->can_change &&
            change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
            change->sides & border_side &&
-           change->trigger_element == border_element)
+#if 1
+           IS_EQUAL_OR_IN_GROUP(border_element, change->trigger_element)
+#else
+           change->trigger_element == border_element
+#endif
+           )
        {
          change_center_element = TRUE;
          center_element_change_page = j;
@@ -7965,7 +8444,12 @@ void TestIfElementTouchesCustomElement(int x, int y)
        if (change->can_change &&
            change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
            change->sides & center_side &&
-           change->trigger_element == center_element)
+#if 1
+           IS_EQUAL_OR_IN_GROUP(center_element, change->trigger_element)
+#else
+           change->trigger_element == center_element
+#endif
+           )
        {
          CheckElementSideChange(xx, yy, border_element, CH_SIDE_ANY,
                                 CE_OTHER_IS_TOUCHING, j);
@@ -8007,15 +8491,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
 
   if (IN_LEV_FIELD(hitx, hity))
   {
-    static int opposite_directions[] =
-    {
-      MV_RIGHT,
-      MV_LEFT,
-      MV_DOWN,
-      MV_UP
-    };
-    int move_dir_bit = MV_DIR_BIT(direction);
-    int opposite_direction = opposite_directions[move_dir_bit];
+    int opposite_direction = MV_DIR_OPPOSITE(direction);
     int hitting_side = direction;
     int touched_side = opposite_direction;
     int touched_element = MovingOrBlocked2Element(hitx, hity);
@@ -8045,7 +8521,13 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
          if (change->can_change &&
              change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) &&
              change->sides & touched_side &&
-             change->trigger_element == touched_element)
+         
+#if 1
+             IS_EQUAL_OR_IN_GROUP(touched_element, change->trigger_element)
+#else
+             change->trigger_element == touched_element
+#endif
+             )
          {
            CheckElementSideChange(x, y, hitting_element,
                                   CH_SIDE_ANY, CE_OTHER_IS_HITTING, i);
@@ -8065,7 +8547,12 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
          if (change->can_change &&
              change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) &&
              change->sides & hitting_side &&
-             change->trigger_element == hitting_element)
+#if 1
+             IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
+#else
+             change->trigger_element == hitting_element
+#endif
+             )
          {
            CheckElementSideChange(hitx, hity, touched_element,
                                   CH_SIDE_ANY, CE_OTHER_GETS_HIT, i);
@@ -8133,7 +8620,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
 
       if (player->shield_deadly_time_left > 0)
        Bang(kill_x, kill_y);
-      else if (!PLAYER_PROTECTED(good_x, good_y))
+      else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
        KillHero(player);
     }
     else
@@ -8225,7 +8712,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
       if (player->shield_deadly_time_left > 0)
        Bang(bad_x, bad_y);
-      else if (!PLAYER_PROTECTED(kill_x, kill_y))
+      else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
        KillHero(player);
     }
     else
@@ -8315,9 +8802,15 @@ void KillHero(struct PlayerInfo *player)
   BuryHero(player);
 }
 
-static void KillHeroUnlessProtected(int x, int y)
+static void KillHeroUnlessEnemyProtected(int x, int y)
 {
-  if (!PLAYER_PROTECTED(x, y))
+  if (!PLAYER_ENEMY_PROTECTED(x, y))
+    KillHero(PLAYERINFO(x, y));
+}
+
+static void KillHeroUnlessExplosionProtected(int x, int y)
+{
+  if (!PLAYER_EXPLOSION_PROTECTED(x, y))
     KillHero(PLAYERINFO(x, y));
 }
 
@@ -8494,6 +8987,10 @@ int DigField(struct PlayerInfo *player,
     case EL_SP_GRAVITY_PORT_RIGHT:
     case EL_SP_GRAVITY_PORT_UP:
     case EL_SP_GRAVITY_PORT_DOWN:
+#if 1
+      if (!canEnterSupaplexPort(x, y, dx, dy))
+       return MF_NO_ACTION;
+#else
       if ((dx == -1 &&
           element != EL_SP_PORT_LEFT &&
           element != EL_SP_GRAVITY_PORT_LEFT &&
@@ -8517,6 +9014,7 @@ int DigField(struct PlayerInfo *player,
          !IN_LEV_FIELD(nextx, nexty) ||
          !IS_FREE(nextx, nexty))
        return MF_NO_ACTION;
+#endif
 
       if (element == EL_SP_GRAVITY_PORT_LEFT ||
          element == EL_SP_GRAVITY_PORT_RIGHT ||
@@ -8526,7 +9024,7 @@ int DigField(struct PlayerInfo *player,
 
       /* automatically move to the next field with double speed */
       player->programmed_action = move_direction;
-#if 0
+#if 1
       if (player->move_delay_reset_counter == 0)
       {
        player->move_delay_reset_counter = 2;   /* two double speed steps */
@@ -8534,9 +9032,9 @@ int DigField(struct PlayerInfo *player,
        DOUBLE_PLAYER_SPEED(player);
       }
 #else
-      DOUBLE_PLAYER_SPEED(player);
-
       player->move_delay_reset_counter = 2;
+
+      DOUBLE_PLAYER_SPEED(player);
 #endif
 
       PlayLevelSound(x, y, SND_CLASS_SP_PORT_PASSING);
@@ -8651,9 +9149,9 @@ int DigField(struct PlayerInfo *player,
          DOUBLE_PLAYER_SPEED(player);
        }
 #else
-       DOUBLE_PLAYER_SPEED(player);
-
        player->move_delay_reset_counter = 2;
+
+       DOUBLE_PLAYER_SPEED(player);
 #endif
 
        PlayLevelSoundAction(x, y, ACTION_PASSING);
@@ -9100,9 +9598,14 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 
   player->MovDir = snap_direction;
 
-  player->is_moving = FALSE;
-  player->is_digging = FALSE;
-  player->is_collecting = FALSE;
+#if 1
+  if (player->MovPos == 0)
+#endif
+  {
+    player->is_moving = FALSE;
+    player->is_digging = FALSE;
+    player->is_collecting = FALSE;
+  }
 
   player->is_dropping = FALSE;
 
@@ -9111,9 +9614,14 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 
   player->is_snapping = TRUE;
 
-  player->is_moving = FALSE;
-  player->is_digging = FALSE;
-  player->is_collecting = FALSE;
+#if 1
+  if (player->MovPos == 0)
+#endif
+  {
+    player->is_moving = FALSE;
+    player->is_digging = FALSE;
+    player->is_collecting = FALSE;
+  }
 
   DrawLevelField(x, y);
   BackToFront();
@@ -9171,6 +9679,11 @@ boolean DropElement(struct PlayerInfo *player)
 
     PlayLevelSoundAction(jx, jy, ACTION_DROPPING);
 
+#if 1
+    /* needed if previous element just changed to "empty" in the last frame */
+    Changed[jx][jy] = 0;               /* allow another change */
+#endif
+
     CheckTriggeredElementChange(jx, jy, new_element, CE_OTHER_GETS_DROPPED);
     CheckElementChange(jx, jy, new_element, CE_DROPPED_BY_PLAYER);
 
@@ -9208,7 +9721,7 @@ boolean DropElement(struct PlayerInfo *player)
     int move_stepsize = element_info[new_element].move_stepsize;
     int direction, dx, dy, nextx, nexty;
 
-    if (element_info[new_element].move_direction_initial == MV_NO_MOVING)
+    if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
       MovDir[jx][jy] = player->MovDir;
 
     direction = MovDir[jx][jy];
@@ -9228,7 +9741,7 @@ boolean DropElement(struct PlayerInfo *player)
     }
     else
     {
-      Changed[jx][jy] = 0;            /* allow another change */
+      Changed[jx][jy] = 0;             /* allow another change */
 
 #if 1
       TestIfElementHitsCustomElement(jx, jy, direction);