rnd-20040422-1-src
[rocksndiamonds.git] / src / game.c
index a19a1a86ca48b6b20856ea284592fd3b73361fcc..0077517dc4f10e01beaa9114fe298556cba1e65f 100644 (file)
@@ -96,6 +96,9 @@
 /* values for other actions */
 #define MOVE_STEPSIZE_NORMAL   (TILEX / MOVE_DELAY_NORMAL_SPEED)
 
+#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 GET_NEW_PUSH_DELAY(e)  (   (element_info[e].push_delay_fixed) + \
@@ -767,6 +770,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
     }
 
     Feld[x][y] = EL_EMPTY;
+
     player->jx = player->last_jx = x;
     player->jy = player->last_jy = y;
   }
@@ -1633,6 +1637,7 @@ void InitGame()
       AmoebaNr[x][y] = 0;
       WasJustMoving[x][y] = 0;
       WasJustFalling[x][y] = 0;
+      CheckCollision[x][y] = 0;
       Stop[x][y] = FALSE;
       Pushed[x][y] = FALSE;
 
@@ -2518,6 +2523,10 @@ static void RemoveField(int x, int y)
   ChangePage[x][y] = -1;
   Pushed[x][y] = FALSE;
 
+#if 0
+  ExplodeField[x][y] = EX_TYPE_NONE;
+#endif
+
   GfxElement[x][y] = EL_UNDEFINED;
   GfxAction[x][y] = ACTION_DEFAULT;
   GfxDir[x][y] = MV_NO_MOVING;
@@ -2643,57 +2652,19 @@ void CheckDynamite(int x, int y)
   Bang(x, y);
 }
 
-void RelocatePlayer(int x, int y, int element_raw)
+void DrawRelocatePlayer(struct PlayerInfo *player)
 {
-  int element = (element_raw == EL_SP_MURPHY ? EL_PLAYER_1 : element_raw);
-  struct PlayerInfo *player = &stored_player[element - EL_PLAYER_1];
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int wait_delay_value = (no_delay ? 0 : frame_delay_value);
-  int old_jx, old_jy;
-
-  if (player->GameOver)                /* do not reanimate dead player */
-    return;
-
-  RemoveField(x, y);           /* temporarily remove newly placed player */
-  DrawLevelField(x, y);
-
-  if (player->present)
-  {
-    while (player->MovPos)
-    {
-      ScrollPlayer(player, SCROLL_GO_ON);
-      ScrollScreen(NULL, SCROLL_GO_ON);
-      FrameCounter++;
-
-      DrawPlayer(player);
-
-      BackToFront();
-      Delay(wait_delay_value);
-    }
-
-    DrawPlayer(player);                /* needed here only to cleanup last field */
-    DrawLevelField(player->jx, player->jy);    /* remove player graphic */
-
-    player->is_moving = FALSE;
-  }
-
-  old_jx = player->jx;
-  old_jy = player->jy;
-
-  Feld[x][y] = element;
-  InitPlayerField(x, y, element, TRUE);
-
-  if (player != local_player)  /* do not visually relocate other players */
-    return;
+  int jx = player->jx;
+  int jy = player->jy;
 
   if (level.instant_relocation)
   {
 #if 1
     int offset = (setup.scroll_delay ? 3 : 0);
-    int jx = local_player->jx;
-    int jy = local_player->jy;
 
     if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
     {
@@ -2743,8 +2714,6 @@ void RelocatePlayer(int x, int y, int element_raw)
 #if 1
 #if 0
     int offset = (setup.scroll_delay ? 3 : 0);
-    int jx = local_player->jx;
-    int jy = local_player->jy;
 #endif
     int scroll_xx = -999, scroll_yy = -999;
 
@@ -2840,7 +2809,120 @@ void RelocatePlayer(int x, int y, int element_raw)
       Delay(wait_delay_value);
     }
 #endif
+
+    DrawPlayer(player);
+    BackToFront();
+    Delay(wait_delay_value);
+  }
+}
+
+void RelocatePlayer(int jx, int jy, int el_player_raw)
+{
+  int el_player = (el_player_raw == EL_SP_MURPHY ? EL_PLAYER_1 :el_player_raw);
+  struct PlayerInfo *player = &stored_player[el_player - EL_PLAYER_1];
+  boolean ffwd_delay = (tape.playing && tape.fast_forward);
+  boolean no_delay = (tape.warp_forward);
+  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+  int old_jx = player->jx;
+  int old_jy = player->jy;
+  int old_element = Feld[old_jx][old_jy];
+  int element = Feld[jx][jy];
+  boolean player_relocated = (old_jx != jx || old_jy != jy);
+
+  static int trigger_sides[4][2] =
+  {
+    /* enter side               leave side */
+    { CH_SIDE_RIGHT,           CH_SIDE_LEFT    },      /* moving left  */
+    { CH_SIDE_LEFT,            CH_SIDE_RIGHT   },      /* moving right */
+    { CH_SIDE_BOTTOM,          CH_SIDE_TOP     },      /* moving up    */
+    { CH_SIDE_TOP,             CH_SIDE_BOTTOM  }       /* moving down  */
+  };
+  int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
+  int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
+  int enter_side_horiz = trigger_sides[MV_DIR_BIT(move_dir_horiz)][0];
+  int enter_side_vert  = trigger_sides[MV_DIR_BIT(move_dir_vert)][0];
+  int enter_side = enter_side_horiz | enter_side_vert;
+  int leave_side_horiz = trigger_sides[MV_DIR_BIT(move_dir_horiz)][1];
+  int leave_side_vert  = trigger_sides[MV_DIR_BIT(move_dir_vert)][1];
+  int leave_side = leave_side_horiz | leave_side_vert;
+
+  if (player->GameOver)                /* do not reanimate dead player */
+    return;
+
+  if (!player_relocated)       /* no need to relocate the player */
+    return;
+
+  if (IS_PLAYER(jx, jy))       /* player already placed at new position */
+  {
+    RemoveField(jx, jy);       /* temporarily remove newly placed player */
+    DrawLevelField(jx, jy);
+  }
+
+  if (player->present)
+  {
+    while (player->MovPos)
+    {
+      ScrollPlayer(player, SCROLL_GO_ON);
+      ScrollScreen(NULL, SCROLL_GO_ON);
+      FrameCounter++;
+
+      DrawPlayer(player);
+
+      BackToFront();
+      Delay(wait_delay_value);
+    }
+
+    DrawPlayer(player);                /* needed here only to cleanup last field */
+    DrawLevelField(player->jx, player->jy);    /* remove player graphic */
+
+    player->is_moving = FALSE;
+  }
+
+#if 1
+  if (IS_CUSTOM_ELEMENT(old_element))
+    CheckElementChangeByPlayer(old_jx, old_jy, old_element,
+                              CE_LEFT_BY_PLAYER,
+                              player->index_bit, leave_side);
+
+  CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
+                                     CE_OTHER_GETS_LEFT,
+                                     player->index_bit, leave_side);
+#endif
+
+  Feld[jx][jy] = el_player;
+  InitPlayerField(jx, jy, el_player, TRUE);
+
+  if (!ELEM_IS_PLAYER(element))        /* player may be set on walkable element */
+  {
+    Feld[jx][jy] = element;
+    InitField(jx, jy, FALSE);
   }
+
+#if 1
+  if (player == local_player)  /* only visually relocate local player */
+    DrawRelocatePlayer(player);
+#endif
+
+#if 1
+  TestIfHeroTouchesBadThing(jx, jy);
+  TestIfPlayerTouchesCustomElement(jx, jy);
+#endif
+
+#if 1
+  /* needed to allow change of walkable custom element by entering player */
+  Changed[jx][jy] = 0;         /* allow another change */
+#endif
+
+#if 1
+  if (IS_CUSTOM_ELEMENT(element))
+    CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
+                              player->index_bit, enter_side);
+
+  CheckTriggeredElementChangeByPlayer(jx, jy, element,
+                                     CE_OTHER_GETS_ENTERED,
+                                     player->index_bit, enter_side);
+#endif
 }
 
 void Explode(int ex, int ey, int phase, int mode)
@@ -3939,6 +4021,7 @@ void Impact(int x, int y)
     return;
   }
 
+  /* !!! not sufficient for all cases -- see EL_PEARL below !!! */
   /* only reset graphic animation if graphic really changes after impact */
   if (impact &&
       el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
@@ -3954,6 +4037,8 @@ void Impact(int x, int y)
   }
   else if (impact && element == EL_PEARL)
   {
+    ResetGfxAnimation(x, y);
+
     Feld[x][y] = EL_PEARL_BREAKING;
     PlayLevelSound(x, y, SND_PEARL_BREAKING);
     return;
@@ -4073,6 +4158,8 @@ void Impact(int x, int y)
        }
        else if (smashed == EL_PEARL)
        {
+         ResetGfxAnimation(x, y);
+
          Feld[x][y + 1] = EL_PEARL_BREAKING;
          PlayLevelSound(x, y, SND_PEARL_BREAKING);
          return;
@@ -5046,12 +5133,16 @@ void StartMoving(int x, int y)
 #endif
     }
 #if 1
-    else if ((game.engine_version < VERSION_IDENT(2,2,0,7) &&
-             CAN_SMASH(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
-             (Feld[x][y + 1] == EL_BLOCKED)) ||
+    else if ((game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+             CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
+
             (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
              CAN_SMASH(element) && WasJustFalling[x][y] &&
-             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))))
+             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
+
+            (game.engine_version < VERSION_IDENT(2,2,0,7) &&
+             CAN_SMASH(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
+             (Feld[x][y + 1] == EL_BLOCKED)))
 
 #else
 #if 1
@@ -5080,6 +5171,8 @@ void StartMoving(int x, int y)
       WasJustFalling[x][y] = 0;
 #endif
 
+      CheckCollision[x][y] = 0;
+
       Impact(x, y);
     }
     else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
@@ -5194,11 +5287,26 @@ void StartMoving(int x, int y)
   }
 
   /* not "else if" because of elements that can fall and move (EL_SPRING) */
+#if 0
+  if (CAN_MOVE(element) && !started_moving && MovDir[x][y] != MV_NO_MOVING)
+#else
   if (CAN_MOVE(element) && !started_moving)
+#endif
   {
     int move_pattern = element_info[element].move_pattern;
     int newx, newy;
 
+#if 0
+#if DEBUG
+    if (MovDir[x][y] == MV_NO_MOVING)
+    {
+      printf("StartMoving(): %d,%d: element %d ['%s'] not moving\n",
+            x, y, element, element_info[element].token_name);
+      printf("StartMoving(): This should never happen!\n");
+    }
+#endif
+#endif
+
     Moving2Blocked(x, y, &newx, &newy);
 
 #if 1
@@ -5213,9 +5321,15 @@ void StartMoving(int x, int y)
 #endif
 
 #if 1
+
+#if 1
+    if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+       CheckCollision[x][y] && IN_LEV_FIELD_AND_NOT_FREE(newx, newy))
+#else
     if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
        WasJustMoving[x][y] && IN_LEV_FIELD(newx, newy) &&
        (Feld[newx][newy] == EL_BLOCKED || IS_PLAYER(newx, newy)))
+#endif
     {
 #if 0
       printf("::: element %d '%s' WasJustMoving %d [%d, %d, %d, %d]\n",
@@ -5231,6 +5345,8 @@ void StartMoving(int x, int y)
       WasJustMoving[x][y] = 0;
 #endif
 
+      CheckCollision[x][y] = 0;
+
       TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
 
 #if 0
@@ -5603,6 +5719,9 @@ void StartMoving(int x, int y)
          DrawLevelField(newx, newy);
        }
 
+       /* if digged element was about to explode, prevent the explosion */
+       ExplodeField[newx][newy] = EX_TYPE_NONE;
+
        PlayLevelSoundAction(x, y, action);
       }
 
@@ -5782,6 +5901,27 @@ void StartMoving(int x, int y)
 
       TurnRound(x, y);
 
+#if 0
+      if (move_pattern & MV_ANY_DIRECTION &&
+         move_pattern == MovDir[x][y])
+      {
+       int blocking_element =
+         (IN_LEV_FIELD(newx, newy) ? Feld[newx][newy] : BorderElement);
+
+#if 0
+       printf("::: '%s' is blocked by '%s'! [%d,%d -> %d,%d]\n",
+              element_info[element].token_name,
+              element_info[blocking_element].token_name,
+              x, y, newx, newy);
+#endif
+
+       CheckElementChangeBySide(x, y, element, blocking_element, CE_BLOCKED,
+                                MovDir[x][y]);
+
+       element = Feld[x][y];   /* element might have changed */
+      }
+#endif
+
 #if 1
       if (GFX_ELEMENT(element) != EL_SAND)     /* !!! FIX THIS (crumble) !!! */
        DrawLevelElementAnimation(x, y, element);
@@ -5865,7 +6005,13 @@ void ContinueMoving(int x, int y)
   Feld[newx][newy] = element;
   MovPos[x][y] = 0;    /* force "not moving" for "crumbled sand" */
 
-  if (element == EL_MOLE)
+#if 1
+  if (Store[x][y] == EL_ACID)  /* element is moving into acid pool */
+  {
+    element = Feld[newx][newy] = EL_ACID;
+  }
+#endif
+  else if (element == EL_MOLE)
   {
     Feld[x][y] = EL_SAND;
 
@@ -5924,10 +6070,12 @@ void ContinueMoving(int x, int y)
 
     Back[x][y] = Back[newx][newy] = 0;
   }
+#if 0
   else if (Store[x][y] == EL_ACID)
   {
     element = Feld[newx][newy] = EL_ACID;
   }
+#endif
 #if 0
   else if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
           ei->move_leave_element != EL_EMPTY &&
@@ -5973,18 +6121,27 @@ void ContinueMoving(int x, int y)
   ResetGfxAnimation(x, y);     /* reset animation values for old field */
 
 #if 1
-  if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) &&
-      ei->move_leave_element != EL_EMPTY &&
-      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED ||
-       stored != EL_EMPTY))
+  /* some elements can leave other elements behind after moving */
+#if 1
+  if (IS_CUSTOM_ELEMENT(element) && ei->move_leave_element != EL_EMPTY &&
+      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
+      (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
+#else
+  if (IS_CUSTOM_ELEMENT(element) && ei->move_leave_element != EL_EMPTY &&
+      (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
+      !IS_PLAYER(x, y))
+#endif
   {
-    /* some elements can leave other elements behind after moving */
+    int move_leave_element = ei->move_leave_element;
 
-    Feld[x][y] = ei->move_leave_element;
+    Feld[x][y] = move_leave_element;
     InitField(x, y, FALSE);
 
     if (GFX_CRUMBLED(Feld[x][y]))
       DrawLevelFieldCrumbledSandNeighbours(x, y);
+
+    if (ELEM_IS_PLAYER(move_leave_element))
+      RelocatePlayer(x, y, move_leave_element);
   }
 #endif
 
@@ -6054,15 +6211,30 @@ void ContinueMoving(int x, int y)
   /* prevent elements on conveyor belt from moving on in last direction */
   if (pushed_by_conveyor && CAN_FALL(element) &&
       direction & MV_HORIZONTAL)
+  {
+#if 0
+    if (CAN_MOVE(element))
+      InitMovDir(newx, newy);
+    else
+      MovDir[newx][newy] = 0;
+#else
     MovDir[newx][newy] = 0;
 #endif
+  }
+#endif
 
   if (!pushed_by_player)
   {
+    int nextx = newx + dx, nexty = newy + dy;
+    boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
+
     WasJustMoving[newx][newy] = 3;
 
     if (CAN_FALL(element) && direction == MV_DOWN)
       WasJustFalling[newx][newy] = 3;
+
+    if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
+      CheckCollision[newx][newy] = 2;
   }
 
   if (DONT_TOUCH(element))     /* object may be nasty to player or others */
@@ -7131,6 +7303,14 @@ static void ChangeActiveTrap(int x, int y)
 static void ChangeElementNowExt(int x, int y, int target_element)
 {
   int previous_move_direction = MovDir[x][y];
+#if 1
+  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+                       IS_WALKABLE(Feld[x][y]));
+#else
+  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+                       IS_WALKABLE(Feld[x][y]) &&
+                       !IS_MOVING(x, y));
+#endif
 
   /* check if element under player changes from accessible to unaccessible
      (needed for special case of dropping element which then changes) */
@@ -7141,42 +7321,65 @@ static void ChangeElementNowExt(int x, int y, int target_element)
     return;
   }
 
-  RemoveField(x, y);
-  Feld[x][y] = target_element;
+#if 1
+  if (!add_player)
+#endif
+  {
+#if 1
+    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+      RemoveMovingField(x, y);
+    else
+      RemoveField(x, y);
 
-  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+    Feld[x][y] = target_element;
+#else
+    RemoveField(x, y);
+    Feld[x][y] = target_element;
+#endif
 
-  ResetGfxAnimation(x, y);
-  ResetRandomAnimationValue(x, y);
+    ResetGfxAnimation(x, y);
+    ResetRandomAnimationValue(x, y);
 
-  if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
-    MovDir[x][y] = previous_move_direction;
+    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = previous_move_direction;
 
 #if 1
-  InitField_WithBug1(x, y, FALSE);
+    InitField_WithBug1(x, y, FALSE);
 #else
-  InitField(x, y, FALSE);
-  if (CAN_MOVE(Feld[x][y]))
-    InitMovDir(x, y);
+    InitField(x, y, FALSE);
+    if (CAN_MOVE(Feld[x][y]))
+      InitMovDir(x, y);
 #endif
 
-  DrawLevelField(x, y);
+    DrawLevelField(x, y);
 
-  if (GFX_CRUMBLED(Feld[x][y]))
-    DrawLevelFieldCrumbledSandNeighbours(x, y);
+    if (GFX_CRUMBLED(Feld[x][y]))
+      DrawLevelFieldCrumbledSandNeighbours(x, y);
+  }
 
+  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+
+#if 0
   TestIfBadThingTouchesHero(x, y);
   TestIfPlayerTouchesCustomElement(x, y);
   TestIfElementTouchesCustomElement(x, y);
+#endif
 
   if (ELEM_IS_PLAYER(target_element))
     RelocatePlayer(x, y, target_element);
+
+#if 1
+  TestIfBadThingTouchesHero(x, y);
+  TestIfPlayerTouchesCustomElement(x, y);
+  TestIfElementTouchesCustomElement(x, y);
+#endif
 }
 
 static boolean ChangeElementNow(int x, int y, int element, int page)
 {
   struct ElementChangeInfo *change = &element_info[element].change_page[page];
   int target_element;
+  int old_element = Feld[x][y];
 
   /* always use default change event to prevent running into a loop */
   if (ChangeEvent[x][y] == CE_BITMASK_DEFAULT)
@@ -7200,7 +7403,10 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
 
   Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
 
+#if 0
+  /* !!! indirect change before direct change !!! */
   CheckTriggeredElementChangeByPage(x,y,Feld[x][y], CE_OTHER_IS_CHANGING,page);
+#endif
 
   if (change->explode)
   {
@@ -7218,7 +7424,10 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
     for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
     {
       boolean is_empty;
+      boolean is_walkable;
       boolean is_diggable;
+      boolean is_collectible;
+      boolean is_removable;
       boolean is_destructible;
       int ex = x + xx - 1;
       int ey = y + yy - 1;
@@ -7252,19 +7461,27 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
 
 #if 1
 
-#if 1
-      is_empty = (IS_FREE(ex, ey) || (IS_FREE_OR_PLAYER(ex, ey) &&
-                                     IS_WALKABLE(content_element)));
+#if 0
+      is_empty = (IS_FREE(ex, ey) ||
+                 (IS_PLAYER(ex, ey) && IS_WALKABLE(content_element)) ||
+                 (IS_WALKABLE(e) && ELEM_IS_PLAYER(content_element) &&
+                  !IS_MOVING(ex, ey) && !IS_BLOCKED(ex, ey)));
 #else
-      is_empty = (IS_FREE(ex, ey) || (IS_PLAYER(ex, ey) &&
-                                     IS_WALKABLE(content_element)));
+      is_empty = (IS_FREE(ex, ey) ||
+                 (IS_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
 #endif
-      is_diggable = (is_empty || IS_DIGGABLE(e));
+      is_walkable     = (is_empty || IS_WALKABLE(e));
+      is_diggable     = (is_empty || IS_DIGGABLE(e));
+      is_collectible  = (is_empty || IS_COLLECTIBLE(e));
       is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
+      is_removable    = (is_diggable || is_collectible);
 
       can_replace[xx][yy] =
-       ((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
-        (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
+       ((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
+        (change->replace_when == CP_WHEN_WALKABLE     && is_walkable) ||
+        (change->replace_when == CP_WHEN_DIGGABLE     && is_diggable) ||
+        (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
+        (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
         (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible));
 
       if (!can_replace[xx][yy])
@@ -7336,6 +7553,11 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
     PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
   }
 
+#if 1
+  /* !!! indirect change before direct change !!! */
+  CheckTriggeredElementChangeByPage(x,y,old_element,CE_OTHER_IS_CHANGING,page);
+#endif
+
   return TRUE;
 }
 
@@ -8179,6 +8401,8 @@ void GameActions()
       WasJustMoving[x][y]--;
     if (WasJustFalling[x][y] > 0)
       WasJustFalling[x][y]--;
+    if (CheckCollision[x][y] > 0)
+      CheckCollision[x][y]--;
 
     GfxFrame[x][y]++;
 
@@ -9263,23 +9487,25 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       int move_direction = player->MovDir;
       int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
       int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
+      int old_element = Feld[old_jx][old_jy];
+      int new_element = Feld[jx][jy];
 
 #if 1
       /* !!! TEST ONLY !!! */
-      if (IS_CUSTOM_ELEMENT(Feld[old_jx][old_jy]))
-       CheckElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+      if (IS_CUSTOM_ELEMENT(old_element))
+       CheckElementChangeByPlayer(old_jx, old_jy, old_element,
                                   CE_LEFT_BY_PLAYER,
                                   player->index_bit, leave_side);
 
-      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
                                          CE_OTHER_GETS_LEFT,
                                          player->index_bit, leave_side);
 
-      if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
-       CheckElementChangeByPlayer(jx, jy, Feld[jx][jy], CE_ENTERED_BY_PLAYER,
+      if (IS_CUSTOM_ELEMENT(new_element))
+       CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
                                   player->index_bit, enter_side);
 
-      CheckTriggeredElementChangeByPlayer(jx, jy, Feld[jx][jy],
+      CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
                                          CE_OTHER_GETS_ENTERED,
                                          player->index_bit, enter_side);
 #endif
@@ -9418,23 +9644,25 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
       int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
       int old_jx = last_jx;
       int old_jy = last_jy;
+      int old_element = Feld[old_jx][old_jy];
+      int new_element = Feld[jx][jy];
 
 #if 1
       /* !!! TEST ONLY !!! */
-      if (IS_CUSTOM_ELEMENT(Feld[old_jx][old_jy]))
-       CheckElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+      if (IS_CUSTOM_ELEMENT(old_element))
+       CheckElementChangeByPlayer(old_jx, old_jy, old_element,
                                   CE_LEFT_BY_PLAYER,
                                   player->index_bit, leave_side);
 
-      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, Feld[old_jx][old_jy],
+      CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
                                          CE_OTHER_GETS_LEFT,
                                          player->index_bit, leave_side);
 
-      if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
-       CheckElementChangeByPlayer(jx, jy, Feld[jx][jy], CE_ENTERED_BY_PLAYER,
+      if (IS_CUSTOM_ELEMENT(new_element))
+       CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
                                   player->index_bit, enter_side);
 
-      CheckTriggeredElementChangeByPlayer(jx, jy, Feld[jx][jy],
+      CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
                                          CE_OTHER_GETS_ENTERED,
                                          player->index_bit, enter_side);
 #endif
@@ -10405,6 +10633,20 @@ int DigField(struct PlayerInfo *player,
       game.engine_version >= VERSION_IDENT(2,2,0,0))
     return MF_NO_ACTION;
 
+#if 1
+  if (game.gravity && !player->is_auto_moving &&
+      canFallDown(player) && move_direction != MV_DOWN &&
+      !canMoveToValidFieldWithGravity(jx, jy, move_direction))
+    return MF_NO_ACTION;       /* player cannot walk here due to gravity */
+#endif
+
+#if 0
+  if (element == EL_EMPTY_SPACE &&
+      game.gravity && !player->is_auto_moving &&
+      canFallDown(player) && move_direction != MV_DOWN)
+    return MF_NO_ACTION;       /* player cannot walk here due to gravity */
+#endif
+
   switch (element)
   {
 #if 0
@@ -10524,14 +10766,20 @@ int DigField(struct PlayerInfo *player,
 
     default:
 
+#if 1
+      if (IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
+#else
       if (IS_WALKABLE(element))
+#endif
       {
        int sound_action = ACTION_WALKING;
 
+#if 0
        if (!ACCESS_FROM(element, opposite_direction))
          return MF_NO_ACTION;  /* field not accessible from this direction */
+#endif
 
-#if 1
+#if 0
        if (element == EL_EMPTY_SPACE &&
            game.gravity && !player->is_auto_moving &&
            canFallDown(player) && move_direction != MV_DOWN)
@@ -10567,13 +10815,18 @@ int DigField(struct PlayerInfo *player,
 
        break;
       }
+#if 1
+      else if (IS_PASSABLE(element) && canPassField(x, y, move_direction))
+#else
       else if (IS_PASSABLE(element))
+#endif
       {
-#if 1
+#if 0
        if (!canPassField(x, y, move_direction))
          return MF_NO_ACTION;
 #else
 
+#if 0
 #if 1
        if (!IN_LEV_FIELD(nextx, nexty) || IS_PLAYER(nextx, nexty) ||
            !IS_WALKABLE_FROM(Feld[nextx][nexty], move_direction) ||
@@ -10583,6 +10836,7 @@ int DigField(struct PlayerInfo *player,
        if (!IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty))
          return MF_NO_ACTION;
 #endif
+#endif
 
 #if 1
        if (!ACCESS_FROM(element, opposite_direction))
@@ -10728,7 +10982,8 @@ int DigField(struct PlayerInfo *player,
          ShowEnvelope(element - EL_ENVELOPE_1);
 #endif
        }
-       else if (IS_DROPPABLE(element)) /* can be collected and dropped */
+       else if (IS_DROPPABLE(element) ||
+                IS_THROWABLE(element)) /* can be collected and dropped */
        {
          int i;
 
@@ -11083,10 +11338,10 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
                        dy == +1 ? MV_DOWN : MV_NO_MOVING);
 
 #if 0
-  if (player->MovPos)
+  if (player->MovPos != 0)
     return FALSE;
 #else
-  if (player->MovPos && game.engine_version >= VERSION_IDENT(2,2,0,0))
+  if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
     return FALSE;
 #endif
 
@@ -11143,8 +11398,16 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
     player->is_collecting = FALSE;
   }
 
+#if 1
+  if (player->MovPos != 0)     /* prevent graphic bugs in versions < 2.2.0 */
+    DrawLevelField(player->last_jx, player->last_jy);
+#endif
+
   DrawLevelField(x, y);
+
+#if 0
   BackToFront();
+#endif
 
   return TRUE;
 }
@@ -11158,10 +11421,10 @@ boolean DropElement(struct PlayerInfo *player)
     CH_SIDE_TOP,       /* dropping up    */
     CH_SIDE_BOTTOM,    /* dropping down  */
   };
-  int jx = player->jx, jy = player->jy;
+  int old_element, new_element;
+  int dropx = player->jx, dropy = player->jy;
   int drop_direction = player->MovDir;
   int drop_side = trigger_sides[MV_DIR_BIT(drop_direction)];
-  int old_element = Feld[jx][jy];
   int drop_element = (player->inventory_size > 0 ?
                      player->inventory_element[player->inventory_size - 1] :
                      player->inventory_infinite_element != EL_UNDEFINED ?
@@ -11169,7 +11432,18 @@ boolean DropElement(struct PlayerInfo *player)
                      player->dynabombs_left > 0 ?
                      EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
                      EL_UNDEFINED);
-  int new_element = drop_element;      /* default: element does not change */
+
+  if (IS_THROWABLE(drop_element))
+  {
+    dropx += GET_DX_FROM_DIR(drop_direction);
+    dropy += GET_DY_FROM_DIR(drop_direction);
+
+    if (!IN_LEV_FIELD(dropx, dropy))
+      return FALSE;
+  }
+
+  old_element = Feld[dropx][dropy];    /* old element at dropping position */
+  new_element = drop_element;          /* default: no change when dropping */
 
   /* check if player is active, not moving and ready to drop */
   if (!player->active || player->MovPos || player->drop_delay > 0)
@@ -11202,10 +11476,10 @@ boolean DropElement(struct PlayerInfo *player)
 #endif
 
   if (old_element != EL_EMPTY)
-    Back[jx][jy] = old_element;                /* store old element on this field */
+    Back[dropx][dropy] = old_element;  /* store old element on this field */
 
-  ResetGfxAnimation(jx, jy);
-  ResetRandomAnimationValue(jx, jy);
+  ResetGfxAnimation(dropx, dropy);
+  ResetRandomAnimationValue(dropx, dropy);
 
   if (player->inventory_size > 0 ||
       player->inventory_infinite_element != EL_UNDEFINED)
@@ -11226,34 +11500,35 @@ boolean DropElement(struct PlayerInfo *player)
        new_element = EL_SP_DISK_RED_ACTIVE;
     }
 
-    Feld[jx][jy] = new_element;
+    Feld[dropx][dropy] = new_element;
 
-    if (IN_SCR_FIELD(SCREENX(jx), SCREENY(jy)))
-      DrawGraphicThruMask(SCREENX(jx), SCREENY(jy), el2img(Feld[jx][jy]), 0);
+    if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
+      DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
+                         el2img(Feld[dropx][dropy]), 0);
 
-    PlayLevelSoundAction(jx, jy, ACTION_DROPPING);
+    PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
 
 #if 1
     /* needed if previous element just changed to "empty" in the last frame */
-    Changed[jx][jy] = 0;               /* allow another change */
+    Changed[dropx][dropy] = 0;         /* allow another change */
 #endif
 
 #if 1
     /* !!! TEST ONLY !!! */
-    CheckElementChangeByPlayer(jx, jy, new_element, CE_DROPPED_BY_PLAYER,
+    CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
                               player->index_bit, drop_side);
-    CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
+    CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
                                        CE_OTHER_GETS_DROPPED,
                                        player->index_bit, drop_side);
 #else
-    CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
+    CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
                                        CE_OTHER_GETS_DROPPED,
                                        player->index_bit, drop_side);
-    CheckElementChangeByPlayer(jx, jy, new_element, CE_DROPPED_BY_PLAYER,
+    CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
                               player->index_bit, drop_side);
 #endif
 
-    TestIfElementTouchesCustomElement(jx, jy);
+    TestIfElementTouchesCustomElement(dropx, dropy);
   }
   else         /* player is dropping a dyna bomb */
   {
@@ -11263,30 +11538,31 @@ boolean DropElement(struct PlayerInfo *player)
     new_element = EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr;
 #endif
 
-    Feld[jx][jy] = new_element;
+    Feld[dropx][dropy] = new_element;
 
-    if (IN_SCR_FIELD(SCREENX(jx), SCREENY(jy)))
-      DrawGraphicThruMask(SCREENX(jx), SCREENY(jy), el2img(Feld[jx][jy]), 0);
+    if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
+      DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
+                         el2img(Feld[dropx][dropy]), 0);
 
-    PlayLevelSoundAction(jx, jy, ACTION_DROPPING);
+    PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
   }
 
 
 
 #if 1
 
-  if (Feld[jx][jy] == new_element)     /* uninitialized unless CE change */
+  if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
   {
 #if 1
-    InitField_WithBug1(jx, jy, FALSE);
+    InitField_WithBug1(dropx, dropy, FALSE);
 #else
-    InitField(jx, jy, FALSE);
-    if (CAN_MOVE(Feld[jx][jy]))
-      InitMovDir(jx, jy);
+    InitField(dropx, dropy, FALSE);
+    if (CAN_MOVE(Feld[dropx][dropy]))
+      InitMovDir(dropx, dropy);
 #endif
   }
 
-  new_element = Feld[jx][jy];          /* element might have changed */
+  new_element = Feld[dropx][dropy];    /* element might have changed */
 
   if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
       element_info[new_element].move_pattern == MV_WHEN_DROPPED)
@@ -11294,37 +11570,46 @@ boolean DropElement(struct PlayerInfo *player)
 #if 0
     int move_stepsize = element_info[new_element].move_stepsize;
 #endif
-    int direction, dx, dy, nextx, nexty;
+    int move_direction, nextx, nexty;
 
     if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
-      MovDir[jx][jy] = player->MovDir;
+      MovDir[dropx][dropy] = drop_direction;
+
+    move_direction = MovDir[dropx][dropy];
+    nextx = dropx + GET_DX_FROM_DIR(move_direction);
+    nexty = dropy + GET_DY_FROM_DIR(move_direction);
 
-    direction = MovDir[jx][jy];
-    dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-    dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-    nextx = jx + dx;
-    nexty = jy + dy;
+#if 1
+      Changed[dropx][dropy] = 0;       /* allow another change */
+      CheckCollision[dropx][dropy] = 2;
+#else
 
     if (IN_LEV_FIELD(nextx, nexty) && IS_FREE(nextx, nexty))
     {
 #if 0
-      WasJustMoving[jx][jy] = 3;
+      WasJustMoving[dropx][dropy] = 3;
 #else
-      InitMovingField(jx, jy, direction);
-      ContinueMoving(jx, jy);
+#if 1
+      InitMovingField(dropx, dropy, move_direction);
+      ContinueMoving(dropx, dropy);
+#endif
 #endif
     }
+#if 1
     else
     {
-      Changed[jx][jy] = 0;             /* allow another change */
+      Changed[dropx][dropy] = 0;       /* allow another change */
 
 #if 1
-      TestIfElementHitsCustomElement(jx, jy, direction);
+      TestIfElementHitsCustomElement(dropx, dropy, move_direction);
 #else
-      CheckElementChangeBySide(jx, jy, new_element, touched_element,
-                              CE_HITTING_SOMETHING, direction);
+      CheckElementChangeBySide(dropx, dropy, new_element, touched_element,
+                              CE_HITTING_SOMETHING, move_direction);
 #endif
     }
+#endif
+
+#endif
 
 #if 0
     player->drop_delay = 2 * TILEX / move_stepsize + 1;