Unstable 0.1500.5.0 (almost forgor edition 💀)

This commit is contained in:
Markus Isberg
2021-10-01 23:56:14 +09:00
parent 3043a9a7bc
commit 08bdfc6cea
150 changed files with 5669 additions and 4403 deletions
@@ -30,55 +30,44 @@ namespace Barotrauma
private bool idFreed;
public virtual bool Removed
{
get;
private set;
}
public virtual bool Removed { get; private set; }
public bool IdFreed
{
get { return idFreed; }
}
public bool IdFreed => idFreed;
public readonly ushort ID;
public virtual Vector2 SimPosition
public virtual Vector2 SimPosition => Vector2.Zero;
public virtual Vector2 Position => Vector2.Zero;
public virtual Vector2 WorldPosition => Submarine == null ? Position : Submarine.Position + Position;
public virtual Vector2 DrawPosition => Submarine == null ? Position : Submarine.DrawPosition + Position;
public Submarine Submarine { get; set; }
public AITarget AiTarget => aiTarget;
public bool InDetectable
{
get { return Vector2.Zero; }
}
public virtual Vector2 Position
{
get { return Vector2.Zero; }
}
public virtual Vector2 WorldPosition
{
get { return Submarine == null ? Position : Submarine.Position + Position; }
}
public virtual Vector2 DrawPosition
{
get { return Submarine == null ? Position : Submarine.DrawPosition + Position; }
}
public Submarine Submarine
{
get;
set;
}
public AITarget AiTarget
{
get { return aiTarget; }
}
public double SpawnTime
{
get { return spawnTime; }
get
{
if (aiTarget != null)
{
return aiTarget.InDetectable;
}
return false;
}
set
{
if (aiTarget != null)
{
aiTarget.InDetectable = value;
}
}
}
public double SpawnTime => spawnTime;
private readonly double spawnTime;
public Entity(Submarine submarine, ushort id)
@@ -88,7 +77,7 @@ namespace Barotrauma
if (id != NullEntityID && dictionary.ContainsKey(id))
{
throw new Exception($"ID {id} is taken by {dictionary[id].ToString()}");
throw new Exception($"ID {id} is taken by {dictionary[id]}");
}
//give a unique ID
@@ -325,6 +325,7 @@ namespace Barotrauma
get { return LevelData.Seed; }
}
public static float? ForcedDifficulty;
public float Difficulty
{
@@ -527,6 +528,7 @@ namespace Barotrauma
//create a tunnel from the lowest point in the main path to the abyss
//to ensure there's a way to the abyss in all levels
Tunnel abyssTunnel = null;
if (GenerationParams.CreateHoleToAbyss)
{
Point lowestPoint = mainPath.Nodes.First();
@@ -534,7 +536,7 @@ namespace Barotrauma
{
if (pathNode.Y < lowestPoint.Y) { lowestPoint = pathNode; }
}
var abyssTunnel = new Tunnel(
abyssTunnel = new Tunnel(
TunnelType.SidePath,
new List<Point>() { lowestPoint, new Point(lowestPoint.X, 0) },
minWidth / 2, parentTunnel: mainPath);
@@ -545,7 +547,7 @@ namespace Barotrauma
for (int j = 0; j < sideTunnelCount; j++)
{
if (mainPath.Nodes.Count < 4) { break; }
var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole);
var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole && t != abyssTunnel);
Tunnel tunnelToBranchOff = validTunnels[Rand.Int(validTunnels.Count, Rand.RandSync.Server)];
if (tunnelToBranchOff == null) { tunnelToBranchOff = mainPath; }
@@ -558,7 +560,7 @@ namespace Barotrauma
Tunnels.Add(new Tunnel(TunnelType.SidePath, sidePathNodes, pathWidth, parentTunnel: tunnelToBranchOff));
}
CalculateTunnelDistanceField(density: 1000);
CalculateTunnelDistanceField(null);
GenerateSeaFloorPositions();
GenerateAbyssArea();
GenerateCaves(mainPath);
@@ -690,7 +692,10 @@ namespace Barotrauma
}
}
}
GenerateWaypoints(tunnel, parentTunnel: tunnel.ParentTunnel);
bool connectToParentTunnel = tunnel.Type != TunnelType.Cave || tunnel.ParentTunnel.Type == TunnelType.Cave;
GenerateWaypoints(tunnel, parentTunnel: connectToParentTunnel ? tunnel.ParentTunnel : null);
EnlargePath(tunnel.Cells, tunnel.MinWidth);
foreach (var pathCell in tunnel.Cells)
{
@@ -790,6 +795,15 @@ namespace Barotrauma
cells.AddRange(abyssIsland.Cells);
}
List<Point> ruinPositions = new List<Point>();
for (int i = 0; i < GenerationParams.RuinCount; i++)
{
Point ruinSize = new Point(5000);
ruinPositions.Add(FindPosAwayFromMainPath((Math.Max(ruinSize.X, ruinSize.Y) + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true,
limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize)));
CalculateTunnelDistanceField(ruinPositions);
}
//----------------------------------------------------------------------------------
// initialize the cells that are still left and insert them into the cell grid
//----------------------------------------------------------------------------------
@@ -812,7 +826,9 @@ namespace Barotrauma
//----------------------------------------------------------------------------------
// mirror if needed
//----------------------------------------------------------------------------------
int asdfasdf = Rand.Int(int.MaxValue, Rand.RandSync.Server);
if (mirror)
{
HashSet<GraphEdge> mirroredEdges = new HashSet<GraphEdge>();
@@ -850,6 +866,11 @@ namespace Barotrauma
island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height);
}
for (int i = 0; i < ruinPositions.Count; i++)
{
ruinPositions[i] = new Point(borders.Width - ruinPositions[i].X, ruinPositions[i].Y);
}
foreach (Cave cave in Caves)
{
cave.Area = new Rectangle(borders.Width - cave.Area.Right, cave.Area.Y, cave.Area.Width, cave.Area.Height);
@@ -895,7 +916,7 @@ namespace Barotrauma
startExitPosition.X = borders.Width - startExitPosition.X;
endExitPosition.X = borders.Width - endExitPosition.X;
CalculateTunnelDistanceField(density: 1000);
CalculateTunnelDistanceField(ruinPositions);
}
foreach (VoronoiCell cell in cells)
@@ -912,8 +933,23 @@ namespace Barotrauma
foreach (Cave cave in Caves)
{
if (cave.Area.Y > 0)
{
CreatePathToClosestTunnel(cave.StartPos);
{
List<VoronoiCell> cavePathCells = CreatePathToClosestTunnel(cave.StartPos);
var mainTunnel = cave.Tunnels.Find(t => t.ParentTunnel.Type != TunnelType.Cave);
WayPoint prevWp = mainTunnel.WayPoints.First();
if (prevWp != null)
{
for (int i = 0; i < cavePathCells.Count; i++)
{
var newWaypoint = new WayPoint(cavePathCells[i].Center, SpawnType.Path, submarine: null);
ConnectWaypoints(prevWp, newWaypoint, 500.0f);
prevWp = newWaypoint;
}
var closestPathPoint = FindClosestWayPoint(prevWp.WorldPosition, mainTunnel.ParentTunnel.WayPoints);
ConnectWaypoints(prevWp, closestPathPoint, 500.0f);
}
}
List<VoronoiCell> caveCells = new List<VoronoiCell>();
@@ -939,9 +975,10 @@ namespace Barotrauma
//----------------------------------------------------------------------------------
Ruins = new List<Ruin>();
for (int i = 0; i < GenerationParams.RuinCount; i++)
for (int i = 0; i < ruinPositions.Count; i++)
{
GenerateRuin(mainPath, mirror);
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed) + i);
GenerateRuin(ruinPositions[i], mirror);
}
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
@@ -1003,7 +1040,6 @@ namespace Barotrauma
}
}
#if CLIENT
List<(List<VoronoiCell> cells, Cave parentCave)> cellBatches = new List<(List<VoronoiCell>, Cave)>
{
@@ -1082,7 +1118,6 @@ namespace Barotrauma
}
#endif
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
//----------------------------------------------------------------------------------
@@ -1100,6 +1135,11 @@ namespace Barotrauma
// connect side paths and cave branches to their parents
//----------------------------------------------------------------------------------
foreach (Ruin ruin in Ruins)
{
GenerateRuinWayPoints(ruin);
}
foreach (Tunnel tunnel in Tunnels)
{
if (tunnel.ParentTunnel == null) { continue; }
@@ -1342,18 +1382,7 @@ namespace Barotrauma
if (wayPoints.Count > 1)
{
wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
}
for (int n = 0; n < wayPoints.Count; n++)
{
if (wayPoints[n].Position != newWaypoint.Position) { continue; }
wayPoints[n].linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(wayPoints[n]);
break;
wayPoints[wayPoints.Count - 2].ConnectTo(newWaypoint);
}
}
@@ -1362,19 +1391,17 @@ namespace Barotrauma
//connect to the tunnel we're branching off from
if (parentTunnel != null)
{
var parentStart = FindClosestWayPoint(wayPoints.First(), parentTunnel);
var parentStart = FindClosestWayPoint(wayPoints.First().WorldPosition, parentTunnel);
if (parentStart != null)
{
wayPoints.First().linkedTo.Add(parentStart);
parentStart.linkedTo.Add(wayPoints.First());
wayPoints.First().ConnectTo(parentStart);
}
if (tunnel.Type != TunnelType.Cave || tunnel.ParentTunnel.Type == TunnelType.Cave)
{
var parentEnd = FindClosestWayPoint(wayPoints.Last(), parentTunnel);
var parentEnd = FindClosestWayPoint(wayPoints.Last().WorldPosition, parentTunnel);
if (parentEnd != null)
{
wayPoints.Last().linkedTo.Add(parentEnd);
parentEnd.linkedTo.Add(wayPoints.Last());
wayPoints.Last().ConnectTo(parentEnd);
}
}
}
@@ -1384,45 +1411,58 @@ namespace Barotrauma
{
foreach (WayPoint wayPoint in tunnel.WayPoints)
{
var closestWaypoint = FindClosestWayPoint(wayPoint, parentTunnel);
var closestWaypoint = FindClosestWayPoint(wayPoint.WorldPosition, parentTunnel);
if (closestWaypoint == null) { continue; }
if (Submarine.PickBody(
ConvertUnits.ToSimUnits(wayPoint.WorldPosition),
ConvertUnits.ToSimUnits(closestWaypoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null)
{
Vector2 diff = closestWaypoint.WorldPosition - wayPoint.WorldPosition;
float dist = diff.Length();
float step = ConvertUnits.ToDisplayUnits(Steering.AutopilotMinDistToPathNode) * 0.8f;
WayPoint prevWaypoint = wayPoint;
for (float x = step; x < dist - step; x += step)
{
var newWaypoint = new WayPoint(wayPoint.WorldPosition + (diff / dist * x), SpawnType.Path, submarine: null)
{
Tunnel = tunnel
};
prevWaypoint.linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(prevWaypoint);
prevWaypoint = newWaypoint;
}
prevWaypoint.linkedTo.Add(closestWaypoint);
closestWaypoint.linkedTo.Add(prevWaypoint);
ConnectWaypoints(wayPoint, closestWaypoint, step).ForEach(wp => wp.Tunnel = tunnel);
}
}
}
private static WayPoint FindClosestWayPoint(WayPoint wayPoint, Tunnel otherTunnel)
private List<WayPoint> ConnectWaypoints(WayPoint wp1, WayPoint wp2, float interval)
{
List<WayPoint> newWaypoints = new List<WayPoint>();
Vector2 diff = wp2.WorldPosition - wp1.WorldPosition;
float dist = diff.Length();
WayPoint prevWaypoint = wp1;
for (float x = interval; x < dist - interval; x += interval)
{
var newWaypoint = new WayPoint(wp1.WorldPosition + (diff / dist * x), SpawnType.Path, submarine: null);
prevWaypoint.ConnectTo(newWaypoint);
prevWaypoint = newWaypoint;
newWaypoints.Add(newWaypoint);
}
prevWaypoint.ConnectTo(wp2);
return newWaypoints;
}
private static WayPoint FindClosestWayPoint(Vector2 worldPosition, Tunnel otherTunnel)
{
return FindClosestWayPoint(worldPosition, otherTunnel.WayPoints);
}
private static WayPoint FindClosestWayPoint(Vector2 worldPosition, IEnumerable<WayPoint> waypoints, Func<WayPoint, bool> filter = null)
{
float closestDist = float.PositiveInfinity;
WayPoint closestWayPoint = null;
foreach (WayPoint otherWayPoint in otherTunnel.WayPoints)
foreach (WayPoint otherWayPoint in waypoints)
{
float dist = Vector2.DistanceSquared(otherWayPoint.WorldPosition, wayPoint.WorldPosition);
float dist = Vector2.DistanceSquared(otherWayPoint.WorldPosition, worldPosition);
if (dist < closestDist)
{
if (filter != null)
{
if (!filter(otherWayPoint)) { continue; }
}
closestDist = dist;
closestWayPoint = otherWayPoint;
}
}
return closestWayPoint;
@@ -1705,7 +1745,7 @@ namespace Barotrauma
GenerateCave(caveParams, parentTunnel, cavePos, caveSize);
CalculateTunnelDistanceField(density: 1000);
CalculateTunnelDistanceField(null);
}
}
@@ -1787,84 +1827,145 @@ namespace Barotrauma
}
}
private void GenerateRuin(Tunnel mainPath, bool mirror)
private void GenerateRuin(Point ruinPos, bool mirror)
{
var ruinGenerationParams = RuinGenerationParams.GetRandom();
Point ruinSize = new Point(
Rand.Range(ruinGenerationParams.SizeMin.X, ruinGenerationParams.SizeMax.X, Rand.RandSync.Server),
Rand.Range(ruinGenerationParams.SizeMin.Y, ruinGenerationParams.SizeMax.Y, Rand.RandSync.Server));
int ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) / 2;
Point ruinPos = FindPosAwayFromMainPath((ruinRadius + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true,
limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize));
VoronoiCell closestPathCell = null;
double closestDist = 0.0f;
foreach (VoronoiCell pathCell in mainPath.Cells)
LocationType locationType = StartLocation?.Type;
if (locationType == null)
{
double dist = MathUtils.DistanceSquared(pathCell.Site.Coord.X, pathCell.Site.Coord.Y, ruinPos.X, ruinPos.Y);
if (closestPathCell == null || dist < closestDist)
locationType = LocationType.List.GetRandom(Rand.RandSync.Server);
if (ruinGenerationParams.AllowedLocationTypes.Any())
{
closestPathCell = pathCell;
closestDist = dist;
locationType = LocationType.List.Where(lt =>
ruinGenerationParams.AllowedLocationTypes.Any(allowedType =>
allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom();
}
}
var ruin = new Ruin(closestPathCell, cells, ruinGenerationParams, new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize), mirror);
var ruin = new Ruin(this, ruinGenerationParams, locationType, ruinPos, mirror);
Ruins.Add(ruin);
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
// TODO: autogenerate waypoints inside the ruins and connect them to the main path in multiple places.
// We need the waypoints for the AI navigation and we could use them for spawning the creatures too.
int waypointCount = 0;
foreach (WayPoint wp in WayPoint.WayPointList)
var tooClose = GetTooCloseCells(ruinPos.ToVector2(), Math.Max(ruin.Area.Width, ruin.Area.Height) * 4);
foreach (VoronoiCell cell in tooClose)
{
if (wp.SpawnType != SpawnType.Enemy || wp.Submarine != null) { continue; }
if (ruin.RuinShapes.Any(rs => rs.Rect.Contains(wp.WorldPosition)))
if (cell.CellType == CellType.Empty) { continue; }
if (ExtraWalls.Any(w => w.Cells.Contains(cell))) { continue; }
foreach (GraphEdge e in cell.Edges)
{
PositionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin, ruin: ruin));
waypointCount++;
}
}
//not enough waypoints inside ruins -> create some spawn positions manually
for (int i = 0; i < 4 - waypointCount && i < ruin.RuinShapes.Count; i++)
{
PositionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin, ruin: ruin));
}
foreach (RuinShape ruinShape in ruin.RuinShapes)
{
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height) * 4);
foreach (VoronoiCell cell in tooClose)
{
if (cell.CellType == CellType.Empty) { continue; }
if (ExtraWalls.Any(w => w.Cells.Contains(cell))) { continue; }
foreach (GraphEdge e in cell.Edges)
if (ruin.Area.Contains(e.Point1) || ruin.Area.Contains(e.Point2) ||
MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, ruin.Area, out _))
{
Rectangle rect = ruinShape.Rect;
rect.Y += rect.Height;
if (ruinShape.Rect.Contains(e.Point1) || ruinShape.Rect.Contains(e.Point2) ||
MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, rect, out _))
cell.CellType = CellType.Removed;
for (int x = 0; x < cellGrid.GetLength(0); x++)
{
cell.CellType = CellType.Removed;
for (int x = 0; x < cellGrid.GetLength(0); x++)
for (int y = 0; y < cellGrid.GetLength(1); y++)
{
for (int y = 0; y < cellGrid.GetLength(1); y++)
{
cellGrid[x, y].Remove(cell);
}
cellGrid[x, y].Remove(cell);
}
cells.Remove(cell);
break;
}
cells.Remove(cell);
break;
}
}
}
CreatePathToClosestTunnel(ruinPos);
ruin.PathCells = CreatePathToClosestTunnel(ruin.Area.Center);
}
private void GenerateRuinWayPoints(Ruin ruin)
{
var tooClose = GetTooCloseCells(ruin.Area.Center.ToVector2(), Math.Max(ruin.Area.Width, ruin.Area.Height) * 6);
List<WayPoint> wayPoints = new List<WayPoint>();
float outSideWaypointInterval = 500.0f;
WayPoint[,] cornerWaypoint = new WayPoint[2, 2];
Rectangle waypointArea = ruin.Area;
waypointArea.Inflate(100, 100);
//generate waypoints around the ruin
for (int i = 0; i < 2; i++)
{
for (float x = waypointArea.X + outSideWaypointInterval; x < waypointArea.Right - outSideWaypointInterval; x += outSideWaypointInterval)
{
var wayPoint = new WayPoint(new Vector2(x, waypointArea.Y + waypointArea.Height * i), SpawnType.Path, null);
wayPoints.Add(wayPoint);
if (x == waypointArea.X + outSideWaypointInterval)
{
cornerWaypoint[i, 0] = wayPoint;
}
else
{
wayPoint.ConnectTo(wayPoints[wayPoints.Count - 2]);
}
}
cornerWaypoint[i, 1] = wayPoints[wayPoints.Count - 1];
}
for (int i = 0; i < 2; i++)
{
WayPoint wayPoint = null;
for (float y = waypointArea.Y; y < waypointArea.Y + waypointArea.Height; y += outSideWaypointInterval)
{
wayPoint = new WayPoint(new Vector2(waypointArea.X + waypointArea.Width * i, y), SpawnType.Path, null);
wayPoints.Add(wayPoint);
if (y == waypointArea.Y)
{
wayPoint.ConnectTo(cornerWaypoint[0, i]);
}
else
{
wayPoint.ConnectTo(wayPoints[wayPoints.Count - 2]);
}
}
wayPoint.ConnectTo(cornerWaypoint[1, i]);
}
//remove waypoints that are inside walls
for (int i = wayPoints.Count - 1; i >= 0; i--)
{
WayPoint wp = wayPoints[i];
var overlappingCell = tooClose.Find(c => c.CellType != CellType.Removed && c.IsPointInside(wp.WorldPosition));
if (overlappingCell == null) { continue; }
if (wp.linkedTo.Count > 1)
{
WayPoint linked1 = wp.linkedTo[0] as WayPoint;
WayPoint linked2 = wp.linkedTo[1] as WayPoint;
linked1.ConnectTo(linked2);
}
wp.Remove();
wayPoints.RemoveAt(i);
}
//connect ruin entrances to the outside waypoints
foreach (Gap g in Gap.GapList)
{
if (g.Submarine != ruin.Submarine || g.IsRoomToRoom) { continue; }
var gapWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == g);
if (gapWaypoint == null) { continue; }
var closestWp = FindClosestWayPoint(gapWaypoint.WorldPosition, wayPoints);
if (closestWp == null) { continue; }
gapWaypoint.ConnectTo(closestWp);
}
//create a waypoint path from the ruin to the closest tunnel
WayPoint prevWp = FindClosestWayPoint(ruin.PathCells.First().Center, wayPoints, (wp) =>
{
return Submarine.PickBody(
ConvertUnits.ToSimUnits(wp.WorldPosition),
ConvertUnits.ToSimUnits(ruin.PathCells.First().Center), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null;
});
if (prevWp != null)
{
for (int i = 0; i < ruin.PathCells.Count; i++)
{
var newWaypoint = new WayPoint(ruin.PathCells[i].Center, SpawnType.Path, submarine: null);
ConnectWaypoints(prevWp, newWaypoint, outSideWaypointInterval);
prevWp = newWaypoint;
}
var closestPathPoint = FindClosestWayPoint(prevWp.WorldPosition, Tunnels.SelectMany(t => t.WayPoints));
ConnectWaypoints(prevWp, closestPathPoint, outSideWaypointInterval);
}
}
private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null)
@@ -1890,8 +1991,9 @@ namespace Barotrauma
}
}
private void CalculateTunnelDistanceField(int density)
private void CalculateTunnelDistanceField(List<Point> ruinPositions)
{
int density = 1000;
distanceField = new List<(Point point, double distance)>();
if (Mirrored)
@@ -1926,6 +2028,23 @@ namespace Barotrauma
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
}
}
if (ruinPositions != null)
{
int ruinSize = 10000;
foreach (Point ruinPos in ruinPositions)
{
double xDiff = Math.Abs(point.X - ruinPos.X);
double yDiff = Math.Abs(point.Y - ruinPos.Y);
if (xDiff < ruinSize || yDiff < ruinSize)
{
shortestDistSqr = 0.0f;
}
else
{
shortestDistSqr = Math.Min(xDiff * xDiff + yDiff * yDiff, shortestDistSqr);
}
}
}
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
@@ -2953,7 +3072,7 @@ namespace Barotrauma
return closestCell;
}
private void CreatePathToClosestTunnel(Point pos)
private List<VoronoiCell> CreatePathToClosestTunnel(Point pos)
{
VoronoiCell closestPathCell = null;
double closestDist = 0.0f;
@@ -2973,6 +3092,7 @@ namespace Barotrauma
//cast a ray from the closest path cell towards the position and remove the cells it hits
List<VoronoiCell> validCells = cells.FindAll(c => c.CellType != CellType.Empty && c.CellType != CellType.Removed);
List<VoronoiCell> pathCells = new List<VoronoiCell>() { closestPathCell };
foreach (VoronoiCell cell in validCells)
{
foreach (GraphEdge e in cell.Edges)
@@ -2987,6 +3107,7 @@ namespace Barotrauma
cellGrid[x, y].Remove(cell);
}
}
pathCells.Add(cell);
cells.Remove(cell);
//go through the edges of this cell and find the ones that are next to a removed cell
@@ -3019,12 +3140,19 @@ namespace Barotrauma
}
}
}
break;
}
}
pathCells.Sort((c1, c2) => { return Vector2.DistanceSquared(c1.Center, pos.ToVector2()).CompareTo(Vector2.DistanceSquared(c2.Center, pos.ToVector2())); });
return pathCells;
}
public string GetWreckIDTag(string originalTag, Submarine wreck)
{
string shortSeed = ToolBox.StringToInt(LevelData.Seed + wreck?.Info.Name).ToString();
if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); }
return originalTag + "_" + shortSeed;
}
public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist);
@@ -3049,10 +3177,8 @@ namespace Barotrauma
var waypoints = WayPoint.WayPointList.Where(wp =>
wp.Submarine == null &&
wp.SpawnType == SpawnType.Path &&
wp.WorldPosition.X < EndExitPosition.X &&
!IsCloseToStart(wp.WorldPosition, minDistance) &&
!IsCloseToEnd(wp.WorldPosition, minDistance)
).ToList();
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
@@ -3137,7 +3263,7 @@ namespace Barotrauma
}
tempSW.Stop();
Debug.WriteLine($"Sub {sub.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)");
sub.SetPosition(spawnPoint, forceUndockFromStaticSubmarines: false);
sub.SetPosition(spawnPoint);
wreckPositions.Add(sub, positions);
blockedRects.Add(sub, rects);
return sub;
@@ -84,25 +84,42 @@ namespace Barotrauma
objectGrid = new List<LevelObject>[
level.Size.X / GridSize,
(level.Size.Y - level.BottomPos) / GridSize];
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
var levelCells = level.GetAllCells();
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall));
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall));
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(level.SeaFloor.Cells, LevelObjectPrefab.SpawnPosType.SeaFloor));
foreach (RuinGeneration.Ruin ruin in level.Ruins)
foreach (Structure structure in Structure.WallList)
{
foreach (var ruinShape in ruin.RuinShapes)
if (!structure.HasBody || structure.HiddenInGame) { continue; }
if (level.Ruins.Any(r => r.Submarine == structure.Submarine))
{
foreach (var wall in ruinShape.Walls)
if (structure.IsHorizontal)
{
bool topHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitY * 64) != null;
bool bottomHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitY * 64) != null;
if (topHull && bottomHull ) { continue; }
availableSpawnPositions.Add(new SpawnPosition(
new GraphEdge(wall.A, wall.B),
(wall.A + wall.B) / 2.0f - ruinShape.Center,
new GraphEdge(new Vector2(structure.WorldRect.X, structure.WorldPosition.Y), new Vector2(structure.WorldRect.Right, structure.WorldPosition.Y)),
bottomHull ? Vector2.UnitY : -Vector2.UnitY,
LevelObjectPrefab.SpawnPosType.RuinWall,
ruinShape.GetLineAlignment(wall)));
bottomHull ? Alignment.Bottom : Alignment.Top));
}
}
else
{
bool rightHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitX * 64) != null;
bool leftHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitX * 64) != null;
if (rightHull && leftHull) { continue; }
availableSpawnPositions.Add(new SpawnPosition(
new GraphEdge(new Vector2(structure.WorldPosition.X, structure.WorldRect.Y), new Vector2(structure.WorldPosition.X, structure.WorldRect.Y - structure.WorldRect.Height)),
leftHull ? Vector2.UnitX : -Vector2.UnitX,
LevelObjectPrefab.SpawnPosType.RuinWall,
leftHull ? Alignment.Left : Alignment.Right));
}
}
}
foreach (var posOfInterest in level.PositionsOfInterest)
@@ -13,7 +13,7 @@ namespace Barotrauma
partial class LevelTrigger
{
[Flags]
enum TriggererType
public enum TriggererType
{
None = 0,
Human = 1,
@@ -258,7 +258,11 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Error in LevelTrigger config: \"" + triggeredByStr + "\" is not a valid triggerer type.");
}
UpdateCollisionCategories();
if (PhysicsBody != null)
{
PhysicsBody.CollidesWith = GetCollisionCategories(triggeredBy);
}
TriggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
var tagsArray = element.GetAttributeStringArray("tags", new string[0]);
@@ -276,26 +280,17 @@ namespace Barotrauma
}
}
string debugName = string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : $"LevelTrigger in {parentDebugName}";
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "statuseffect":
statusEffects.Add(StatusEffect.Load(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in "+ parentDebugName));
LoadStatusEffect(statusEffects, subElement, debugName);
break;
case "attack":
case "damage":
var attack = new Attack(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in " + parentDebugName);
if (!triggerOnce)
{
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
attack.Afflictions.Clear();
foreach (Affliction affliction in multipliedAfflictions)
{
attack.Afflictions.Add(affliction, null);
}
}
attacks.Add(attack);
LoadAttack(subElement, debugName, triggerOnce, attacks);
break;
}
}
@@ -304,16 +299,13 @@ namespace Barotrauma
randomTriggerTimer = Rand.Range(0.0f, randomTriggerInterval);
}
private void UpdateCollisionCategories()
public static Category GetCollisionCategories(TriggererType triggeredBy)
{
if (PhysicsBody == null) return;
var collidesWith = Physics.CollisionNone;
if (triggeredBy.HasFlag(TriggererType.Human) || triggeredBy.HasFlag(TriggererType.Creature)) { collidesWith |= Physics.CollisionCharacter; }
if (triggeredBy.HasFlag(TriggererType.Item)) { collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile; }
if (triggeredBy.HasFlag(TriggererType.Submarine)) { collidesWith |= Physics.CollisionWall; }
PhysicsBody.CollidesWith = collidesWith;
return collidesWith;
}
private void CalculateDirectionalForce()
@@ -326,33 +318,31 @@ namespace Barotrauma
-sa * unrotatedForce.X + ca * unrotatedForce.Y);
}
private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact)
public static void LoadStatusEffect(List<StatusEffect> statusEffects, XElement element, string parentDebugName)
{
statusEffects.Add(StatusEffect.Load(element, parentDebugName));
}
public static void LoadAttack(XElement element, string parentDebugName, bool triggerOnce, List<Attack> attacks)
{
var attack = new Attack(element, parentDebugName);
if (!triggerOnce)
{
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
attack.Afflictions.Clear();
foreach (Affliction affliction in multipliedAfflictions)
{
attack.Afflictions.Add(affliction, null);
}
}
attacks.Add(attack);
}
private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact)
{
Entity entity = GetEntity(fixtureB);
if (entity == null) return false;
if (entity is Character character)
{
if (character.CurrentHull != null) return false;
if (character.IsHuman)
{
if (!triggeredBy.HasFlag(TriggererType.Human)) return false;
}
else
{
if (!triggeredBy.HasFlag(TriggererType.Creature)) return false;
}
}
else if (entity is Item item)
{
if (item.CurrentHull != null) return false;
if (!triggeredBy.HasFlag(TriggererType.Item)) return false;
}
else if (entity is Submarine)
{
if (!triggeredBy.HasFlag(TriggererType.Submarine)) return false;
}
if (entity == null) { return false; }
if (!IsTriggeredByEntity(entity, triggeredBy, mustBeOutside: true)) { return false; }
if (!triggerers.Contains(entity))
{
if (!IsTriggered)
@@ -365,6 +355,34 @@ namespace Barotrauma
return true;
}
public static bool IsTriggeredByEntity(Entity entity, TriggererType triggeredBy, bool mustBeOutside = false, (bool mustBe, Submarine sub) mustBeOnSpecificSub = default)
{
if (entity is Character character)
{
if (mustBeOutside && character.CurrentHull != null) { return false; }
if (mustBeOnSpecificSub.mustBe && character.Submarine != mustBeOnSpecificSub.sub) { return false; }
if (character.IsHuman)
{
if (!triggeredBy.HasFlag(TriggererType.Human)) { return false; }
}
else
{
if (!triggeredBy.HasFlag(TriggererType.Creature)) { return false; }
}
}
else if (entity is Item item)
{
if (mustBeOutside && item.CurrentHull != null) { return false; }
if (mustBeOnSpecificSub.mustBe && item.Submarine != mustBeOnSpecificSub.sub) { return false; }
if (!triggeredBy.HasFlag(TriggererType.Item)) { return false; }
}
else if (entity is Submarine)
{
if (!triggeredBy.HasFlag(TriggererType.Submarine)) { return false; }
}
return true;
}
private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact)
{
Entity entity = GetEntity(fixtureB);
@@ -379,10 +397,21 @@ namespace Barotrauma
return;
}
if (CheckContactsForOtherFixtures(PhysicsBody, fixtureB, entity)) { return; }
if (triggerers.Contains(entity))
{
TriggererPosition.Remove(entity);
triggerers.Remove(entity);
}
}
public static bool CheckContactsForOtherFixtures(PhysicsBody triggerBody, Fixture otherFixture, Entity separatingEntity)
{
//check if there are contacts with any other fixture of the trigger
//(the OnSeparation callback happens when two fixtures separate,
//e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body)
foreach (Fixture fixture in PhysicsBody.FarseerBody.FixtureList)
foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList)
{
ContactEdge contactEdge = fixture.Body.ContactList;
while (contactEdge != null)
@@ -393,30 +422,24 @@ namespace Barotrauma
{
if (contactEdge.Contact.FixtureA != fixture && contactEdge.Contact.FixtureB != fixture)
{
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == otherFixture ?
contactEdge.Contact.FixtureB :
contactEdge.Contact.FixtureA);
if (otherEntity == entity) { return; }
if (otherEntity == separatingEntity) { return true; }
}
}
contactEdge = contactEdge.Next;
}
}
if (triggerers.Contains(entity))
{
TriggererPosition.Remove(entity);
triggerers.Remove(entity);
}
return false;
}
private Entity GetEntity(Fixture fixture)
public static Entity GetEntity(Fixture fixture)
{
if (fixture.Body == null || fixture.Body.UserData == null) { return null; }
if (fixture.Body.UserData is Entity entity) { return entity; }
if (fixture.Body.UserData is Limb limb) { return limb.character; }
if (fixture.Body.UserData is SubmarineBody subBody) { return subBody.Submarine; }
return null;
}
@@ -452,15 +475,7 @@ namespace Barotrauma
triggerers.RemoveWhere(t => t.Removed);
if (PhysicsBody != null)
{
//failsafe to ensure triggerers get removed when they're far from the trigger
float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(PhysicsBody.GetMaxExtent() * 5), 5000.0f);
triggerers.RemoveWhere(t =>
{
return Vector2.Distance(t.WorldPosition, WorldPosition) > maxExtent;
});
}
RemoveDistantTriggerers(PhysicsBody, triggerers, WorldPosition);
bool isNotClient = true;
#if CLIENT
@@ -525,57 +540,15 @@ namespace Barotrauma
foreach (Entity triggerer in triggerers)
{
foreach (StatusEffect effect in statusEffects)
{
if (effect.type == ActionType.OnBroken) { continue; }
Vector2? position = null;
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; }
if (triggerer is Character character)
{
effect.Apply(effect.type, deltaTime, triggerer, character, position);
if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null)
{
foreach (Item item in character.Inventory.AllItemsMod)
{
if (item.ContainedItems == null) { continue; }
foreach (Item containedItem in item.ContainedItems)
{
effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position);
}
}
}
}
else if (triggerer is Item item)
{
effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
effect.Apply(effect.type, deltaTime, triggerer, targets);
}
}
ApplyStatusEffects(statusEffects, worldPosition, triggerer, deltaTime, targets);
if (triggerer is IDamageable damageable)
{
foreach (Attack attack in attacks)
{
attack.DoDamage(null, damageable, WorldPosition, deltaTime, false);
}
ApplyAttacks(attacks, damageable, worldPosition, deltaTime);
}
else if (triggerer is Submarine submarine)
{
foreach (Attack attack in attacks)
{
float structureDamage = attack.GetStructureDamage(deltaTime);
if (structureDamage > 0.0f)
{
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f);
}
}
ApplyAttacks(attacks, worldPosition, deltaTime);
if (!string.IsNullOrWhiteSpace(InfectIdentifier))
{
submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance);
@@ -586,16 +559,16 @@ namespace Barotrauma
{
if (triggerer is Character character)
{
ApplyForce(character.AnimController.Collider, deltaTime);
ApplyForce(character.AnimController.Collider);
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
ApplyForce(limb.body, deltaTime);
ApplyForce(limb.body);
}
}
else if (triggerer is Submarine submarine)
{
ApplyForce(submarine.SubBody.Body, deltaTime);
ApplyForce(submarine.SubBody.Body);
}
}
@@ -606,12 +579,84 @@ namespace Barotrauma
}
}
private void ApplyForce(PhysicsBody body, float deltaTime)
public static void RemoveDistantTriggerers(PhysicsBody physicsBody, HashSet<Entity> triggerers, Vector2 calculateDistanceTo)
{
//failsafe to ensure triggerers get removed when they're far from the trigger
if (physicsBody == null) { return; }
float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(physicsBody.GetMaxExtent() * 5), 5000.0f);
triggerers.RemoveWhere(t =>
{
return Vector2.Distance(t.WorldPosition, calculateDistanceTo) > maxExtent;
});
}
public static void ApplyStatusEffects(List<StatusEffect> statusEffects, Vector2 worldPosition, Entity triggerer, float deltaTime, List<ISerializableEntity> targets)
{
foreach (StatusEffect effect in statusEffects)
{
if (effect.type == ActionType.OnBroken) { return; }
Vector2? position = null;
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = worldPosition; }
if (triggerer is Character character)
{
effect.Apply(effect.type, deltaTime, triggerer, character, position);
if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null)
{
foreach (Item item in character.Inventory.AllItemsMod)
{
if (item.ContainedItems == null) { continue; }
foreach (Item containedItem in item.ContainedItems)
{
effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position);
}
}
}
}
else if (triggerer is Item item)
{
effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
effect.Apply(effect.type, deltaTime, triggerer, targets);
}
}
}
/// <summary>
/// Applies attacks to a damageable.
/// </summary>
public static void ApplyAttacks(List<Attack> attacks, IDamageable damageable, Vector2 worldPosition, float deltaTime)
{
foreach (Attack attack in attacks)
{
attack.DoDamage(null, damageable, worldPosition, deltaTime, false);
}
}
/// <summary>
/// Applies attacks to structures.
/// </summary>
public static void ApplyAttacks(List<Attack> attacks, Vector2 worldPosition, float deltaTime)
{
foreach (Attack attack in attacks)
{
float structureDamage = attack.GetStructureDamage(deltaTime);
if (structureDamage > 0.0f)
{
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f);
}
}
}
private void ApplyForce(PhysicsBody body)
{
float distFactor = 1.0f;
if (ForceFalloff)
{
distFactor = 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(body.SimPosition, PhysicsBody.SimPosition)) / ColliderRadius;
distFactor = GetDistanceFactor(body, PhysicsBody, ColliderRadius);
if (distFactor < 0.0f) return;
}
@@ -648,6 +693,11 @@ namespace Barotrauma
}
}
public static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius)
{
return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition)) / colliderRadius;
}
public Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
{
Vector2 baseVel = GetWaterFlowVelocity();
@@ -1,190 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.RuinGeneration
{
/// <summary>
/// nodes of a binary tree used for generating underwater "dungeons"
/// </summary>
class BTRoom : RuinShape
{
private BTRoom[] subRooms;
public BTRoom Parent
{
get;
private set;
}
public Corridor Corridor
{
get;
set;
}
public BTRoom[] SubRooms
{
get { return subRooms; }
}
public BTRoom Adjacent
{
get;
private set;
}
public BTRoom(Rectangle rect)
{
this.rect = rect;
}
public void Split(float minDivRatio, float verticalProbability = 0.5f, int minWidth = 200, int minHeight = 200)
{
bool verticalSplit = Rand.Range(0.0f, rect.Height / (float)rect.Width, Rand.RandSync.Server) < verticalProbability;
if (rect.Width * minDivRatio < minWidth && rect.Height * minDivRatio < minHeight)
{
minDivRatio = 0.5f;
}
else if (rect.Width * minDivRatio < minWidth)
{
verticalSplit = false;
}
else if (rect.Height * minDivRatio < minHeight)
{
verticalSplit = true;
}
subRooms = new BTRoom[2];
if (verticalSplit)
{
SplitVertical(minDivRatio);
}
else
{
SplitHorizontal(minDivRatio);
}
subRooms[0].Parent = this;
subRooms[1].Parent = this;
subRooms[0].Adjacent = subRooms[1];
subRooms[1].Adjacent = subRooms[0];
}
private void SplitHorizontal(float minDivRatio)
{
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, rect.Width, (int)(rect.Height * div)));
subRooms[1] = new BTRoom(new Rectangle(rect.X, rect.Y + subRooms[0].rect.Height, rect.Width, rect.Height - subRooms[0].rect.Height));
}
private void SplitVertical(float minDivRatio)
{
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, (int)(rect.Width * div), rect.Height));
subRooms[1] = new BTRoom(new Rectangle(rect.X + subRooms[0].rect.Width, rect.Y, rect.Width - subRooms[0].rect.Width, rect.Height));
}
public override void CreateWalls()
{
Walls = new List<Line>
{
new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y)),
new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom)),
new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom)),
new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom))
};
}
public void Scale(Vector2 scale)
{
rect.Inflate((scale.X - 1.0f) * 0.5f * rect.Width, (scale.Y - 1.0f) * 0.5f * rect.Height);
}
public List<BTRoom> GetLeaves()
{
return GetLeaves(new List<BTRoom>());
}
private List<BTRoom> GetLeaves(List<BTRoom> leaves)
{
if (subRooms == null)
{
leaves.Add(this);
}
else
{
subRooms[0].GetLeaves(leaves);
subRooms[1].GetLeaves(leaves);
}
return leaves;
}
public void GenerateCorridors(int minWidth, int maxWidth, List<Corridor> corridors)
{
if (Adjacent != null && Corridor == null)
{
Corridor = new Corridor(this, Rand.Range(minWidth, maxWidth, Rand.RandSync.Server), corridors);
}
if (subRooms != null)
{
subRooms[0].GenerateCorridors(minWidth, maxWidth, corridors);
subRooms[1].GenerateCorridors(minWidth, maxWidth, corridors);
}
}
public static void CalculateDistancesFromEntrance(BTRoom entrance, List<BTRoom> rooms, List<Corridor> corridors)
{
entrance.CalculateDistanceFromEntrance(0, rooms, new List<Corridor>(corridors));
}
private void CalculateDistanceFromEntrance(int currentDist, List<BTRoom> rooms, List<Corridor> corridors)
{
DistanceFromEntrance = DistanceFromEntrance == 0 ? currentDist : Math.Min(currentDist, DistanceFromEntrance);
currentDist++;
var roomRect = Rect;
roomRect.Inflate(5, 5);
foreach (var corridor in corridors)
{
var corridorRect = corridor.Rect;
corridorRect.Inflate(5, 5);
if (!corridorRect.Intersects(roomRect)) continue;
corridor.DistanceFromEntrance = corridor.DistanceFromEntrance == 0 ?
DistanceFromEntrance + 1 :
Math.Min(corridor.DistanceFromEntrance, DistanceFromEntrance + 1);
List<BTRoom> connectedRooms = new List<BTRoom>();
foreach (var otherRoom in rooms)
{
if (otherRoom == this) continue;
if (otherRoom.DistanceFromEntrance > 0 && otherRoom.DistanceFromEntrance < currentDist) continue;
var otherRoomRect = otherRoom.Rect;
otherRoomRect.Inflate(5, 5);
if (corridorRect.Intersects(otherRoomRect)) { connectedRooms.Add(otherRoom); }
}
connectedRooms.Sort((r1, r2) =>
{
return
(Math.Abs(r1.Rect.Center.X - Rect.Center.X) + Math.Abs(r1.Rect.Center.Y - Rect.Center.Y)) -
(Math.Abs(r2.Rect.Center.X - Rect.Center.X) + Math.Abs(r2.Rect.Center.Y - Rect.Center.Y));
});
for (int i = 0; i < connectedRooms.Count; i++)
{
connectedRooms[i].CalculateDistanceFromEntrance(currentDist + 1 + i, rooms, corridors);
}
}
}
}
}
@@ -1,210 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace Barotrauma.RuinGeneration
{
class Corridor : RuinShape
{
private readonly bool isHorizontal;
public bool IsHorizontal
{
get { return isHorizontal; }
}
public BTRoom[] ConnectedRooms
{
get;
private set;
}
public Corridor(Rectangle rect)
{
this.rect = rect;
isHorizontal = rect.Width > rect.Height;
}
public Corridor(BTRoom room, int width, List<Corridor> corridors)
{
System.Diagnostics.Debug.Assert(room.Adjacent != null);
ConnectedRooms = new BTRoom[2];
ConnectedRooms[0] = room;
ConnectedRooms[1] = room.Adjacent;
Rectangle room1, room2;
room1 = room.Rect;
room2 = room.Adjacent.Rect;
isHorizontal = (room1.Right <= room2.X || room2.Right <= room1.X);
//use the leaves as starting points for the corridor
if (room.SubRooms != null)
{
var leaves1 = room.GetLeaves();
var leaves2 = room.Adjacent.GetLeaves();
var suitableLeaves = GetSuitableLeafRooms(leaves1, leaves2, width, isHorizontal);
if (suitableLeaves == null || suitableLeaves.Length < 2)
{
// No suitable leaves found due to intersections
//DebugConsole.ThrowError("Error while generating ruins. Could not find a suitable position for a corridor. The width of the corridors may be too large compared to the sizes of the rooms.");
return;
}
else
{
ConnectedRooms[0] = suitableLeaves[0];
ConnectedRooms[1] = suitableLeaves[1];
}
}
else
{
rect = CalculateRectangle(room1, room2, width, isHorizontal);
if (rect.Width <= 0 || rect.Height <= 0)
{
DebugConsole.ThrowError("Error while generating ruins. Attempted to create a corridor with a width or height of <= 0");
return;
}
}
room.Corridor = this;
room.Adjacent.Corridor = this;
for (int i = corridors.Count - 1; i >= 0; i--)
{
var corridor = corridors[i];
if (corridor.rect.Intersects(this.rect))
{
if (isHorizontal && corridor.isHorizontal)
{
if (this.rect.Width < corridor.rect.Width)
return;
else
corridors.RemoveAt(i);
}
else if (!isHorizontal && !corridor.isHorizontal)
{
if (this.rect.Height < corridor.rect.Height)
return;
else
corridors.RemoveAt(i);
}
}
}
corridors.Add(this);
}
public override void CreateWalls()
{
Walls = new List<Line>();
if (IsHorizontal)
{
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y)));
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom)));
}
else
{
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom)));
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom)));
}
}
/// <summary>
/// Find two rooms which have two face-two-face walls that we can place a corridor in between
/// </summary>
/// <returns></returns>
private BTRoom[] GetSuitableLeafRooms(List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
{
int iOffset = Rand.Int(leaves1.Count, Rand.RandSync.Server);
int jOffset = Rand.Int(leaves2.Count, Rand.RandSync.Server);
for (int iCount = 0; iCount < leaves1.Count; iCount++)
{
int i = (iCount + iOffset) % leaves1.Count;
for (int jCount = 0; jCount < leaves2.Count; jCount++)
{
int j = (jCount + jOffset) % leaves2.Count;
if (isHorizontal)
{
if (leaves1[i].Rect.Y > leaves2[j].Rect.Bottom - width) continue;
if (leaves1[i].Rect.Bottom < leaves2[j].Rect.Y + width) continue;
}
else
{
if (leaves1[i].Rect.X > leaves2[j].Rect.Right - width) continue;
if (leaves1[i].Rect.Right < leaves2[j].Rect.X + width) continue;
}
// Check if the given corridor rect would intersect over a third room
if (CheckForIntersection(leaves1[i], leaves2[j], leaves1, leaves2, width, isHorizontal)) continue;
return new BTRoom[] { leaves1[i], leaves2[j] };
}
}
return null;
}
private bool CheckForIntersection(BTRoom potential1, BTRoom potential2, List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
{
Rectangle potentialCorridorRectangle = CalculateRectangle(potential1.Rect, potential2.Rect, width, isHorizontal);
if (potentialCorridorRectangle.Width <= 0 || potentialCorridorRectangle.Height <= 0) return true; // Invalid rectangle
for (int i = 0; i < leaves1.Count; i++)
{
if (leaves1[i] == potential1) continue;
if (potentialCorridorRectangle.Intersects(leaves1[i].Rect)) return true;
}
for (int i = 0; i < leaves2.Count; i++)
{
if (leaves2[i] == potential2) continue;
if (potentialCorridorRectangle.Intersects(leaves2[i].Rect)) return true;
}
rect = potentialCorridorRectangle; // Save the rectangle that passes the test
return false;
}
private Rectangle CalculateRectangle(Rectangle rect1, Rectangle rect2, int width, bool isHorizontal)
{
if (isHorizontal)
{
int left = Math.Min(rect1.Right, rect2.Right);
int right = Math.Max(rect1.X, rect2.X);
int top = Math.Max(rect1.Y, rect2.Y);
//int bottom = Math.Min(room1.Bottom, room2.Bottom);
int yPos = top;//Rand.Range(top, bottom - width, Rand.RandSync.Server);
return new Rectangle(left, yPos, right - left, width);
}
else if (rect1.Y > rect2.Bottom || rect2.Y > rect1.Bottom)
{
int left = Math.Max(rect1.X, rect2.X);
int right = Math.Min(rect1.Right, rect2.Right);
int top = Math.Min(rect1.Bottom, rect2.Bottom);
int bottom = Math.Max(rect1.Y, rect2.Y);
int xPos = Rand.Range(left, right - width, Rand.RandSync.Server);
return new Rectangle(xPos, top, width, bottom - top);
}
else
{
DebugConsole.ThrowError("wat");
return new Rectangle();
}
}
}
}
@@ -18,9 +18,9 @@ namespace Barotrauma.RuinGeneration
Wall, Back, Door, Hatch, Prop
}
class RuinGenerationParams : ISerializableEntity
class RuinGenerationParams : OutpostGenerationParams
{
public static List<RuinGenerationParams> List
public static List<RuinGenerationParams> RuinParams
{
get
{
@@ -34,102 +34,14 @@ namespace Barotrauma.RuinGeneration
private static List<RuinGenerationParams> paramsList;
private string filePath;
private readonly List<RuinRoom> roomTypeList;
public string Name => "RuinGenerationParams";
private readonly string filePath;
public override string Name => "RuinGenerationParams";
[Serialize("5000,5000", false), Editable]
public Point SizeMin
{
get;
set;
}
[Serialize("8000,8000", false), Editable]
public Point SizeMax
{
get;
set;
}
[Serialize(3, false, description: "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the minimum number of times the split is done."), Editable(MinValueInt = 1, MaxValueInt = 10)]
public int RoomDivisionIterationsMin
private RuinGenerationParams(XElement element, string filePath) : base(element, filePath)
{
get;
set;
}
[Serialize(4, false, description: "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the maximum number of times the split is done."), Editable(MinValueInt = 1, MaxValueInt = 10)]
public int RoomDivisionIterationsMax
{
get;
set;
}
[Serialize(0.5f, false, description: "The probability for the split algorithm to split the area vertically. High values tend to create tall, vertical rooms, and low values wide, horizontal rooms."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.9f)]
public float VerticalSplitProbability
{
get;
set;
}
[Serialize(400, false, description: "The splitting algorithm attempts to keep the width of the split areas larger than this. If the width of the split areas would be smaller than this after a vertical split, the algorithm would do a horizontal split."), Editable]
public int MinSplitWidth
{
get;
set;
}
[Serialize(400, false, description: "The splitting algorithm attempts to keep the height of the split areas larger than this. If the height of the split areas would be smaller than this after a vertical split, the algorithm would do a horizontal split."), Editable]
public int MinSplitHeight
{
get;
set;
}
[Serialize("0.5,0.9", false, description: "The minimum and maximum width of a room relative to the areas created by the split algorithm."), Editable]
public Vector2 RoomWidthRange
{
get;
set;
}
[Serialize("0.5,0.9", false, description: "The minimum and maximum height of a room relative to the areas created by the split algorithm."), Editable]
public Vector2 RoomHeightRange
{
get;
set;
}
[Serialize("200,256", false, description: "The minimum and maximum width of the corridors between rooms."), Editable]
public Point CorridorWidthRange
{
get;
set;
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
} = new Dictionary<string, SerializableProperty>();
public IEnumerable<RuinRoom> RoomTypeList
{
get { return roomTypeList; }
}
private RuinGenerationParams(XElement element)
{
roomTypeList = new List<RuinRoom>();
if (element != null)
{
foreach (XElement subElement in element.Elements())
{
roomTypeList.Add(new RuinRoom(subElement));
}
}
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
this.filePath = filePath;
}
public static RuinGenerationParams GetRandom()
@@ -139,7 +51,7 @@ namespace Barotrauma.RuinGeneration
if (paramsList.Count == 0)
{
DebugConsole.ThrowError("No ruin configuration files found in any content package.");
return new RuinGenerationParams(null);
return new RuinGenerationParams(null, null);
}
return paramsList[Rand.Int(paramsList.Count, Rand.RandSync.Server)];
@@ -151,23 +63,24 @@ namespace Barotrauma.RuinGeneration
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
{
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
if (doc == null) { continue; }
var mainElement = doc.Root;
if (doc.Root.IsOverride())
if (doc?.Root == null) { continue; }
foreach (XElement subElement in doc.Root.Elements())
{
mainElement = doc.Root.FirstElement();
paramsList.Clear();
DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow);
var mainElement = subElement;
if (subElement.IsOverride())
{
mainElement = subElement.FirstElement();
paramsList.Clear();
DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow);
}
else if (paramsList.Any())
{
DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'");
}
var newParams = new RuinGenerationParams(mainElement, configFile.Path);
paramsList.Add(newParams);
}
else if (paramsList.Any())
{
DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'");
}
var newParams = new RuinGenerationParams(mainElement)
{
filePath = configFile.Path
};
paramsList.Add(newParams);
}
}
@@ -185,11 +98,11 @@ namespace Barotrauma.RuinGeneration
NewLineOnAttributes = true
};
foreach (RuinGenerationParams generationParams in List)
foreach (RuinGenerationParams generationParams in RuinParams)
{
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
{
if (configFile.Path != generationParams.filePath) continue;
if (configFile.Path != generationParams.filePath) { continue; }
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
if (doc == null) { continue; }
@@ -205,298 +118,4 @@ namespace Barotrauma.RuinGeneration
}
}
}
class RuinRoom : ISerializableEntity
{
public enum RoomPlacement
{
Any,
First,
Last
}
public string Name
{
get;
private set;
}
[Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
public float Commonness { get; private set; }
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
} = new Dictionary<string, SerializableProperty>();
[Serialize(RoomPlacement.Any, false), Editable]
public RoomPlacement Placement
{
get;
set;
}
[Serialize(0, false), Editable]
public int PlacementOffset
{
get;
set;
}
[Serialize(false, false), Editable]
public bool IsCorridor
{
get;
set;
}
[Serialize(1.0f, false), Editable]
public float MinWaterAmount
{
get;
set;
}
[Serialize(1.0f, false), Editable]
public float MaxWaterAmount
{
get;
set;
}
private List<RuinEntityConfig> entityList = new List<RuinEntityConfig>();
public RuinRoom(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
Name = element.GetAttributeString("name", "");
if (element != null)
{
int groupIndex = 0;
LoadEntities(element, ref groupIndex);
}
void LoadEntities(XElement element2, ref int groupIndex)
{
foreach (XElement subElement in element2.Elements())
{
if (subElement.Name.ToString().Equals("chooseone", StringComparison.OrdinalIgnoreCase))
{
groupIndex++;
LoadEntities(subElement, ref groupIndex);
}
else
{
entityList.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex });
}
}
}
}
public RuinEntityConfig GetRandomEntity(RuinEntityType type, Alignment alignment)
{
var matchingEntities = entityList.FindAll(rs =>
rs.Type == type &&
rs.Alignment.HasFlag(alignment));
if (!matchingEntities.Any()) return null;
return ToolBox.SelectWeightedRandom(
matchingEntities,
matchingEntities.Select(s => s.Commonness).ToList(),
Rand.RandSync.Server);
}
public List<RuinEntityConfig> GetPropList(RuinShape room, Rand.RandSync randSync)
{
Dictionary<int, List<RuinEntityConfig>> propGroups = new Dictionary<int, List<RuinEntityConfig>>();
foreach (RuinEntityConfig entityConfig in entityList)
{
if (entityConfig.Type != RuinEntityType.Prop) { continue; }
if (room.Rect.Width < entityConfig.MinRoomSize.X || room.Rect.Height < entityConfig.MinRoomSize.Y) { continue; }
if (room.Rect.Width > entityConfig.MaxRoomSize.X || room.Rect.Height > entityConfig.MaxRoomSize.Y) { continue; }
if (!propGroups.ContainsKey(entityConfig.SingleGroupIndex))
{
propGroups[entityConfig.SingleGroupIndex] = new List<RuinEntityConfig>();
}
propGroups[entityConfig.SingleGroupIndex].Add(entityConfig);
}
List<RuinEntityConfig> props = new List<RuinEntityConfig>();
foreach (KeyValuePair<int, List<RuinEntityConfig>> propGroup in propGroups)
{
if (propGroup.Key == 0)
{
props.AddRange(propGroup.Value);
}
else
{
props.Add(propGroup.Value[Rand.Int(propGroup.Value.Count, randSync)]);
}
}
return props;
}
}
class RuinEntityConfig : ISerializableEntity
{
public readonly MapEntityPrefab Prefab;
public enum RelativePlacement
{
SameRoom,
NextRoom,
NextCorridor,
PreviousRoom,
PreviousCorridor,
FirstRoom,
FirstCorridor,
LastRoom,
LastCorridor
}
public class EntityConnection
{
//which type of room to search for the item to connect to
//sameroom, nextroom, previousroom, firstroom and lastroom are also valid
public string RoomName
{
get;
private set;
}
public string TargetEntityIdentifier
{
get;
private set;
}
//Identifier of the item to run the wire from. Only needed in item assemblies to determine which item in the assembly to use.
public string SourceEntityIdentifier
{
get;
private set;
}
//if set, the connection is done by running a wire from
//(Pair.First = the name of the connection in this item) to (Pair.Second = the name of the connection in the target item)
public Pair<string, string> WireConnection
{
get;
private set;
}
public EntityConnection(XElement element)
{
RoomName = element.GetAttributeString("roomname", "");
TargetEntityIdentifier = element.GetAttributeString("targetentity", "");
SourceEntityIdentifier = element.GetAttributeString("sourceentity", "");
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals("wire", StringComparison.OrdinalIgnoreCase))
{
WireConnection = new Pair<string, string>(
subElement.GetAttributeString("from", ""),
subElement.GetAttributeString("to", ""));
}
}
}
}
[Serialize(Alignment.Bottom, false), Editable]
public Alignment Alignment { get; private set; }
[Serialize("0,0", false, description: "Minimum offset from the anchor position, relative to the size of the room." +
" For example, a value of { -0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-left corner of the room and bottom-center."), Editable]
public Vector2 MinOffset { get; private set; }
[Serialize("0,0", false, description: "Maximum offset from the anchor position, relative to the size of the room." +
" For example, a value of { 0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-right corner of the room and bottom-center."), Editable]
public Vector2 MaxOffset { get; private set; }
[Serialize(RuinEntityType.Prop, false), Editable]
public RuinEntityType Type { get; private set; }
[Serialize(false, false), Editable]
public bool Expand { get; private set; }
[Serialize(RelativePlacement.SameRoom, false), Editable]
public RelativePlacement PlacementRelativeToParent { get; private set; }
[Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
public float Commonness { get; private set; }
[Serialize(1, false)]
public int MinAmount { get; private set; }
[Serialize(1, false)]
public int MaxAmount { get; private set; }
[Serialize("0,0", false)]
public Point MinRoomSize { get; private set; }
[Serialize("100000,100000", false)]
public Point MaxRoomSize { get; private set; }
[Serialize("", false)]
public string TargetContainer { get; private set; }
public List<EntityConnection> EntityConnections { get; private set; } = new List<EntityConnection>();
public int SingleGroupIndex;
private readonly List<RuinEntityConfig> childEntities = new List<RuinEntityConfig>();
public IEnumerable<RuinEntityConfig> ChildEntities
{
get { return childEntities; }
}
public string Name => Prefab == null ? "null" : Prefab.Name;
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
} = new Dictionary<string, SerializableProperty>();
public RuinEntityConfig(XElement element)
{
string name = element.GetAttributeString("prefab", "");
Prefab = MapEntityPrefab.Find(name: null, identifier: name);
if (Prefab == null)
{
DebugConsole.ThrowError("Loading ruin entity config failed - map entity prefab \"" + name + "\" not found.");
return;
}
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
int gIndex = 0;
LoadChildren(element, ref gIndex);
void LoadChildren(XElement element2, ref int groupIndex)
{
foreach (XElement subElement in element2.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "connection":
case "entityconnection":
EntityConnections.Add(new EntityConnection(subElement));
break;
case "chooseone":
groupIndex++;
LoadChildren(subElement, ref groupIndex);
break;
default:
childEntities.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex });
break;
}
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1006,12 +1006,22 @@ namespace Barotrauma
stockToRemove.ForEach(i => stock.Remove(i));
StoreStock = stock;
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval)
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval ||
DailySpecials.Count() != DailySpecialsCount + extraSpecialSalesCount)
{
CreateStoreSpecials();
}
}
private int GetExtraSpecialSalesCount()
{
var characters = GameSession.GetSessionCrewCharacters();
if (!characters.Any()) { return 0; }
return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
}
private void GenerateRandomPriceModifier()
{
StorePriceModifier = Rand.Range(-StorePriceModifierRange, StorePriceModifierRange);
@@ -1035,7 +1045,9 @@ namespace Barotrauma
}
availableStock.Add(stockItem.ItemPrefab, weight);
}
for (int i = 0; i < DailySpecialsCount; i++)
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
for (int i = 0; i < DailySpecialsCount + extraSpecialSalesCount; i++)
{
if (availableStock.None()) { break; }
var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced);
@@ -224,12 +224,6 @@ namespace Barotrauma
}
}
public RuinGeneration.Ruin ParentRuin
{
get;
set;
}
[Serialize(true, true)]
public bool RemoveIfLinkedOutpostDoorInUse
{
@@ -11,7 +11,7 @@ namespace Barotrauma
{
public static List<OutpostGenerationParams> Params { get; private set; }
public string Name { get; private set; }
public virtual string Name { get; private set; }
public string Identifier { get; private set; }
@@ -67,6 +67,34 @@ namespace Barotrauma
set;
}
[Serialize(true, isSaveable: true), Editable]
public bool LockUnusedDoors
{
get;
set;
}
[Serialize(true, isSaveable: true), Editable]
public bool RemoveUnusedGaps
{
get;
set;
}
[Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
public float MinWaterPercentage
{
get;
set;
}
[Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
public float MaxWaterPercentage
{
get;
set;
}
[Serialize("", isSaveable: true), Editable]
public string ReplaceInRadiation { get; set; }
@@ -81,12 +109,14 @@ namespace Barotrauma
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
private OutpostGenerationParams(XElement element, string filePath)
protected OutpostGenerationParams(XElement element, string filePath)
{
Identifier = element.GetAttributeString("identifier", "");
Name = element.GetAttributeString("name", Identifier);
allowedLocationTypes = element.GetAttributeStringArray("allowedlocationtypes", Array.Empty<string>()).ToList();
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
if (element == null) { return; }
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -85,6 +85,7 @@ namespace Barotrauma
var subInfo = new SubmarineInfo(outpostModuleFile.Path);
if (subInfo.OutpostModuleInfo != null)
{
if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") != generationParams is RuinGeneration.RuinGenerationParams) { continue; }
outpostModules.Add(subInfo);
}
}
@@ -162,7 +163,7 @@ namespace Barotrauma
selectedModules.Add(new PlacedModule(initialModule, null, OutpostModuleInfo.GapPosition.None));
selectedModules.Last().FulfilledModuleTypes.Add(initialModuleFlag);
AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType);
AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams);
if (pendingModuleFlags.Any(flag => !flag.Equals("none", StringComparison.OrdinalIgnoreCase)))
{
remainingTries--;
@@ -233,17 +234,23 @@ namespace Barotrauma
var selectedModule = selectedModules[i];
sub.Info.GameVersion = selectedModule.Info.GameVersion;
var moduleEntities = MapEntity.LoadAll(sub, selectedModule.Info.SubmarineElement, selectedModule.Info.FilePath, idOffset);
idOffset = moduleEntities.Max(e => e.ID);
MapEntity.InitializeLoadedLinks(moduleEntities);
foreach (MapEntity entity in moduleEntities)
foreach (MapEntity entity in moduleEntities.ToList())
{
entity.OriginalModuleIndex = i;
if (!(entity is Item item)) { continue; }
item.GetComponent<Door>()?.RefreshLinkedGap();
var door = item.GetComponent<Door>();
if (door != null)
{
door.RefreshLinkedGap();
if (!moduleEntities.Contains(door.LinkedGap)) { moduleEntities.Add(door.LinkedGap); }
}
item.GetComponent<ConnectionPanel>()?.InitializeLinks();
item.GetComponent<ItemContainer>()?.OnMapLoaded();
}
idOffset = moduleEntities.Max(e => e.ID);
var wallEntities = moduleEntities.Where(e => e is Structure).Cast<Structure>();
var hullEntities = moduleEntities.Where(e => e is Hull).Cast<Hull>();
@@ -345,11 +352,33 @@ namespace Barotrauma
Submarine.RepositionEntities(module.Offset + sub.HiddenSubPosition, entities[module]);
}
Gap.UpdateHulls();
allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities));
allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities, generationParams is RuinGeneration.RuinGenerationParams));
LinkOxygenGenerators(allEntities);
LockUnusedDoors(selectedModules, entities);
if (generationParams.LockUnusedDoors)
{
LockUnusedDoors(selectedModules, entities, generationParams.RemoveUnusedGaps);
}
AlignLadders(selectedModules, entities);
PowerUpOutpost(entities.SelectMany(e => e.Value));
if (generationParams.MaxWaterPercentage > 0.0f)
{
foreach (var entity in allEntities)
{
if (entity is Hull hull)
{
float diff = generationParams.MaxWaterPercentage - generationParams.MinWaterPercentage;
if (diff < 0.01f)
{
// Overfill the hulls to get rid of air pockets in the vertical hallways. Airpockets make it impossible to swim up the hallways.
hull.WaterVolume = hull.Volume * 2;
}
else
{
hull.WaterVolume = hull.Volume * Rand.Range(generationParams.MinWaterPercentage, generationParams.MaxWaterPercentage, Rand.RandSync.Server) * 0.01f;
}
}
}
}
}
return allEntities;
@@ -414,7 +443,8 @@ namespace Barotrauma
List<string> pendingModuleFlags,
List<PlacedModule> selectedModules,
LocationType locationType,
bool retry = true)
bool retry = true,
bool allowExtendBelowInitialModule = false)
{
if (pendingModuleFlags.Count == 0) { return true; }
@@ -422,8 +452,11 @@ namespace Barotrauma
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions().Randomize(Rand.RandSync.Server))
{
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
//don't continue downwards if it'd extend below the airlock
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
if (!allowExtendBelowInitialModule)
{
//don't continue downwards if it'd extend below the airlock
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
}
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
{
var newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType);
@@ -438,7 +471,7 @@ namespace Barotrauma
//try to append to some other module first
foreach (PlacedModule otherModule in selectedModules)
{
if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false))
if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
{
return true;
}
@@ -454,7 +487,7 @@ namespace Barotrauma
//retry
currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType);
if (currentModule == null) { break; }
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false))
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
{
return true;
}
@@ -676,6 +709,10 @@ namespace Barotrauma
else
{
availableModules = modules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
if (moduleFlag != "hallwayhorizontal" && moduleFlag != "hallwayvertical")
{
availableModules = availableModules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayhorizontal") && !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayvertical"));
}
}
if (availableModules.Count() == 0) { return null; }
@@ -840,7 +877,7 @@ namespace Barotrauma
return from.AllowAttachToModules.Any(s => to.ModuleFlags.Contains(s));
}
private static List<MapEntity> GenerateHallways(Submarine sub, LocationType locationType, IEnumerable<PlacedModule> placedModules, IEnumerable<SubmarineInfo> availableModules, Dictionary<PlacedModule, List<MapEntity>> allEntities)
private static List<MapEntity> GenerateHallways(Submarine sub, LocationType locationType, IEnumerable<PlacedModule> placedModules, IEnumerable<SubmarineInfo> availableModules, Dictionary<PlacedModule, List<MapEntity>> allEntities, bool isRuin)
{
//if a hallway is shorter than this, one of the doors at the ends of the hallway is removed
const float MinTwoDoorHallwayLength = 32.0f;
@@ -1193,14 +1230,13 @@ namespace Barotrauma
}
}
private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities)
private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities, bool removeUnusedGaps)
{
foreach (PlacedModule module in placedModules)
{
foreach (MapEntity me in entities[module])
{
var gap = me as Gap;
if (gap == null) { continue; }
if (!(me is Gap gap)) { continue; }
var door = gap.ConnectedDoor;
if (door != null && !door.UseBetweenOutpostModules) { continue; }
if (placedModules.Any(m => m.PreviousGap == gap || m.ThisGap == gap))
@@ -1247,11 +1283,11 @@ namespace Barotrauma
if (connectionPanel != null) { connectionPanel.Locked = true; }
}
}
else
else if (removeUnusedGaps)
{
gap.Remove();
WayPoint.WayPointList.Where(wp => wp.ConnectedGap == gap).ForEachMod(wp => wp.Remove());
}
}
}
entities[module].RemoveAll(e => e.Removed);
}
@@ -89,11 +89,13 @@ namespace Barotrauma
if (newFlags.Contains("hallwayhorizontal"))
{
moduleFlags.Add("hallwayhorizontal");
if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); }
return;
}
if (newFlags.Contains("hallwayvertical"))
{
moduleFlags.Add("hallwayvertical");
if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); }
return;
}
if (!newFlags.Any())
@@ -23,7 +23,7 @@ namespace Barotrauma
HideInMenus = 2
}
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine }
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine, Ruin }
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
partial class SubmarineInfo : IDisposable
@@ -97,11 +97,10 @@ namespace Barotrauma
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
//TODO: replace when the ruin branch is merged
public bool IsRuin => false;
public bool IsWreck => Type == SubmarineType.Wreck;
public bool IsBeacon => Type == SubmarineType.BeaconStation;
public bool IsPlayer => Type == SubmarineType.Player;
public bool IsRuin => Type == SubmarineType.Ruin;
public bool IsCampaignCompatible => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus) && SubmarineClass != SubmarineClass.Undefined;
public bool IsCampaignCompatibleIgnoreClass => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus);
@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.RuinGeneration;
using Barotrauma.Extensions;
namespace Barotrauma
@@ -189,61 +188,141 @@ namespace Barotrauma
door.Body.Enabled = true;
}
}
bool isFlooded = submarine.Info.IsRuin || submarine.Info.Type == SubmarineType.OutpostModule && submarine.Info.OutpostModuleInfo.ModuleFlags.Contains("ruin");
float diffFromHullEdge = 50;
float minDist = 100.0f;
float heightFromFloor = 110.0f;
float hullMinHeight = 100;
var removals = new List<WayPoint>();
foreach (Hull hull in Hull.hullList)
{
// Ignore hulls that a human couldn't fit in.
// Doesn't take multi-hull rooms into account, but it's probably best to leave them to be setup manually.
if (hull.Rect.Height < hullMinHeight) { continue; }
// Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor.
Body floor = null;
for (int i = 0; i < 5; i++)
if (isFlooded)
{
float horizontalOffset = 0;
switch (i)
diffFromHullEdge = 75;
var hullWaypoints = new List<WayPoint>();
float top = hull.Rect.Y;
float bottom = hull.Rect.Y - hull.Rect.Height;
if (hull.Rect.Width < 300 || hull.Rect.Height < 300)
{
case 1:
horizontalOffset = hull.RectWidth * 0.2f;
break;
case 2:
horizontalOffset = hull.RectWidth * 0.4f;
break;
case 3:
horizontalOffset = -hull.RectWidth * 0.2f;
break;
case 4:
horizontalOffset = -hull.RectWidth * 0.4f;
break;
// For narrow hulls, create one line of waypoints either horizontally or vertically
if (hull.Rect.Width > hull.Rect.Height)
{
// Horizontal
float y = hull.Rect.Y - hull.Rect.Height / 2;
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
{
hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
}
}
else
{
// Vertical
float x = hull.Rect.X + hull.Rect.Width / 2;
for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist)
{
hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
}
}
}
if (hullWaypoints.None())
{
// Try to create a grid-like network of waypoints
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
{
for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist)
{
hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
}
}
if (hullWaypoints.None())
{
// If that fails, just create one waypoint at the center.
hullWaypoints.Add(new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height / 2), SpawnType.Path, submarine));
}
foreach (WayPoint wp in hullWaypoints)
{
foreach (Structure wall in Structure.WallList)
{
if (wall.HasBody)
{
// Remove waypoints that are too close/inside the walls.
Rectangle rect = wall.Rect;
rect.Inflate(10, 10);
if (rect.ContainsWorld(wp.Position))
{
removals.Add(wp);
}
}
}
}
}
// Connect the waypoints
foreach (var wayPoint in hullWaypoints)
{
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.9f, minDist));
if (closest != null && closest.CurrentHull == wayPoint.CurrentHull)
{
wayPoint.ConnectTo(closest);
}
closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(minDist, minDist * 1.9f));
if (closest != null && closest.CurrentHull == wayPoint.CurrentHull)
{
wayPoint.ConnectTo(closest);
}
}
}
horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset);
Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
if (floor != null) { break; }
}
if (floor == null) { continue; }
float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
if (hull.Rect.Width < diffFromHullEdge * 3.0f)
{
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
}
else
{
WayPoint prevWaypoint = null;
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
if (hull.Rect.Height < hullMinHeight) { continue; }
// Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor.
Body floor = null;
for (int i = 0; i < 5; i++)
{
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); }
prevWaypoint = wayPoint;
float horizontalOffset = 0;
switch (i)
{
case 1:
horizontalOffset = hull.RectWidth * 0.2f;
break;
case 2:
horizontalOffset = hull.RectWidth * 0.4f;
break;
case 3:
horizontalOffset = -hull.RectWidth * 0.2f;
break;
case 4:
horizontalOffset = -hull.RectWidth * 0.4f;
break;
}
horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset);
Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
if (floor != null) { break; }
}
if (prevWaypoint == null)
if (floor == null) { continue; }
float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
if (hull.Rect.Width < diffFromHullEdge * 3.0f)
{
// Ensure that we always create at least one waypoint per hull.
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
}
else
{
WayPoint previousWaypoint = null;
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
{
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
if (previousWaypoint != null) { wayPoint.ConnectTo(previousWaypoint); }
previousWaypoint = wayPoint;
}
if (previousWaypoint == null)
{
// Ensure that we always create at least one waypoint per hull.
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
}
}
}
}
@@ -278,7 +357,7 @@ namespace Barotrauma
}
float outSideWaypointInterval = 100.0f;
if (submarine.Info.Type != SubmarineType.OutpostModule)
if (!isFlooded && submarine.Info.Type != SubmarineType.OutpostModule)
{
List<(WayPoint, int)> outsideWaypoints = new List<(WayPoint, int)>();
@@ -381,7 +460,6 @@ namespace Barotrauma
}
}
// Remove unwanted points
var removals = new List<WayPoint>();
WayPoint previous = null;
float tooClose = outSideWaypointInterval / 2;
foreach (var wayPoint in outsideWaypoints)
@@ -412,7 +490,6 @@ namespace Barotrauma
foreach (WayPoint wp in removals)
{
outsideWaypoints.RemoveAll(w => w.Item1 == wp);
wp.Remove();
}
for (int i = 0; i < outsideWaypoints.Count; i++)
{
@@ -433,41 +510,35 @@ namespace Barotrauma
}
}
}
List<Structure> stairList = new List<Structure>();
foreach (MapEntity me in mapEntityList)
{
if (!(me is Structure stairs)) { continue; }
if (stairs.StairDirection != Direction.None) stairList.Add(stairs);
}
foreach (Structure stairs in stairList)
foreach (Structure wall in Structure.WallList)
{
if (wall.StairDirection == Direction.None) { continue; }
WayPoint[] stairPoints = new WayPoint[3];
stairPoints[0] = new WayPoint(
new Vector2(stairs.Rect.X - 32.0f,
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
new Vector2(wall.Rect.X - 32.0f,
wall.Rect.Y - (wall.StairDirection == Direction.Left ? 80 : wall.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
stairPoints[1] = new WayPoint(
new Vector2(stairs.Rect.Right + 32.0f,
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
new Vector2(wall.Rect.Right + 32.0f,
wall.Rect.Y - (wall.StairDirection == Direction.Left ? wall.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
for (int i = 0; i < 2; i++ )
for (int i = 0; i < 2; i++)
{
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(100, 70));
if (closest == null) { continue; }
stairPoints[i].ConnectTo(closest);
}
}
}
stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine);
stairPoints[0].ConnectTo(stairPoints[2]);
stairPoints[2].ConnectTo(stairPoints[1]);
}
removals.ForEach(wp => wp.Remove());
removals.Clear();
foreach (Item item in Item.ItemList)
{
@@ -605,12 +676,25 @@ namespace Barotrauma
{
if (gap.IsHorizontal)
{
// Too small to walk through
if (gap.Rect.Height < hullMinHeight) { continue; }
if ( isFlooded)
{
// Too small to swim through
if (gap.Rect.Height < 50) { continue; }
}
else
{
// Too small to walk through
if (gap.Rect.Height < hullMinHeight) { continue; }
}
Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor);
if (isFlooded)
{
pos.Y = gap.Rect.Y - gap.Rect.Height / 2;
}
var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
// The closest waypoint can be quite far if the gap is at an exterior door.
Vector2 tolerance = gap.IsRoomToRoom ? new Vector2(150, 70) : new Vector2(1000, 1000);
Vector2 tolerance = gap.IsRoomToRoom && !isFlooded ? new Vector2(150, 70) : new Vector2(1000, 1000);
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, tolerance, gap.ConnectedDoor?.Body.FarseerBody);
@@ -623,7 +707,7 @@ namespace Barotrauma
else
{
// Create waypoints on vertical gaps on the outer walls, also hatches.
if (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull)) { continue; }
if (!isFlooded && (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull))) { continue; }
// Too small to swim through
if (gap.Rect.Width < 50.0f) { continue; }
Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2);
@@ -632,11 +716,20 @@ namespace Barotrauma
var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull);
int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y);
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(50, 100));
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100));
if (closest != null)
{
wayPoint.ConnectTo(closest);
}
if (isFlooded)
{
closest = wayPoint.FindClosest(-dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100));
if (closest != null)
{
wayPoint.ConnectTo(closest);
}
}
// Link to outside
for (dir = -1; dir <= 1; dir += 2)
{
closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), gap.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null);
@@ -656,7 +749,7 @@ namespace Barotrauma
foreach (WayPoint wp in WayPointList)
{
if (wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2)
if (wp.SpawnType == SpawnType.Path && wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2)
{
DebugConsole.ThrowError($"Couldn't automatically link the waypoint {wp.ID} outside of the submarine. You should do it manually. The waypoint ID is shown in red color.");
}
@@ -775,11 +868,10 @@ namespace Barotrauma
}
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false)
{
return WayPointList.GetRandom(wp =>
wp.Submarine == sub &&
wp.ParentRuin == ruin &&
wp.spawnType == spawnType &&
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);