rnd-20040222-1-src
[rocksndiamonds.git] / src / game.c
index c81c860348d9a261e18471ca5b17671fbf545a22..f8289cc9138937767facf986edaa3b21fa908628 100644 (file)
 #define GET_MAX_MOVE_DELAY(e)  (   (element_info[e].move_delay_fixed) + \
                                    (element_info[e].move_delay_random))
 
-#if 1
+#define ELEMENT_CAN_ENTER_FIELD_BASE(e, x, y, condition)               \
+               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
+                                        Feld[x][y] == EL_ACID) ||      \
+                                       (condition)))
+
+#if 0
 #define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        (condition) ||                  \
 #define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
                                        (condition) ||                  \
+                                       (CAN_MOVE_INTO_ACID(e) &&       \
+                                        Feld[x][y] == EL_ACID) ||      \
                                        (DONT_COLLIDE_WITH(e) &&        \
-                                        IS_FREE_OR_PLAYER(x, y))))
+                                        IS_PLAYER(x, y) &&             \
+                                        !PLAYER_ENEMY_PROTECTED(x, y))))
 #endif
 
 #define ELEMENT_CAN_ENTER_FIELD_GENERIC_2(x, y, condition)             \
 #define ELEMENT_CAN_ENTER_FIELD_OR_ACID_2(x, y)                                \
        ELEMENT_CAN_ENTER_FIELD_GENERIC_2(x, y, (Feld[x][y] == EL_ACID))
 
-#define ENEMY_CAN_ENTER_FIELD(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
+#if 0
+#define ENEMY_CAN_ENTER_FIELD(e, x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
+#else
+#define ENEMY_CAN_ENTER_FIELD(e, x, y) ELEMENT_CAN_ENTER_FIELD_BASE(e, x, y, 0)
+#endif
 
 #define YAMYAM_CAN_ENTER_FIELD(x, y)                                   \
                (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) ||      \
                                        Feld[x][y] == EL_EXIT_OPEN ||   \
                                        Feld[x][y] == EL_ACID))
 
-#if 0
-#if 1
-#define MAZE_RUNNER_CAN_ENTER_FIELD(x, y)                              \
-               (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
-#else
-#define MAZE_RUNNER_CAN_ENTER_FIELD(x, y)                              \
-               (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
-                                       IS_FOOD_DARK_YAMYAM(Feld[x][y])))
-#endif
-#endif
-
 #define GROUP_NR(e)            ((e) - EL_GROUP_START)
 #define MOVE_ENTER_EL(e)       (element_info[e].move_enter_element)
 #define IS_IN_GROUP(e, nr)     (element_info[e].in_group[nr] == TRUE)
 #define IS_EQUAL_OR_IN_GROUP(e, ge)                                    \
        (IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge))
 
-#if 1
+#if 0
 #define CE_ENTER_FIELD_COND(e, x, y)                                   \
                (!IS_PLAYER(x, y) &&                                    \
                 (Feld[x][y] == EL_ACID ||                              \
 #else
 #define CE_ENTER_FIELD_COND(e, x, y)                                   \
                (!IS_PLAYER(x, y) &&                                    \
-                (Feld[x][y] == EL_ACID ||                              \
-                 Feld[x][y] == MOVE_ENTER_EL(e) ||                     \
-                 (IS_GROUP_ELEMENT(MOVE_ENTER_EL(e)) &&                \
-                  IS_IN_GROUP_EL(Feld[x][y], MOVE_ENTER_EL(e)))))
+                IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
 #endif
 
 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y)                                \
@@ -862,6 +861,36 @@ static void InitField(int x, int y, boolean init_game)
   }
 }
 
+static inline void InitField_WithBug1(int x, int y, boolean init_game)
+{
+  InitField(x, y, init_game);
+
+  /* not needed to call InitMovDir() -- already done by InitField()! */
+  if (game.engine_version < VERSION_IDENT(3,0,9,0) &&
+      CAN_MOVE(Feld[x][y]))
+    InitMovDir(x, y);
+}
+
+static inline void InitField_WithBug2(int x, int y, boolean init_game)
+{
+  int old_element = Feld[x][y];
+
+  InitField(x, y, init_game);
+
+  /* not needed to call InitMovDir() -- already done by InitField()! */
+  if (game.engine_version < VERSION_IDENT(3,0,9,0) &&
+      CAN_MOVE(old_element) &&
+      (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
+    InitMovDir(x, y);
+
+  /* this case is in fact a combination of not less than three bugs:
+     first, it calls InitMovDir() for elements that can move, although this is
+     already done by InitField(); then, it checks the element that was at this
+     field _before_ the call to InitField() (which can change it)
+
+ */
+}
+
 void DrawGameDoorValues()
 {
   int i, j;
@@ -2306,8 +2335,25 @@ void RemoveMovingField(int x, int y)
   if (IS_MOVING(x, y))
   {
     Moving2Blocked(x, y, &newx, &newy);
+#if 0
     if (Feld[newx][newy] != EL_BLOCKED)
       return;
+#else
+    if (Feld[newx][newy] != EL_BLOCKED)
+    {
+      /* element is moving, but target field is not free (blocked), but
+        already occupied by something different (example: acid pool);
+        in this case, only remove the moving field, but not the target */
+
+      RemoveField(oldx, oldy);
+
+      Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+
+      DrawLevelField(oldx, oldy);
+
+      return;
+    }
+#endif
   }
   else if (element == EL_BLOCKED)
   {
@@ -2866,11 +2912,21 @@ void Explode(int ex, int ey, int phase, int mode)
     ChangeDelay[x][y] = 0;
     ChangePage[x][y] = -1;
 
+#if 1
+    InitField_WithBug2(x, y, FALSE);
+#else
     InitField(x, y, FALSE);
 #if 1
     /* !!! not needed !!! */
+#if 1
+    if (game.engine_version < VERSION_IDENT(3,0,9,0) &&
+       CAN_MOVE(Feld[x][y]) && Feld[x][y] != EL_MOLE)
+      InitMovDir(x, y);
+#else
     if (CAN_MOVE(element))
       InitMovDir(x, y);
+#endif
+#endif
 #endif
     DrawLevelField(x, y);
 
@@ -3061,23 +3117,42 @@ void Bang(int x, int y)
 
 void SplashAcid(int x, int y)
 {
+#if 1
+  if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
+      (!IN_LEV_FIELD(x - 1, y - 2) ||
+       !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
+    Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
+
+  if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
+      (!IN_LEV_FIELD(x + 1, y - 2) ||
+       !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
+    Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
+
+  PlayLevelSound(x, y, SND_ACID_SPLASHING);
+#else
+  /* input: position of element entering acid (obsolete) */
+
   int element = Feld[x][y];
 
+  if (!IN_LEV_FIELD(x, y + 1) || Feld[x][y + 1] != EL_ACID)
+    return;
+
   if (element != EL_ACID_SPLASH_LEFT &&
       element != EL_ACID_SPLASH_RIGHT)
   {
     PlayLevelSound(x, y, SND_ACID_SPLASHING);
 
-    if (IN_LEV_FIELD(x-1, y) && IS_FREE(x-1, y) &&
-       (!IN_LEV_FIELD(x-1, y-1) ||
-        !CAN_FALL(MovingOrBlocked2Element(x-1, y-1))))
-      Feld[x-1][y] = EL_ACID_SPLASH_LEFT;
+    if (IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y) &&
+       (!IN_LEV_FIELD(x - 1, y - 1) ||
+        !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 1))))
+      Feld[x - 1][y] = EL_ACID_SPLASH_LEFT;
 
-    if (IN_LEV_FIELD(x+1, y) && IS_FREE(x+1, y) &&
-       (!IN_LEV_FIELD(x+1, y-1) ||
-        !CAN_FALL(MovingOrBlocked2Element(x+1, y-1))))
-      Feld[x+1][y] = EL_ACID_SPLASH_RIGHT;
+    if (IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y) &&
+       (!IN_LEV_FIELD(x + 1, y - 1) ||
+        !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 1))))
+      Feld[x + 1][y] = EL_ACID_SPLASH_RIGHT;
   }
+#endif
 }
 
 static void InitBeltMovement()
@@ -3454,7 +3529,7 @@ void Impact(int x, int y)
 
   if (!lastline && smashed == EL_ACID) /* element falls into acid */
   {
-    SplashAcid(x, y);
+    SplashAcid(x, y + 1);
     return;
   }
 
@@ -3705,9 +3780,9 @@ inline static void TurnRoundExt(int x, int y)
   {
     TestIfBadThingTouchesOtherBadThing(x, y);
 
-    if (ENEMY_CAN_ENTER_FIELD(right_x, right_y))
+    if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
       MovDir[x][y] = right_dir;
-    else if (!ENEMY_CAN_ENTER_FIELD(move_x, move_y))
+    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = left_dir;
 
     if (element == EL_BUG && MovDir[x][y] != old_move_dir)
@@ -3720,9 +3795,9 @@ inline static void TurnRoundExt(int x, int y)
   {
     TestIfBadThingTouchesOtherBadThing(x, y);
 
-    if (ENEMY_CAN_ENTER_FIELD(left_x, left_y))
+    if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
       MovDir[x][y] = left_dir;
-    else if (!ENEMY_CAN_ENTER_FIELD(move_x, move_y))
+    else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
       MovDir[x][y] = right_dir;
 
     if ((element == EL_SPACESHIP ||
@@ -4347,7 +4422,9 @@ static boolean JustBeingPushed(int x, int y)
 
 void StartMoving(int x, int y)
 {
+#if 0
   boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0,0));
+#endif
   boolean started_moving = FALSE;      /* some elements can fall _and_ move */
   int element = Feld[x][y];
 
@@ -4496,7 +4573,7 @@ void StartMoving(int x, int y)
     else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
 #endif
     {
-      SplashAcid(x, y);
+      SplashAcid(x, y + 1);
 
       InitMovingField(x, y, MV_DOWN);
       started_moving = TRUE;
@@ -4544,7 +4621,7 @@ void StartMoving(int x, int 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 && level.use_spring_bug)
     {
       if (MovDir[x][y] == MV_NO_MOVING)
       {
@@ -4901,6 +4978,18 @@ void StartMoving(int x, int y)
 #endif
 
     }
+#if 1
+#if 1
+    else if (CAN_MOVE_INTO_ACID(element) &&
+            IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
+            (MovDir[x][y] == MV_DOWN ||
+             game.engine_version > VERSION_IDENT(3,0,8,0)))
+#else
+    else if (CAN_MOVE_INTO_ACID(element) && MovDir[x][y] == MV_DOWN &&
+            IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID)
+#endif
+#else
+
     else if ((element == EL_PENGUIN ||
              element == EL_ROBOT ||
              element == EL_SATELLITE ||
@@ -4908,8 +4997,9 @@ void StartMoving(int x, int y)
              IS_CUSTOM_ELEMENT(element)) &&
             IN_LEV_FIELD(newx, newy) &&
             MovDir[x][y] == MV_DOWN && Feld[newx][newy] == EL_ACID)
+#endif
     {
-      SplashAcid(x, y);
+      SplashAcid(newx, newy);
       Store[x][y] = EL_ACID;
     }
     else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
@@ -5124,8 +5214,15 @@ void StartMoving(int x, int y)
          AmoebaCnt[AmoebaNr[newx][newy]]--;
       }
 
+#if 0
+      /* !!! test !!! */
+      if (IS_MOVING(newx, newy) || IS_BLOCKED(newx, newy))
+#else
       if (IS_MOVING(newx, newy))
+#endif
+      {
        RemoveMovingField(newx, newy);
+      }
       else
       {
        Feld[newx][newy] = EL_EMPTY;
@@ -5228,22 +5325,22 @@ void ContinueMoving(int x, int y)
   int nextx = newx + dx, nexty = newy + dy;
 #endif
 #if 1
-  boolean pushed = (Pushed[x][y] && IS_PLAYER(x, y));
+  boolean pushed_by_player   = (Pushed[x][y] && IS_PLAYER(x, y));
   boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
 #else
-  boolean pushed = Pushed[x][y];
+  boolean pushed_by_player = Pushed[x][y];
 #endif
 
   MovPos[x][y] += getElementMoveStepsize(x, y);
 
 #if 0
-  if (pushed && IS_PLAYER(x, y))
+  if (pushed_by_player && IS_PLAYER(x, y))
   {
     /* special case: moving object pushed by player */
     MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
   }
 #else
-  if (pushed)          /* special case: moving object pushed by player */
+  if (pushed_by_player)        /* special case: moving object pushed by player */
     MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
 #endif
 
@@ -5397,7 +5494,7 @@ void ContinueMoving(int x, int y)
   Stop[newx][newy] = TRUE;     /* ignore this element until the next frame */
 
   /* prevent pushed element from moving on in pushed direction */
-  if (pushed && CAN_MOVE(element) &&
+  if (pushed_by_player && CAN_MOVE(element) &&
       element_info[element].move_pattern & MV_ANY_DIRECTION &&
       !(element_info[element].move_pattern & direction))
     TurnRound(newx, newy);
@@ -5409,7 +5506,7 @@ void ContinueMoving(int x, int y)
     MovDir[newx][newy] = 0;
 #endif
 
-  if (!pushed) /* special case: moving object pushed by player */
+  if (!pushed_by_player)
   {
     WasJustMoving[newx][newy] = 3;
 
@@ -6439,9 +6536,13 @@ static void ChangeElementNowExt(int x, int y, int target_element)
   if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
     MovDir[x][y] = previous_move_direction;
 
+#if 1
+  InitField_WithBug1(x, y, FALSE);
+#else
   InitField(x, y, FALSE);
   if (CAN_MOVE(Feld[x][y]))
     InitMovDir(x, y);
+#endif
 
   DrawLevelField(x, y);
 
@@ -7239,7 +7340,19 @@ void GameActions()
 #endif
 
 #if 1
+  /* for downwards compatibility, the following code emulates a fixed bug that
+     occured when pushing elements (causing elements that just made their last
+     pushing step to already (if possible) make their first falling step in the
+     same game frame, which is bad); this code is also needed to use the famous
+     "spring push bug" which is used in older levels and might be wanted to be
+     used also in newer levels, but in this case the buggy pushing code is only
+     affecting the "spring" element and no other elements */
+
+#if 1
+  if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
+#else
   if (game.engine_version < VERSION_IDENT(2,2,0,7))
+#endif
   {
     for (i = 0; i < MAX_PLAYERS; i++)
     {
@@ -7247,8 +7360,15 @@ void GameActions()
       int x = player->jx;
       int y = player->jy;
 
+#if 1
+      if (player->active && player->is_pushing && player->is_moving &&
+         IS_MOVING(x, y) &&
+         (game.engine_version < VERSION_IDENT(2,2,0,7) ||
+          Feld[x][y] == EL_SPRING))
+#else
       if (player->active && player->is_pushing && player->is_moving &&
          IS_MOVING(x, y))
+#endif
       {
        ContinueMoving(x, y);
 
@@ -7915,7 +8035,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
   {
     if (element == EL_ACID && dx == 0 && dy == 1)
     {
-      SplashAcid(jx, jy);
+      SplashAcid(new_jx, new_jy);
       Feld[jx][jy] = EL_PLAYER_1;
       InitMovingField(jx, jy, MV_DOWN);
       Store[jx][jy] = EL_ACID;
@@ -8972,7 +9092,9 @@ int DigField(struct PlayerInfo *player,
     CH_SIDE_BOTTOM,    /* moving up    */
     CH_SIDE_TOP,       /* moving down  */
   };
+#if 0
   boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0,0));
+#endif
   int jx = oldx, jy = oldy;
   int dx = x - jx, dy = y - jy;
   int nextx = x + dx, nexty = y + dy;
@@ -9386,7 +9508,7 @@ int DigField(struct PlayerInfo *player,
          return MF_NO_ACTION;
 
        if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
-           !(element == EL_SPRING && use_spring_bug))
+           !(element == EL_SPRING && level.use_spring_bug))
          return MF_NO_ACTION;
 
 #if 1
@@ -9803,9 +9925,13 @@ boolean DropElement(struct PlayerInfo *player)
 
   if (Feld[jx][jy] == new_element)     /* uninitialized unless CE change */
   {
+#if 1
+    InitField_WithBug1(jx, jy, FALSE);
+#else
     InitField(jx, jy, FALSE);
     if (CAN_MOVE(Feld[jx][jy]))
       InitMovDir(jx, jy);
+#endif
   }
 
   new_element = Feld[jx][jy];