rnd-20051214-1-src
[rocksndiamonds.git] / src / game.c
index 239a6a7008911d93ad1e48920c546da37a8fd315..7f6186c4f58130a519c16626ca5306ae9dc338b8 100644 (file)
 #define USE_NEW_AMOEBA_CODE    FALSE
 
 /* EXPERIMENTAL STUFF */
-#define USE_NEW_STUFF                  (TRUE                           * 1)
-
-#define USE_NEW_SP_SLIPPERY            (TRUE   * USE_NEW_STUFF         * 1)
-#define USE_NEW_COLLECT_COUNT          (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_STUFF                  (                         1)
 
+#define USE_NEW_SP_SLIPPERY            (USE_NEW_STUFF          * 1)
+#define USE_NEW_COLLECT_COUNT          (USE_NEW_STUFF          * 1)
+#define USE_NEW_PLAYER_ANIM            (USE_NEW_STUFF          * 1)
+#define USE_NEW_ALL_SLIPPERY           (USE_NEW_STUFF          * 1)
+#define USE_NEW_PLAYER_SPEED           (USE_NEW_STUFF          * 1)
+#define USE_NEW_DELAYED_ACTION         (USE_NEW_STUFF          * 1)
 
 /* for DigField() */
 #define DF_NO_PUSH             0
 #define INITIAL_MOVE_DELAY_ON  0
 
 /* values for player movement speed (which is in fact a delay value) */
+#define MOVE_DELAY_MIN_SPEED   32
 #define MOVE_DELAY_NORMAL_SPEED        8
 #define MOVE_DELAY_HIGH_SPEED  4
+#define MOVE_DELAY_MAX_SPEED   1
 
+#if 0
 #define DOUBLE_MOVE_DELAY(x)   (x = (x <= MOVE_DELAY_HIGH_SPEED ? x * 2 : x))
 #define HALVE_MOVE_DELAY(x)    (x = (x >= MOVE_DELAY_HIGH_SPEED ? x / 2 : x))
+#else
+#define DOUBLE_MOVE_DELAY(x)   (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
+#define HALVE_MOVE_DELAY(x)    (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
+#endif
 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY((p)->move_delay_value))
 #define HALVE_PLAYER_SPEED(p)  (DOUBLE_MOVE_DELAY((p)->move_delay_value))
 
@@ -235,8 +245,8 @@ static void InitBeltMovement(void);
 static void CloseAllOpenTimegates(void);
 static void CheckGravityMovement(struct PlayerInfo *);
 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
-static void KillHeroUnlessEnemyProtected(int, int);
-static void KillHeroUnlessExplosionProtected(int, int);
+static void KillPlayerUnlessEnemyProtected(int, int);
+static void KillPlayerUnlessExplosionProtected(int, int);
 
 static void TestIfPlayerTouchesCustomElement(int, int);
 static void TestIfElementTouchesCustomElement(int, int);
@@ -1253,6 +1263,9 @@ static void InitGameEngine()
     ei->change->change_function      = ch_delay->change_function;
     ei->change->post_change_function = ch_delay->post_change_function;
 
+    ei->change->can_change = TRUE;
+    ei->change->can_change_or_has_action = TRUE;
+
     ei->has_change_event[CE_DELAY] = TRUE;
 
     SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
@@ -1574,6 +1587,8 @@ void InitGame()
     player->move_delay       = game.initial_move_delay;
     player->move_delay_value = game.initial_move_delay_value;
 
+    player->move_delay_value_next = -1;
+
     player->move_delay_reset_counter = 0;
 
     player->push_delay       = -1;     /* initialized when pushing starts */
@@ -1701,6 +1716,26 @@ void InitGame()
                    emulate_sb ? EMU_SOKOBAN :
                    emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
 
+#if USE_NEW_ALL_SLIPPERY
+  /* initialize type of slippery elements */
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      /* default: elements slip down either to the left or right randomly */
+      element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
+
+      /* SP style elements prefer to slip down on the left side */
+      if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
+       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+
+      /* BD style elements prefer to slip down on the left side */
+      if (game.emulation == EMU_BOULDERDASH)
+       element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+    }
+  }
+#endif
+
   /* initialize explosion and ignition delay */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
   {
@@ -1898,7 +1933,7 @@ void InitGame()
 
       for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
       {
-       content = element_info[element].content[xx][yy];
+       content = element_info[element].content.e[xx][yy];
        is_player = ELEM_IS_PLAYER(content);
 
        if (is_player && (found_rating < 2 || element < found_element))
@@ -1915,7 +1950,9 @@ void InitGame()
 
        for (i = 0; i < element_info[element].num_change_pages; i++)
        {
-         content= element_info[element].change_page[i].target_content[xx][yy];
+         content =
+           element_info[element].change_page[i].target_content.e[xx][yy];
+
          is_player = ELEM_IS_PLAYER(content);
 
          if (is_player && (found_rating < 1 || element < found_element))
@@ -2301,7 +2338,7 @@ void GameWon()
     PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
   }
 
-  /* Hero disappears */
+  /* player disappears */
   if (ExitX >= 0 && ExitY >= 0)
     DrawLevelField(ExitX, ExitY);
 
@@ -2869,7 +2906,7 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
   if (player == local_player)  /* only visually relocate local player */
     DrawRelocatePlayer(player);
 
-  TestIfHeroTouchesBadThing(jx, jy);
+  TestIfPlayerTouchesBadThing(jx, jy);
   TestIfPlayerTouchesCustomElement(jx, jy);
 
   if (IS_CUSTOM_ELEMENT(element))
@@ -3031,10 +3068,10 @@ void Explode(int ex, int ey, int phase, int mode)
       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][xx][yy];
+       Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
       else if (IS_CUSTOM_ELEMENT(center_element) &&
-              element_info[center_element].content[xx][yy] != EL_EMPTY)
-       Store[x][y] = element_info[center_element].content[xx][yy];
+              element_info[center_element].content.e[xx][yy] != EL_EMPTY)
+       Store[x][y] = element_info[center_element].content.e[xx][yy];
       else if (element == EL_WALL_EMERALD)
        Store[x][y] = EL_EMERALD;
       else if (element == EL_WALL_DIAMOND)
@@ -3052,7 +3089,7 @@ void Explode(int ex, int ey, int phase, int mode)
       else if (element == EL_WALL_CRYSTAL)
        Store[x][y] = EL_CRYSTAL;
       else if (IS_CUSTOM_ELEMENT(element) && !CAN_EXPLODE(element))
-       Store[x][y] = element_info[element].content[1][1];
+       Store[x][y] = element_info[element].content.e[1][1];
       else
        Store[x][y] = EL_EMPTY;
 
@@ -3120,7 +3157,7 @@ void Explode(int ex, int ey, int phase, int mode)
     if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
        !PLAYER_EXPLOSION_PROTECTED(x, y))
     {
-      KillHeroUnlessExplosionProtected(x, y);
+      KillPlayerUnlessExplosionProtected(x, y);
       border_explosion = TRUE;
     }
     else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
@@ -3714,7 +3751,7 @@ void Impact(int x, int y)
   if (impact && element == EL_AMOEBA_DROP)
   {
     if (object_hit && IS_PLAYER(x, y + 1))
-      KillHeroUnlessEnemyProtected(x, y + 1);
+      KillPlayerUnlessEnemyProtected(x, y + 1);
     else if (object_hit && smashed == EL_PENGUIN)
       Bang(x, y + 1);
     else
@@ -3756,7 +3793,7 @@ void Impact(int x, int y)
     {
       if (CAN_SMASH_PLAYER(element))
       {
-       KillHeroUnlessEnemyProtected(x, y + 1);
+       KillPlayerUnlessEnemyProtected(x, y + 1);
        return;
       }
     }
@@ -4759,11 +4796,26 @@ void StartMoving(int x, int y)
                                 Feld[x + 1][y + 1] == EL_ACID));
       boolean can_fall_any  = (can_fall_left || can_fall_right);
       boolean can_fall_both = (can_fall_left && can_fall_right);
+      int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
 
-      if (can_fall_any && IS_CUSTOM_ELEMENT(Feld[x][y + 1]))
+#if USE_NEW_ALL_SLIPPERY
+      if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
       {
-       int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
+       if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
+         can_fall_right = FALSE;
+       else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
+         can_fall_left = FALSE;
+       else if (slippery_type == SLIPPERY_ONLY_LEFT)
+         can_fall_right = FALSE;
+       else if (slippery_type == SLIPPERY_ONLY_RIGHT)
+         can_fall_left = FALSE;
 
+       can_fall_any  = (can_fall_left || can_fall_right);
+       can_fall_both = FALSE;
+      }
+#else
+      if (can_fall_any && IS_CUSTOM_ELEMENT(Feld[x][y + 1]))
+      {
        if (slippery_type == SLIPPERY_ONLY_LEFT)
          can_fall_right = FALSE;
        else if (slippery_type == SLIPPERY_ONLY_RIGHT)
@@ -4776,7 +4828,10 @@ void StartMoving(int x, int y)
        can_fall_any  = (can_fall_left || can_fall_right);
        can_fall_both = (can_fall_left && can_fall_right);
       }
+#endif
 
+#if USE_NEW_ALL_SLIPPERY
+#else
 #if USE_NEW_SP_SLIPPERY
       /* !!! better use the same properties as for custom elements here !!! */
       else if (game.engine_version >= VERSION_IDENT(3,1,1,0) &&
@@ -4786,7 +4841,19 @@ void StartMoving(int x, int y)
        can_fall_both = FALSE;
       }
 #endif
+#endif
+
+#if USE_NEW_ALL_SLIPPERY
+      if (can_fall_both)
+      {
+       if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
+         can_fall_right = FALSE;       /* slip down on left side */
+       else
+         can_fall_left = !(can_fall_right = RND(2));
 
+       can_fall_both = FALSE;
+      }
+#else
       if (can_fall_both)
       {
        if (game.emulation == EMU_BOULDERDASH ||
@@ -4797,6 +4864,7 @@ void StartMoving(int x, int y)
 
        can_fall_both = FALSE;
       }
+#endif
 
       if (can_fall_any)
       {
@@ -4999,7 +5067,7 @@ void StartMoving(int x, int y)
        IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
        !PLAYER_ENEMY_PROTECTED(newx, newy))
     {
-      TestIfBadThingRunsIntoHero(x, y, MovDir[x][y]);
+      TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
 
       return;
     }
@@ -5300,7 +5368,7 @@ void StartMoving(int x, int y)
        DrawLevelElementAnimation(x, y, element);
 
       if (DONT_TOUCH(element))
-       TestIfBadThingTouchesHero(x, y);
+       TestIfBadThingTouchesPlayer(x, y);
 
       return;
     }
@@ -5314,11 +5382,6 @@ void StartMoving(int x, int y)
     ContinueMoving(x, y);
 }
 
-/* (emacs is confused here for some reason; this makes it happy again ;-) ) */
-void dummy()
-{
-}
-
 void ContinueMoving(int x, int y)
 {
   int element = Feld[x][y];
@@ -5527,7 +5590,7 @@ void ContinueMoving(int x, int y)
 
   if (DONT_TOUCH(element))     /* object may be nasty to player or others */
   {
-    TestIfBadThingTouchesHero(newx, newy);
+    TestIfBadThingTouchesPlayer(newx, newy);
     TestIfBadThingTouchesFriend(newx, newy);
 
     if (!IS_CUSTOM_ELEMENT(element))
@@ -5967,10 +6030,14 @@ void AmoebeAbleger(int ax, int ay)
 void Life(int ax, int ay)
 {
   int x1, y1, x2, y2;
+#if 0
   static int life[4] = { 2, 3, 3, 3 }; /* parameters for "game of life" */
+#endif
   int life_time = 40;
   int element = Feld[ax][ay];
   int graphic = el2img(element);
+  int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
+                        level.biomaze);
   boolean changed = FALSE;
 
   if (IS_ANIMATED(graphic))
@@ -6013,7 +6080,8 @@ void Life(int ax, int ay)
 
     if (xx == ax && yy == ay)          /* field in the middle */
     {
-      if (nachbarn < life[0] || nachbarn > life[1])
+      if (nachbarn < life_parameter[0] ||
+         nachbarn > life_parameter[1])
       {
        Feld[xx][yy] = EL_EMPTY;
        if (!Stop[xx][yy])
@@ -6024,7 +6092,8 @@ void Life(int ax, int ay)
     }
     else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
     {                                  /* free border field */
-      if (nachbarn >= life[2] && nachbarn <= life[3])
+      if (nachbarn >= life_parameter[2] &&
+         nachbarn <= life_parameter[3])
       {
        Feld[xx][yy] = element;
        MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
@@ -6446,11 +6515,12 @@ static int getSpecialActionElement(int element, int number, int base_element)
 static int getModifiedActionNumber(int value_old, int operator, int operand,
                                   int value_min, int value_max)
 {
-  int value_new = (operator == CA_MODE_ADD      ? value_old + operand :
+  int value_new = (operator == CA_MODE_SET      ? operand :
+                  operator == CA_MODE_ADD      ? value_old + operand :
                   operator == CA_MODE_SUBTRACT ? value_old - operand :
                   operator == CA_MODE_MULTIPLY ? value_old * operand :
                   operator == CA_MODE_DIVIDE   ? value_old / MAX(1, operand) :
-                  operator == CA_MODE_SET      ? operand :
+                  operator == CA_MODE_MODULO   ? value_old % MAX(1, operand) :
                   value_old);
 
   return (value_new < value_min ? value_min :
@@ -6532,19 +6602,17 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
                            action_mode, action_arg_number,
                            action_arg_number_min, action_arg_number_max);
 
-  /* (for explicit player choice, set invalid value to "no player") */
   int action_arg_player_bits =
-    (action_arg == CA_ARG_PLAYER_ANY ? action_arg - CA_ARG_PLAYER :
+    (action_arg == CA_ARG_PLAYER_ANY ? PLAYER_BITS_ANY :
      action_arg >= CA_ARG_PLAYER_1 &&
      action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
      action_arg >= CA_ARG_1 &&
-     action_arg <= CA_ARG_PLAYER_4 ? (1 << (action_arg - 1)) :
+     action_arg <= CA_ARG_PLAYER_4 ? (1 << (action_arg - CA_ARG_1)) :
      action_arg_element >= EL_PLAYER_1 &&
      action_arg_element <= EL_PLAYER_4 ?
      (1 << (action_arg_element - EL_PLAYER_1)) :
-     0);
+     PLAYER_BITS_ANY);
 
-  /* (for implicit player choice, set invalid value to "all players") */
   int trigger_player_bits =
     (change->actual_trigger_player >= EL_PLAYER_1 &&
      change->actual_trigger_player <= EL_PLAYER_4 ?
@@ -6573,7 +6641,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     {
       for (i = 0; i < MAX_PLAYERS; i++)
        if (action_arg_player_bits & (1 << i))
-         KillHero(&stored_player[i]);
+         KillPlayer(&stored_player[i]);
 
       break;
     }
@@ -6650,6 +6718,14 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
        {
          int move_stepsize = TILEX / stored_player[i].move_delay_value;
 
+         if (action_mode == CA_MODE_ADD || action_mode == CA_MODE_SUBTRACT)
+         {
+           /* translate "+" and "-" to "*" and "/" with powers of two */
+           action_arg_number = 1 << action_arg_number;
+           action_mode = (action_mode == CA_MODE_ADD ? CA_MODE_MULTIPLY :
+                          CA_MODE_DIVIDE);
+         }
+
          move_stepsize =
            getModifiedActionNumber(move_stepsize,
                                    action_mode,
@@ -6660,11 +6736,12 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
          /* make sure that value is power of 2 */
          move_stepsize = (1 << log_2(move_stepsize));
 
-         stored_player[i].move_delay_value = TILEX / move_stepsize;
+         /* do no immediately change -- the player might just be moving */
+         stored_player[i].move_delay_value_next = TILEX / move_stepsize;
 
 #if 0
-         printf("::: move_delay_value == %d\n",
-                stored_player[i].move_delay_value);
+         printf("::: move_delay_value == %d [%d]\n",
+                stored_player[i].move_delay_value_next, action_arg_number);
 #endif
        }
       }
@@ -6691,7 +6768,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
        if (!TimeLeft && setup.time_limit)
          for (i = 0; i < MAX_PLAYERS; i++)
-           KillHero(&stored_player[i]);
+           KillPlayer(&stored_player[i]);
       }
 
       break;
@@ -6834,7 +6911,7 @@ static void ChangeElementNowExt(struct ElementChangeInfo *change,
   Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
 #endif
 
-  TestIfBadThingTouchesHero(x, y);
+  TestIfBadThingTouchesPlayer(x, y);
   TestIfPlayerTouchesCustomElement(x, y);
   TestIfElementTouchesCustomElement(x, y);
 }
@@ -6895,7 +6972,7 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
       boolean is_destructible;
       int ex = x + xx - 1;
       int ey = y + yy - 1;
-      int content_element = change->target_content[xx][yy];
+      int content_element = change->target_content.e[xx][yy];
       int e;
 
       can_replace[xx][yy] = TRUE;
@@ -6967,7 +7044,7 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
 
          ChangeEvent[ex][ey] = ChangeEvent[x][y];
 
-         content_element = change->target_content[xx][yy];
+         content_element = change->target_content.e[xx][yy];
          target_element = GET_TARGET_ELEMENT(content_element, change);
 
          ChangeElementNowExt(change, ex, ey, target_element);
@@ -6981,7 +7058,10 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
       }
 
       if (something_has_changed)
+      {
        PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
+      }
     }
   }
   else
@@ -6991,6 +7071,7 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
     ChangeElementNowExt(change, x, y, target_element);
 
     PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+    PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
   }
 
   /* this uses direct change before indirect change */
@@ -6999,6 +7080,100 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
   return TRUE;
 }
 
+#if USE_NEW_DELAYED_ACTION
+
+static void ChangeElement(int x, int y, int page)
+{
+  int element = MovingOrBlocked2Element(x, y);
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+
+#ifdef DEBUG
+  if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
+      !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
+  {
+    printf("\n\n");
+    printf("ChangeElement(): %d,%d: element = %d ('%s')\n",
+          x, y, element, element_info[element].token_name);
+    printf("ChangeElement(): This should never happen!\n");
+    printf("\n\n");
+  }
+#endif
+
+  /* this can happen with classic bombs on walkable, changing elements */
+  if (!CAN_CHANGE_OR_HAS_ACTION(element))
+  {
+#if 0
+    if (!CAN_CHANGE(Back[x][y]))       /* prevent permanent repetition */
+      ChangeDelay[x][y] = 0;
+#endif
+
+    return;
+  }
+
+  if (ChangeDelay[x][y] == 0)          /* initialize element change */
+  {
+    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
+
+    if (change->can_change)
+    {
+      ResetGfxAnimation(x, y);
+      ResetRandomAnimationValue(x, y);
+
+      if (change->pre_change_function)
+       change->pre_change_function(x, y);
+    }
+  }
+
+  ChangeDelay[x][y]--;
+
+  if (ChangeDelay[x][y] != 0)          /* continue element change */
+  {
+    if (change->can_change)
+    {
+      int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+      if (IS_ANIMATED(graphic))
+       DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+      if (change->change_function)
+       change->change_function(x, y);
+    }
+  }
+  else                                 /* finish element change */
+  {
+    if (ChangePage[x][y] != -1)                /* remember page from delayed change */
+    {
+      page = ChangePage[x][y];
+      ChangePage[x][y] = -1;
+
+      change = &ei->change_page[page];
+    }
+
+    if (IS_MOVING(x, y))               /* never change a running system ;-) */
+    {
+      ChangeDelay[x][y] = 1;           /* try change after next move step */
+      ChangePage[x][y] = page;         /* remember page to use for change */
+
+      return;
+    }
+
+    if (change->can_change)
+    {
+      if (ChangeElementNow(x, y, element, page))
+      {
+       if (change->post_change_function)
+         change->post_change_function(x, y);
+      }
+    }
+
+    if (change->has_action)
+      ExecuteCustomElementAction(x, y, element, page);
+  }
+}
+
+#else
+
 static void ChangeElement(int x, int y, int page)
 {
   int element = MovingOrBlocked2Element(x, y);
@@ -7076,6 +7251,8 @@ static void ChangeElement(int x, int y, int page)
   }
 }
 
+#endif
+
 static boolean CheckTriggeredElementChangeExt(int trigger_element,
                                              int trigger_event,
                                              int trigger_player,
@@ -7127,9 +7304,19 @@ static boolean CheckTriggeredElementChangeExt(int trigger_element,
                ChangeEvent[x][y] = trigger_event;
                ChangeElement(x, y, p);
              }
-
+#if USE_NEW_DELAYED_ACTION
+             else if (change->has_action)
+             {
+               ExecuteCustomElementAction(x, y, element, p);
+               PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+             }
+#else
              if (change->has_action)
+             {
                ExecuteCustomElementAction(x, y, element, p);
+               PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+             }
+#endif
            }
          }
 
@@ -7196,9 +7383,19 @@ static boolean CheckElementChangeExt(int x, int y,
 
        change_done = TRUE;
       }
-
+#if USE_NEW_DELAYED_ACTION
+      else if (change->has_action)
+      {
+       ExecuteCustomElementAction(x, y, element, p);
+       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+      }
+#else
       if (change->has_action)
+      {
        ExecuteCustomElementAction(x, y, element, p);
+       PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+      }
+#endif
     }
   }
 
@@ -7439,12 +7636,25 @@ void AdvanceFrameAndPlayerCounters(int player_nr)
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     boolean advance_player_counters = (player_nr == -1 || player_nr == i);
-    int move_frames =
-      MOVE_DELAY_NORMAL_SPEED /  stored_player[i].move_delay_value;
+    int move_delay_value = stored_player[i].move_delay_value;
+    int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
 
     if (!advance_player_counters)      /* not all players may be affected */
       continue;
 
+#if USE_NEW_PLAYER_ANIM
+    if (move_frames == 0)      /* less than one move per game frame */
+    {
+      int stepsize = TILEX / move_delay_value;
+      int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
+      int count = (stored_player[i].is_moving ?
+                  ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
+
+      if (count % delay == 0)
+       move_frames = 1;
+    }
+#endif
+
     stored_player[i].Frame += move_frames;
 
     if (stored_player[i].MovPos != 0)
@@ -7698,11 +7908,25 @@ void GameActions()
     if (IS_CHANGING(x, y) &&
        (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
     {
+      int page = element_info[element].event_page_nr[CE_DELAY];
+#if 0
+      ChangeElement(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] : page);
+#else
+
 #if 0
-      ChangeElement(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] :
-                   element_info[element].event_page_nr[CE_DELAY]);
+      printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]);
+#endif
+
+#if 1
+      ChangeElement(x, y, page);
 #else
-      ChangeElement(x, y, element_info[element].event_page_nr[CE_DELAY]);
+      if (CAN_CHANGE(element))
+       ChangeElement(x, y, page);
+
+      if (HAS_ACTION(element))
+       ExecuteCustomElementAction(x, y, element, page);
+#endif
+
 #endif
 
       element = Feld[x][y];
@@ -7946,7 +8170,7 @@ void GameActions()
 
        if (!TimeLeft && setup.time_limit)
          for (i = 0; i < MAX_PLAYERS; i++)
-           KillHero(&stored_player[i]);
+           KillPlayer(&stored_player[i]);
       }
       else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
        DrawGameValue_Time(TimePlayed);
@@ -8182,10 +8406,10 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
       InitMovingField(jx, jy, MV_DOWN);
       Store[jx][jy] = EL_ACID;
       ContinueMoving(jx, jy);
-      BuryHero(player);
+      BuryPlayer(player);
     }
     else
-      TestIfHeroRunsIntoBadThing(jx, jy, player->MovDir);
+      TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
 
     return MF_MOVING;
   }
@@ -8205,6 +8429,12 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
   player->jy = new_jy;
   StorePlayer[new_jx][new_jy] = player->element_nr;
 
+  if (player->move_delay_value_next != -1)
+  {
+    player->move_delay_value = player->move_delay_value_next;
+    player->move_delay_value_next = -1;
+  }
+
   player->MovPos =
     (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
 
@@ -8400,12 +8630,12 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
   if (game.engine_version < VERSION_IDENT(3,0,7,0))
   {
-    TestIfHeroTouchesBadThing(jx, jy);
+    TestIfPlayerTouchesBadThing(jx, jy);
     TestIfPlayerTouchesCustomElement(jx, jy);
   }
 
   if (!player->active)
-    RemoveHero(player);
+    RemovePlayer(player);
 
   return moved;
 }
@@ -8416,8 +8646,16 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   int last_jx = player->last_jx, last_jy = player->last_jy;
   int move_stepsize = TILEX / player->move_delay_value;
 
-  if (!player->active || !player->MovPos)
+#if USE_NEW_PLAYER_SPEED
+  if (!player->active)
+    return;
+
+  if (player->MovPos == 0 && mode == SCROLL_GO_ON)     /* player not moving */
     return;
+#else
+  if (!player->active || player->MovPos == 0)
+    return;
+#endif
 
   if (mode == SCROLL_INIT)
   {
@@ -8447,20 +8685,47 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
       MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
     }
 
+#if USE_NEW_PLAYER_SPEED
+    if (player->MovPos != 0)   /* player has not yet reached destination */
+      return;
+#else
     return;
+#endif
   }
   else if (!FrameReached(&player->actual_frame_counter, 1))
     return;
 
+#if 0
+  printf("::: player->MovPos: %d -> %d\n",
+        player->MovPos,
+        player->MovPos + (player->MovPos > 0 ? -1 : 1) * move_stepsize);
+#endif
+
+#if USE_NEW_PLAYER_SPEED
+  if (player->MovPos != 0)
+  {
+    player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
+    player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
+
+    /* before DrawPlayer() to draw correct player graphic for this case */
+    if (player->MovPos == 0)
+      CheckGravityMovement(player);
+  }
+#else
   player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
   player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
 
   /* before DrawPlayer() to draw correct player graphic for this case */
   if (player->MovPos == 0)
     CheckGravityMovement(player);
+#endif
 
   if (player->MovPos == 0)     /* player reached destination field */
   {
+#if 0
+    printf("::: player reached destination field\n");
+#endif
+
     if (player->move_delay_reset_counter > 0)
     {
       player->move_delay_reset_counter--;
@@ -8483,7 +8748,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        Feld[jx][jy] == EL_SP_EXIT_OPENING)     /* <-- special case */
     {
       DrawPlayer(player);      /* needed here only to cleanup last field */
-      RemoveHero(player);
+      RemovePlayer(player);
 
       if (local_player->friends_still_needed == 0 ||
          IS_SP_ELEMENT(Feld[jx][jy]))
@@ -8518,7 +8783,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
 
     if (game.engine_version >= VERSION_IDENT(3,0,7,0))
     {
-      TestIfHeroTouchesBadThing(jx, jy);
+      TestIfPlayerTouchesBadThing(jx, jy);
       TestIfPlayerTouchesCustomElement(jx, jy);
 
       /* needed because pushed element has not yet reached its destination,
@@ -8527,7 +8792,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        TestIfElementTouchesCustomElement(jx, jy);      /* for empty space */
 
       if (!player->active)
-       RemoveHero(player);
+       RemovePlayer(player);
     }
 
     if (level.use_step_counter)
@@ -8547,7 +8812,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
 
        if (!TimeLeft && setup.time_limit)
          for (i = 0; i < MAX_PLAYERS; i++)
-           KillHero(&stored_player[i]);
+           KillPlayer(&stored_player[i]);
       }
       else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
        DrawGameValue_Time(TimePlayed);
@@ -8886,7 +9151,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
          !IS_INDESTRUCTIBLE(bad_element))
        Bang(kill_x, kill_y);
       else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
-       KillHero(player);
+       KillPlayer(player);
     }
     else
       Bang(good_x, good_y);
@@ -8979,29 +9244,29 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
          !IS_INDESTRUCTIBLE(bad_element))
        Bang(bad_x, bad_y);
       else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
-       KillHero(player);
+       KillPlayer(player);
     }
     else
       Bang(kill_x, kill_y);
   }
 }
 
-void TestIfHeroTouchesBadThing(int x, int y)
+void TestIfPlayerTouchesBadThing(int x, int y)
 {
   TestIfGoodThingHitsBadThing(x, y, MV_NO_MOVING);
 }
 
-void TestIfHeroRunsIntoBadThing(int x, int y, int move_dir)
+void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
 {
   TestIfGoodThingHitsBadThing(x, y, move_dir);
 }
 
-void TestIfBadThingTouchesHero(int x, int y)
+void TestIfBadThingTouchesPlayer(int x, int y)
 {
   TestIfBadThingHitsGoodThing(x, y, MV_NO_MOVING);
 }
 
-void TestIfBadThingRunsIntoHero(int x, int y, int move_dir)
+void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
 {
   TestIfBadThingHitsGoodThing(x, y, move_dir);
 }
@@ -9050,7 +9315,7 @@ void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
     Bang(bad_x, bad_y);
 }
 
-void KillHero(struct PlayerInfo *player)
+void KillPlayer(struct PlayerInfo *player)
 {
   int jx = player->jx, jy = player->jy;
 
@@ -9065,22 +9330,22 @@ void KillHero(struct PlayerInfo *player)
   player->shield_deadly_time_left = 0;
 
   Bang(jx, jy);
-  BuryHero(player);
+  BuryPlayer(player);
 }
 
-static void KillHeroUnlessEnemyProtected(int x, int y)
+static void KillPlayerUnlessEnemyProtected(int x, int y)
 {
   if (!PLAYER_ENEMY_PROTECTED(x, y))
-    KillHero(PLAYERINFO(x, y));
+    KillPlayer(PLAYERINFO(x, y));
 }
 
-static void KillHeroUnlessExplosionProtected(int x, int y)
+static void KillPlayerUnlessExplosionProtected(int x, int y)
 {
   if (!PLAYER_EXPLOSION_PROTECTED(x, y))
-    KillHero(PLAYERINFO(x, y));
+    KillPlayer(PLAYERINFO(x, y));
 }
 
-void BuryHero(struct PlayerInfo *player)
+void BuryPlayer(struct PlayerInfo *player)
 {
   int jx = player->jx, jy = player->jy;
 
@@ -9091,10 +9356,10 @@ void BuryHero(struct PlayerInfo *player)
   PlayLevelSound(jx, jy, SND_GAME_LOSING);
 
   player->GameOver = TRUE;
-  RemoveHero(player);
+  RemovePlayer(player);
 }
 
-void RemoveHero(struct PlayerInfo *player)
+void RemovePlayer(struct PlayerInfo *player)
 {
   int jx = player->jx, jy = player->jy;
   int i, found = FALSE;
@@ -9105,6 +9370,9 @@ void RemoveHero(struct PlayerInfo *player)
   if (!ExplodeField[jx][jy])
     StorePlayer[jx][jy] = 0;
 
+  if (player->is_moving)
+    DrawLevelField(player->last_jx, player->last_jy);
+
   for (i = 0; i < MAX_PLAYERS; i++)
     if (stored_player[i].active)
       found = TRUE;