added setup option for muting double falling sounds in BD engine
[rocksndiamonds.git] / src / game_bd / bd_caveengine.c
index 61f71d37dd28dd32ca84a34518b3efd96b662ca2..2793d26f2f93c968e7b4d4d83ff2df997abac611 100644 (file)
@@ -126,9 +126,19 @@ void gd_cave_set_seconds_sound(GdCave *cave)
   }
 }
 
+// returns true if the element can fall
+static inline boolean el_can_fall(const int element)
+{
+  return (gd_elements[element & O_MASK].properties & P_CAN_FALL) != 0;
+}
+
 // play diamond or stone sound of given element.
 static void play_sound_of_element(GdCave *cave, GdElement element, int x, int y)
 {
+  // check if sound should be skipped for falling elements (and only be played on impact)
+  if (el_can_fall(element) && skip_bd_falling_sounds())
+    return;
+
   // stone and diamond fall sounds.
   switch (element)
   {
@@ -449,7 +459,7 @@ static inline boolean rotates_ccw (const GdCave *cave, const int x, const int y)
 }
 
 // returns true if the element is a player
-static inline boolean is_player(const GdCave *cave, const int x, const int y)
+boolean is_player(const GdCave *cave, const int x, const int y)
 {
   return (gd_elements[get(cave, x, y) & O_MASK].properties & P_PLAYER) != 0;
 }
@@ -467,6 +477,13 @@ static inline boolean can_be_hammered_dir(const GdCave *cave, const int x, const
   return (gd_elements[get_dir(cave, x, y, dir) & O_MASK].properties & P_CAN_BE_HAMMERED) != 0;
 }
 
+// returns true if the element can be pushed
+boolean can_be_pushed_dir(const GdCave *cave, const int x, const int y,
+                         const GdDirection dir)
+{
+  return (gd_elements[get_dir(cave, x, y, dir) & O_MASK].properties & P_PUSHABLE) != 0;
+}
+
 // returns true if the element is explodable and explodes to space, for example the player
 static inline boolean is_first_stage_of_explosion(const GdCave *cave, const int x, const int y)
 {
@@ -748,6 +765,13 @@ static void explode(GdCave *cave, int x, int y)
       creature_explode(cave, x, y, O_EXPLODE_1);
       break;
 
+    case O_ROCKET_1:
+    case O_ROCKET_2:
+    case O_ROCKET_3:
+    case O_ROCKET_4:
+      creature_explode(cave, x, y, O_EXPLODE_1);
+      break;
+
     case O_BUTTER_1:
     case O_BUTTER_2:
     case O_BUTTER_3:
@@ -780,6 +804,7 @@ static void explode(GdCave *cave, int x, int y)
     case O_PLAYER_BOMB:
     case O_PLAYER_GLUED:
     case O_PLAYER_STIRRING:
+    case O_PLAYER_ROCKET_LAUNCHER:
     case O_PLAYER_PNEUMATIC_LEFT:
     case O_PLAYER_PNEUMATIC_RIGHT:
       creature_explode(cave, x, y, O_EXPLODE_1);
@@ -983,17 +1008,18 @@ static GdElement player_get_element(GdCave* cave, const GdElement object, int x,
   process a crazy dream-style teleporter.
   called from gd_cave_iterate, for a player or a player_bomb.
   player is standing at px, py, and trying to move in the direction player_move,
-  where there is a teleporter.
-  we check the whole cave, from px+1,py, till we get back to px,py (by wrapping
+  where there is a teleporter at (tx_start, ty_start). we check the whole cave,
+  from (tx_start + 1, ty_start), till we get back to (tx_start, ty_start) (by wrapping
   around). the first teleporter we find, and which is suitable, will be the destination.
   return TRUE if teleporter worked, FALSE if cound not find any suitable teleporter.
- */
+*/
 static boolean do_teleporter(GdCave *cave, int px, int py, GdDirection player_move)
 {
-  int tx, ty;
-
-  tx = px;
-  ty = py;
+  // start at teleporter position (not at player position!)
+  int tx_start = px + gd_dx[player_move];
+  int ty_start = py + gd_dy[player_move];
+  int tx = tx_start;
+  int ty = ty_start;
 
   do
   {
@@ -1025,7 +1051,7 @@ static boolean do_teleporter(GdCave *cave, int px, int py, GdDirection player_mo
     }
   }
   // loop until we get back to original coordinates
-  while (tx != px || ty != py);
+  while (tx != tx_start || ty != ty_start);
 
   // return false as we did not find any usable teleporter
   return FALSE;
@@ -1362,6 +1388,9 @@ static boolean do_fall_try_magic(GdCave *cave, int x, int y,
     // active or non-active or anything, element falling in will always disappear
     store(cave, x, y, O_SPACE);
 
+    if (cave->magic_wall_breakscan && cave->amoeba_state == GD_AM_AWAKE)
+      cave->convert_amoeba_this_frame = TRUE;
+
     return TRUE;
   }
   else
@@ -1595,6 +1624,9 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
   // score collected this frame
   cave->score = 0;
 
+  // to implement buggy bd1 amoeba+magic wall behaviour
+  cave->convert_amoeba_this_frame = FALSE;
+
   // suicide only kills the active player
   // player_x, player_y was set by the previous iterate routine, or the cave setup.
   // we must check if there is a player or not - he may have exploded or something like that
@@ -1746,6 +1778,19 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
                    move(cave, x, y, player_move, O_PLAYER_BOMB);
                  break;
 
+               case O_ROCKET_LAUNCHER:
+                 // if its a rocket launcher, remember he now has one.
+                 // we do not change the "remains" and "what" variables,
+                 // so that part of the code will be ineffective
+                 gd_sound_play(cave, GD_S_BOMB_COLLECTING, what, x, y);
+                 store_dir(cave, x, y, player_move, O_SPACE);
+
+                 if (player_fire)
+                   store(cave, x, y, O_PLAYER_ROCKET_LAUNCHER);
+                 else
+                   move(cave, x, y, player_move, O_PLAYER_ROCKET_LAUNCHER);
+                 break;
+
                case O_POT:
                  // we do not change the "remains" and "what" variables,
                  // so that part of the code will be ineffective
@@ -1887,6 +1932,98 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
          }
          break;
 
+       case O_PLAYER_ROCKET_LAUNCHER:
+         // much simpler; cannot snap-push stones
+         if (cave->kill_player)
+         {
+           explode(cave, x, y);
+           break;
+         }
+
+         cave->player_seen_ago = 0;
+         // bd4 intermission caves have many players. so if one of them has exited,
+         // do not change the flag anymore. so this if () is needed
+         if (cave->player_state != GD_PL_EXITED)
+           cave->player_state = GD_PL_LIVING;
+
+         // firing a rocket?
+         if (player_move != GD_MV_STILL)
+         {
+           // if the player does not move, nothing to do
+           GdElement what = get_dir(cave, x, y, player_move);
+           GdElement remains = what;
+
+           // to fire a rocket, diagonal movement should not be allowed.
+           // so either x or y must be zero
+           if (player_fire)
+           {
+             // placing a rocket into empty space
+             if (is_space_dir(cave, x, y, player_move))
+             {
+               switch (player_move)
+               {
+                 case GD_MV_RIGHT:
+                   store_dir(cave, x, y, player_move, O_ROCKET_1);
+                   if (!cave->infinite_rockets)
+                     store(cave, x, y, O_PLAYER);
+                   break;
+
+                 case GD_MV_UP:
+                   store_dir(cave, x, y, player_move, O_ROCKET_2);
+                   if (!cave->infinite_rockets)
+                     store(cave, x, y, O_PLAYER);
+                   break;
+
+                 case GD_MV_LEFT:
+                   store_dir(cave, x, y, player_move, O_ROCKET_3);
+                   if (!cave->infinite_rockets)
+                     store(cave, x, y, O_PLAYER);
+                   break;
+
+                 case GD_MV_DOWN:
+                   store_dir(cave, x, y, player_move, O_ROCKET_4);
+                   if (!cave->infinite_rockets)
+                     store(cave, x, y, O_PLAYER);
+                   break;
+
+                 default:
+                   // cannot fire in other directions
+                   break;
+               }
+
+               gd_sound_play(cave, GD_S_BOMB_PLACING, O_BOMB, x, y);
+             }
+
+             // a player with rocket launcher cannot snap elements, so stop here
+             break;
+           }
+
+           // pushing and collecting
+           // if we are 'eating' a teleporter, and the function returns true
+           // (teleporting worked), break here
+           if (what == O_TELEPORTER && do_teleporter(cave, x, y, player_move))
+             break;
+
+           // player fire is false...
+           if (do_push(cave, x, y, player_move, FALSE))
+           {
+             remains = O_SPACE;
+           }
+           else
+           {
+             // get element. if cannot get, player_get_element will return the same
+             remains = player_get_element(cave, what, x, y);
+           }
+
+           // if something changed, OR there is space, move.
+           if (remains != what || remains == O_SPACE)
+           {
+             // if anything changed, apply the change.
+             move(cave, x, y, player_move, O_PLAYER_ROCKET_LAUNCHER);
+           }
+         }
+         break;
+
        case O_PLAYER_STIRRING:
          if (cave->kill_player)
          {
@@ -2733,6 +2870,13 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
          // ============================================================================
 
        case O_AMOEBA:
+         // emulating BD1 amoeba+magic wall bug
+         if (cave->convert_amoeba_this_frame && amoeba_found_enclosed)
+         {
+           store(cave, x, y, cave->amoeba_enclosed_effect);
+           break;
+         }
+
          amoeba_count++;
          switch (cave->amoeba_state)
          {
@@ -3040,6 +3184,7 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
              }
            }
            else
+           {
              // or space over the slime? elements may pass from bottom to up then.
              if (is_space_dir(cave, x, y, oppos))
              {
@@ -3063,6 +3208,7 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
                  play_sound_of_element(cave, O_SLIME, x, y);
                }
              }
+           }
          }
          break;
 
@@ -3166,6 +3312,38 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
          }
          break;
 
+         // ============================================================================
+         //    R O C K E T S
+         // ============================================================================
+
+       case O_ROCKET_1:
+         if (is_space_dir(cave, x, y, GD_MV_RIGHT))
+           move(cave, x, y, GD_MV_RIGHT, O_ROCKET_1);
+         else
+           explode(cave, x, y);
+         break;
+
+       case O_ROCKET_2:
+         if (is_space_dir(cave, x, y, GD_MV_UP))
+           move(cave, x, y, GD_MV_UP, O_ROCKET_2);
+         else
+           explode(cave, x, y);
+         break;
+
+       case O_ROCKET_3:
+         if (is_space_dir(cave, x, y, GD_MV_LEFT))
+           move(cave, x, y, GD_MV_LEFT, O_ROCKET_3);
+         else
+           explode(cave, x, y);
+         break;
+
+       case O_ROCKET_4:
+         if (is_space_dir(cave, x, y, GD_MV_DOWN))
+           move(cave, x, y, GD_MV_DOWN, O_ROCKET_4);
+         else
+           explode(cave, x, y);
+         break;
+
          // ============================================================================
          //    S I M P L E   C H A N G I N G;   E X P L O S I O N S
          // ============================================================================
@@ -3470,7 +3648,7 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
   // PLAYER
 
   // check if player is alive.
-  if ((cave->player_state == GD_PL_LIVING && cave->player_seen_ago > 15) || cave->kill_player)
+  if ((cave->player_state == GD_PL_LIVING && cave->player_seen_ago > 1) || cave->kill_player)
     cave->player_state = GD_PL_DIED;
 
   // check if any voodoo exploded, and kill players the next scan if that happended.
@@ -3565,7 +3743,8 @@ void gd_cave_iterate(GdCave *cave, GdDirection player_move, boolean player_fire,
 
   // magic wall; if active&wait or not wait for hatching
   if (cave->magic_wall_state == GD_MW_ACTIVE &&
-      (cave->hatched || !cave->magic_timer_wait_for_hatching))
+      (cave->hatched || !cave->magic_timer_wait_for_hatching) &&
+      !(cave->magic_wall_time == 0 && cave->magic_timer_zero_is_infinite))
   {
     cave->magic_wall_time -= cave->speed;
     if (cave->magic_wall_time < 0)