v1.4.4.1 (Blood in the Water Update)
This commit is contained in:
@@ -119,7 +119,6 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Caves, ruins, outposts and similar enclosed areas
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsEnclosedArea()
|
||||
{
|
||||
return
|
||||
@@ -127,6 +126,7 @@ namespace Barotrauma
|
||||
PositionType == PositionType.Ruin ||
|
||||
PositionType == PositionType.Outpost ||
|
||||
PositionType == PositionType.BeaconStation ||
|
||||
PositionType == PositionType.Wreck ||
|
||||
PositionType == PositionType.AbyssCave;
|
||||
}
|
||||
}
|
||||
@@ -698,106 +698,8 @@ namespace Barotrauma
|
||||
//----------------------------------------------------------------------------------
|
||||
//generate voronoi sites
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
Point siteInterval = GenerationParams.VoronoiSiteInterval;
|
||||
int siteIntervalSqr = (siteInterval.X * siteInterval.X + siteInterval.Y * siteInterval.Y);
|
||||
Point siteVariance = GenerationParams.VoronoiSiteVariance;
|
||||
siteCoordsX = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
||||
siteCoordsY = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
||||
const int caveSiteInterval = 500;
|
||||
for (int x = siteInterval.X / 2; x < borders.Width - siteInterval.X / 2; x += siteInterval.X)
|
||||
{
|
||||
for (int y = siteInterval.Y / 2; y < borders.Height - siteInterval.Y / 2; y += siteInterval.Y)
|
||||
{
|
||||
int siteX = x + Rand.Range(-siteVariance.X, siteVariance.X + 1, Rand.RandSync.ServerAndClient);
|
||||
int siteY = y + Rand.Range(-siteVariance.Y, siteVariance.Y + 1, Rand.RandSync.ServerAndClient);
|
||||
|
||||
bool closeToTunnel = false;
|
||||
bool closeToCave = false;
|
||||
foreach (Tunnel tunnel in Tunnels)
|
||||
{
|
||||
float minDist = Math.Max(tunnel.MinWidth * 2.0f, Math.Max(siteInterval.X, siteInterval.Y));
|
||||
for (int i = 1; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
if (siteX < Math.Min(tunnel.Nodes[i - 1].X, tunnel.Nodes[i].X) - minDist) { continue; }
|
||||
if (siteX > Math.Max(tunnel.Nodes[i - 1].X, tunnel.Nodes[i].X) + minDist) { continue; }
|
||||
if (siteY < Math.Min(tunnel.Nodes[i - 1].Y, tunnel.Nodes[i].Y) - minDist) { continue; }
|
||||
if (siteY > Math.Max(tunnel.Nodes[i - 1].Y, tunnel.Nodes[i].Y) + minDist) { continue; }
|
||||
|
||||
double tunnelDistSqr = MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], new Point(siteX, siteY));
|
||||
if (Math.Sqrt(tunnelDistSqr) < minDist)
|
||||
{
|
||||
closeToTunnel = true;
|
||||
//tunnelDistSqr = MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], new Point(siteX, siteY));
|
||||
if (tunnel.Type == TunnelType.Cave)
|
||||
{
|
||||
closeToCave = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!closeToTunnel)
|
||||
{
|
||||
//make the graph less dense (90% less nodes) in areas far away from tunnels where we don't need a lot of geometry
|
||||
if (Rand.Range(0, 10, Rand.RandSync.ServerAndClient) != 0) { continue; }
|
||||
}
|
||||
|
||||
if (!TooCloseToOtherSites(siteX, siteY))
|
||||
{
|
||||
siteCoordsX.Add(siteX);
|
||||
siteCoordsY.Add(siteY);
|
||||
}
|
||||
|
||||
if (closeToCave)
|
||||
{
|
||||
for (int x2 = x - siteInterval.X; x2 < x + siteInterval.X; x2 += caveSiteInterval)
|
||||
{
|
||||
for (int y2 = y - siteInterval.Y; y2 < y + siteInterval.Y; y2 += caveSiteInterval)
|
||||
{
|
||||
int caveSiteX = x2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
int caveSiteY = y2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
|
||||
if (!TooCloseToOtherSites(caveSiteX, caveSiteY, caveSiteInterval))
|
||||
{
|
||||
siteCoordsX.Add(caveSiteX);
|
||||
siteCoordsY.Add(caveSiteY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TooCloseToOtherSites(double siteX, double siteY, float minDistance = 10.0f)
|
||||
{
|
||||
float minDistanceSqr = minDistance * minDistance;
|
||||
for (int i = 0; i < siteCoordsX.Count; i++)
|
||||
{
|
||||
if (MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteX, siteY) < minDistanceSqr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < siteCoordsX.Count; i++)
|
||||
{
|
||||
Debug.Assert(
|
||||
siteCoordsX[i] > 0 || siteCoordsY[i] > 0,
|
||||
$"Potential error in level generation: a voronoi site was outside the bounds of the level ({siteCoordsX[i]}, {siteCoordsY[i]})");
|
||||
Debug.Assert(
|
||||
siteCoordsX[i] < borders.Width || siteCoordsY[i] < borders.Height,
|
||||
$"Potential error in level generation: a voronoi site was outside the bounds of the level ({siteCoordsX[i]}, {siteCoordsY[i]})");
|
||||
for (int j = i + 1; j < siteCoordsX.Count; j++)
|
||||
{
|
||||
Debug.Assert(
|
||||
MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteCoordsX[j], siteCoordsY[j]) > 1.0f,
|
||||
"Potential error in level generation: two voronoi sites are extremely close to each other.");
|
||||
}
|
||||
}
|
||||
GenerateVoronoiSites();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen);
|
||||
|
||||
@@ -809,13 +711,52 @@ namespace Barotrauma
|
||||
sw2.Start();
|
||||
|
||||
Debug.Assert(siteCoordsX.Count == siteCoordsY.Count);
|
||||
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(siteCoordsX.ToArray(), siteCoordsY.ToArray(), borders.Width, borders.Height);
|
||||
|
||||
Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
//construct voronoi cells based on the graph edges
|
||||
cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid);
|
||||
int remainingRetries = 5;
|
||||
bool voronoiGraphInvalid = false;
|
||||
do
|
||||
{
|
||||
remainingRetries--;
|
||||
voronoiGraphInvalid = false;
|
||||
//construct voronoi cells based on the graph edges
|
||||
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(siteCoordsX.ToArray(), siteCoordsY.ToArray(), borders.Width, borders.Height);
|
||||
cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid);
|
||||
for (int i = 0; i < cells.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < cells.Count; j++)
|
||||
{
|
||||
//sites can never be inside multiple cells in a voronoi graph by definition
|
||||
//if they are, that'll cause severe issues with the rest of the level generation.
|
||||
|
||||
//There seems to be a very rare issue that sometimes causes the graph to generate incorrectly (see #10944 and #12980),
|
||||
//leading to a crash due. I haven't been able to figure out what's causing that - there don't seem to be any issues in the sites,
|
||||
//so I'm getting the feeling it could be a bug with the voronoi graph generation.
|
||||
|
||||
//If that happens, let's just retry a couple of times (re-randomizing the sites and regenerating
|
||||
//the map seems to fix the issue in all cases I've seen)
|
||||
if (cells[j].IsPointInside(cells[i].Center))
|
||||
{
|
||||
voronoiGraphInvalid = true;
|
||||
break;
|
||||
}
|
||||
if (voronoiGraphInvalid) { break; }
|
||||
}
|
||||
}
|
||||
if (voronoiGraphInvalid)
|
||||
{
|
||||
string errorMsg = "Unknown error during level generation. Invalid voronoi graph: the same voronoi site was inside multiple cells.";
|
||||
if (remainingRetries > 0)
|
||||
{
|
||||
DebugConsole.AddWarning(errorMsg + " Retrying...");
|
||||
GenerateVoronoiSites();
|
||||
}
|
||||
else
|
||||
{
|
||||
//throw a console error and let the generation finish, hoping for the best
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
}
|
||||
}
|
||||
} while (remainingRetries > 0 && voronoiGraphInvalid);
|
||||
|
||||
GenerateAbyssGeometry();
|
||||
GenerateAbyssPositions();
|
||||
@@ -1449,6 +1390,116 @@ namespace Barotrauma
|
||||
Generating = false;
|
||||
}
|
||||
|
||||
|
||||
private void GenerateVoronoiSites()
|
||||
{
|
||||
Point siteInterval = GenerationParams.VoronoiSiteInterval;
|
||||
int siteIntervalSqr = (siteInterval.X * siteInterval.X + siteInterval.Y * siteInterval.Y);
|
||||
Point siteVariance = GenerationParams.VoronoiSiteVariance;
|
||||
siteCoordsX = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
||||
siteCoordsY = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
||||
const int caveSiteInterval = 500;
|
||||
for (int x = siteInterval.X / 2; x < borders.Width - siteInterval.X / 2; x += siteInterval.X)
|
||||
{
|
||||
for (int y = siteInterval.Y / 2; y < borders.Height - siteInterval.Y / 2; y += siteInterval.Y)
|
||||
{
|
||||
int siteX = x + Rand.Range(-siteVariance.X, siteVariance.X + 1, Rand.RandSync.ServerAndClient);
|
||||
int siteY = y + Rand.Range(-siteVariance.Y, siteVariance.Y + 1, Rand.RandSync.ServerAndClient);
|
||||
|
||||
bool closeToTunnel = false;
|
||||
bool closeToCave = false;
|
||||
foreach (Tunnel tunnel in Tunnels)
|
||||
{
|
||||
float minDist = Math.Max(tunnel.MinWidth * 2.0f, Math.Max(siteInterval.X, siteInterval.Y));
|
||||
for (int i = 1; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
if (siteX < Math.Min(tunnel.Nodes[i - 1].X, tunnel.Nodes[i].X) - minDist) { continue; }
|
||||
if (siteX > Math.Max(tunnel.Nodes[i - 1].X, tunnel.Nodes[i].X) + minDist) { continue; }
|
||||
if (siteY < Math.Min(tunnel.Nodes[i - 1].Y, tunnel.Nodes[i].Y) - minDist) { continue; }
|
||||
if (siteY > Math.Max(tunnel.Nodes[i - 1].Y, tunnel.Nodes[i].Y) + minDist) { continue; }
|
||||
|
||||
double tunnelDistSqr = MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], new Point(siteX, siteY));
|
||||
if (Math.Sqrt(tunnelDistSqr) < minDist)
|
||||
{
|
||||
closeToTunnel = true;
|
||||
//tunnelDistSqr = MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], new Point(siteX, siteY));
|
||||
if (tunnel.Type == TunnelType.Cave)
|
||||
{
|
||||
closeToCave = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!closeToTunnel)
|
||||
{
|
||||
//make the graph less dense (90% less nodes) in areas far away from tunnels where we don't need a lot of geometry
|
||||
if (Rand.Range(0, 10, Rand.RandSync.ServerAndClient) != 0) { continue; }
|
||||
}
|
||||
|
||||
if (!TooCloseToOtherSites(siteX, siteY))
|
||||
{
|
||||
siteCoordsX.Add(siteX);
|
||||
siteCoordsY.Add(siteY);
|
||||
}
|
||||
|
||||
if (closeToCave)
|
||||
{
|
||||
for (int x2 = x - siteInterval.X; x2 < x + siteInterval.X; x2 += caveSiteInterval)
|
||||
{
|
||||
for (int y2 = y - siteInterval.Y; y2 < y + siteInterval.Y; y2 += caveSiteInterval)
|
||||
{
|
||||
int caveSiteX = x2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
int caveSiteY = y2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
|
||||
if (!TooCloseToOtherSites(caveSiteX, caveSiteY, caveSiteInterval))
|
||||
{
|
||||
siteCoordsX.Add(caveSiteX);
|
||||
siteCoordsY.Add(caveSiteY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TooCloseToOtherSites(double siteX, double siteY, float minDistance = 10.0f)
|
||||
{
|
||||
float minDistanceSqr = minDistance * minDistance;
|
||||
for (int i = 0; i < siteCoordsX.Count; i++)
|
||||
{
|
||||
if (MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteX, siteY) < minDistanceSqr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < siteCoordsX.Count; i++)
|
||||
{
|
||||
Debug.Assert(
|
||||
!double.IsNaN(siteCoordsX[i]) && !double.IsInfinity(siteCoordsX[i]),
|
||||
$"Potential error in level generation: invalid voronoi site ({siteCoordsX[i]})");
|
||||
Debug.Assert(
|
||||
!double.IsNaN(siteCoordsY[i]) && !double.IsInfinity(siteCoordsY[i]),
|
||||
$"Potential error in level generation: invalid voronoi site ({siteCoordsY[i]})");
|
||||
Debug.Assert(
|
||||
siteCoordsX[i] > 0 || siteCoordsY[i] > 0,
|
||||
$"Potential error in level generation: a voronoi site was outside the bounds of the level ({siteCoordsX[i]}, {siteCoordsY[i]})");
|
||||
Debug.Assert(
|
||||
siteCoordsX[i] < borders.Width || siteCoordsY[i] < borders.Height,
|
||||
$"Potential error in level generation: a voronoi site was outside the bounds of the level ({siteCoordsX[i]}, {siteCoordsY[i]})");
|
||||
for (int j = i + 1; j < siteCoordsX.Count; j++)
|
||||
{
|
||||
Debug.Assert(
|
||||
MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteCoordsX[j], siteCoordsY[j]) > 10.0f,
|
||||
"Potential error in level generation: two voronoi sites are extremely close to each other.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Point> GeneratePathNodes(Point startPosition, Point endPosition, Rectangle pathBorders, Tunnel parentTunnel, float variance)
|
||||
{
|
||||
List<Point> pathNodes = new List<Point> { startPosition };
|
||||
@@ -3165,7 +3216,7 @@ namespace Barotrauma
|
||||
private List<ClusterLocation> GetAllValidClusterLocations()
|
||||
{
|
||||
var subBorders = new List<Rectangle>();
|
||||
Wrecks.ForEach(w => AddBordersToList(w));
|
||||
Wrecks.ForEach(AddBordersToList);
|
||||
AddBordersToList(BeaconStation);
|
||||
|
||||
var locations = new List<ClusterLocation>();
|
||||
@@ -3186,6 +3237,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (s == null) { return; }
|
||||
var rect = Submarine.AbsRect(s.WorldPosition, s.Borders.Size.ToVector2());
|
||||
// range of piezo crystal discharge is 3500, pad the rect to ensure no such kind of hazards spawn near
|
||||
rect.Inflate(4000, 4000);
|
||||
subBorders.Add(rect);
|
||||
}
|
||||
|
||||
@@ -3202,9 +3255,14 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var r in subBorders)
|
||||
{
|
||||
if (Submarine.RectContains(r, e.Point1)) { return true; }
|
||||
if (Submarine.RectContains(r, e.Point2)) { return true; }
|
||||
if (Submarine.RectContains(r, eCenter)) { return true; }
|
||||
if (r.Contains(e.Point1)) { return true; }
|
||||
if (r.Contains(e.Point2)) { return true; }
|
||||
if (r.Contains(eCenter)) { return true; }
|
||||
|
||||
if (MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, r, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3291,7 +3349,7 @@ namespace Barotrauma
|
||||
Vector2 endPos = startPos - Vector2.UnitY * Size.Y;
|
||||
|
||||
//try to find a level wall below the position unless the position is indoors
|
||||
if (!potentialPos.IsEnclosedArea())
|
||||
if (!potentialPos.PositionType.IsEnclosedArea())
|
||||
{
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(startPos),
|
||||
@@ -3684,7 +3742,12 @@ namespace Barotrauma
|
||||
var placement = info.BeaconStationInfo?.Placement ?? PlacementType.Bottom;
|
||||
|
||||
// Add some margin so that the sub doesn't block the path entirely. It's still possible that some larger subs can't pass by.
|
||||
Point paddedDimensions = new Point(subBorders.Width + 3000, subBorders.Height + 3000);
|
||||
int padding = 1500;
|
||||
Rectangle paddedBorders = new Rectangle(
|
||||
subBorders.X - padding,
|
||||
subBorders.Y + padding,
|
||||
subBorders.Width + padding * 2,
|
||||
subBorders.Height + padding * 2);
|
||||
|
||||
var positions = new List<Vector2>();
|
||||
var rects = new List<Rectangle>();
|
||||
@@ -3733,7 +3796,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (Rand.Value(Rand.RandSync.ServerAndClient) <= Loaded.GenerationParams.WreckHullFloodingChance)
|
||||
{
|
||||
hull.WaterVolume = hull.Volume * Rand.Range(Loaded.GenerationParams.WreckFloodingHullMinWaterPercentage, Loaded.GenerationParams.WreckFloodingHullMaxWaterPercentage, Rand.RandSync.ServerAndClient);
|
||||
hull.WaterVolume =
|
||||
Math.Max(hull.WaterVolume, hull.Volume * Rand.Range(Loaded.GenerationParams.WreckFloodingHullMinWaterPercentage, Loaded.GenerationParams.WreckFloodingHullMaxWaterPercentage, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
}
|
||||
// Only spawn thalamus when the wreck has some thalamus items defined.
|
||||
@@ -3806,10 +3870,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
positions.Add(spawnPoint);
|
||||
bool isBlocked = IsBlocked(spawnPoint, subBorders.Size - new Point(step + 50));
|
||||
//shrink the bounds a bit to allow the sub to go slightly inside the wall
|
||||
//(just enough that it doesn't look like it's floating)
|
||||
int shrinkAmount = step + 50;
|
||||
Rectangle shrunkenBorders = new Rectangle(
|
||||
subBorders.X + shrinkAmount,
|
||||
subBorders.Y - shrinkAmount,
|
||||
subBorders.Width - shrinkAmount * 2,
|
||||
subBorders.Height - shrinkAmount * 2);
|
||||
bool isBlocked = IsBlocked(spawnPoint, shrunkenBorders);
|
||||
if (isBlocked)
|
||||
{
|
||||
rects.Add(ToolBox.GetWorldBounds(spawnPoint.ToPoint(), subBorders.Size));
|
||||
rects.Add(ToolBox.GetWorldBounds(spawnPoint.ToPoint() + subBorders.Location, subBorders.Size));
|
||||
Debug.WriteLine($"Invalid position {spawnPoint}. Blocked by level walls.");
|
||||
}
|
||||
else if (!bottomFound)
|
||||
@@ -3832,7 +3904,8 @@ namespace Barotrauma
|
||||
float maxMovement = 5000;
|
||||
float totalAmount = 0;
|
||||
bool foundBottom = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
while (!IsSideBlocked(subBorders, amount > 0))
|
||||
//move until the side is no longer blocked
|
||||
while (IsSideBlocked(subBorders, front: amount < 0))
|
||||
{
|
||||
foundBottom = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
totalAmount += amount;
|
||||
@@ -3854,7 +3927,7 @@ namespace Barotrauma
|
||||
{
|
||||
var wp = waypoints.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
waypoints.Remove(wp);
|
||||
if (!IsBlocked(wp.WorldPosition, paddedDimensions))
|
||||
if (!IsBlocked(wp.WorldPosition, paddedBorders))
|
||||
{
|
||||
spawnPoint = wp.WorldPosition;
|
||||
return true;
|
||||
@@ -3867,76 +3940,73 @@ namespace Barotrauma
|
||||
{
|
||||
// Shoot five rays and pick the highest hit point.
|
||||
int rayCount = 5;
|
||||
var positions = new Vector2[rayCount];
|
||||
var hitPositions = new Vector2[rayCount];
|
||||
bool hit = false;
|
||||
for (int i = 0; i < rayCount; i++)
|
||||
{
|
||||
float quarterWidth = subBorders.Width * 0.25f;
|
||||
Vector2 rayStart = spawnPoint;
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
rayStart = new Vector2(spawnPoint.X - quarterWidth, spawnPoint.Y);
|
||||
break;
|
||||
case 2:
|
||||
rayStart = new Vector2(spawnPoint.X + quarterWidth, spawnPoint.Y);
|
||||
break;
|
||||
case 3:
|
||||
rayStart = new Vector2(spawnPoint.X - quarterWidth / 2, spawnPoint.Y);
|
||||
break;
|
||||
case 4:
|
||||
rayStart = new Vector2(spawnPoint.X + quarterWidth / 2, spawnPoint.Y);
|
||||
break;
|
||||
}
|
||||
//cast rays starting from the left side of the sub, offset by 20% to 80% of the sub's width
|
||||
//(ignoring the very back and front of the sub, it's fine if they overlap a bit)
|
||||
float xOffset =
|
||||
subBorders.Width * MathHelper.Lerp(0.2f, 0.8f, i / (float)(rayCount - 1));
|
||||
Vector2 rayStart = new Vector2(
|
||||
spawnPoint.X + subBorders.Location.X + xOffset,
|
||||
spawnPoint.Y);
|
||||
var simPos = ConvertUnits.ToSimUnits(rayStart);
|
||||
var body = Submarine.PickBody(simPos, new Vector2(simPos.X, placement == PlacementType.Bottom ? -1 : Size.Y + 1),
|
||||
customPredicate: f => f.Body == TopBarrier || f.Body == BottomBarrier || (f.Body?.UserData is VoronoiCell cell && cell.Body.BodyType == BodyType.Static && !ExtraWalls.Any(w => w.Body == f.Body)),
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
||||
if (body != null)
|
||||
{
|
||||
positions[i] =
|
||||
ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) +
|
||||
new Vector2(0, subBorders.Height / 2 * (placement == PlacementType.Bottom ? 1 : -1));
|
||||
hitPositions[i] = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition);
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
float highestPoint = placement == PlacementType.Bottom ? positions.Max(p => p.Y) : positions.Min(p => p.Y);
|
||||
spawnPoint = new Vector2(spawnPoint.X, highestPoint);
|
||||
int dir = placement == PlacementType.Bottom ? -1 : 1;
|
||||
float highestPoint = placement == PlacementType.Bottom ? hitPositions.Max(p => p.Y) : hitPositions.Min(p => p.Y);
|
||||
float halfHeight = subBorders.Height / 2;
|
||||
float centerOffset = subBorders.Y - halfHeight;
|
||||
spawnPoint = new Vector2(spawnPoint.X, highestPoint + halfHeight * -dir - centerOffset);
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool IsSideBlocked(Rectangle subBorders, bool front)
|
||||
{
|
||||
Point centerOffset = new Point(subBorders.Center.X, subBorders.Y - subBorders.Height / 2);
|
||||
|
||||
// Shoot three rays and check whether any of them hits.
|
||||
int rayCount = 3;
|
||||
int dir = front ? 1 : -1;
|
||||
Vector2 halfSize = subBorders.Size.ToVector2() / 2;
|
||||
Vector2 quarterSize = halfSize / 2;
|
||||
var positions = new Vector2[rayCount];
|
||||
for (int i = 0; i < rayCount; i++)
|
||||
{
|
||||
float dir = front ? 1 : -1;
|
||||
Vector2 rayStart;
|
||||
Vector2 to;
|
||||
Vector2 rayStart, to;
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y + quarterSize.Y);
|
||||
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
|
||||
break;
|
||||
case 2:
|
||||
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y - quarterSize.Y);
|
||||
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
|
||||
float yOffset = quarterSize.Y * (i == 1 ? 1 : -1);
|
||||
//from a position half-way towards the edge, to the edge.
|
||||
//we start half-way towards the edge instead of the center, because we want to allow things to poke partially inside the sub
|
||||
rayStart = new Vector2(spawnPoint.X + quarterSize.X * dir, spawnPoint.Y + yOffset);
|
||||
to = new Vector2(spawnPoint.X + halfSize.X * dir, rayStart.Y);
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
//center to center-right
|
||||
rayStart = spawnPoint;
|
||||
to = new Vector2(spawnPoint.X + halfSize.X * dir, rayStart.Y);
|
||||
break;
|
||||
}
|
||||
|
||||
rayStart += centerOffset.ToVector2();
|
||||
to += centerOffset.ToVector2();
|
||||
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(rayStart);
|
||||
if (Submarine.PickBody(simPos, ConvertUnits.ToSimUnits(to),
|
||||
customPredicate: f => f.Body?.UserData is VoronoiCell cell,
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
|
||||
allowInsideFixture: true) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -3944,10 +4014,11 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsBlocked(Vector2 pos, Point size, float maxDistanceMultiplier = 1)
|
||||
bool IsBlocked(Vector2 pos, Rectangle submarineBounds)
|
||||
{
|
||||
float maxDistance = size.Multiply(maxDistanceMultiplier).ToVector2().LengthSquared();
|
||||
Rectangle bounds = ToolBox.GetWorldBounds(pos.ToPoint(), size);
|
||||
Rectangle bounds = new Rectangle(
|
||||
(int)pos.X + submarineBounds.X, (int)pos.Y + submarineBounds.Y,
|
||||
submarineBounds.Width, submarineBounds.Height);
|
||||
if (Ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).IntersectsWorld(bounds)))
|
||||
{
|
||||
return true;
|
||||
@@ -3958,13 +4029,16 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return cells.Any(c => c.Body != null && Vector2.DistanceSquared(pos, c.Center) <= maxDistance && c.BodyVertices.Any(v => bounds.ContainsWorld(v)));
|
||||
return cells.Any(c =>
|
||||
c.Body != null &&
|
||||
c.BodyVertices.Any(v => bounds.ContainsWorld(v)));
|
||||
}
|
||||
}
|
||||
|
||||
// For debugging
|
||||
private readonly Dictionary<Submarine, List<Vector2>> wreckPositions = new Dictionary<Submarine, List<Vector2>>();
|
||||
private readonly Dictionary<Submarine, List<Rectangle>> blockedRects = new Dictionary<Submarine, List<Rectangle>>();
|
||||
|
||||
private void CreateWrecks()
|
||||
{
|
||||
var totalSW = new Stopwatch();
|
||||
@@ -4005,6 +4079,18 @@ namespace Barotrauma
|
||||
wreckCount = Math.Max(wreckCount, 1);
|
||||
}
|
||||
|
||||
if (LevelData.ForceWreck != null)
|
||||
{
|
||||
//force the desired wreck to be chosen first
|
||||
var matchingFile = wreckFiles.FirstOrDefault(w => w.Path == LevelData.ForceWreck.FilePath);
|
||||
if (matchingFile != null)
|
||||
{
|
||||
wreckFiles.Remove(matchingFile);
|
||||
wreckFiles.Insert(0, matchingFile);
|
||||
}
|
||||
wreckCount = Math.Max(wreckCount, 1);
|
||||
}
|
||||
|
||||
Wrecks = new List<Submarine>(wreckCount);
|
||||
for (int i = 0; i < wreckCount; i++)
|
||||
{
|
||||
@@ -4286,7 +4372,7 @@ namespace Barotrauma
|
||||
|
||||
private void CreateBeaconStation()
|
||||
{
|
||||
if (!LevelData.HasBeaconStation && string.IsNullOrEmpty(GenerationParams.ForceBeaconStation)) { return; }
|
||||
if (!LevelData.HasBeaconStation && LevelData.ForceBeaconStation == null && string.IsNullOrEmpty(GenerationParams.ForceBeaconStation)) { return; }
|
||||
var beaconStationFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<BeaconStationFile>())
|
||||
.OrderBy(f => f.UintIdentifier).ToList();
|
||||
@@ -4307,6 +4393,10 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Failed to find the beacon station \"{GenerationParams.ForceBeaconStation}\". Using a random one instead...");
|
||||
}
|
||||
}
|
||||
else if (LevelData.ForceBeaconStation != null)
|
||||
{
|
||||
contentFile = beaconStationFiles.FirstOrDefault(b => b.Path == LevelData.ForceBeaconStation.FilePath);
|
||||
}
|
||||
|
||||
if (contentFile == null)
|
||||
{
|
||||
@@ -4384,10 +4474,17 @@ namespace Barotrauma
|
||||
fuelPrefab, reactorContainer.Inventory,
|
||||
onSpawned: (it) => reactorComponent.PowerUpImmediately());
|
||||
}
|
||||
beaconSonar.CurrentMode = Sonar.Mode.Active;
|
||||
if (beaconSonar == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Beacon station \"{BeaconStation.Info.Name}\" has no sonar. Beacon missions might not work correctly with this beacon station.");
|
||||
}
|
||||
else
|
||||
{
|
||||
beaconSonar.CurrentMode = Sonar.Mode.Active;
|
||||
#if SERVER
|
||||
beaconSonar.Item.CreateServerEvent(beaconSonar);
|
||||
beaconSonar.Item.CreateServerEvent(beaconSonar);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
@@ -4470,7 +4567,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var connectedSub in parentSub.GetConnectedSubs())
|
||||
{
|
||||
connectedSub.RealWorldCrushDepth = Math.Max(connectedSub.RealWorldCrushDepth, GetRealWorldDepth(0) + 1000);
|
||||
connectedSub.SetCrushDepth(Math.Max(connectedSub.RealWorldCrushDepth, GetRealWorldDepth(0) + 1000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4631,7 +4728,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the "real" depth in meters from the surface of Europa
|
||||
/// Calculate the "real" depth in meters from the surface of Europa (the value you see on the nav terminal)
|
||||
/// </summary>
|
||||
public float GetRealWorldDepth(float worldPositionY)
|
||||
{
|
||||
@@ -4733,4 +4830,30 @@ namespace Barotrauma
|
||||
Loaded = null;
|
||||
}
|
||||
}
|
||||
|
||||
static class PositionTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Caves, ruins, outposts and similar enclosed areas
|
||||
/// </summary>
|
||||
public static bool IsEnclosedArea(this Level.PositionType positionType)
|
||||
{
|
||||
return
|
||||
positionType == Level.PositionType.Cave ||
|
||||
positionType == Level.PositionType.AbyssCave ||
|
||||
positionType.IsIndoorsArea();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Area inside a submarine (outpost, wreck, beacon station)
|
||||
/// </summary>
|
||||
public static bool IsIndoorsArea(this Level.PositionType positionType)
|
||||
{
|
||||
return
|
||||
positionType == Level.PositionType.Outpost ||
|
||||
positionType == Level.PositionType.BeaconStation ||
|
||||
positionType == Level.PositionType.Ruin ||
|
||||
positionType == Level.PositionType.Wreck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user