rnd-20030616-1-src
[rocksndiamonds.git] / src / game.c
index 1691c6924bc816d43a101c2c0df6b28d87bd058c..99cbfbbdfdf76adb0848ece543ecf5eda3ddf29c 100644 (file)
@@ -14,9 +14,9 @@
 #include "libgame/libgame.h"
 
 #include "game.h"
+#include "init.h"
 #include "tools.h"
 #include "screens.h"
-#include "init.h"
 #include "files.h"
 #include "tape.h"
 #include "network.h"
 
 #define        INIT_GFX_RANDOM()       (SimpleRND(1000000))
 
+#define GET_NEW_PUSH_DELAY(e)  (   (element_info[e].push_delay_fixed) + \
+                                RND(element_info[e].push_delay_random))
+#define GET_NEW_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
+                                RND(element_info[e].move_delay_random))
+
 /* game button identifiers */
 #define GAME_CTRL_ID_STOP              0
 #define GAME_CTRL_ID_PAUSE             1
@@ -109,6 +114,8 @@ static void CloseAllOpenTimegates(void);
 static void CheckGravityMovement(struct PlayerInfo *);
 static void KillHeroUnlessProtected(int, int);
 
+static void ChangeElementDoIt(int, int, int);
+
 static void PlaySoundLevel(int, int, int);
 static void PlaySoundLevelNearest(int, int, int);
 static void PlaySoundLevelAction(int, int, int);
@@ -154,38 +161,144 @@ struct ChangingElementInfo
 
 static struct ChangingElementInfo changing_element_list[] =
 {
-  { EL_NUT_BREAKING,           EL_EMERALD,              6, NULL, NULL, NULL },
-  { EL_PEARL_BREAKING,         EL_EMPTY,                8, NULL, NULL, NULL },
-  { EL_EXIT_OPENING,           EL_EXIT_OPEN,           29, NULL, NULL, NULL },
-
-  { EL_SWITCHGATE_OPENING,     EL_SWITCHGATE_OPEN,     29, NULL, NULL, NULL },
-  { EL_SWITCHGATE_CLOSING,     EL_SWITCHGATE_CLOSED,   29, NULL, NULL, NULL },
-
-  { EL_TIMEGATE_OPENING,       EL_TIMEGATE_OPEN,       29, NULL, NULL, NULL },
-  { EL_TIMEGATE_CLOSING,       EL_TIMEGATE_CLOSED,     29, NULL, NULL, NULL },
-
-  { EL_ACID_SPLASH_LEFT,       EL_EMPTY,                8, NULL, NULL, NULL },
-  { EL_ACID_SPLASH_RIGHT,      EL_EMPTY,                8, NULL, NULL, NULL },
-
-  { EL_SP_BUGGY_BASE,          EL_SP_BUGGY_BASE_ACTIVATING, 0,
-    InitBuggyBase, NULL, NULL },
-  { EL_SP_BUGGY_BASE_ACTIVATING,EL_SP_BUGGY_BASE_ACTIVE, 0,
-    InitBuggyBase, NULL, NULL },
-  { EL_SP_BUGGY_BASE_ACTIVE,   EL_SP_BUGGY_BASE,        0,
-    InitBuggyBase, WarnBuggyBase, NULL },
-
-  { EL_TRAP,                   EL_TRAP_ACTIVE,          0,
-    InitTrap, NULL, ActivateTrap },
-  { EL_TRAP_ACTIVE,            EL_TRAP,                31,
-    NULL, ChangeActiveTrap, NULL },
-
-  { EL_ROBOT_WHEEL_ACTIVE,     EL_ROBOT_WHEEL,          0,
-    InitRobotWheel, RunRobotWheel, StopRobotWheel },
+  {
+    EL_NUT_BREAKING,
+    EL_EMERALD,
+    6,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_PEARL_BREAKING,
+    EL_EMPTY,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_EXIT_OPENING,
+    EL_EXIT_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SWITCHGATE_OPENING,
+    EL_SWITCHGATE_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SWITCHGATE_CLOSING,
+    EL_SWITCHGATE_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_TIMEGATE_OPENING,
+    EL_TIMEGATE_OPEN,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_TIMEGATE_CLOSING,
+    EL_TIMEGATE_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
 
-  { EL_TIMEGATE_SWITCH_ACTIVE, EL_TIMEGATE_SWITCH,      0,
-    InitTimegateWheel, RunTimegateWheel, NULL },
+  {
+    EL_ACID_SPLASH_LEFT,
+    EL_EMPTY,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_ACID_SPLASH_RIGHT,
+    EL_EMPTY,
+    8,
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_BUGGY_BASE,
+    EL_SP_BUGGY_BASE_ACTIVATING,
+    0,
+    InitBuggyBase,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_BUGGY_BASE_ACTIVATING,
+    EL_SP_BUGGY_BASE_ACTIVE,
+    0,
+    InitBuggyBase,
+    NULL,
+    NULL
+  },
+  {
+    EL_SP_BUGGY_BASE_ACTIVE,
+    EL_SP_BUGGY_BASE,
+    0,
+    InitBuggyBase,
+    WarnBuggyBase,
+    NULL
+  },
+  {
+    EL_TRAP,
+    EL_TRAP_ACTIVE,
+    0,
+    InitTrap,
+    NULL,
+    ActivateTrap
+  },
+  {
+    EL_TRAP_ACTIVE,
+    EL_TRAP,
+    31,
+    NULL,
+    ChangeActiveTrap,
+    NULL
+  },
+  {
+    EL_ROBOT_WHEEL_ACTIVE,
+    EL_ROBOT_WHEEL,
+    0,
+    InitRobotWheel,
+    RunRobotWheel,
+    StopRobotWheel
+  },
+  {
+    EL_TIMEGATE_SWITCH_ACTIVE,
+    EL_TIMEGATE_SWITCH,
+    0,
+    InitTimegateWheel,
+    RunTimegateWheel,
+    NULL
+  },
 
-  { EL_UNDEFINED,              EL_UNDEFINED,           -1, NULL        }
+  {
+    EL_UNDEFINED,
+    EL_UNDEFINED,
+    -1,
+    NULL,
+    NULL,
+    NULL
+  }
 };
 
 static struct ChangingElementInfo changing_element[MAX_NUM_ELEMENTS];
@@ -266,7 +379,9 @@ static int getBeltDirFromBeltSwitchElement(int element)
 
 static void InitField(int x, int y, boolean init_game)
 {
-  switch (Feld[x][y])
+  int element = Feld[x][y];
+
+  switch (element)
   {
     case EL_SP_MURPHY:
       if (init_game)
@@ -463,6 +578,8 @@ static void InitField(int x, int y, boolean init_game)
       break;
 
     default:
+      if (IS_CUSTOM_ELEMENT(element) && CAN_MOVE(element))
+       InitMovDir(x, y);
       break;
   }
 }
@@ -500,9 +617,13 @@ static void InitGameEngine()
 {
   int i;
 
+  /* set game engine from tape file when re-playing, else from level file */
   game.engine_version = (tape.playing ? tape.engine_version :
                         level.game_version);
 
+  /* dynamically adjust element properties according to game engine version */
+  InitElementPropertiesEngine(game.engine_version);
+
 #if 0
     printf("level %d: level version == %06d\n", level_nr, level.game_version);
     printf("          tape version == %06d [%s] [file: %06d]\n",
@@ -520,30 +641,6 @@ static void InitGameEngine()
   game.initial_move_delay_value =
     (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
 
-  /* dynamically adjust element properties according to game engine version */
-  {
-    static int ep_em_slippery_wall[] =
-    {
-      EL_STEELWALL,
-      EL_WALL,
-      EL_EXPANDABLE_WALL,
-      EL_EXPANDABLE_WALL_HORIZONTAL,
-      EL_EXPANDABLE_WALL_VERTICAL,
-      EL_EXPANDABLE_WALL_ANY
-    };
-    static int ep_em_slippery_wall_num = SIZEOF_ARRAY_INT(ep_em_slippery_wall);
-
-    /* special EM style gems behaviour */
-    for (i=0; i<ep_em_slippery_wall_num; i++)
-      SET_PROPERTY(ep_em_slippery_wall[i], EP_EM_SLIPPERY_WALL,
-                  level.em_slippery_gems);
-
-    /* "EL_EXPANDABLE_WALL_GROWING" wasn't slippery for EM gems in 2.0.1 */
-    SET_PROPERTY(EL_EXPANDABLE_WALL_GROWING, EP_EM_SLIPPERY_WALL,
-                (level.em_slippery_gems &&
-                 game.engine_version > VERSION_IDENT(2,0,1)));
-  }
-
   /* initialize changing elements information */
   for (i=0; i<MAX_NUM_ELEMENTS; i++)
   {
@@ -555,6 +652,7 @@ static void InitGameEngine()
     changing_element[i].post_change_function = NULL;
   }
 
+  /* add changing elements from pre-defined list */
   i = 0;
   while (changing_element_list[i].base_element != EL_UNDEFINED)
   {
@@ -570,6 +668,22 @@ static void InitGameEngine()
 
     i++;
   }
+
+  /* add changing elements from custom element configuration */
+  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+    struct ElementChangeInfo *change = &element_info[element].change;
+
+    /* only add custom elements that change after fixed/random frame delay */
+    if (!CAN_CHANGE(element) || !HAS_CHANGE_EVENT(element, CE_DELAY))
+      continue;
+
+    changing_element[element].base_element = element;
+    changing_element[element].next_element = change->successor;
+    changing_element[element].change_delay = (change->delay_fixed *
+                                             change->delay_frames);
+  }
 }
 
 
@@ -699,7 +813,7 @@ void InitGame()
 
   AllPlayersGone = FALSE;
 
-  game.yam_content_nr = 0;
+  game.yamyam_content_nr = 0;
   game.magic_wall_active = FALSE;
   game.magic_wall_time_left = 0;
   game.light_time_left = 0;
@@ -723,7 +837,8 @@ void InitGame()
     {
       Feld[x][y] = Ur[x][y];
       MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
-      Store[x][y] = Store2[x][y] = StorePlayer[x][y] = 0;
+      ChangeDelay[x][y] = 0;
+      Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
       AmoebaNr[x][y] = 0;
       JustStopped[x][y] = 0;
       Stop[x][y] = FALSE;
@@ -941,7 +1056,7 @@ void InitGame()
   if (setup.sound_music)
     PlayMusic(level_nr);
 
-  KeyboardAutoRepeatOff();
+  KeyboardAutoRepeatOffUnlessAutoplay();
 
   if (options.debug)
   {
@@ -1027,30 +1142,66 @@ void InitMovDir(int x, int y)
       break;
 
     default:
-      MovDir[x][y] = 1 << RND(4);
-      if (element != EL_BUG &&
-         element != EL_SPACESHIP &&
-         element != EL_BD_BUTTERFLY &&
-         element != EL_BD_FIREFLY)
-       break;
-
-      for (i=0; i<4; i++)
+      if (IS_CUSTOM_ELEMENT(element))
       {
-       int x1 = x + xy[i][0];
-       int y1 = y + xy[i][1];
-
-       if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
+       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)
+         MovDir[x][y] = 1 << RND(4);
+       else if (element_info[element].move_pattern == MV_HORIZONTAL)
+         MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
+       else if (element_info[element].move_pattern == MV_VERTICAL)
+         MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
+       else if (element_info[element].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)
        {
-         if (element == EL_BUG || element == EL_BD_BUTTERFLY)
+         for (i=0; i<4; i++)
          {
-           MovDir[x][y] = direction[0][i];
-           break;
+           int x1 = x + xy[i][0];
+           int y1 = y + xy[i][1];
+
+           if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
+           {
+             if (element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE)
+               MovDir[x][y] = direction[0][i];
+             else
+               MovDir[x][y] = direction[1][i];
+
+             break;
+           }
          }
-         else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
-                  element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
+       }                
+      }
+      else
+      {
+       MovDir[x][y] = 1 << RND(4);
+
+       if (element != EL_BUG &&
+           element != EL_SPACESHIP &&
+           element != EL_BD_BUTTERFLY &&
+           element != EL_BD_FIREFLY)
+         break;
+
+       for (i=0; i<4; i++)
+       {
+         int x1 = x + xy[i][0];
+         int y1 = y + xy[i][1];
+
+         if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
          {
-           MovDir[x][y] = direction[1][i];
-           break;
+           if (element == EL_BUG || element == EL_BD_BUTTERFLY)
+           {
+             MovDir[x][y] = direction[0][i];
+             break;
+           }
+           else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
+                    element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
+           {
+             MovDir[x][y] = direction[1][i];
+             break;
+           }
          }
        }
       }
@@ -1176,7 +1327,7 @@ void GameWon()
 
   if ((hi_pos = NewHiScore()) >= 0) 
   {
-    game_status = HALLOFFAME;
+    game_status = GAME_MODE_SCORES;
     DrawHallOfFame(hi_pos);
     if (raise_level)
     {
@@ -1186,7 +1337,7 @@ void GameWon()
   }
   else
   {
-    game_status = MAINMENU;
+    game_status = GAME_MODE_MAIN;
     if (raise_level)
     {
       level_nr++;
@@ -1382,6 +1533,7 @@ static void RemoveField(int x, int y)
   MovPos[x][y] = 0;
   MovDir[x][y] = 0;
   MovDelay[x][y] = 0;
+  ChangeDelay[x][y] = 0;
 }
 
 void RemoveMovingField(int x, int y)
@@ -1418,6 +1570,7 @@ void RemoveMovingField(int x, int y)
   Feld[newx][newy] = EL_EMPTY;
   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
+  ChangeDelay[oldx][oldy] = ChangeDelay[newx][newy] = 0;
   GfxAction[oldx][oldy] = GfxAction[newx][newy] = ACTION_DEFAULT;
 
   DrawLevelField(oldx, oldy);
@@ -1433,13 +1586,18 @@ void DrawDynamite(int x, int y)
   if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
     return;
 
-  if (Store[x][y])
+  if (IS_WALKABLE_INSIDE(Back[x][y]))
+    return;
+
+  if (Back[x][y])
+    DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
+  else if (Store[x][y])
     DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
 
   frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
 #if 1
-  if (Store[x][y])
+  if (Back[x][y] || Store[x][y])
     DrawGraphicThruMask(sx, sy, graphic, frame);
   else
     DrawGraphic(sx, sy, graphic, frame);
@@ -1500,6 +1658,10 @@ void Explode(int ex, int ey, int phase, int mode)
   {
     int center_element = Feld[ex][ey];
 
+    /* remove things displayed in background while burning dynamite */
+    if (!IS_INDESTRUCTIBLE(Back[ex][ey]))
+      Back[ex][ey] = 0;
+
     if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
     {
       /* put moving element to center field (and let it explode there) */
@@ -1508,7 +1670,7 @@ void Explode(int ex, int ey, int phase, int mode)
       Feld[ex][ey] = center_element;
     }
 
-    for (y=ey-1; y<=ey+1; y++) for(x=ex-1; x<=ex+1; x++)
+    for (y = ey - 1; y <= ey + 1; y++) for(x = ex - 1; x <= ex + 1; x++)
     {
       int element;
 
@@ -1526,14 +1688,14 @@ void Explode(int ex, int ey, int phase, int mode)
       }
 
 #if 1
+      if (IS_EXPLOSION_PROOF(element))
+       continue;
+#else
       if ((IS_INDESTRUCTIBLE(element) &&
           (game.engine_version < VERSION_IDENT(2,2,0) ||
            (!IS_WALKABLE_OVER(element) && !IS_WALKABLE_UNDER(element)))) ||
          element == EL_FLAMES)
        continue;
-#else
-      if (IS_INDESTRUCTIBLE(element) || element == EL_FLAMES)
-        continue;
 #endif
 
       if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
@@ -1548,19 +1710,14 @@ void Explode(int ex, int ey, int phase, int mode)
        continue;
       }
 
+      /* save walkable background elements while explosion on same tile */
+      if (IS_INDESTRUCTIBLE(element))
+       Back[x][y] = element;
+
+      /* ignite explodable elements reached by other explosion */
       if (element == EL_EXPLOSION)
        element = Store2[x][y];
 
-#if 1
-      if (IS_INDESTRUCTIBLE(Store[x][y]))      /* hard element under bomb */
-       element = Store[x][y];
-#endif
-
-#if 0
-      if (!IS_INDESTRUCTIBLE(Store[x][y]) != !IS_PFORTE(Store[x][y]))
-       printf("\n::: %d\n", Store[x][y]);
-#endif
-
       if (IS_PLAYER(ex, ey) && !PLAYER_PROTECTED(ex, ey))
       {
        switch(StorePlayer[ex][ey])
@@ -1593,10 +1750,14 @@ void Explode(int ex, int ey, int phase, int mode)
        Store[x][y] = EL_BD_DIAMOND;
       else if (center_element == EL_SP_ELECTRON)
        Store[x][y] = EL_SP_INFOTRON;
-      else if (center_element == EL_YAMYAM)
-       Store[x][y] = level.yam_content[game.yam_content_nr][x-ex+1][y-ey+1];
       else if (center_element == EL_AMOEBA_TO_DIAMOND)
        Store[x][y] = level.amoeba_content;
+      else if (center_element == EL_YAMYAM)
+       Store[x][y] =
+         level.yamyam_content[game.yamyam_content_nr][x - ex + 1][y - ey + 1];
+      else if (IS_CUSTOM_ELEMENT(center_element))
+       Store[x][y] =
+         element_info[center_element].content[x - ex + 1][y - ey + 1];
       else if (element == EL_WALL_EMERALD)
        Store[x][y] = EL_EMERALD;
       else if (element == EL_WALL_DIAMOND)
@@ -1613,24 +1774,8 @@ void Explode(int ex, int ey, int phase, int mode)
        Store[x][y] = EL_PEARL;
       else if (element == EL_WALL_CRYSTAL)
        Store[x][y] = EL_CRYSTAL;
-#if 1
-      else if (IS_INDESTRUCTIBLE(element) && IS_ACCESSIBLE(element))
-       Store[x][y] = element;
       else
        Store[x][y] = EL_EMPTY;
-#else
-
-#if 1
-      else if (IS_PFORTE(element))
-       Store[x][y] = element;
-      else
-       Store[x][y] = EL_EMPTY;
-#else
-      else if (!IS_PFORTE(Store[x][y]))
-       Store[x][y] = EL_EMPTY;
-#endif
-
-#endif
 
       if (x != ex || y != ey ||
          center_element == EL_AMOEBA_TO_DIAMOND || mode == EX_BORDER)
@@ -1646,7 +1791,11 @@ void Explode(int ex, int ey, int phase, int mode)
       }
 
       Feld[x][y] = EL_EXPLOSION;
+#if 1
+      GfxElement[x][y] = center_element;
+#else
       GfxElement[x][y] = EL_UNDEFINED;
+#endif
       MovDir[x][y] = MovPos[x][y] = 0;
       AmoebaNr[x][y] = 0;
       ExplodePhase[x][y] = 1;
@@ -1654,7 +1803,8 @@ void Explode(int ex, int ey, int phase, int mode)
     }
 
     if (center_element == EL_YAMYAM)
-      game.yam_content_nr = (game.yam_content_nr + 1) % level.num_yam_contents;
+      game.yamyam_content_nr =
+       (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
 
     return;
   }
@@ -1684,7 +1834,7 @@ void Explode(int ex, int ey, int phase, int mode)
 
     if (IS_PLAYER(x, y))
       KillHeroUnlessProtected(x, y);
-    else if (IS_EXPLOSIVE(element))
+    else if (CAN_EXPLODE_BY_FIRE(element))
     {
       Feld[x][y] = Store2[x][y];
       Store2[x][y] = 0;
@@ -1700,9 +1850,15 @@ void Explode(int ex, int ey, int phase, int mode)
 
     element = Feld[x][y] = Store[x][y];
     Store[x][y] = Store2[x][y] = 0;
-    MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
+    GfxElement[x][y] = EL_UNDEFINED;
+
+    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] = ChangeDelay[x][y] = 0;
     InitField(x, y, FALSE);
-    if (CAN_MOVE(element) || COULD_MOVE(element))
+    if (CAN_MOVE(element))
       InitMovDir(x, y);
     DrawLevelField(x, y);
 
@@ -1711,37 +1867,31 @@ void Explode(int ex, int ey, int phase, int mode)
   }
   else if (phase >= delay && 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 (phase == delay)
       DrawLevelFieldCrumbledSand(x, y);
 
-#if 1
-    if (IS_WALKABLE_OVER(Store[x][y]))
+    if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
     {
-      DrawLevelElement(x, y, Store[x][y]);
+      DrawLevelElement(x, y, Back[x][y]);
       DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
     }
-    else if (IS_WALKABLE_UNDER(Store[x][y]))
+    else if (IS_WALKABLE_UNDER(Back[x][y]))
     {
       DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
-      DrawLevelElementThruMask(x, y, Store[x][y]);
+      DrawLevelElementThruMask(x, y, Back[x][y]);
     }
-    else
+    else if (!IS_WALKABLE_INSIDE(Back[x][y]))
       DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
-#else
-    if (IS_PFORTE(Store[x][y]))
-    {
-      DrawLevelElement(x, y, Store[x][y]);
-      DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
-    }
-    else
-      DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
-#endif
   }
 }
 
@@ -1801,10 +1951,22 @@ void Bang(int x, int y)
 {
   int element = Feld[x][y];
 
+  if (IS_PLAYER(x, y))
+  {
+    struct PlayerInfo *player = PLAYERINFO(x, y);
+
+    element = Feld[x][y] = (player->use_murphy_graphic ? EL_SP_MURPHY :
+                           player->element_nr);
+  }
+
+#if 1
+  PlaySoundLevelAction(x, y, ACTION_EXPLODING);
+#else
   if (game.emulation == EMU_SUPAPLEX)
     PlaySoundLevel(x, y, SND_SP_ELEMENT_EXPLODING);
   else
     PlaySoundLevel(x, y, SND_ELEMENT_EXPLODING);
+#endif
 
 #if 0
   if (IS_PLAYER(x, y)) /* remove objects that might cause smaller explosion */
@@ -2180,19 +2342,22 @@ void Impact(int x, int y)
 {
   boolean lastline = (y == lev_fieldy-1);
   boolean object_hit = FALSE;
+  boolean impact = (lastline || object_hit);
   int element = Feld[x][y];
-  int smashed = 0;
+  int smashed = EL_UNDEFINED;
 
   if (!lastline)       /* check if element below was hit */
   {
-    if (Feld[x][y+1] == EL_PLAYER_IS_LEAVING)
+    if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
       return;
 
-    object_hit = (!IS_FREE(x, y+1) && (!IS_MOVING(x, y+1) ||
-                                     MovDir[x][y+1] != MV_DOWN ||
-                                     MovPos[x][y+1] <= TILEY / 2));
+    object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
+                                        MovDir[x][y + 1] != MV_DOWN ||
+                                        MovPos[x][y + 1] <= TILEY / 2));
     if (object_hit)
-      smashed = MovingOrBlocked2Element(x, y+1);
+      smashed = MovingOrBlocked2Element(x, y + 1);
+
+    impact = (lastline || object_hit);
   }
 
   if (!lastline && smashed == EL_ACID) /* element falls into acid */
@@ -2201,33 +2366,46 @@ void Impact(int x, int y)
     return;
   }
 
-  if (lastline || object_hit)
+  if (impact)
   {
     ResetGfxAnimation(x, y);
     DrawLevelField(x, y);
   }
 
+#if 1
+  if (impact && CAN_EXPLODE_IMPACT(element))
+#else
   if ((element == EL_BOMB ||
        element == EL_SP_DISK_ORANGE ||
        element == EL_DX_SUPABOMB) &&
       (lastline || object_hit))                /* element is bomb */
+#endif
   {
     Bang(x, y);
     return;
   }
-  else if (element == EL_PEARL)
+  else if (impact && element == EL_PEARL)
   {
     Feld[x][y] = EL_PEARL_BREAKING;
     PlaySoundLevel(x, y, SND_PEARL_BREAKING);
     return;
   }
+  else if (impact && CAN_CHANGE(element) &&
+          HAS_CHANGE_EVENT(element, CE_IMPACT))
+  {
+    PlaySoundLevelElementAction(x, y, element, ACTION_IMPACT);
+
+    ChangeElementDoIt(x, y, element_info[element].change.successor);
+
+    return;
+  }
 
-  if (element == EL_AMOEBA_DROP && (lastline || object_hit))
+  if (impact && element == EL_AMOEBA_DROP)
   {
-    if (object_hit && IS_PLAYER(x, y+1))
-      KillHeroUnlessProtected(x, y+1);
+    if (object_hit && IS_PLAYER(x, y + 1))
+      KillHeroUnlessProtected(x, y + 1);
     else if (object_hit && smashed == EL_PENGUIN)
-      Bang(x, y+1);
+      Bang(x, y + 1);
     else
     {
       Feld[x][y] = EL_AMOEBA_GROWING;
@@ -2238,9 +2416,13 @@ void Impact(int x, int y)
     return;
   }
 
+#if 1
+  if (object_hit)              /* check which object was hit */
+#else
   if (!lastline && object_hit)         /* check which object was hit */
+#endif
   {
-    if (CAN_CHANGE(element) && 
+    if (CAN_PASS_MAGIC_WALL(element) && 
        (smashed == EL_MAGIC_WALL ||
         smashed == EL_BD_MAGIC_WALL))
     {
@@ -2265,17 +2447,23 @@ void Impact(int x, int y)
 
     if (IS_PLAYER(x, y + 1))
     {
-      KillHeroUnlessProtected(x, y+1);
-      return;
+      if (CAN_SMASH_PLAYER(element))
+      {
+       KillHeroUnlessProtected(x, y + 1);
+       return;
+      }
     }
     else if (smashed == EL_PENGUIN)
     {
-      Bang(x, y + 1);
-      return;
+      if (CAN_SMASH_PLAYER(element))
+      {
+       Bang(x, y + 1);
+       return;
+      }
     }
     else if (element == EL_BD_DIAMOND)
     {
-      if (IS_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
+      if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
       {
        Bang(x, y + 1);
        return;
@@ -2290,11 +2478,18 @@ void Impact(int x, int y)
       Bang(x, y + 1);
       return;
     }
+#if 1
+    else if (CAN_SMASH_EVERYTHING(element))
+#else
     else if (element == EL_ROCK ||
             element == EL_SP_ZONK ||
             element == EL_BD_ROCK)
+#endif
     {
-      if (IS_ENEMY(smashed) ||
+      if (IS_CLASSIC_ENEMY(smashed) ||
+#if 1
+         CAN_EXPLODE_SMASHED(smashed))
+#else
          smashed == EL_BOMB ||
          smashed == EL_SP_DISK_ORANGE ||
          smashed == EL_DX_SUPABOMB ||
@@ -2302,11 +2497,16 @@ void Impact(int x, int y)
          smashed == EL_PIG ||
          smashed == EL_DRAGON ||
          smashed == EL_MOLE)
+#endif
       {
        Bang(x, y + 1);
        return;
       }
+#if 1
+      else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
+#else
       else if (!IS_MOVING(x, y + 1))
+#endif
       {
        if (smashed == EL_LAMP ||
            smashed == EL_LAMP_ACTIVE)
@@ -2316,36 +2516,45 @@ void Impact(int x, int y)
        }
        else if (smashed == EL_NUT)
        {
-         Feld[x][y+1] = EL_NUT_BREAKING;
+         Feld[x][y + 1] = EL_NUT_BREAKING;
          PlaySoundLevel(x, y, SND_NUT_BREAKING);
          RaiseScoreElement(EL_NUT);
          return;
        }
        else if (smashed == EL_PEARL)
        {
-         Feld[x][y+1] = EL_PEARL_BREAKING;
+         Feld[x][y + 1] = EL_PEARL_BREAKING;
          PlaySoundLevel(x, y, SND_PEARL_BREAKING);
          return;
        }
        else if (smashed == EL_DIAMOND)
        {
-         Feld[x][y+1] = EL_EMPTY;
+#if 1
+         Feld[x][y + 1] = EL_DIAMOND_BREAKING;
+#else
+         Feld[x][y + 1] = EL_EMPTY;
+#endif
          PlaySoundLevel(x, y, SND_DIAMOND_BREAKING);
          return;
        }
        else if (IS_BELT_SWITCH(smashed))
        {
-         ToggleBeltSwitch(x, y+1);
+         ToggleBeltSwitch(x, y + 1);
        }
        else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
                 smashed == EL_SWITCHGATE_SWITCH_DOWN)
        {
-         ToggleSwitchgateSwitch(x, y+1);
+         ToggleSwitchgateSwitch(x, y + 1);
        }
        else if (smashed == EL_LIGHT_SWITCH ||
                 smashed == EL_LIGHT_SWITCH_ACTIVE)
        {
-         ToggleLightSwitch(x, y+1);
+         ToggleLightSwitch(x, y + 1);
+       }
+       else if (CAN_CHANGE(smashed) &&
+                HAS_CHANGE_EVENT(smashed, CE_SMASHED))
+       {
+         ChangeElementDoIt(x, y + 1, element_info[smashed].change.successor);
        }
       }
     }
@@ -2353,12 +2562,12 @@ void Impact(int x, int y)
 
   /* play sound of magic wall / mill */
   if (!lastline &&
-      (Feld[x][y+1] == EL_MAGIC_WALL_ACTIVE ||
-       Feld[x][y+1] == EL_BD_MAGIC_WALL_ACTIVE))
+      (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
+       Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
   {
-    if (Feld[x][y+1] == EL_MAGIC_WALL_ACTIVE)
+    if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
       PlaySoundLevel(x, y, SND_MAGIC_WALL_FILLING);
-    else if (Feld[x][y+1] == EL_BD_MAGIC_WALL_ACTIVE)
+    else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
       PlaySoundLevel(x, y, SND_BD_MAGIC_WALL_FILLING);
 
     return;
@@ -2376,26 +2585,26 @@ void TurnRound(int x, int y)
     int x, y;
   } move_xy[] =
   {
-    { 0, 0 },
-    {-1, 0 },
-    {+1, 0 },
-    { 0, 0 },
-    { 0, -1 },
-    { 0, 0 }, { 0, 0 }, { 0, 0 },
-    { 0, +1 }
+    {  0,  0 },
+    { -1,  0 },
+    { +1,  0 },
+    {  0,  0 },
+    {  0, -1 },
+    {  0,  0 }, { 0, 0 }, { 0, 0 },
+    {  0, +1 }
   };
   static struct
   {
     int left, right, back;
   } turn[] =
   {
-    { 0,       0,              0 },
+    { 0,       0,              0        },
     { MV_DOWN, MV_UP,          MV_RIGHT },
-    { MV_UP,   MV_DOWN,        MV_LEFT },
-    { 0,       0,              0 },
-    { MV_LEFT, MV_RIGHT,       MV_DOWN },
-    { 0,0,0 }, { 0,0,0 },      { 0,0,0 },
-    { MV_RIGHT,        MV_LEFT,        MV_UP }
+    { MV_UP,   MV_DOWN,        MV_LEFT  },
+    { 0,       0,              0        },
+    { MV_LEFT, MV_RIGHT,       MV_DOWN  },
+    { 0,0,0 }, { 0,0,0 },      { 0,0,0  },
+    { MV_RIGHT,        MV_LEFT,        MV_UP    }
   };
 
   int element = Feld[x][y];
@@ -2529,13 +2738,13 @@ void TurnRound(int x, int y)
     int rnd = RND(rnd_value);
 
     if (IN_LEV_FIELD(left_x, left_y) &&
-       (IS_FREE(left_x, left_y) || IS_GEM(Feld[left_x][left_y])))
+       (IS_FREE(left_x, left_y) || IS_FOOD_PIG(Feld[left_x][left_y])))
       can_turn_left = TRUE;
     if (IN_LEV_FIELD(right_x, right_y) &&
-       (IS_FREE(right_x, right_y) || IS_GEM(Feld[right_x][right_y])))
+       (IS_FREE(right_x, right_y) || IS_FOOD_PIG(Feld[right_x][right_y])))
       can_turn_right = TRUE;
     if (IN_LEV_FIELD(move_x, move_y) &&
-       (IS_FREE(move_x, move_y) || IS_GEM(Feld[move_x][move_y])))
+       (IS_FREE(move_x, move_y) || IS_FOOD_PIG(Feld[move_x][move_y])))
       can_move_on = TRUE;
 
     if (can_turn_left &&
@@ -2587,7 +2796,7 @@ void TurnRound(int x, int y)
       MovDir[x][y] = back_dir;
 
     if (!IS_FREE(x+move_xy[MovDir[x][y]].x, y+move_xy[MovDir[x][y]].y) &&
-       !IS_GEM(Feld[x+move_xy[MovDir[x][y]].x][y+move_xy[MovDir[x][y]].y]))
+       !IS_FOOD_PIG(Feld[x+move_xy[MovDir[x][y]].x][y+move_xy[MovDir[x][y]].y]))
       MovDir[x][y] = old_move_dir;
 
     MovDelay[x][y] = 0;
@@ -2714,8 +2923,8 @@ void TurnRound(int x, int y)
 
       for (i=0; i<4; i++)
       {
-       int ex = x + xy[i%4][0];
-       int ey = y + xy[i%4][1];
+       int ex = x + xy[i % 4][0];
+       int ey = y + xy[i % 4][1];
 
        if (IN_LEV_FIELD(ex, ey) && Feld[ex][ey] == EL_EXIT_OPEN)
        {
@@ -2727,21 +2936,21 @@ void TurnRound(int x, int y)
     }
 
     MovDir[x][y] = MV_NO_MOVING;
-    if (attr_x<x)
+    if (attr_x < x)
       MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
-    else if (attr_x>x)
+    else if (attr_x > x)
       MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
-    if (attr_y<y)
+    if (attr_y < y)
       MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
-    else if (attr_y>y)
+    else if (attr_y > y)
       MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
 
     if (element == EL_ROBOT)
     {
       int newx, newy;
 
-      if ((MovDir[x][y]&(MV_LEFT|MV_RIGHT)) && (MovDir[x][y]&(MV_UP|MV_DOWN)))
-       MovDir[x][y] &= (RND(2) ? (MV_LEFT|MV_RIGHT) : (MV_UP|MV_DOWN));
+      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+       MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
       Moving2Blocked(x, y, &newx, &newy);
 
       if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
@@ -2755,13 +2964,13 @@ void TurnRound(int x, int y)
 
       MovDelay[x][y] = 1;
 
-      if ((MovDir[x][y]&(MV_LEFT|MV_RIGHT)) && (MovDir[x][y]&(MV_UP|MV_DOWN)))
+      if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
       {
        boolean first_horiz = RND(2);
        int new_move_dir = MovDir[x][y];
 
        MovDir[x][y] =
-         new_move_dir & (first_horiz ? (MV_LEFT|MV_RIGHT) : (MV_UP|MV_DOWN));
+         new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
        if (IN_LEV_FIELD(newx, newy) &&
@@ -2773,7 +2982,7 @@ void TurnRound(int x, int y)
          return;
 
        MovDir[x][y] =
-         new_move_dir & (!first_horiz ? (MV_LEFT|MV_RIGHT) : (MV_UP|MV_DOWN));
+         new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
        Moving2Blocked(x, y, &newx, &newy);
 
        if (IN_LEV_FIELD(newx, newy) &&
@@ -2789,6 +2998,148 @@ void TurnRound(int x, int y)
       }
     }
   }
+  else if (element_info[element].move_pattern == MV_ALL_DIRECTIONS)
+  {
+    boolean can_turn_left = FALSE, can_turn_right = FALSE;
+
+    if (IN_LEV_FIELD(left_x, left_y) &&
+       (IS_FREE(left_x, left_y) ||
+        (DONT_COLLIDE_WITH(element) && IS_FREE_OR_PLAYER(left_x, left_y))))
+      can_turn_left = TRUE;
+    if (IN_LEV_FIELD(right_x, right_y) &&
+       (IS_FREE(right_x, right_y) ||
+        (DONT_COLLIDE_WITH(element) && IS_FREE_OR_PLAYER(right_x, right_y))))
+      can_turn_right = TRUE;
+
+    if (can_turn_left && can_turn_right)
+      MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+    else if (can_turn_left)
+      MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+    else if (can_turn_right)
+      MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+    else
+      MovDir[x][y] = back_dir;
+
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (element_info[element].move_pattern == MV_HORIZONTAL ||
+          element_info[element].move_pattern == MV_VERTICAL)
+  {
+    if (element_info[element].move_pattern & old_move_dir)
+      MovDir[x][y] = back_dir;
+    else if (element_info[element].move_pattern == MV_HORIZONTAL)
+      MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
+    else if (element_info[element].move_pattern == MV_VERTICAL)
+      MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
+
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (element_info[element].move_pattern & MV_ANY_DIRECTION)
+  {
+    MovDir[x][y] = element_info[element].move_pattern;
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (element_info[element].move_pattern == MV_ALONG_LEFT_SIDE)
+  {
+    if (IN_LEV_FIELD(left_x, left_y) &&
+       (IS_FREE(left_x, left_y) ||
+        (DONT_COLLIDE_WITH(element) && IS_FREE_OR_PLAYER(left_x, left_y))))
+      MovDir[x][y] = left_dir;
+    else if (!IN_LEV_FIELD(move_x, move_y) ||
+            (!IS_FREE(move_x, move_y) &&
+             (!DONT_COLLIDE_WITH(element) || !IS_FREE_OR_PLAYER(move_x, move_y))))
+      MovDir[x][y] = right_dir;
+
+    if (MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE)
+  {
+    if (IN_LEV_FIELD(right_x, right_y) &&
+       (IS_FREE(right_x, right_y) ||
+        (DONT_COLLIDE_WITH(element) && IS_FREE_OR_PLAYER(right_x, right_y))))
+      MovDir[x][y] = right_dir;
+    else if (!IN_LEV_FIELD(move_x, move_y) ||
+            (!IS_FREE(move_x, move_y) &&
+             (!DONT_COLLIDE_WITH(element) || !IS_FREE_OR_PLAYER(move_x, move_y))))
+      MovDir[x][y] = left_dir;
+
+    if (MovDir[x][y] != old_move_dir)
+      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+  }
+  else if (element_info[element].move_pattern == MV_TOWARDS_PLAYER ||
+          element_info[element].move_pattern == MV_AWAY_FROM_PLAYER)
+  {
+    int attr_x = -1, attr_y = -1;
+    int newx, newy;
+    boolean move_away =
+      (element_info[element].move_pattern == MV_AWAY_FROM_PLAYER);
+
+    if (AllPlayersGone)
+    {
+      attr_x = ExitX;
+      attr_y = ExitY;
+    }
+    else
+    {
+      int i;
+
+      for (i=0; i<MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int jx = player->jx, jy = player->jy;
+
+       if (!player->active)
+         continue;
+
+       if (attr_x == -1 || ABS(jx-x)+ABS(jy-y) < ABS(attr_x-x)+ABS(attr_y-y))
+       {
+         attr_x = jx;
+         attr_y = jy;
+       }
+      }
+    }
+
+    MovDir[x][y] = MV_NO_MOVING;
+    if (attr_x < x)
+      MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
+    else if (attr_x > x)
+      MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
+    if (attr_y < y)
+      MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
+    else if (attr_y > y)
+      MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
+
+    MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+
+    if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+    {
+      boolean first_horiz = RND(2);
+      int new_move_dir = MovDir[x][y];
+
+      MovDir[x][y] =
+       new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+      Moving2Blocked(x, y, &newx, &newy);
+
+      if (IN_LEV_FIELD(newx, newy) && (IS_FREE(newx, newy) ||
+                                      (DONT_COLLIDE_WITH(element) &&
+                                       IS_FREE_OR_PLAYER(newx, newy)) ||
+                                      Feld[newx][newy] == EL_ACID))
+       return;
+
+      MovDir[x][y] =
+       new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+      Moving2Blocked(x, y, &newx, &newy);
+
+      if (IN_LEV_FIELD(newx, newy) && (IS_FREE(newx, newy) ||
+                                      (DONT_COLLIDE_WITH(element) &&
+                                       IS_FREE_OR_PLAYER(newx, newy)) ||
+                                      Feld[newx][newy] == EL_ACID))
+       return;
+
+      MovDir[x][y] = old_move_dir;
+    }
+  }
 }
 
 static boolean JustBeingPushed(int x, int y)
@@ -2814,7 +3165,7 @@ static boolean JustBeingPushed(int x, int y)
 
 void StartMoving(int x, int y)
 {
-  static boolean use_spring_bug = TRUE;
+  boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0));
   boolean started_moving = FALSE;      /* some elements can fall _and_ move */
   int element = Feld[x][y];
 
@@ -2831,7 +3182,7 @@ void StartMoving(int x, int y)
 
     if (element == EL_QUICKSAND_FULL)
     {
-      if (IS_FREE(x, y+1))
+      if (IS_FREE(x, y + 1))
       {
        InitMovingField(x, y, MV_DOWN);
        started_moving = TRUE;
@@ -2844,7 +3195,7 @@ void StartMoving(int x, int y)
        PlaySoundLevel(x, y, SND_QUICKSAND_EMPTYING);
 #endif
       }
-      else if (Feld[x][y+1] == EL_QUICKSAND_EMPTY)
+      else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
       {
        if (!MovDelay[x][y])
          MovDelay[x][y] = TILEY + 1;
@@ -2857,8 +3208,8 @@ void StartMoving(int x, int y)
        }
 
        Feld[x][y] = EL_QUICKSAND_EMPTY;
-       Feld[x][y+1] = EL_QUICKSAND_FULL;
-       Store[x][y+1] = Store[x][y];
+       Feld[x][y + 1] = EL_QUICKSAND_FULL;
+       Store[x][y + 1] = Store[x][y];
        Store[x][y] = 0;
 #if 1
        PlaySoundLevelAction(x, y, ACTION_FILLING);
@@ -2868,7 +3219,7 @@ void StartMoving(int x, int y)
       }
     }
     else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
-            Feld[x][y+1] == EL_QUICKSAND_EMPTY)
+            Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
     {
       InitMovingField(x, y, MV_DOWN);
       started_moving = TRUE;
@@ -2883,7 +3234,7 @@ void StartMoving(int x, int y)
     }
     else if (element == EL_MAGIC_WALL_FULL)
     {
-      if (IS_FREE(x, y+1))
+      if (IS_FREE(x, y + 1))
       {
        InitMovingField(x, y, MV_DOWN);
        started_moving = TRUE;
@@ -2891,7 +3242,7 @@ void StartMoving(int x, int y)
        Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
        Store[x][y] = EL_CHANGED(Store[x][y]);
       }
-      else if (Feld[x][y+1] == EL_MAGIC_WALL_ACTIVE)
+      else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
          MovDelay[x][y] = TILEY/4 + 1;
@@ -2904,14 +3255,14 @@ void StartMoving(int x, int y)
        }
 
        Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
-       Feld[x][y+1] = EL_MAGIC_WALL_FULL;
-       Store[x][y+1] = EL_CHANGED(Store[x][y]);
+       Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
+       Store[x][y + 1] = EL_CHANGED(Store[x][y]);
        Store[x][y] = 0;
       }
     }
     else if (element == EL_BD_MAGIC_WALL_FULL)
     {
-      if (IS_FREE(x, y+1))
+      if (IS_FREE(x, y + 1))
       {
        InitMovingField(x, y, MV_DOWN);
        started_moving = TRUE;
@@ -2919,7 +3270,7 @@ void StartMoving(int x, int y)
        Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
        Store[x][y] = EL_CHANGED2(Store[x][y]);
       }
-      else if (Feld[x][y+1] == EL_BD_MAGIC_WALL_ACTIVE)
+      else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
          MovDelay[x][y] = TILEY/4 + 1;
@@ -2932,27 +3283,27 @@ void StartMoving(int x, int y)
        }
 
        Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
-       Feld[x][y+1] = EL_BD_MAGIC_WALL_FULL;
-       Store[x][y+1] = EL_CHANGED2(Store[x][y]);
+       Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
+       Store[x][y + 1] = EL_CHANGED2(Store[x][y]);
        Store[x][y] = 0;
       }
     }
-    else if (CAN_CHANGE(element) &&
-            (Feld[x][y+1] == EL_MAGIC_WALL_ACTIVE ||
-             Feld[x][y+1] == EL_BD_MAGIC_WALL_ACTIVE))
+    else if (CAN_PASS_MAGIC_WALL(element) &&
+            (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
+             Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
     {
       InitMovingField(x, y, MV_DOWN);
       started_moving = TRUE;
 
       Feld[x][y] =
-       (Feld[x][y+1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
+       (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
         EL_BD_MAGIC_WALL_FILLING);
       Store[x][y] = element;
     }
 #if 0
-    else if (CAN_SMASH(element) && Feld[x][y+1] == EL_ACID)
+    else if (CAN_SMASH(element) && Feld[x][y + 1] == EL_ACID)
 #else
-    else if (CAN_FALL(element) && Feld[x][y+1] == EL_ACID)
+    else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
 #endif
     {
       SplashAcid(x, y);
@@ -2963,15 +3314,15 @@ void StartMoving(int x, int y)
       Store[x][y] = EL_ACID;
 #if 0
       /* !!! TEST !!! better use "_FALLING" etc. !!! */
-      GfxAction[x][y+1] = ACTION_ACTIVE;
+      GfxAction[x][y + 1] = ACTION_ACTIVE;
 #endif
     }
-    else if (CAN_SMASH(element) && Feld[x][y+1] == EL_BLOCKED &&
+    else if (CAN_SMASH(element) && Feld[x][y + 1] == EL_BLOCKED &&
             JustStopped[x][y])
     {
       Impact(x, y);
     }
-    else if (IS_FREE(x, y+1) && element == EL_SPRING && use_spring_bug)
+    else if (IS_FREE(x, y + 1) && element == EL_SPRING && use_spring_bug)
     {
       if (MovDir[x][y] == MV_NO_MOVING)
       {
@@ -2979,7 +3330,7 @@ void StartMoving(int x, int y)
        started_moving = TRUE;
       }
     }
-    else if (IS_FREE(x, y+1))
+    else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
     {
       if (JustStopped[x][y])   /* prevent animation from being restarted */
        MovDir[x][y] = MV_DOWN;
@@ -2992,28 +3343,28 @@ void StartMoving(int x, int y)
       Feld[x][y] = EL_AMOEBA_GROWING;
       Store[x][y] = EL_AMOEBA_WET;
     }
-    /* Store[x][y+1] must be zero, because:
-       (EL_QUICKSAND_FULL -> EL_ROCK): Store[x][y+1] == EL_QUICKSAND_EMPTY
+    /* Store[x][y + 1] must be zero, because:
+       (EL_QUICKSAND_FULL -> EL_ROCK): Store[x][y + 1] == EL_QUICKSAND_EMPTY
     */
 #if 0
 #if OLD_GAME_BEHAVIOUR
-    else if (IS_SLIPPERY(Feld[x][y+1]) && !Store[x][y+1])
+    else if (IS_SLIPPERY(Feld[x][y + 1]) && !Store[x][y + 1])
 #else
-    else if (IS_SLIPPERY(Feld[x][y+1]) && !Store[x][y+1] &&
-            !IS_FALLING(x, y+1) && !JustStopped[x][y+1] &&
+    else if (IS_SLIPPERY(Feld[x][y + 1]) && !Store[x][y + 1] &&
+            !IS_FALLING(x, y + 1) && !JustStopped[x][y + 1] &&
             element != EL_DX_SUPABOMB)
 #endif
 #else
-    else if ((IS_SLIPPERY(Feld[x][y+1]) ||
-             (IS_EM_SLIPPERY_WALL(Feld[x][y+1]) && IS_GEM(element))) &&
-            !IS_FALLING(x, y+1) && !JustStopped[x][y+1] &&
+    else if ((IS_SLIPPERY(Feld[x][y + 1]) ||
+             (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
+            !IS_FALLING(x, y + 1) && !JustStopped[x][y + 1] &&
             element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
 #endif
     {
       boolean left  = (x>0 && IS_FREE(x-1, y) &&
-                      (IS_FREE(x-1, y+1) || Feld[x-1][y+1] == EL_ACID));
+                      (IS_FREE(x-1, y + 1) || Feld[x-1][y + 1] == EL_ACID));
       boolean right = (x<lev_fieldx-1 && IS_FREE(x+1, y) &&
-                      (IS_FREE(x+1, y+1) || Feld[x+1][y+1] == EL_ACID));
+                      (IS_FREE(x+1, y + 1) || Feld[x+1][y + 1] == EL_ACID));
 
       if (left || right)
       {
@@ -3026,11 +3377,11 @@ void StartMoving(int x, int y)
        started_moving = TRUE;
       }
     }
-    else if (IS_BELT_ACTIVE(Feld[x][y+1]))
+    else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
     {
       boolean left_is_free  = (x>0 && IS_FREE(x-1, y));
       boolean right_is_free = (x<lev_fieldx-1 && IS_FREE(x+1, y));
-      int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y+1]);
+      int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
       int belt_dir = game.belt_dir[belt_nr];
 
       if ((belt_dir == MV_LEFT  && left_is_free) ||
@@ -3076,7 +3427,8 @@ void StartMoving(int x, int y)
 
       if (element != EL_YAMYAM &&
          element != EL_DARK_YAMYAM &&
-         element != EL_PACMAN)
+         element != EL_PACMAN &&
+         !(element_info[element].move_pattern & MV_ANY_DIRECTION))
       {
        TurnRound(x, y);
 
@@ -3093,11 +3445,36 @@ void StartMoving(int x, int y)
     {
       MovDelay[x][y]--;
 
+#if 0
+      if (element == EL_YAMYAM)
+      {
+       printf("::: %d\n",
+              el_act_dir2img(EL_YAMYAM, ACTION_WAITING, MV_LEFT));
+       DrawLevelElementAnimation(x, y, element);
+      }
+#endif
+
+      if (MovDelay[x][y])      /* element still has to wait some time */
+      {
+#if 0
+       /* !!! PLACE THIS SOMEWHERE AFTER "TurnRound()" !!! */
+       ResetGfxAnimation(x, y);
+#endif
+       GfxAction[x][y] = ACTION_WAITING;
+      }
+
       if (element == EL_ROBOT ||
+#if 0
+         element == EL_PACMAN ||
+#endif
          element == EL_YAMYAM ||
          element == EL_DARK_YAMYAM)
       {
+#if 0
+       DrawLevelElementAnimation(x, y, element);
+#else
        DrawLevelElementAnimationIfNeeded(x, y, element);
+#endif
        PlaySoundLevelAction(x, y, ACTION_WAITING);
       }
       else if (element == EL_SP_ELECTRON)
@@ -3120,15 +3497,14 @@ void StartMoving(int x, int y)
          int sx = SCREENX(xx), sy = SCREENY(yy);
          int flame_graphic = graphic + (i - 1);
 
-         if (!IN_LEV_FIELD(xx, yy) ||
-             IS_SOLID(Feld[xx][yy]) || Feld[xx][yy] == EL_EXPLOSION)
+         if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
            break;
 
          if (MovDelay[x][y])
          {
            int flamed = MovingOrBlocked2Element(xx, yy);
 
-           if (IS_ENEMY(flamed) || IS_EXPLOSIVE(flamed))
+           if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_FIRE(flamed))
              Bang(xx, yy);
            else
              RemoveMovingField(xx, yy);
@@ -3152,29 +3528,33 @@ void StartMoving(int x, int y)
 
        return;
       }
+
+      GfxAction[x][y] = ACTION_MOVING;
     }
 
     /* now make next step */
 
     Moving2Blocked(x, y, &newx, &newy);        /* get next screen position */
 
-    if (IS_ENEMY(element) && IS_PLAYER(newx, newy) &&
+    if (DONT_COLLIDE_WITH(element) && IS_PLAYER(newx, newy) &&
        !PLAYER_PROTECTED(newx, newy))
     {
-
 #if 1
       TestIfBadThingRunsIntoHero(x, y, MovDir[x][y]);
       return;
 #else
-      /* enemy got the player */
+      /* player killed by element which is deadly when colliding with */
       MovDir[x][y] = 0;
       KillHero(PLAYERINFO(newx, newy));
       return;
 #endif
 
     }
-    else if ((element == EL_PENGUIN || element == EL_ROBOT ||
-             element == EL_SATELLITE || element == EL_BALLOON) &&
+    else if ((element == EL_PENGUIN ||
+             element == EL_ROBOT ||
+             element == EL_SATELLITE ||
+             element == EL_BALLOON ||
+             IS_CUSTOM_ELEMENT(element)) &&
             IN_LEV_FIELD(newx, newy) &&
             MovDir[x][y] == MV_DOWN && Feld[newx][newy] == EL_ACID)
     {
@@ -3208,6 +3588,8 @@ void StartMoving(int x, int y)
       }
       else if (!IS_FREE(newx, newy))
       {
+       GfxAction[x][y] = ACTION_WAITING;
+
        if (IS_PLAYER(x, y))
          DrawPlayerField(x, y);
        else
@@ -3217,7 +3599,7 @@ void StartMoving(int x, int y)
     }
     else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
     {
-      if (IS_GEM(Feld[newx][newy]))
+      if (IS_FOOD_PIG(Feld[newx][newy]))
       {
        if (IS_MOVING(newx, newy))
          RemoveMovingField(newx, newy);
@@ -3259,7 +3641,9 @@ void StartMoving(int x, int y)
        int element2 = (IN_LEV_FIELD(newx2, newy2) ?
                        MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
 
-       if ((wanna_flame || IS_ENEMY(element1) || IS_ENEMY(element2)) &&
+       if ((wanna_flame ||
+            IS_CLASSIC_ENEMY(element1) ||
+            IS_CLASSIC_ENEMY(element2)) &&
            element1 != EL_DRAGON && element2 != EL_DRAGON &&
            element1 != EL_FLAMES && element2 != EL_FLAMES)
        {
@@ -3352,23 +3736,30 @@ void StartMoving(int x, int y)
 
       TurnRound(x, y);
 
-      if (element == EL_BUG || element == EL_SPACESHIP ||
+#if 1
+      DrawLevelElementAnimation(x, y, element);
+#else
+      if (element == EL_BUG ||
+         element == EL_SPACESHIP ||
          element == EL_SP_SNIKSNAK)
        DrawLevelField(x, y);
-      else if (element == EL_BUG || element == EL_SPACESHIP ||
-              element == EL_SP_SNIKSNAK || element == EL_MOLE)
+      else if (element == EL_MOLE)
        DrawLevelField(x, y);
-      else if (element == EL_BD_BUTTERFLY || element == EL_BD_FIREFLY)
+      else if (element == EL_BD_BUTTERFLY ||
+              element == EL_BD_FIREFLY)
        DrawLevelElementAnimationIfNeeded(x, y, element);
       else if (element == EL_SATELLITE)
        DrawLevelElementAnimationIfNeeded(x, y, element);
       else if (element == EL_SP_ELECTRON)
        DrawLevelElementAnimationIfNeeded(x, y, element);
+#endif
 
       if (DONT_TOUCH(element))
        TestIfBadThingTouchesHero(x, y);
 
+#if 0
       PlaySoundLevelAction(x, y, ACTION_WAITING);
+#endif
 
       return;
     }
@@ -3390,7 +3781,7 @@ void ContinueMoving(int x, int y)
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
   int horiz_move = (dx != 0);
   int newx = x + dx, newy = y + dy;
-  int step = (horiz_move ? dx : dy) * TILEX / 8;
+  int step = (horiz_move ? dx : dy) * TILEX / MOVE_DELAY_NORMAL_SPEED;
 
   if (element == EL_AMOEBA_DROP || element == EL_AMOEBA_DROPPING)
     step /= 2;
@@ -3407,6 +3798,8 @@ void ContinueMoving(int x, int y)
     step /= 2;
   else if (element == EL_SPRING && horiz_move)
     step *= 2;
+  else if (IS_CUSTOM_ELEMENT(element))
+    step = SIGN(step) * element_info[element].move_stepsize;
 
 #if OLD_GAME_BEHAVIOUR
   else if (CAN_FALL(element) && horiz_move && !IS_SP_ELEMENT(element))
@@ -3419,6 +3812,7 @@ void ContinueMoving(int x, int y)
   {
     Feld[x][y] = EL_EMPTY;
     Feld[newx][newy] = element;
+    MovPos[x][y] = 0;  /* force "not moving" for "crumbled sand" */
 
     if (element == EL_MOLE)
     {
@@ -3498,6 +3892,9 @@ void ContinueMoving(int x, int y)
     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
     MovDelay[newx][newy] = 0;
 
+    /* copy element change control values to new field */
+    ChangeDelay[newx][newy] = ChangeDelay[x][y];
+
     /* copy animation control values to new field */
     GfxFrame[newx][newy]  = GfxFrame[x][y];
     GfxAction[newx][newy] = GfxAction[x][y];   /* keep action one frame */
@@ -3536,9 +3933,15 @@ void ContinueMoving(int x, int y)
     else if (element == EL_PENGUIN)
       TestIfFriendTouchesBadThing(newx, newy);
 
+#if 1
+    if (CAN_FALL(element) && direction == MV_DOWN &&
+       (newy == lev_fieldy - 1 || !IS_FREE(x, newy + 1)))
+      Impact(x, newy);
+#else
     if (CAN_SMASH(element) && direction == MV_DOWN &&
-       (newy == lev_fieldy-1 || !IS_FREE(x, newy+1)))
+       (newy == lev_fieldy - 1 || !IS_FREE(x, newy + 1)))
       Impact(x, newy);
+#endif
   }
   else                         /* still moving on */
   {
@@ -4036,7 +4439,7 @@ void Life(int ax, int ay)
 
 static void InitRobotWheel(int x, int y)
 {
-  MovDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+  ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
 }
 
 static void RunRobotWheel(int x, int y)
@@ -4052,7 +4455,7 @@ static void StopRobotWheel(int x, int y)
 
 static void InitTimegateWheel(int x, int y)
 {
-  MovDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+  ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
 }
 
 static void RunTimegateWheel(int x, int y)
@@ -4186,22 +4589,22 @@ void MauerWaechst(int x, int y)
     {
       if (MovDir[x][y] == MV_LEFT)
       {
-       if (IN_LEV_FIELD(x - 1, y) && IS_MAUER(Feld[x - 1][y]))
+       if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
          DrawLevelField(x - 1, y);
       }
       else if (MovDir[x][y] == MV_RIGHT)
       {
-       if (IN_LEV_FIELD(x + 1, y) && IS_MAUER(Feld[x + 1][y]))
+       if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
          DrawLevelField(x + 1, y);
       }
       else if (MovDir[x][y] == MV_UP)
       {
-       if (IN_LEV_FIELD(x, y - 1) && IS_MAUER(Feld[x][y - 1]))
+       if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
          DrawLevelField(x, y - 1);
       }
       else
       {
-       if (IN_LEV_FIELD(x, y + 1) && IS_MAUER(Feld[x][y + 1]))
+       if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
          DrawLevelField(x, y + 1);
       }
 
@@ -4300,13 +4703,13 @@ void MauerAbleger(int ax, int ay)
   if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
     DrawLevelField(ax, ay);
 
-  if (!IN_LEV_FIELD(ax, ay-1) || IS_MAUER(Feld[ax][ay-1]))
+  if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
     oben_massiv = TRUE;
-  if (!IN_LEV_FIELD(ax, ay+1) || IS_MAUER(Feld[ax][ay+1]))
+  if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
     unten_massiv = TRUE;
-  if (!IN_LEV_FIELD(ax-1, ay) || IS_MAUER(Feld[ax-1][ay]))
+  if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
     links_massiv = TRUE;
-  if (!IN_LEV_FIELD(ax+1, ay) || IS_MAUER(Feld[ax+1][ay]))
+  if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
     rechts_massiv = TRUE;
 
   if (((oben_massiv && unten_massiv) ||
@@ -4378,7 +4781,7 @@ static void InitBuggyBase(int x, int y)
   int element = Feld[x][y];
   int activating_delay = FRAMES_PER_SECOND / 4;
 
-  MovDelay[x][y] =
+  ChangeDelay[x][y] =
     (element == EL_SP_BUGGY_BASE ?
      2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
      element == EL_SP_BUGGY_BASE_ACTIVATING ?
@@ -4413,7 +4816,7 @@ static void WarnBuggyBase(int x, int y)
 
 static void InitTrap(int x, int y)
 {
-  MovDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
+  ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
 }
 
 static void ActivateTrap(int x, int y)
@@ -4430,13 +4833,65 @@ static void ChangeActiveTrap(int x, int y)
     DrawLevelFieldCrumbledSand(x, y);
 }
 
+static void ChangeElementDoIt(int x, int y, int element)
+{
+  RemoveField(x, y);
+  Feld[x][y] = element;
+
+  ResetGfxAnimation(x, y);
+  ResetRandomAnimationValue(x, y);
+
+  InitField(x, y, FALSE);
+  if (CAN_MOVE(Feld[x][y]))
+    InitMovDir(x, y);
+
+  DrawLevelField(x, y);
+
+  if (CAN_BE_CRUMBLED(Feld[x][y]))
+  {
+    int sx = SCREENX(x), sy = SCREENY(y);
+    static int xy[4][2] =
+    {
+      { 0, -1 },
+      { -1, 0 },
+      { +1, 0 },
+      { 0, +1 }
+    };
+    int i;
+
+    for(i=0; i<4; i++)
+    {
+      int xx = x + xy[i][0];
+      int yy = y + xy[i][1];
+      int sxx = sx + xy[i][0];
+      int syy = sy + xy[i][1];
+
+      if (!IN_LEV_FIELD(xx, yy) ||
+         !IN_SCR_FIELD(sxx, syy) ||
+         !CAN_BE_CRUMBLED(Feld[xx][yy]) ||
+         IS_MOVING(xx, yy))
+       continue;
+
+      DrawLevelField(xx, yy);
+    }
+  }
+}
+
 static void ChangeElement(int x, int y)
 {
   int element = Feld[x][y];
 
-  if (MovDelay[x][y] == 0)             /* initialize element change */
+  if (ChangeDelay[x][y] == 0)          /* initialize element change */
   {
-    MovDelay[x][y] = changing_element[element].change_delay + 1;
+    ChangeDelay[x][y] = changing_element[element].change_delay + 1;
+
+    if (IS_CUSTOM_ELEMENT(element) && HAS_CHANGE_EVENT(element, CE_DELAY))
+    {
+      int max_random_delay = element_info[element].change.delay_random;
+      int delay_frames = element_info[element].change.delay_frames;
+
+      ChangeDelay[x][y] += RND(max_random_delay * delay_frames);
+    }
 
     ResetGfxAnimation(x, y);
     ResetRandomAnimationValue(x, y);
@@ -4445,25 +4900,72 @@ static void ChangeElement(int x, int y)
       changing_element[element].pre_change_function(x, y);
   }
 
-  MovDelay[x][y]--;
+  ChangeDelay[x][y]--;
 
-  if (MovDelay[x][y] != 0)             /* continue element change */
+  if (ChangeDelay[x][y] != 0)          /* continue element change */
   {
-    if (IS_ANIMATED(el2img(element)))
-      DrawLevelElementAnimationIfNeeded(x, y, element);
+    int graphic = el_act_dir2img(element, GfxAction[x][y], MovDir[x][y]);
+
+    if (IS_ANIMATED(graphic))
+      DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
     if (changing_element[element].change_function)
       changing_element[element].change_function(x, y);
   }
   else                                 /* finish element change */
   {
+    if (IS_MOVING(x, y))               /* never change a running system ;-) */
+    {
+      ChangeDelay[x][y] = 1;           /* try change after next move step */
+
+      return;
+    }
+
+#if 1
+    ChangeElementDoIt(x, y, changing_element[element].next_element);
+#else
+    RemoveField(x, y);
     Feld[x][y] = changing_element[element].next_element;
 
     ResetGfxAnimation(x, y);
     ResetRandomAnimationValue(x, y);
 
+    InitField(x, y, FALSE);
+    if (CAN_MOVE(Feld[x][y]))
+      InitMovDir(x, y);
+
     DrawLevelField(x, y);
 
+    if (CAN_BE_CRUMBLED(Feld[x][y]))
+    {
+      int sx = SCREENX(x), sy = SCREENY(y);
+      static int xy[4][2] =
+      {
+       { 0, -1 },
+       { -1, 0 },
+       { +1, 0 },
+       { 0, +1 }
+      };
+      int i;
+
+      for(i=0; i<4; i++)
+      {
+       int xx = x + xy[i][0];
+       int yy = y + xy[i][1];
+       int sxx = sx + xy[i][0];
+       int syy = sy + xy[i][1];
+
+       if (!IN_LEV_FIELD(xx, yy) ||
+           !IN_SCR_FIELD(sxx, syy) ||
+           !CAN_BE_CRUMBLED(Feld[xx][yy]) ||
+           IS_MOVING(xx, yy))
+         continue;
+
+       DrawLevelField(xx, yy);
+      }
+    }
+#endif
+
     if (changing_element[element].post_change_function)
       changing_element[element].post_change_function(x, y);
   }
@@ -4548,7 +5050,7 @@ void GameActions()
   byte *recorded_player_action;
   byte summarized_player_action = 0;
 
-  if (game_status != PLAYING)
+  if (game_status != GAME_MODE_PLAYING)
     return;
 
   action_delay_value =
@@ -4574,7 +5076,7 @@ void GameActions()
     HandleNetworking();
 #endif
 
-    if (game_status != PLAYING)
+    if (game_status != GAME_MODE_PLAYING)
       return;
 
     if (!network_player_action_received)
@@ -4663,7 +5165,20 @@ void GameActions()
   for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
   {
     element = Feld[x][y];
+#if 1
+    graphic = el_act_dir2img(element, GfxAction[x][y], MovDir[x][y]);
+#else
     graphic = el2img(element);
+#endif
+
+#if 0
+    if (element == -1)
+    {
+      printf("::: %d,%d: %d [%d]\n", x, y, element, FrameCounter);
+
+      element = graphic = 0;
+    }
+#endif
 
     if (graphic_info[graphic].anim_global_sync)
       GfxFrame[x][y] = FrameCounter;
@@ -4686,15 +5201,46 @@ void GameActions()
       continue;
     }
 
+#if 1
+    /* this may take place after moving, so 'element' may have changed */
+    if (IS_AUTO_CHANGING(element))
+    {
+      ChangeElement(x, y);
+      element = Feld[x][y];
+      graphic = el_act_dir2img(element, GfxAction[x][y], MovDir[x][y]);
+    }
+#endif
+
     if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
     {
       StartMoving(x, y);
 
+#if 1
+      graphic = el_act_dir2img(element, GfxAction[x][y], MovDir[x][y]);
+#if 0
+      if (element == EL_PACMAN)
+       printf("::: %d, %d, %d\n",
+              IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y]);
+#endif
+#if 0
+      if (element == EL_YAMYAM)
+       printf("::: %d, %d, %d\n",
+              IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y]);
+#endif
+#endif
+
       if (IS_ANIMATED(graphic) &&
          !IS_MOVING(x, y) &&
          !Stop[x][y])
+      {
        DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
+#if 0
+       if (element == EL_YAMYAM)
+         printf("::: %d, %d\n", graphic, GfxFrame[x][y]);
+#endif
+      }
+
       if (IS_GEM(element) || element == EL_SP_INFOTRON)
        EdelsteinFunkeln(x, y);
     }
@@ -4741,13 +5287,21 @@ void GameActions()
       MauerAbleger(x, y);
     else if (element == EL_FLAMES)
       CheckForDragon(x, y);
+#if 0
     else if (IS_AUTO_CHANGING(element))
       ChangeElement(x, y);
+#endif
     else if (element == EL_EXPLOSION)
       ;        /* drawing of correct explosion animation is handled separately */
-    else if (IS_ANIMATED(graphic))
+    else if (IS_ANIMATED(graphic) && !IS_AUTO_CHANGING(element))
       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
 
+#if 0
+    /* this may take place after moving, so 'element' may have changed */
+    if (IS_AUTO_CHANGING(Feld[x][y]))
+      ChangeElement(x, y);
+#endif
+
     if (IS_BELT_ACTIVE(element))
       PlaySoundLevelAction(x, y, ACTION_ACTIVE);
 
@@ -5119,7 +5673,7 @@ boolean MoveFigureOneStep(struct PlayerInfo *player,
   element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
 #endif
 
-  if (DONT_GO_TO(element))
+  if (DONT_RUN_INTO(element))
   {
     if (element == EL_ACID && dx == 0 && dy == 1)
     {
@@ -5457,11 +6011,11 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
     test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
 #endif
 
-    /* 1st case: good thing is moving towards DONT_GO_TO style bad thing;
+    /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
        2nd case: DONT_TOUCH style bad thing does not move away from good thing
     */
-    if ((DONT_GO_TO(test_element) && good_move_dir == test_dir[i]) ||
-       (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
+    if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
+       (DONT_TOUCH(test_element)    && test_move_dir != test_dir[i]))
     {
       kill_x = test_x;
       kill_y = test_y;
@@ -5521,11 +6075,11 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
     test_element = Feld[test_x][test_y];
 
-    /* 1st case: good thing is moving towards DONT_GO_TO style bad thing;
+    /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
        2nd case: DONT_TOUCH style bad thing does not move away from good thing
     */
-    if ((DONT_GO_TO(bad_element) &&  bad_move_dir == test_dir[i]) ||
-       (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
+    if ((DONT_RUN_INTO(bad_element) &&  bad_move_dir == test_dir[i]) ||
+       (DONT_TOUCH(bad_element)    && test_move_dir != test_dir[i]))
     {
       /* good thing is player or penguin that does not move away */
       if (IS_PLAYER(test_x, test_y))
@@ -5653,13 +6207,8 @@ void KillHero(struct PlayerInfo *player)
   if (!player->active)
     return;
 
-#if 1
   /* remove accessible field at the player's position */
   Feld[jx][jy] = EL_EMPTY;
-#else
-  if (IS_PFORTE(Feld[jx][jy]))
-    Feld[jx][jy] = EL_EMPTY;
-#endif
 
   /* deactivate shield (else Bang()/Explode() would not work right) */
   player->shield_normal_time_left = 0;
@@ -5682,7 +6231,11 @@ void BuryHero(struct PlayerInfo *player)
   if (!player->active)
     return;
 
+#if 1
+  PlaySoundLevelElementAction(jx, jy, player->element_nr, ACTION_DYING);
+#else
   PlaySoundLevel(jx, jy, SND_CLASS_PLAYER_DYING);
+#endif
   PlaySoundLevel(jx, jy, SND_GAME_LOSING);
 
   player->GameOver = TRUE;
@@ -5711,17 +6264,31 @@ void RemoveHero(struct PlayerInfo *player)
   ExitY = ZY = jy;
 }
 
-#if 0
 /*
   checkDiagonalPushing()
   -----------------------------------------------------------------------------
   check if diagonal input device direction results in pushing of object
+  (by checking if the alternative direction is walkable, diggable, ...)
 */
 
-static boolean checkDiagonalPushing(int x, int y, int real_dx, int real_dy)
+static boolean checkDiagonalPushing(struct PlayerInfo *player,
+                                   int x, int y, int real_dx, int real_dy)
 {
+  int jx, jy, dx, dy, xx, yy;
+
+  if (real_dx == 0 || real_dy == 0)    /* no diagonal direction => push */
+    return TRUE;
+
+  /* diagonal direction: check alternative direction */
+  jx = player->jx;
+  jy = player->jy;
+  dx = x - jx;
+  dy = y - jy;
+  xx = jx + (dx == 0 ? real_dx : 0);
+  yy = jy + (dy == 0 ? real_dy : 0);
+
+  return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
 }
-#endif
 
 /*
   DigField()
@@ -5747,22 +6314,29 @@ int DigField(struct PlayerInfo *player,
     player->is_collecting = FALSE;
   }
 
-  if (player->MovPos == 0)
+  if (player->MovPos == 0)     /* last pushing move finished */
     player->Pushing = FALSE;
 
-  if (mode == DF_NO_PUSH)
+  if (mode == DF_NO_PUSH)      /* player just stopped pushing */
   {
     player->Switching = FALSE;
     player->push_delay = 0;
+
     return MF_NO_ACTION;
   }
 
   if (IS_MOVING(x, y) || IS_PLAYER(x, y))
     return MF_NO_ACTION;
 
-  if (IS_TUBE(Feld[jx][jy]))
+#if 0
+  if (IS_TUBE(Feld[jx][jy]) || IS_TUBE(Back[jx][jy]))
+#else
+  if (IS_TUBE(Feld[jx][jy]) ||
+      (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0)))
+#endif
   {
     int i = 0;
+    int tube_element = (IS_TUBE(Feld[jx][jy]) ? Feld[jx][jy] : Back[jx][jy]);
     int tube_leave_directions[][2] =
     {
       { EL_TUBE_ANY,                   MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
@@ -5779,7 +6353,7 @@ int DigField(struct PlayerInfo *player,
       { -1,                            MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN }
     };
 
-    while (tube_leave_directions[i][0] != Feld[jx][jy])
+    while (tube_leave_directions[i][0] != tube_element)
     {
       i++;
       if (tube_leave_directions[i][0] == -1)   /* should not happen */
@@ -5792,6 +6366,12 @@ int DigField(struct PlayerInfo *player,
 
   element = Feld[x][y];
 
+#if 1
+  if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
+      game.engine_version >= VERSION_IDENT(2,2,0))
+    return MF_NO_ACTION;
+#endif
+
   switch (element)
   {
     case EL_EMPTY:
@@ -6067,14 +6647,17 @@ int DigField(struct PlayerInfo *player,
 
       player->Pushing = TRUE;
 
+#if 0
+      if (element == EL_ROCK)
+       printf("::: wanna push [%d] [%d]\n",
+              FrameCounter, player->push_delay_value);
+#endif
+
       if (!IN_LEV_FIELD(x+dx, y+dy) || !IS_FREE(x+dx, y+dy))
        return MF_NO_ACTION;
 
-      if (real_dy)
-      {
-       if (IN_LEV_FIELD(jx, jy+real_dy) && !IS_SOLID(Feld[jx][jy+real_dy]))
-         return MF_NO_ACTION;
-      }
+      if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
+       return MF_NO_ACTION;
 
       if (player->push_delay == 0)
        player->push_delay = FrameCounter;
@@ -6316,16 +6899,8 @@ int DigField(struct PlayerInfo *player,
                  || !IS_SB_ELEMENT(element))))
        return MF_NO_ACTION;
 
-      if (dx && real_dy)
-      {
-       if (IN_LEV_FIELD(jx, jy+real_dy) && !IS_SOLID(Feld[jx][jy+real_dy]))
-         return MF_NO_ACTION;
-      }
-      else if (dy && real_dx)
-      {
-       if (IN_LEV_FIELD(jx+real_dx, jy) && !IS_SOLID(Feld[jx+real_dx][jy]))
-         return MF_NO_ACTION;
-      }
+      if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
+       return MF_NO_ACTION;
 
       if (player->push_delay == 0)
        player->push_delay = FrameCounter;
@@ -6412,7 +6987,41 @@ int DigField(struct PlayerInfo *player,
       break;
 
     default:
-      if (IS_PUSHABLE(element))
+
+      if (IS_WALKABLE(element))
+      {
+       break;
+      }
+      else if (IS_DIGGABLE(element))
+      {
+       RemoveField(x, y);
+#if 1
+       if (mode != DF_SNAP)
+       {
+         GfxElement[x][y] =
+           (CAN_BE_CRUMBLED(element) ? EL_SAND : GFX_ELEMENT(element));
+         player->is_digging = TRUE;
+       }
+#endif
+       PlaySoundLevelElementAction(x, y, element, ACTION_DIGGING);
+
+       break;
+      }
+      else if (IS_COLLECTIBLE(element))
+      {
+       RemoveField(x, y);
+#if 1
+       if (mode != DF_SNAP)
+       {
+         GfxElement[x][y] = element;
+         player->is_collecting = TRUE;
+       }
+#endif
+       PlaySoundLevelElementAction(x, y, element, ACTION_COLLECTING);
+
+       break;
+      }
+      else if (IS_PUSHABLE(element))
       {
        if (mode == DF_SNAP)
          return MF_NO_ACTION;
@@ -6420,23 +7029,19 @@ int DigField(struct PlayerInfo *player,
        if (CAN_FALL(element) && dy)
          return MF_NO_ACTION;
 
+       if (!player->Pushing &&
+           game.engine_version >= RELEASE_IDENT(2,2,0,7))
+         player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+
        player->Pushing = TRUE;
 
-       if (!IN_LEV_FIELD(x+dx, y+dy) || !IS_FREE(x+dx, y+dy))
+       if (!IN_LEV_FIELD(x + dx, y + dy) || !IS_FREE(x + dx, y + dy))
          return MF_NO_ACTION;
 
-       if (dx && real_dy)
-       {
-         if (IN_LEV_FIELD(jx, jy+real_dy) && !IS_SOLID(Feld[jx][jy+real_dy]))
-           return MF_NO_ACTION;
-       }
-       else if (dy && real_dx)
-       {
-         if (IN_LEV_FIELD(jx+real_dx, jy) && !IS_SOLID(Feld[jx+real_dx][jy]))
-           return MF_NO_ACTION;
-       }
+       if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
+         return MF_NO_ACTION;
 
-       if (player->push_delay == 0)
+       if (player->push_delay == 0)    /* new pushing; restart delay */
          player->push_delay = FrameCounter;
 
        if (!FrameReached(&player->push_delay, player->push_delay_value) &&
@@ -6446,7 +7051,12 @@ int DigField(struct PlayerInfo *player,
        RemoveField(x, y);
        Feld[x + dx][y + dy] = element;
 
+#if 1
+       if (game.engine_version < RELEASE_IDENT(2,2,0,7))
+         player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+#else
        player->push_delay_value = 2 + RND(8);
+#endif
 
        DrawLevelField(x + dx, y + dy);
        PlaySoundLevelElementAction(x, y, element, ACTION_PUSHING);
@@ -6470,6 +7080,9 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
   int jx = player->jx, jy = player->jy;
   int x = jx + dx, y = jy + dy;
 
+  if (player->MovPos && game.engine_version >= VERSION_IDENT(2,2,0))
+    return FALSE;
+
   if (!player->active || !IN_LEV_FIELD(x, y))
     return FALSE;
 
@@ -6500,7 +7113,7 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
                    dy < 0 ? MV_UP :
                    dy > 0 ? MV_DOWN :  MV_NO_MOVING);
 
-  if (!DigField(player, x, y, 0, 0, DF_SNAP))
+  if (DigField(player, x, y, 0, 0, DF_SNAP) == MF_NO_ACTION)
     return FALSE;
 
   player->snapped = TRUE;
@@ -6527,8 +7140,19 @@ boolean PlaceBomb(struct PlayerInfo *player)
       IS_ACTIVE_BOMB(element) || element == EL_EXPLOSION)
     return FALSE;
 
+#if 0
   if (element != EL_EMPTY)
+    return FALSE;
+#endif
+
+  if (element != EL_EMPTY)
+  {
+#if 0
     Store[jx][jy] = element;
+#else
+    Back[jx][jy] = element;
+#endif
+  }
 
   MovDelay[jx][jy] = 96;
 
@@ -6691,17 +7315,26 @@ void RaiseScoreElement(int element)
     case EL_EMERALD_YELLOW:
     case EL_EMERALD_RED:
     case EL_EMERALD_PURPLE:
+    case EL_SP_INFOTRON:
       RaiseScore(level.score[SC_EMERALD]);
       break;
     case EL_DIAMOND:
       RaiseScore(level.score[SC_DIAMOND]);
       break;
+    case EL_CRYSTAL:
+      RaiseScore(level.score[SC_CRYSTAL]);
+      break;
+    case EL_PEARL:
+      RaiseScore(level.score[SC_PEARL]);
+      break;
     case EL_BUG:
     case EL_BD_BUTTERFLY:
+    case EL_SP_ELECTRON:
       RaiseScore(level.score[SC_BUG]);
       break;
     case EL_SPACESHIP:
     case EL_BD_FIREFLY:
+    case EL_SP_SNIKSNAK:
       RaiseScore(level.score[SC_SPACESHIP]);
       break;
     case EL_YAMYAM:
@@ -6718,8 +7351,18 @@ void RaiseScoreElement(int element)
       RaiseScore(level.score[SC_NUT]);
       break;
     case EL_DYNAMITE:
+    case EL_DYNABOMB_INCREASE_NUMBER:
+    case EL_DYNABOMB_INCREASE_SIZE:
+    case EL_DYNABOMB_INCREASE_POWER:
       RaiseScore(level.score[SC_DYNAMITE]);
       break;
+    case EL_SHIELD_NORMAL:
+    case EL_SHIELD_DEADLY:
+      RaiseScore(level.score[SC_SHIELD]);
+      break;
+    case EL_EXTRA_TIME:
+      RaiseScore(level.score[SC_TIME_BONUS]);
+      break;
     case EL_KEY_1:
     case EL_KEY_2:
     case EL_KEY_3:
@@ -6745,7 +7388,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
     else
 #endif
     {
-      game_status = MAINMENU;
+      game_status = GAME_MODE_MAIN;
       DrawMainMenu();
     }
   }
@@ -6906,7 +7549,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
 {
   int id = gi->custom_id;
 
-  if (game_status != PLAYING)
+  if (game_status != GAME_MODE_PLAYING)
     return;
 
   switch (id)