v0.11.0.9
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CaveGenerationParams : ISerializableEntity
|
||||
{
|
||||
public static List<CaveGenerationParams> CaveParams { get; private set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Identifier; }
|
||||
}
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
private int minWidth, maxWidth;
|
||||
private int minHeight, maxHeight;
|
||||
|
||||
private int minBranchCount, maxBranchCount;
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the commonness of the object in a specific level type.
|
||||
/// Key = name of the level type, value = commonness in that level type.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, float> OverrideCommonness = new Dictionary<string, float>();
|
||||
|
||||
[Editable, Serialize(1.0f, true)]
|
||||
public float Commonness
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(8000, true), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
public int MinWidth
|
||||
{
|
||||
get { return minWidth; }
|
||||
set { minWidth = Math.Max(value, 1000); }
|
||||
}
|
||||
|
||||
[Serialize(10000, true), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
public int MaxWidth
|
||||
{
|
||||
get { return maxWidth; }
|
||||
set { maxWidth = Math.Max(value, minWidth); }
|
||||
}
|
||||
|
||||
[Serialize(8000, true), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
public int MinHeight
|
||||
{
|
||||
get { return minHeight; }
|
||||
set { minHeight = Math.Max(value, 1000); }
|
||||
}
|
||||
|
||||
[Serialize(10000, true), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
public int MaxHeight
|
||||
{
|
||||
get { return maxHeight; }
|
||||
set { maxHeight = Math.Max(value, minHeight); }
|
||||
}
|
||||
|
||||
[Serialize(2, true), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MinBranchCount
|
||||
{
|
||||
get { return minBranchCount; }
|
||||
set { minBranchCount = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(4, true), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MaxBranchCount
|
||||
{
|
||||
get { return maxBranchCount; }
|
||||
set { maxBranchCount = Math.Max(value, minBranchCount); }
|
||||
}
|
||||
|
||||
[Serialize(50, true), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.1f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1.0f, DecimalCount = 2 )]
|
||||
public float DestructibleWallRatio
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Sprite WallSprite { get; private set; }
|
||||
public Sprite WallEdgeSprite { get; private set; }
|
||||
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, Rand.RandSync rand)
|
||||
{
|
||||
if (CaveParams.All(p => p.GetCommonness(generationParams) <= 0.0f))
|
||||
{
|
||||
return CaveParams.First();
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(CaveParams, CaveParams.Select(p => p.GetCommonness(generationParams)).ToList(), rand);
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
return Commonness;
|
||||
}
|
||||
|
||||
private CaveGenerationParams(XElement element)
|
||||
{
|
||||
Identifier = element == null ? "default" : element.GetAttributeString("identifier", null) ?? element.Name.ToString();
|
||||
Identifier = Identifier.ToLowerInvariant();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "wall":
|
||||
WallSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "walledge":
|
||||
WallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadPresets()
|
||||
{
|
||||
CaveParams = new List<CaveGenerationParams>();
|
||||
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.CaveGenerationParameters);
|
||||
if (!files.Any())
|
||||
{
|
||||
files = new List<ContentFile>() { new ContentFile("Content/Map/CaveGenerationParameters.xml", ContentType.CaveGenerationParameters) };
|
||||
}
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
CaveParams.Clear();
|
||||
DebugConsole.NewMessage($"Overriding cave generation parameters with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
if (isOverride)
|
||||
{
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", "");
|
||||
var existingParams = CaveParams.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingParams != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the cave generation parameters '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
CaveParams.Remove(existingParams);
|
||||
}
|
||||
CaveParams.Add(new CaveGenerationParams(element.FirstElement()));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", "");
|
||||
var existingParams = CaveParams.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingParams != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate cave generation parameters: '{identifier}' defined in {element.Name} of '{file.Path}'. Use <override></override> tags to override the generation parameters.");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
CaveParams.Add(new CaveGenerationParams(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision.Shapes;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -80,7 +81,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (MathUtils.NearlyEqual(ge.Point2.X, borders.X) || MathUtils.NearlyEqual(ge.Point2.X, borders.Right) ||
|
||||
MathUtils.NearlyEqual(ge.Point2.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Bottom))
|
||||
MathUtils.NearlyEqual(ge.Point2.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Bottom))
|
||||
{
|
||||
if (point1 == null)
|
||||
{
|
||||
@@ -95,14 +96,43 @@ namespace Barotrauma
|
||||
if (point1.HasValue && point2.HasValue)
|
||||
{
|
||||
Debug.Assert(point1 != point2);
|
||||
var newEdge = new GraphEdge(point1.Value, point2.Value)
|
||||
bool point1OnSide = MathUtils.NearlyEqual(point1.Value.X, borders.X) || MathUtils.NearlyEqual(point1.Value.X, borders.Right);
|
||||
bool point2OnSide = MathUtils.NearlyEqual(point2.Value.X, borders.X) || MathUtils.NearlyEqual(point2.Value.X, borders.Right);
|
||||
//one point is one the side, another on top/bottom
|
||||
// -> the cell is in the corner of the level, we need 2 edges
|
||||
if (point1OnSide != point2OnSide)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
};
|
||||
cell.Edges.Add(newEdge);
|
||||
Vector2 cornerPos = new Vector2(
|
||||
point1.Value.X < borders.Center.X ? borders.X : borders.Right,
|
||||
point1.Value.Y < borders.Center.Y ? borders.Y : borders.Bottom);
|
||||
cell.Edges.Add(
|
||||
new GraphEdge(point1.Value, cornerPos)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
});
|
||||
cell.Edges.Add(
|
||||
new GraphEdge(point2.Value, cornerPos)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.Edges.Add(
|
||||
new GraphEdge(point1.Value, point2.Value)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -111,44 +141,16 @@ namespace Barotrauma
|
||||
return cells;
|
||||
}
|
||||
|
||||
|
||||
private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null)
|
||||
{
|
||||
if (cell == null) { cell = edge.AdjacentCell(null); }
|
||||
if (cell == null) { return Vector2.UnitX; }
|
||||
|
||||
CompareCCW compare = new CompareCCW(cell.Center);
|
||||
if (compare.Compare(edge.Point1, edge.Point2) == -1)
|
||||
{
|
||||
var temp = edge.Point1;
|
||||
edge.Point1 = edge.Point2;
|
||||
edge.Point2 = temp;
|
||||
}
|
||||
|
||||
Vector2 normal = Vector2.Normalize(edge.Point2 - edge.Point1);
|
||||
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.Point2);
|
||||
|
||||
normal = new Vector2(-normal.Y, normal.X);
|
||||
if (Vector2.Dot(normal, diffToCell) < 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<Point> pathNodes, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
|
||||
public static void GeneratePath(Level.Tunnel tunnel, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, Rectangle limits)
|
||||
{
|
||||
var targetCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < pathNodes.Count; i++)
|
||||
for (int i = 0; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
//a search depth of 2 is large enough to find a cell in almost all maps, but in case it fails, we increase the depth
|
||||
int searchDepth = 2;
|
||||
while (searchDepth < 5)
|
||||
{
|
||||
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, searchDepth);
|
||||
int cellIndex = FindCellIndex(tunnel.Nodes[i], cells, cellGrid, gridCellSize, searchDepth);
|
||||
if (cellIndex > -1)
|
||||
{
|
||||
targetCells.Add(cells[cellIndex]);
|
||||
@@ -157,25 +159,15 @@ namespace Barotrauma
|
||||
|
||||
searchDepth++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return GeneratePath(targetCells, cells, cellGrid, gridCellSize, limits, wanderAmount, mirror);
|
||||
tunnel.Cells.AddRange(GeneratePath(targetCells, cells, limits));
|
||||
}
|
||||
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<VoronoiCell> targetCells, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells, Rectangle limits)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
//how heavily the path "steers" towards the endpoint
|
||||
//lower values will cause the path to "wander" more, higher will make it head straight to the end
|
||||
wanderAmount = MathHelper.Clamp(wanderAmount, 0.0f, 1.0f);
|
||||
|
||||
List<GraphEdge> allowedEdges = new List<GraphEdge>();
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
VoronoiCell currentCell = targetCells[0];
|
||||
@@ -190,41 +182,24 @@ namespace Barotrauma
|
||||
{
|
||||
int edgeIndex = 0;
|
||||
|
||||
allowedEdges.Clear();
|
||||
foreach (GraphEdge edge in currentCell.Edges)
|
||||
double smallestDist = double.PositiveInfinity;
|
||||
for (int i = 0; i < currentCell.Edges.Count; i++)
|
||||
{
|
||||
var adjacentCell = edge.AdjacentCell(currentCell);
|
||||
if (adjacentCell != null && limits.Contains(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y))
|
||||
var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell);
|
||||
if (adjacentCell == null) { continue; }
|
||||
double dist = MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
|
||||
dist += MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, currentCell.Site.Coord.X, currentCell.Site.Coord.Y) * 0.5f;
|
||||
//disfavor small edges to prevent generating a very small passage
|
||||
if (Vector2.Distance(currentCell.Edges[i].Point1, currentCell.Edges[i].Point2) < 200.0f)
|
||||
{
|
||||
allowedEdges.Add(edge);
|
||||
dist += 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
//steer towards target
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > wanderAmount || allowedEdges.Count == 0)
|
||||
{
|
||||
double smallestDist = double.PositiveInfinity;
|
||||
for (int i = 0; i < currentCell.Edges.Count; i++)
|
||||
if (dist < smallestDist)
|
||||
{
|
||||
var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell);
|
||||
if (adjacentCell == null) { continue; }
|
||||
double dist = MathUtils.Distance(
|
||||
adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y,
|
||||
targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
|
||||
if (dist < smallestDist)
|
||||
{
|
||||
edgeIndex = i;
|
||||
smallestDist = dist;
|
||||
}
|
||||
edgeIndex = i;
|
||||
smallestDist = dist;
|
||||
}
|
||||
}
|
||||
//choose random edge (ignoring ones where the adjacent cell is outside limits)
|
||||
else
|
||||
{
|
||||
edgeIndex = Rand.Int(allowedEdges.Count, Rand.RandSync.Server);
|
||||
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
|
||||
edgeIndex = currentCell.Edges.IndexOf(allowedEdges[edgeIndex]);
|
||||
}
|
||||
|
||||
currentCell = currentCell.Edges[edgeIndex].AdjacentCell(currentCell);
|
||||
currentCell.CellType = CellType.Path;
|
||||
@@ -234,8 +209,8 @@ namespace Barotrauma
|
||||
|
||||
if (currentCell == targetCells[currentTargetIndex])
|
||||
{
|
||||
currentTargetIndex += 1;
|
||||
if (currentTargetIndex >= targetCells.Count) break;
|
||||
currentTargetIndex++;
|
||||
if (currentTargetIndex >= targetCells.Count) { break; }
|
||||
}
|
||||
|
||||
} while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
|
||||
@@ -256,17 +231,42 @@ namespace Barotrauma
|
||||
List<GraphEdge> tempEdges = new List<GraphEdge>();
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid)
|
||||
if (!edge.IsSolid || edge.OutsideLevel)
|
||||
{
|
||||
tempEdges.Add(edge);
|
||||
continue;
|
||||
}
|
||||
|
||||
//If the edge is next to an empty cell and there's another solid cell at the other side of the empty one,
|
||||
//don't touch this edge. Otherwise we may end up closing off small passages between cells.
|
||||
var adjacentEmptyCell = edge.AdjacentCell(cell);
|
||||
if (adjacentEmptyCell?.CellType == CellType.Solid) { adjacentEmptyCell = null; }
|
||||
if (adjacentEmptyCell != null)
|
||||
{
|
||||
GraphEdge adjacentEdge = null;
|
||||
//find the edge at the opposite side of the adjacent cell
|
||||
foreach (GraphEdge otherEdge in adjacentEmptyCell.Edges)
|
||||
{
|
||||
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) < 0 &&
|
||||
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType == CellType.Solid)
|
||||
{
|
||||
adjacentEdge = otherEdge;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (adjacentEdge != null)
|
||||
{
|
||||
tempEdges.Add(edge);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
List<Vector2> edgePoints = new List<Vector2>();
|
||||
Vector2 edgeNormal = GetEdgeNormal(edge, cell);
|
||||
Vector2 edgeNormal = edge.GetNormal(cell);
|
||||
|
||||
float edgeLength = Vector2.Distance(edge.Point1, edge.Point2);
|
||||
int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1);
|
||||
Vector2 edgeDir = (edge.Point2 - edge.Point1);
|
||||
Vector2 edgeDir = edge.Point2 - edge.Point1;
|
||||
for (int i = 0; i <= pointCount; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
@@ -279,16 +279,48 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
float centerF = 0.5f - Math.Abs(0.5f - (i / (float)pointCount));
|
||||
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.Server);
|
||||
edgePoints.Add(
|
||||
Vector2 extrudedPoint =
|
||||
edge.Point1 +
|
||||
edgeDir * (i / (float)pointCount) -
|
||||
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF);
|
||||
edgeDir * (i / (float)pointCount) +
|
||||
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF;
|
||||
|
||||
var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 2);
|
||||
bool isInside = false;
|
||||
foreach (var nearbyCell in nearbyCells)
|
||||
{
|
||||
if (nearbyCell == cell || nearbyCell.CellType != CellType.Solid) { continue; }
|
||||
//check if extruding the edge causes it to go inside another one
|
||||
if (nearbyCell.IsPointInside(extrudedPoint))
|
||||
{
|
||||
isInside = true;
|
||||
break;
|
||||
}
|
||||
//check if another edge will be inside this cell after the extrusion
|
||||
Vector2 triangleCenter = (edge.Point1 + edge.Point2 + extrudedPoint) / 3;
|
||||
foreach (GraphEdge nearbyEdge in nearbyCell.Edges)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2))
|
||||
{
|
||||
isInside = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isInside) { break; }
|
||||
}
|
||||
|
||||
if (!isInside)
|
||||
{
|
||||
edgePoints.Add(extrudedPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
for (int i = 0; i < edgePoints.Count - 1; i++)
|
||||
{
|
||||
tempEdges.Add(new GraphEdge(edgePoints[i], edgePoints[i + 1])
|
||||
{
|
||||
@@ -297,7 +329,10 @@ namespace Barotrauma
|
||||
IsSolid = edge.IsSolid,
|
||||
Site1 = edge.Site1,
|
||||
Site2 = edge.Site2,
|
||||
OutsideLevel = edge.OutsideLevel
|
||||
OutsideLevel = edge.OutsideLevel,
|
||||
NextToCave = edge.NextToCave,
|
||||
NextToMainPath = edge.NextToMainPath,
|
||||
NextToSidePath = edge.NextToSidePath
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -347,15 +382,27 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, cell.Center));
|
||||
|
||||
if (bodyPoints.Count < 2) continue;
|
||||
Vector2 minVert = tempVertices[0];
|
||||
Vector2 maxVert = tempVertices[0];
|
||||
foreach (var vert in tempVertices)
|
||||
{
|
||||
minVert = new Vector2(
|
||||
Math.Min(minVert.X, vert.X),
|
||||
Math.Min(minVert.Y, vert.Y));
|
||||
maxVert = new Vector2(
|
||||
Math.Max(maxVert.X, vert.X),
|
||||
Math.Max(maxVert.Y, vert.Y));
|
||||
}
|
||||
Vector2 center = (minVert + maxVert) / 2;
|
||||
renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, center));
|
||||
|
||||
if (bodyPoints.Count < 2) { continue; }
|
||||
|
||||
if (bodyPoints.Count < 3)
|
||||
{
|
||||
foreach (Vector2 vertex in tempVertices)
|
||||
{
|
||||
if (bodyPoints.Contains(vertex)) continue;
|
||||
if (bodyPoints.Contains(vertex)) { continue; }
|
||||
bodyPoints.Add(vertex);
|
||||
break;
|
||||
}
|
||||
@@ -366,11 +413,11 @@ namespace Barotrauma
|
||||
cell.BodyVertices.Add(bodyPoints[i]);
|
||||
bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
|
||||
}
|
||||
|
||||
if (cell.CellType == CellType.Empty) continue;
|
||||
|
||||
if (cell.CellType == CellType.Empty) { continue; }
|
||||
|
||||
cellBody.UserData = cell;
|
||||
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center));
|
||||
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(center));
|
||||
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
@@ -380,13 +427,17 @@ namespace Barotrauma
|
||||
Vector2 b = triangles[i][1];
|
||||
Vector2 c = triangles[i][2];
|
||||
float area = Math.Abs(a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y)) / 2.0f;
|
||||
if (area < 1.0f) continue;
|
||||
if (area < 1.0f) { continue; }
|
||||
|
||||
Vertices bodyVertices = new Vertices(triangles[i]);
|
||||
var newFixture = cellBody.CreatePolygon(bodyVertices, 5.0f);
|
||||
newFixture.UserData = cell;
|
||||
PolygonShape polygon = new PolygonShape(bodyVertices, 5.0f);
|
||||
Fixture fixture = new Fixture(polygon)
|
||||
{
|
||||
UserData = cell
|
||||
};
|
||||
cellBody.Add(fixture, resetMassData: false);
|
||||
|
||||
if (newFixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon)
|
||||
if (fixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + ")");
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
@@ -394,11 +445,13 @@ namespace Barotrauma
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Warning,
|
||||
"Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + "). Seed: " + level.Seed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
cell.Body = cellBody;
|
||||
}
|
||||
|
||||
cellBody.CollisionCategories = Physics.CollisionLevel;
|
||||
cellBody.ResetMassData();
|
||||
|
||||
return cellBody;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class DestructibleLevelWall : LevelWall, IDamageable
|
||||
{
|
||||
public bool NetworkUpdatePending;
|
||||
|
||||
public float Damage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float MaxHealth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 1000.0f;
|
||||
|
||||
public bool Destroyed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float FadeOutDuration
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float FadeOutTimer
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 SimPosition
|
||||
{
|
||||
get { return Body.Position; }
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(Body.Position); }
|
||||
}
|
||||
|
||||
public float Health
|
||||
{
|
||||
get { return MaxHealth - Damage; }
|
||||
}
|
||||
|
||||
public DestructibleLevelWall(List<Vector2> vertices, Color color, Level level, float? health = null, bool giftWrap = false)
|
||||
: base (vertices, color, level, giftWrap)
|
||||
{
|
||||
MaxHealth = health ?? MathHelper.Clamp(Body.Mass, 100.0f, 1000.0f);
|
||||
Cells.ForEach(c => c.IsDestructible = true);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (FadeOutDuration > 0.0f)
|
||||
{
|
||||
FadeOutTimer += deltaTime;
|
||||
if (FadeOutTimer > FadeOutDuration && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsClient)) { Destroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDamage(float damage, Vector2 worldPosition)
|
||||
{
|
||||
AddDamageProjSpecific(damage, worldPosition);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (Destroyed) { return; }
|
||||
if (!MathUtils.NearlyEqual(damage, 0.0f)) { NetworkUpdatePending = true; }
|
||||
Damage += damage;
|
||||
if (Damage >= MaxHealth)
|
||||
{
|
||||
CreateFragments();
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
partial void AddDamageProjSpecific(float damage, Vector2 worldPosition);
|
||||
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
|
||||
{
|
||||
AddDamage(attack.StructureDamage, worldPosition);
|
||||
return new AttackResult(attack.StructureDamage);
|
||||
}
|
||||
|
||||
private void CreateFragments()
|
||||
{
|
||||
#if CLIENT
|
||||
SoundPlayer.PlaySound("icebreak", WorldPosition);
|
||||
#endif
|
||||
//generate initial triangles (one triangle from each edge to the center of the cell)
|
||||
List<List<Vector2>> triangles = new List<List<Vector2>>();
|
||||
foreach (var cell in Cells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
List<Vector2> triangleVerts = new List<Vector2>
|
||||
{
|
||||
edge.Point1 + cell.Translation,
|
||||
edge.Point2 + cell.Translation,
|
||||
cell.Center
|
||||
};
|
||||
triangles.Add(triangleVerts);
|
||||
}
|
||||
}
|
||||
|
||||
//split triangles that have edges more than 1000 units long
|
||||
Pair<int, int> longestEdge = new Pair<int, int>(-1, -1);
|
||||
float longestEdgeLength = 0.0f;
|
||||
do
|
||||
{
|
||||
longestEdge.First = -1;
|
||||
longestEdge.Second = -1;
|
||||
longestEdgeLength = 0.0f;
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
for (int edge = 0; edge < 3; edge++)
|
||||
{
|
||||
float edgeLength = Vector2.Distance(triangles[i][edge], triangles[i][(edge + 1) % 3]);
|
||||
if (edgeLength > longestEdgeLength)
|
||||
{
|
||||
longestEdge.First = i;
|
||||
longestEdge.Second = edge;
|
||||
longestEdgeLength = edgeLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (longestEdgeLength < 1000.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Vector2 p0 = triangles[longestEdge.First][longestEdge.Second];
|
||||
Vector2 p1 = triangles[longestEdge.First][(longestEdge.Second + 1) % 3];
|
||||
Vector2 p2 = triangles[longestEdge.First][(longestEdge.Second + 2) % 3];
|
||||
triangles[longestEdge.First] = new List<Vector2> { p0, (p0 + p1) / 2, p2 };
|
||||
triangles.Add(new List<Vector2> { (p0 + p1) / 2, p1, p2 });
|
||||
|
||||
|
||||
} while (triangles.Count < 32);
|
||||
|
||||
//generate fragments
|
||||
foreach (var triangle in triangles)
|
||||
{
|
||||
Vector2 triangleCenter = (triangle[0] + triangle[1]+ triangle[2]) / 3;
|
||||
triangle[0] -= triangleCenter;
|
||||
triangle[1] -= triangleCenter;
|
||||
triangle[2] -= triangleCenter;
|
||||
Vector2 simTriangleCenter = ConvertUnits.ToSimUnits(triangleCenter);
|
||||
|
||||
DestructibleLevelWall fragment = new DestructibleLevelWall(triangle, Color.White, Level.Loaded, giftWrap: true);
|
||||
fragment.Damage = fragment.MaxHealth;
|
||||
fragment.Body.Position = simTriangleCenter;
|
||||
fragment.Body.BodyType = BodyType.Dynamic;
|
||||
fragment.Body.FixedRotation = false;
|
||||
fragment.Body.LinearDamping = Rand.Range(0.2f, 0.3f);
|
||||
fragment.Body.AngularDamping = Rand.Range(0.1f, 0.2f);
|
||||
fragment.Body.GravityScale = 0.1f;
|
||||
fragment.Body.Mass *= 10.0f;
|
||||
fragment.Body.CollisionCategories = Physics.CollisionNone;
|
||||
fragment.Body.CollidesWith = Physics.CollisionWall;
|
||||
fragment.FadeOutDuration = 20.0f;
|
||||
|
||||
Vector2 bodyDiff = simTriangleCenter - Body.Position;
|
||||
fragment.Body.LinearVelocity = (bodyDiff + Rand.Vector(0.5f)).ClampLength(15.0f);
|
||||
fragment.Body.AngularVelocity = Rand.Range(-0.5f, 0.5f);// MathHelper.Clamp(-bodyDiff.X * 0.1f, -0.5f, 0.5f);
|
||||
|
||||
Level.Loaded.UnsyncedExtraWalls.Add(fragment);
|
||||
|
||||
#if CLIENT
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
int startEdgeIndex = Rand.Int(3);
|
||||
Vector2 pos1 = triangle[startEdgeIndex];
|
||||
Vector2 pos2 = triangle[(startEdgeIndex + 1) % 3];
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("iceshards",
|
||||
triangleCenter + Vector2.Lerp(pos1, pos2, Rand.Range(0.0f, 1.0f)),
|
||||
Rand.Vector(Rand.Range(50.0f, 1000.0f)) + fragment.Body.LinearVelocity * 100.0f);
|
||||
if (particle != null)
|
||||
{
|
||||
particle.Size *= Rand.Range(1.0f, 5.0f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (Destroyed) { return; }
|
||||
Destroyed = true;
|
||||
level?.UnsyncedExtraWalls?.Remove(this);
|
||||
foreach (var cell in Cells)
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
}
|
||||
GameMain.World.Remove(Body);
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,13 +26,33 @@ namespace Barotrauma
|
||||
|
||||
public readonly LevelGenerationParams GenerationParams;
|
||||
|
||||
public bool HasBeaconStation;
|
||||
public bool IsBeaconActive;
|
||||
|
||||
public OutpostGenerationParams ForceOutpostGenerationParams;
|
||||
|
||||
public readonly Point Size;
|
||||
|
||||
public readonly int InitialDepth;
|
||||
|
||||
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
|
||||
public float CrushDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
return Math.Max(Size.Y, Level.DefaultRealWorldCrushDepth / Physics.DisplayToRealWorldRatio) - InitialDepth;
|
||||
}
|
||||
}
|
||||
public float RealWorldCrushDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
return Math.Max(Size.Y * Physics.DisplayToRealWorldRatio, Level.DefaultRealWorldCrushDepth);
|
||||
}
|
||||
}
|
||||
|
||||
public LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
|
||||
{
|
||||
Seed = seed ?? throw new ArgumentException("Seed was null");
|
||||
@@ -44,6 +64,8 @@ namespace Barotrauma
|
||||
sizeFactor = MathHelper.Clamp(sizeFactor, 0.0f, 1.0f);
|
||||
int width = (int)MathHelper.Lerp(generationParams.MinWidth, generationParams.MaxWidth, sizeFactor);
|
||||
|
||||
InitialDepth = (int)MathHelper.Lerp(generationParams.InitialDepthMin, generationParams.InitialDepthMax, sizeFactor);
|
||||
|
||||
Size = new Point(
|
||||
(int)MathUtils.Round(width, Level.GridCellSize),
|
||||
(int)MathUtils.Round(generationParams.Height, Level.GridCellSize));
|
||||
@@ -56,8 +78,11 @@ namespace Barotrauma
|
||||
Size = element.GetAttributePoint("size", new Point(1000));
|
||||
Enum.TryParse(element.GetAttributeString("type", "LocationConnection"), out Type);
|
||||
|
||||
HasBeaconStation = element.GetAttributeBool("hasbeaconstation", false);
|
||||
IsBeaconActive = element.GetAttributeBool("isbeaconactive", false);
|
||||
|
||||
string generationParamsId = element.GetAttributeString("generationparams", "");
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId);
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId || l.OldIdentifier == generationParamsId);
|
||||
if (GenerationParams == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading a level. Could not find level generation params with the ID \"{generationParamsId}\".");
|
||||
@@ -68,8 +93,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
InitialDepth = element.GetAttributeInt("initialdepth", GenerationParams.InitialDepthMin);
|
||||
|
||||
string biomeIdentifier = element.GetAttributeString("biome", "");
|
||||
Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier);
|
||||
Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier || b.OldIdentifier == biomeIdentifier);
|
||||
if (Biome == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in level data: could not find the biome \"{biomeIdentifier}\".");
|
||||
@@ -104,6 +131,12 @@ namespace Barotrauma
|
||||
Size = new Point(
|
||||
(int)MathUtils.Round(width, Level.GridCellSize),
|
||||
(int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
|
||||
|
||||
HasBeaconStation = rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
IsBeaconActive = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,6 +152,7 @@ namespace Barotrauma
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, (float)rand.NextDouble());
|
||||
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
|
||||
Size = new Point(
|
||||
(int)MathUtils.Round(width, Level.GridCellSize),
|
||||
(int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
|
||||
@@ -136,16 +170,24 @@ namespace Barotrauma
|
||||
LevelType type = generationParams == null ? LevelData.LevelType.LocationConnection : generationParams.Type;
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); }
|
||||
var biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
|
||||
var biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
|
||||
LevelGenerationParams.GetBiomes().GetRandom(Rand.RandSync.Server);
|
||||
|
||||
return new LevelData(
|
||||
var levelData = new LevelData(
|
||||
seed,
|
||||
difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.Server),
|
||||
generationParams,
|
||||
biome);
|
||||
if (type == LevelType.LocationConnection)
|
||||
{
|
||||
float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
|
||||
levelData.HasBeaconStation = beaconRng < 0.5f;
|
||||
levelData.IsBeaconActive = beaconRng > 0.25f;
|
||||
}
|
||||
GameMain.GameSession?.GameMode?.Mission?.AdjustLevelData(levelData);
|
||||
return levelData;
|
||||
}
|
||||
|
||||
public void Save(XElement parentElement)
|
||||
@@ -156,7 +198,15 @@ namespace Barotrauma
|
||||
new XAttribute("type", Type.ToString()),
|
||||
new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("size", XMLExtensions.PointToString(Size)),
|
||||
new XAttribute("generationparams", GenerationParams.Identifier));
|
||||
new XAttribute("generationparams", GenerationParams.Identifier),
|
||||
new XAttribute("initialdepth", InitialDepth));
|
||||
|
||||
if (HasBeaconStation)
|
||||
{
|
||||
newElement.Add(
|
||||
new XAttribute("hasbeaconstation", HasBeaconStation.ToString()),
|
||||
new XAttribute("isbeaconactive", IsBeaconActive.ToString()));
|
||||
}
|
||||
|
||||
if (Type == LevelType.Outpost)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -10,6 +9,7 @@ namespace Barotrauma
|
||||
class Biome
|
||||
{
|
||||
public readonly string Identifier;
|
||||
public readonly string OldIdentifier;
|
||||
public readonly string DisplayName;
|
||||
public readonly string Description;
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Barotrauma
|
||||
public Biome(XElement element)
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
OldIdentifier = element.GetAttributeString("oldidentifier", null);
|
||||
if (string.IsNullOrEmpty(Identifier))
|
||||
{
|
||||
Identifier = element.GetAttributeString("name", "");
|
||||
@@ -61,6 +62,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly string OldIdentifier;
|
||||
|
||||
private int minWidth, maxWidth, height;
|
||||
|
||||
private Point voronoiSiteInterval;
|
||||
@@ -72,9 +75,7 @@ namespace Barotrauma
|
||||
//x = min interval, y = max interval
|
||||
private Point mainPathNodeIntervalRange;
|
||||
|
||||
private int smallTunnelCount;
|
||||
//x = min length, y = max length
|
||||
private Point smallTunnelLengthRange;
|
||||
private int caveCount;
|
||||
|
||||
//how large portion of the bottom of the level should be "carved out"
|
||||
//if 0.0f, the bottom will be completely solid (making the abyss unreachable)
|
||||
@@ -96,6 +97,8 @@ namespace Barotrauma
|
||||
|
||||
private float waterParticleScale;
|
||||
|
||||
private int initialDepthMin, initialDepthMax;
|
||||
|
||||
//which biomes can this type of level appear in
|
||||
private readonly List<Biome> allowedBiomes = new List<Biome>();
|
||||
|
||||
@@ -146,7 +149,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private Vector2 startPosition;
|
||||
[Serialize("0,0", true, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable]
|
||||
[Serialize("0,0", true, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
get { return startPosition; }
|
||||
@@ -159,7 +162,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private Vector2 endPosition;
|
||||
[Serialize("1,0", true, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable]
|
||||
[Serialize("1,0", true, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 EndPosition
|
||||
{
|
||||
get { return endPosition; }
|
||||
@@ -185,25 +188,46 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(80, true, description: "The total number of decorative background creatures."), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int BackgroundCreatureAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable]
|
||||
public int MinWidth
|
||||
{
|
||||
get { return minWidth; }
|
||||
set { minWidth = Math.Max(value, 2000); }
|
||||
set { minWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(100000, true), Editable]
|
||||
public int MaxWidth
|
||||
{
|
||||
get { return maxWidth; }
|
||||
set { maxWidth = Math.Max(value, 2000); }
|
||||
set { maxWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(50000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(50000, true), Editable]
|
||||
public int Height
|
||||
{
|
||||
get { return height; }
|
||||
set { height = Math.Max(value, 2000); }
|
||||
set { height = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(80000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMin
|
||||
{
|
||||
get { return initialDepthMin; }
|
||||
set { initialDepthMin = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(80000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMax
|
||||
{
|
||||
get { return initialDepthMax; }
|
||||
set { initialDepthMax = Math.Max(value, initialDepthMin); }
|
||||
}
|
||||
|
||||
[Serialize(6500, true), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
@@ -213,6 +237,29 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0,1", true), Editable]
|
||||
public Point SideTunnelCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float SideTunnelVariance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2000,6000", true), Editable]
|
||||
public Point MinSideTunnelRadius
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("3000, 3000", true, description: "How far from each other voronoi sites are placed. " +
|
||||
"Sites determine shape of the voronoi graph which the level walls are generated from. " +
|
||||
"(Decreasing this value causes the number of sites, and the complexity of the level, to increase exponentially - be careful when adjusting)")]
|
||||
@@ -239,7 +286,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueInt = 100, MaxValueInt = 10000), Serialize(1000, true, description: "The edges of the individual wall cells are subdivided into edges of this size. "
|
||||
[Editable(MinValueInt = 500, MaxValueInt = 10000), Serialize(5000, true, description: "The edges of the individual wall cells are subdivided into edges of this size. "
|
||||
+ "Can be used in conjunction with the rounding values to make the cells rounder. Smaller values will make the cells look smoother, " +
|
||||
"but make the level more performance-intensive as the number of polygons used in rendering and physics calculations increases.")]
|
||||
public int CellSubdivisionLength
|
||||
@@ -287,23 +334,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(5, true, description: "The number of small tunnels placed along the main path.")]
|
||||
public int SmallTunnelCount
|
||||
[Serialize(0.5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float MainPathVariance
|
||||
{
|
||||
get { return smallTunnelCount; }
|
||||
set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); }
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
|
||||
Serialize("5000, 10000", true, description: "The minimum and maximum length of small tunnels placed along the main path.")]
|
||||
public Point SmallTunnelLengthRange
|
||||
[Editable, Serialize(5, true, description: "The number of caves placed along the main path.")]
|
||||
public int CaveCount
|
||||
{
|
||||
get { return smallTunnelLengthRange; }
|
||||
set
|
||||
{
|
||||
smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100, MinWidth);
|
||||
smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, MinWidth);
|
||||
}
|
||||
get { return caveCount; }
|
||||
set { caveCount = MathHelper.Clamp(value, 0, 100); }
|
||||
}
|
||||
|
||||
[Serialize(100, true), Editable(MinValueInt = 0, MaxValueInt = 10000)]
|
||||
@@ -313,6 +355,34 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("19200,38400", true, description: "The minimum and maximum distance between two resource spawn points on a path."), Editable(100, 100000)]
|
||||
public Point ResourceIntervalRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("9600,19200", true, description: "The minimum and maximum distance between two resource spawn points on a cave path."), Editable(100, 100000)]
|
||||
public Point CaveResourceIntervalRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2,8", true, description: "The minimum and maximum amount of resources in a single cluster. " +
|
||||
"In addition to this, resource commonness affects the cluster size. Less common resources spawn in smaller clusters."), Editable(1, 20)]
|
||||
public Point ResourceClusterSizeRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.3f, true, description: "How likely a resource spawn point on a path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float ResourceSpawnChance { get; set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "How likely a resource spawn point on a cave path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float CaveResourceSpawnChance { get; set; }
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int FloatingIceChunkCount
|
||||
{
|
||||
@@ -320,6 +390,20 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 100)]
|
||||
public int IslandCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int IceSpireCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(300000, true, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
public int SeaFloorDepth
|
||||
{
|
||||
@@ -415,12 +499,41 @@ namespace Barotrauma
|
||||
private set { waterParticleScale = Math.Max(value, 0.01f); }
|
||||
}
|
||||
|
||||
[Serialize(2048.0f, true, description: "Size of the level wall texture."), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
public float WallTextureSize
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(2048.0f, true), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeTextureWidth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, true, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
|
||||
public float WallEdgeExpandOutwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, true, description: "How far inside the level walls the edge texture continues."), Editable(minValue: 0.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeExpandInwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite BackgroundSprite { get; private set; }
|
||||
public Sprite BackgroundTopSprite { get; private set; }
|
||||
public Sprite WallSprite { get; private set; }
|
||||
public Sprite WallSpriteSpecular { get; private set; }
|
||||
public Sprite WallEdgeSprite { get; private set; }
|
||||
public Sprite WallEdgeSpriteSpecular { get; private set; }
|
||||
public Sprite DestructibleWallSprite { get; private set; }
|
||||
public Sprite DestructibleWallEdgeSprite { get; private set; }
|
||||
public Sprite WallSpriteDestroyed { get; private set; }
|
||||
public Sprite WaterParticles { get; private set; }
|
||||
|
||||
public static IEnumerable<Biome> GetBiomes()
|
||||
@@ -469,6 +582,8 @@ namespace Barotrauma
|
||||
{
|
||||
Identifier = element == null ? "default" :
|
||||
element.GetAttributeString("identifier", null) ?? element.Name.ToString();
|
||||
OldIdentifier = element?.GetAttributeString("oldidentifier", null)?.ToLowerInvariant();
|
||||
Identifier = Identifier.ToLowerInvariant();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
if (element == null) { return; }
|
||||
@@ -486,7 +601,8 @@ namespace Barotrauma
|
||||
string biomeName = biomeNames[i].Trim().ToLowerInvariant();
|
||||
if (biomeName == "none") { continue; }
|
||||
|
||||
Biome matchingBiome = biomes.Find(b => b.Identifier.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
|
||||
Biome matchingBiome = biomes.Find(b =>
|
||||
b.Identifier.Equals(biomeName, StringComparison.OrdinalIgnoreCase) || (b.OldIdentifier?.Equals(biomeName, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||
if (matchingBiome == null)
|
||||
{
|
||||
matchingBiome = biomes.Find(b => b.DisplayName.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -518,14 +634,17 @@ namespace Barotrauma
|
||||
case "wall":
|
||||
WallSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "wallspecular":
|
||||
WallSpriteSpecular = new Sprite(subElement);
|
||||
break;
|
||||
case "walledge":
|
||||
WallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "walledgespecular":
|
||||
WallEdgeSpriteSpecular = new Sprite(subElement);
|
||||
case "destructiblewall":
|
||||
DestructibleWallSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "destructiblewalledge":
|
||||
DestructibleWallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "walldestroyed":
|
||||
WallSpriteDestroyed = new Sprite(subElement);
|
||||
break;
|
||||
case "waterparticles":
|
||||
WaterParticles = new Sprite(subElement);
|
||||
@@ -546,8 +665,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<XElement> biomeElements = new List<XElement>();
|
||||
List<XElement> levelParamElements = new List<XElement>();
|
||||
|
||||
Dictionary<string, XElement> levelParamElements = new Dictionary<string, XElement>();
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
@@ -557,18 +675,18 @@ namespace Barotrauma
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
biomeElements.Clear();
|
||||
levelParamElements.Clear();
|
||||
DebugConsole.NewMessage($"Overriding the level generation parameters and biomes with '{file.Path}'", Color.Yellow);
|
||||
DebugConsole.NewMessage($"Overriding biomes with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
else if (biomeElements.Any() || levelParamElements.Any())
|
||||
else if (biomeElements.Any() && mainElement.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': Another level generation parameter file already loaded! Use <override></override> tags to override it.");
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': Another level generation parameter file already loaded! Use <override></override> tags to override the biomes.");
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
bool isOverride = element.IsOverride();
|
||||
if (isOverride)
|
||||
{
|
||||
if (element.FirstElement().Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -578,18 +696,31 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
levelParamElements.Clear();
|
||||
DebugConsole.NewMessage($"Overriding the level generation parameters with '{file.Path}'", Color.Yellow);
|
||||
levelParamElements.AddRange(element.Elements());
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", null) ?? element.GetAttributeString("name", "");
|
||||
if (levelParamElements.ContainsKey(identifier))
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the level generation parameters '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
levelParamElements.Remove(identifier);
|
||||
}
|
||||
levelParamElements.Add(identifier, element.FirstElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
biomeElements.AddRange(element.Elements());
|
||||
}
|
||||
else
|
||||
{
|
||||
levelParamElements.Add(element);
|
||||
string identifier = element.GetAttributeString("identifier", null) ?? element.GetAttributeString("name", "");
|
||||
if (levelParamElements.ContainsKey(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate level generation parameters: '{identifier}' defined in {element.Name} of '{file.Path}'. Use <override></override> tags to override the generation parameters.");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
levelParamElements.Add(identifier, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -599,7 +730,7 @@ namespace Barotrauma
|
||||
biomes.Add(new Biome(biomeElement));
|
||||
}
|
||||
|
||||
foreach (XElement levelParamElement in levelParamElements)
|
||||
foreach (XElement levelParamElement in levelParamElements.Values)
|
||||
{
|
||||
LevelParams.Add(new LevelGenerationParams(levelParamElement));
|
||||
}
|
||||
|
||||
@@ -37,18 +37,23 @@ namespace Barotrauma
|
||||
|
||||
public bool NeedsNetworkSyncing
|
||||
{
|
||||
get { return Triggers.Any(t => t.NeedsNetworkSyncing); }
|
||||
set { Triggers.ForEach(t => t.NeedsNetworkSyncing = false); }
|
||||
get { return Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing); }
|
||||
set
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedsUpdate
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return spriteIndex < 0 || Prefab.Sprites.Count == 0 ? null : Prefab.Sprites[spriteIndex % Prefab.Sprites.Count]; }
|
||||
}
|
||||
public Sprite SpecularSprite
|
||||
{
|
||||
get { return spriteIndex < 0 || Prefab.SpecularSprites.Count == 0 ? null : Prefab.SpecularSprites[spriteIndex % Prefab.SpecularSprites.Count]; }
|
||||
}
|
||||
|
||||
Vector2 ISpatialEntity.Position => new Vector2(Position.X, Position.Y);
|
||||
|
||||
@@ -60,8 +65,6 @@ namespace Barotrauma
|
||||
|
||||
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
|
||||
{
|
||||
Triggers = new List<LevelTrigger>();
|
||||
|
||||
ActivePrefab = Prefab = prefab;
|
||||
Position = position;
|
||||
Scale = scale;
|
||||
@@ -69,13 +72,26 @@ namespace Barotrauma
|
||||
|
||||
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
|
||||
|
||||
if (prefab.PhysicsBodyElement != null)
|
||||
if (Sprite != null && prefab.SpriteSpecificPhysicsBodyElements.ContainsKey(Sprite))
|
||||
{
|
||||
PhysicsBody = new PhysicsBody(prefab.SpriteSpecificPhysicsBodyElements[Sprite], ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale);
|
||||
}
|
||||
else if (prefab.PhysicsBodyElement != null)
|
||||
{
|
||||
PhysicsBody = new PhysicsBody(prefab.PhysicsBodyElement, ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale);
|
||||
}
|
||||
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition, -Rotation);
|
||||
PhysicsBody.BodyType = BodyType.Static;
|
||||
PhysicsBody.CollisionCategories = Physics.CollisionLevel;
|
||||
PhysicsBody.CollidesWith = Physics.CollisionWall | Physics.CollisionCharacter;
|
||||
}
|
||||
|
||||
foreach (XElement triggerElement in prefab.LevelTriggerElements)
|
||||
{
|
||||
Triggers ??= new List<LevelTrigger>();
|
||||
Vector2 triggerPosition = triggerElement.GetAttributeVector2("position", Vector2.Zero) * scale;
|
||||
|
||||
if (rotation != 0.0f)
|
||||
@@ -90,10 +106,12 @@ namespace Barotrauma
|
||||
|
||||
var newTrigger = new LevelTrigger(triggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale, prefab.Name);
|
||||
int parentTriggerIndex = prefab.LevelTriggerElements.IndexOf(triggerElement.Parent);
|
||||
if (parentTriggerIndex > -1) newTrigger.ParentTrigger = Triggers[parentTriggerIndex];
|
||||
if (parentTriggerIndex > -1) { newTrigger.ParentTrigger = Triggers[parentTriggerIndex]; }
|
||||
Triggers.Add(newTrigger);
|
||||
}
|
||||
|
||||
NeedsUpdate = NeedsNetworkSyncing || (Triggers != null && Triggers.Any()) || Prefab.PhysicsBodyTriggerIndex > -1;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
@@ -131,9 +149,10 @@ namespace Barotrauma
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c)
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
for (int j = 0; j < Triggers.Count; j++)
|
||||
{
|
||||
if (!Triggers[j].UseNetworkSyncing) continue;
|
||||
if (!Triggers[j].UseNetworkSyncing) { continue; }
|
||||
Triggers[j].ServerWrite(msg, c);
|
||||
}
|
||||
}
|
||||
|
||||
+191
-93
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -17,9 +18,10 @@ namespace Barotrauma
|
||||
const int GridSize = 2000;
|
||||
|
||||
private List<LevelObject> objects;
|
||||
private List<LevelObject> updateableObjects;
|
||||
private List<LevelObject>[,] objectGrid;
|
||||
|
||||
public LevelObjectManager() : base(null)
|
||||
public LevelObjectManager() : base(null, Entity.NullEntityID)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,20 +29,36 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly GraphEdge GraphEdge;
|
||||
public readonly Vector2 Normal;
|
||||
public readonly LevelObjectPrefab.SpawnPosType SpawnPosType;
|
||||
public readonly List<LevelObjectPrefab.SpawnPosType> SpawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>();
|
||||
public readonly Alignment Alignment;
|
||||
public readonly float Length;
|
||||
|
||||
private readonly float noiseVal;
|
||||
|
||||
|
||||
public SpawnPosition(GraphEdge graphEdge, Vector2 normal, LevelObjectPrefab.SpawnPosType spawnPosType, Alignment alignment)
|
||||
: this(graphEdge, normal, spawnPosType.ToEnumerable(), alignment)
|
||||
{ }
|
||||
|
||||
public SpawnPosition(GraphEdge graphEdge, Vector2 normal, IEnumerable<LevelObjectPrefab.SpawnPosType> spawnPosTypes, Alignment alignment)
|
||||
{
|
||||
GraphEdge = graphEdge;
|
||||
Normal = normal;
|
||||
SpawnPosType = spawnPosType;
|
||||
Alignment = alignment;
|
||||
|
||||
Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2);
|
||||
Normal = normal.NearlyEquals(Vector2.Zero) ? Vector2.UnitY : Vector2.Normalize(normal);
|
||||
SpawnPosTypes.AddRange(spawnPosTypes);
|
||||
|
||||
if (spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.MainPath) ||
|
||||
spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelStart) ||
|
||||
spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelEnd))
|
||||
{
|
||||
Length = 1000.0f;
|
||||
Normal = Vector2.Zero;
|
||||
Alignment = Alignment.Any;
|
||||
}
|
||||
else
|
||||
{
|
||||
Alignment = alignment;
|
||||
Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2);
|
||||
}
|
||||
|
||||
noiseVal =
|
||||
(float)(PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 10000.0f, GraphEdge.Point1.Y / 10000.0f, 0.5f) +
|
||||
@@ -83,7 +101,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var posOfInterest in level.PositionsOfInterest)
|
||||
{
|
||||
if (posOfInterest.PositionType != Level.PositionType.MainPath) continue;
|
||||
if (posOfInterest.PositionType != Level.PositionType.MainPath && posOfInterest.PositionType != Level.PositionType.SidePath) { continue; }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(posOfInterest.Position.ToVector2(), posOfInterest.Position.ToVector2() + Vector2.UnitX),
|
||||
@@ -101,49 +119,29 @@ namespace Barotrauma
|
||||
|
||||
var availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List);
|
||||
objects = new List<LevelObject>();
|
||||
updateableObjects = new List<LevelObject>();
|
||||
|
||||
Dictionary<LevelObjectPrefab, List<SpawnPosition>> suitableSpawnPositions = new Dictionary<LevelObjectPrefab, List<SpawnPosition>>();
|
||||
Dictionary<LevelObjectPrefab, List<float>> spawnPositionWeights = new Dictionary<LevelObjectPrefab, List<float>>();
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams.Identifier, availablePrefabs);
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams, availablePrefabs);
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
suitableSpawnPositions.Add(prefab, availableSpawnPositions.Where(sp =>
|
||||
prefab.SpawnPos.HasFlag(sp.SpawnPosType) && (sp.Length >= prefab.MinSurfaceWidth && prefab.Alignment.HasFlag(sp.Alignment) || sp.SpawnPosType == LevelObjectPrefab.SpawnPosType.LevelEnd || sp.SpawnPosType == LevelObjectPrefab.SpawnPosType.LevelStart)).ToList());
|
||||
suitableSpawnPositions.Add(prefab,
|
||||
availableSpawnPositions.Where(sp =>
|
||||
sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) &&
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
|
||||
float rotation = 0.0f;
|
||||
if (prefab.AlignWithSurface && spawnPosition != null)
|
||||
{
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
|
||||
}
|
||||
rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.Server);
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
Vector2 edgeDir = Vector2.UnitX;
|
||||
if (spawnPosition == null)
|
||||
{
|
||||
position = new Vector2(
|
||||
Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server));
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
|
||||
position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
var newObject = new LevelObject(prefab,
|
||||
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation);
|
||||
AddObject(newObject, level);
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
if (prefab.MaxCount < amount)
|
||||
{
|
||||
if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
|
||||
@@ -151,38 +149,119 @@ namespace Barotrauma
|
||||
availablePrefabs.Remove(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
|
||||
foreach (Level.Cave cave in level.Caves)
|
||||
{
|
||||
availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List.FindAll(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall)));
|
||||
availableSpawnPositions.Clear();
|
||||
suitableSpawnPositions.Clear();
|
||||
spawnPositionWeights.Clear();
|
||||
|
||||
var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
|
||||
List<VoronoiCell> caveWallCells = new List<VoronoiCell>();
|
||||
foreach (var edge in caveCells.SelectMany(c => c.Edges))
|
||||
{
|
||||
int childCount = Rand.Range(child.MinCount, child.MaxCount, Rand.RandSync.Server);
|
||||
for (int j = 0; j < childCount; j++)
|
||||
{
|
||||
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
|
||||
int prefabCount = matchingPrefabs.Count();
|
||||
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.Server));
|
||||
if (childPrefab == null) continue;
|
||||
|
||||
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server) * prefab.MinSurfaceWidth;
|
||||
|
||||
var childObject = new LevelObject(childPrefab,
|
||||
new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.Server)),
|
||||
Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.Server),
|
||||
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.Server));
|
||||
|
||||
AddObject(childObject, level);
|
||||
}
|
||||
if (!edge.NextToCave) { continue; }
|
||||
if (edge.Cell1?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell1); }
|
||||
if (edge.Cell2?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell2); }
|
||||
}
|
||||
}
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));
|
||||
|
||||
for (int i = 0; i < cave.CaveGenerationParams.LevelObjectAmount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs);
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
suitableSpawnPositions.Add(prefab,
|
||||
availableSpawnPositions.Where(sp =>
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
if (prefab.MaxCount < amount)
|
||||
{
|
||||
if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
|
||||
{
|
||||
availablePrefabs.Remove(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceObject(LevelObjectPrefab prefab, SpawnPosition spawnPosition, Level level)
|
||||
{
|
||||
float rotation = 0.0f;
|
||||
if (prefab.AlignWithSurface && spawnPosition.Normal.LengthSquared() > 0.001f && spawnPosition != null)
|
||||
{
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
|
||||
}
|
||||
rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.Server);
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
Vector2 edgeDir = Vector2.UnitX;
|
||||
if (spawnPosition == null)
|
||||
{
|
||||
position = new Vector2(
|
||||
Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server));
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
|
||||
position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
if (!MathUtils.NearlyEqual(prefab.RandomOffset.X, 0.0f) || !MathUtils.NearlyEqual(prefab.RandomOffset.Y, 0.0f))
|
||||
{
|
||||
Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.Server);
|
||||
position += offsetDir * Rand.Range(prefab.RandomOffset.X, prefab.RandomOffset.Y, Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
var newObject = new LevelObject(prefab,
|
||||
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation);
|
||||
AddObject(newObject, level);
|
||||
|
||||
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
|
||||
{
|
||||
int childCount = Rand.Range(child.MinCount, child.MaxCount, Rand.RandSync.Server);
|
||||
for (int j = 0; j < childCount; j++)
|
||||
{
|
||||
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
|
||||
int prefabCount = matchingPrefabs.Count();
|
||||
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.Server));
|
||||
if (childPrefab == null) continue;
|
||||
|
||||
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server) * prefab.MinSurfaceWidth;
|
||||
|
||||
var childObject = new LevelObject(childPrefab,
|
||||
new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.Server)),
|
||||
Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.Server),
|
||||
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.Server));
|
||||
|
||||
AddObject(childObject, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddObject(LevelObject newObject, Level level)
|
||||
{
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
{
|
||||
trigger.OnTriggered += (levelTrigger, obj) =>
|
||||
if (newObject.Triggers != null)
|
||||
{
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
{
|
||||
OnObjectTriggered(newObject, levelTrigger, obj);
|
||||
};
|
||||
trigger.OnTriggered += (levelTrigger, obj) =>
|
||||
{
|
||||
OnObjectTriggered(newObject, levelTrigger, obj);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var spriteCorners = new List<Vector2>
|
||||
@@ -223,22 +302,24 @@ namespace Barotrauma
|
||||
float minY = spriteCorners.Min(c => c.Y) - newObject.Position.Z - level.BottomPos;
|
||||
float maxY = spriteCorners.Max(c => c.Y) + newObject.Position.Z - level.BottomPos;
|
||||
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
if (newObject.Triggers != null)
|
||||
{
|
||||
if (trigger.PhysicsBody == null) continue;
|
||||
for (int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
{
|
||||
trigger.PhysicsBody.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
|
||||
trigger.PhysicsBody.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
|
||||
if (trigger.PhysicsBody == null) { continue; }
|
||||
for (int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
|
||||
{
|
||||
trigger.PhysicsBody.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
|
||||
trigger.PhysicsBody.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
|
||||
|
||||
minX = Math.Min(minX, ConvertUnits.ToDisplayUnits(aabb.LowerBound.X));
|
||||
maxX = Math.Max(maxX, ConvertUnits.ToDisplayUnits(aabb.UpperBound.X));
|
||||
minY = Math.Min(minY, ConvertUnits.ToDisplayUnits(aabb.LowerBound.Y) - level.BottomPos);
|
||||
maxY = Math.Max(maxY, ConvertUnits.ToDisplayUnits(aabb.UpperBound.Y) - level.BottomPos);
|
||||
minX = Math.Min(minX, ConvertUnits.ToDisplayUnits(aabb.LowerBound.X));
|
||||
maxX = Math.Max(maxX, ConvertUnits.ToDisplayUnits(aabb.UpperBound.X));
|
||||
minY = Math.Min(minY, ConvertUnits.ToDisplayUnits(aabb.LowerBound.Y) - level.BottomPos);
|
||||
maxY = Math.Max(maxY, ConvertUnits.ToDisplayUnits(aabb.UpperBound.Y) - level.BottomPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if CLIENT
|
||||
if (newObject.ParticleEmitters != null)
|
||||
{
|
||||
@@ -253,6 +334,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
objects.Add(newObject);
|
||||
if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
|
||||
newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
|
||||
|
||||
int xStart = (int)Math.Floor(minX / GridSize);
|
||||
@@ -290,7 +372,7 @@ namespace Barotrauma
|
||||
return objects;
|
||||
}
|
||||
|
||||
private readonly static List<LevelObject> objectsInRange = new List<LevelObject>();
|
||||
private readonly static HashSet<LevelObject> objectsInRange = new HashSet<LevelObject>();
|
||||
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
|
||||
{
|
||||
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
|
||||
@@ -309,10 +391,10 @@ namespace Barotrauma
|
||||
{
|
||||
for (int y = minIndices.Y; y <= maxIndices.Y; y++)
|
||||
{
|
||||
if (objectGrid[x, y] == null) continue;
|
||||
if (objectGrid[x, y] == null) { continue; }
|
||||
foreach (LevelObject obj in objectGrid[x, y])
|
||||
{
|
||||
if (!objectsInRange.Contains(obj)) objectsInRange.Add(obj);
|
||||
objectsInRange.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,12 +404,14 @@ namespace Barotrauma
|
||||
|
||||
private List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
|
||||
{
|
||||
List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid || edge.OutsideLevel) continue;
|
||||
if (!edge.IsSolid || edge.OutsideLevel) { continue; }
|
||||
if (spawnPosType != LevelObjectPrefab.SpawnPosType.CaveWall && edge.NextToCave) { continue; }
|
||||
Vector2 normal = edge.GetNormal(cell);
|
||||
|
||||
Alignment edgeAlignment = 0;
|
||||
@@ -340,7 +424,13 @@ namespace Barotrauma
|
||||
else if(normal.X > 0.5f)
|
||||
edgeAlignment |= Alignment.Right;
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosType, edgeAlignment));
|
||||
spawnPosTypes.Clear();
|
||||
spawnPosTypes.Add(spawnPosType);
|
||||
if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.MainPathWall) && edge.NextToMainPath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.MainPathWall); }
|
||||
if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.SidePathWall) && edge.NextToSidePath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.SidePathWall); }
|
||||
if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall) && edge.NextToCave) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.CaveWall); }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosTypes, edgeAlignment));
|
||||
}
|
||||
}
|
||||
return availableSpawnPositions;
|
||||
@@ -348,7 +438,7 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (LevelObject obj in objects)
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
@@ -361,21 +451,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.PhysicsBody != null)
|
||||
{
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered;
|
||||
obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = obj.PhysicsBody.Rotation;
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
|
||||
/*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = -obj.PhysicsBody.Rotation;*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,10 +479,10 @@ namespace Barotrauma
|
||||
|
||||
private void OnObjectTriggered(LevelObject triggeredObject, LevelTrigger trigger, Entity triggerer)
|
||||
{
|
||||
if (trigger.TriggerOthersDistance <= 0.0f) return;
|
||||
if (trigger.TriggerOthersDistance <= 0.0f) { return; }
|
||||
foreach (LevelObject obj in objects)
|
||||
{
|
||||
if (obj == triggeredObject) continue;
|
||||
if (obj == triggeredObject || obj.Triggers == null) { continue; }
|
||||
foreach (LevelTrigger otherTrigger in obj.Triggers)
|
||||
{
|
||||
otherTrigger.OtherTriggered(triggeredObject, trigger);
|
||||
@@ -397,16 +490,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(string levelType)
|
||||
{
|
||||
return GetRandomPrefab(levelType, LevelObjectPrefab.List);
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(string levelType, IList<LevelObjectPrefab> availablePrefabs)
|
||||
private LevelObjectPrefab GetRandomPrefab(LevelGenerationParams generationParams, IList<LevelObjectPrefab> availablePrefabs)
|
||||
{
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(generationParams)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(levelType)).ToList(), Rand.RandSync.Server);
|
||||
availablePrefabs.Select(p => p.GetCommonness(generationParams)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs)
|
||||
{
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(caveParams)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(caveParams)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
@@ -418,6 +515,7 @@ namespace Barotrauma
|
||||
obj.Remove();
|
||||
}
|
||||
objects.Clear();
|
||||
updateableObjects.Clear();
|
||||
}
|
||||
RemoveProjSpecific();
|
||||
|
||||
|
||||
+98
-39
@@ -8,11 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class LevelObjectPrefab : ISerializableEntity
|
||||
{
|
||||
private static List<LevelObjectPrefab> list = new List<LevelObjectPrefab>();
|
||||
public static List<LevelObjectPrefab> List
|
||||
{
|
||||
get { return list; }
|
||||
}
|
||||
public static List<LevelObjectPrefab> List { get; } = new List<LevelObjectPrefab>();
|
||||
|
||||
public class ChildObject
|
||||
{
|
||||
@@ -38,12 +34,15 @@ namespace Barotrauma
|
||||
public enum SpawnPosType
|
||||
{
|
||||
None = 0,
|
||||
Wall = 1,
|
||||
RuinWall = 2,
|
||||
SeaFloor = 4,
|
||||
MainPath = 8,
|
||||
LevelStart = 16,
|
||||
LevelEnd = 32,
|
||||
MainPathWall = 1,
|
||||
SidePathWall = 2,
|
||||
CaveWall = 4,
|
||||
RuinWall = 8,
|
||||
SeaFloor = 16,
|
||||
MainPath = 32,
|
||||
LevelStart = 64,
|
||||
LevelEnd = 128,
|
||||
Wall = MainPathWall | SidePathWall | CaveWall,
|
||||
}
|
||||
|
||||
public List<Sprite> Sprites
|
||||
@@ -52,12 +51,6 @@ namespace Barotrauma
|
||||
private set;
|
||||
} = new List<Sprite>();
|
||||
|
||||
public List<Sprite> SpecularSprites
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Sprite>();
|
||||
|
||||
public DeformableSprite DeformableSprite
|
||||
{
|
||||
get;
|
||||
@@ -117,7 +110,13 @@ namespace Barotrauma
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
} = -1;
|
||||
|
||||
public Dictionary<Sprite, XElement> SpriteSpecificPhysicsBodyElements
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Dictionary<Sprite, XElement>();
|
||||
|
||||
|
||||
[Serialize(10000, false, description: "Maximum number of this specific object per level."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
@@ -162,6 +161,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0", true, description: "Random offset from the surface the object spawns on.")]
|
||||
public Vector2 RandomOffset
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true, description: "Should the object be rotated to align it with the wall surface it spawns on.")]
|
||||
public bool AlignWithSurface
|
||||
{
|
||||
@@ -243,12 +249,18 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Identifier; }
|
||||
}
|
||||
|
||||
public List<ChildObject> ChildObjects
|
||||
{
|
||||
get;
|
||||
@@ -272,12 +284,12 @@ namespace Barotrauma
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "LevelObjectPrefab (" + Name + ")";
|
||||
return "LevelObjectPrefab (" + Identifier + ")";
|
||||
}
|
||||
|
||||
public static void LoadAll()
|
||||
{
|
||||
list.Clear();
|
||||
List.Clear();
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.LevelObjectPrefabs);
|
||||
if (files.Count() > 0)
|
||||
{
|
||||
@@ -303,35 +315,62 @@ namespace Barotrauma
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
DebugConsole.NewMessage($"Overriding all level object prefabs with '{configPath}'", Color.Yellow);
|
||||
list.Clear();
|
||||
List.Clear();
|
||||
}
|
||||
else if (list.Any())
|
||||
else if (List.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Loading additional level object prefabs from file '{configPath}'");
|
||||
DebugConsole.Log($"Loading additional level object prefabs from file '{configPath}'");
|
||||
}
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
foreach (XElement subElement in mainElement.Elements())
|
||||
{
|
||||
list.Add(new LevelObjectPrefab(element));
|
||||
var element = subElement.IsOverride() ? subElement.FirstElement() : subElement;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
var existingPrefab = List.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingPrefab != null)
|
||||
{
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the existing level object prefab '{identifier}' using the file '{configPath}'", Color.Yellow);
|
||||
List.Remove(existingPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{configPath}': Duplicate level object prefab '{identifier}' found in '{configPath}'! Each level object prefab must have a unique identifier. " +
|
||||
"Use <override></override> tags to override prefabs.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
List.Add(new LevelObjectPrefab(element));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(String.Format("Failed to load LevelObject prefabs from {0}", configPath), e);
|
||||
DebugConsole.ThrowError(string.Format("Failed to load LevelObject prefabs from {0}", configPath), e);
|
||||
}
|
||||
}
|
||||
|
||||
public LevelObjectPrefab(XElement element)
|
||||
public LevelObjectPrefab(XElement element, string identifier = null)
|
||||
{
|
||||
ChildObjects = new List<ChildObject>();
|
||||
LevelTriggerElements = new List<XElement>();
|
||||
OverrideProperties = new List<LevelObjectPrefab>();
|
||||
OverrideCommonness = new Dictionary<string, float>();
|
||||
|
||||
Identifier = null;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
if (element != null)
|
||||
{
|
||||
Config = element;
|
||||
Name = element.Name.ToString();
|
||||
Identifier = element.GetAttributeString("identifier", null) ?? identifier;
|
||||
if (string.IsNullOrEmpty(Identifier))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#else
|
||||
DebugConsole.AddWarning($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#endif
|
||||
Identifier = element.Name.ToString();
|
||||
}
|
||||
LoadElements(element, -1);
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
@@ -346,21 +385,27 @@ namespace Barotrauma
|
||||
|
||||
private void LoadElements(XElement element, int parentTriggerIndex)
|
||||
{
|
||||
int propertyOverrideCount = 0;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
Sprites.Add( new Sprite(subElement, lazyLoad: true));
|
||||
break;
|
||||
case "specularsprite":
|
||||
SpecularSprites.Add(new Sprite(subElement, lazyLoad: true));
|
||||
var newSprite = new Sprite(subElement, lazyLoad: true);
|
||||
Sprites.Add(newSprite);
|
||||
var spriteSpecificPhysicsBodyElement =
|
||||
subElement.Element("PhysicsBody") ?? subElement.Element("Body") ??
|
||||
subElement.Element("physicsbody") ?? subElement.Element("body");
|
||||
if (spriteSpecificPhysicsBodyElement != null)
|
||||
{
|
||||
SpriteSpecificPhysicsBodyElements.Add(newSprite, spriteSpecificPhysicsBodyElement);
|
||||
}
|
||||
break;
|
||||
case "deformablesprite":
|
||||
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
case "overridecommonness":
|
||||
string levelType = subElement.GetAttributeString("leveltype", "");
|
||||
string levelType = subElement.GetAttributeString("leveltype", "").ToLowerInvariant();
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
{
|
||||
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
|
||||
@@ -376,13 +421,14 @@ namespace Barotrauma
|
||||
ChildObjects.Add(new ChildObject(subElement));
|
||||
break;
|
||||
case "overrideproperties":
|
||||
var propertyOverride = new LevelObjectPrefab(subElement);
|
||||
var propertyOverride = new LevelObjectPrefab(subElement, identifier: Identifier + "-" + propertyOverrideCount);
|
||||
OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
|
||||
if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null)
|
||||
{
|
||||
propertyOverride.Sprites = Sprites;
|
||||
propertyOverride.DeformableSprite = DeformableSprite;
|
||||
}
|
||||
propertyOverrideCount++;
|
||||
break;
|
||||
case "body":
|
||||
case "physicsbody":
|
||||
@@ -395,13 +441,26 @@ namespace Barotrauma
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public float GetCommonness(string levelType)
|
||||
|
||||
public float GetCommonness(CaveGenerationParams generationParams)
|
||||
{
|
||||
if (!OverrideCommonness.TryGetValue(levelType, out float commonness))
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return Commonness;
|
||||
return commonness;
|
||||
}
|
||||
return commonness;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
(OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
|
||||
(generationParams.OldIdentifier != null && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
return Commonness;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,18 @@ namespace Barotrauma
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string InfectIdentifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float InfectionChance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public LevelTrigger(XElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
|
||||
{
|
||||
@@ -211,6 +223,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
|
||||
|
||||
InfectIdentifier = element.GetAttributeString("infectidentifier", null);
|
||||
InfectionChance = element.GetAttributeFloat("infectionchance", 0.05f);
|
||||
|
||||
stayTriggeredDelay = element.GetAttributeFloat("staytriggereddelay", 0.0f);
|
||||
randomTriggerInterval = element.GetAttributeFloat("randomtriggerinterval", 0.0f);
|
||||
@@ -513,9 +528,14 @@ namespace Barotrauma
|
||||
float structureDamage = attack.GetStructureDamage(deltaTime);
|
||||
if (structureDamage > 0.0f)
|
||||
{
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage);
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(InfectIdentifier))
|
||||
{
|
||||
submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance);
|
||||
}
|
||||
}
|
||||
|
||||
if (Force.LengthSquared() > 0.01f)
|
||||
@@ -598,7 +618,7 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 GetWaterFlowVelocity()
|
||||
{
|
||||
if (Force == Vector2.Zero) return Vector2.Zero;
|
||||
if (Force == Vector2.Zero || ForceMode == TriggerForceMode.LimitVelocity) { return Vector2.Zero; }
|
||||
|
||||
Vector2 vel = Force;
|
||||
if (ForceMode == TriggerForceMode.Acceleration)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Voronoi2;
|
||||
using System.Linq;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
@@ -11,18 +12,15 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelWall : IDisposable
|
||||
{
|
||||
private List<VoronoiCell> cells;
|
||||
public List<VoronoiCell> Cells
|
||||
{
|
||||
get { return cells; }
|
||||
}
|
||||
{
|
||||
public List<VoronoiCell> Cells { get; private set; }
|
||||
|
||||
private Body body;
|
||||
public Body Body
|
||||
{
|
||||
get { return body; }
|
||||
}
|
||||
public Body Body { get; private set; }
|
||||
|
||||
protected readonly Level level;
|
||||
|
||||
private readonly List<Vector2[]> triangles;
|
||||
private readonly Color color;
|
||||
|
||||
private float moveState;
|
||||
private float moveLength;
|
||||
@@ -38,6 +36,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float wallDamageOnTouch;
|
||||
public float WallDamageOnTouch
|
||||
{
|
||||
get { return wallDamageOnTouch; }
|
||||
set
|
||||
{
|
||||
Cells.ForEach(c => c.DoesDamage = !MathUtils.NearlyEqual(value, 0.0f));
|
||||
wallDamageOnTouch = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float MoveSpeed;
|
||||
|
||||
private Vector2? originalPos;
|
||||
@@ -50,30 +59,32 @@ namespace Barotrauma
|
||||
|
||||
public LevelWall(List<Vector2> vertices, Color color, Level level, bool giftWrap = false)
|
||||
{
|
||||
if (giftWrap)
|
||||
this.level = level;
|
||||
this.color = color;
|
||||
List<Vector2> originalVertices = new List<Vector2>(vertices);
|
||||
if (giftWrap) { vertices = MathUtils.GiftWrap(vertices); }
|
||||
if (vertices.Count < 3)
|
||||
{
|
||||
vertices = MathUtils.GiftWrap(vertices);
|
||||
throw new ArgumentException("Failed to generate a wall (not enough vertices). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString())));
|
||||
}
|
||||
|
||||
VoronoiCell wallCell = new VoronoiCell(vertices.ToArray());
|
||||
for (int i = 0; i < wallCell.Edges.Count; i++)
|
||||
{
|
||||
wallCell.Edges[i].Cell1 = wallCell;
|
||||
wallCell.Edges[i].IsSolid = true;
|
||||
}
|
||||
cells = new List<VoronoiCell>() { wallCell };
|
||||
|
||||
body = CaveGenerator.GeneratePolygons(cells, level, out List<Vector2[]> triangles);
|
||||
Cells = new List<VoronoiCell>() { wallCell };
|
||||
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
|
||||
#if CLIENT
|
||||
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
|
||||
SetBodyVertices(bodyVertices.ToArray(), color);
|
||||
SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color);
|
||||
GenerateVertices();
|
||||
#endif
|
||||
}
|
||||
|
||||
public LevelWall(List<Vector2> edgePositions, Vector2 extendAmount, Color color, Level level)
|
||||
{
|
||||
cells = new List<VoronoiCell>();
|
||||
this.level = level;
|
||||
this.color = color;
|
||||
Cells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < edgePositions.Count - 1; i++)
|
||||
{
|
||||
Vector2[] vertices = new Vector2[4];
|
||||
@@ -84,7 +95,7 @@ namespace Barotrauma
|
||||
|
||||
VoronoiCell wallCell = new VoronoiCell(vertices)
|
||||
{
|
||||
CellType = CellType.Edge
|
||||
CellType = CellType.Solid
|
||||
};
|
||||
wallCell.Edges[0].Cell1 = wallCell;
|
||||
wallCell.Edges[1].Cell1 = wallCell;
|
||||
@@ -94,31 +105,28 @@ namespace Barotrauma
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
wallCell.Edges[3].Cell2 = cells[i - 1];
|
||||
cells[i - 1].Edges[1].Cell2 = wallCell;
|
||||
wallCell.Edges[3].Cell2 = Cells[i - 1];
|
||||
Cells[i - 1].Edges[1].Cell2 = wallCell;
|
||||
}
|
||||
|
||||
cells.Add(wallCell);
|
||||
Cells.Add(wallCell);
|
||||
}
|
||||
|
||||
body = CaveGenerator.GeneratePolygons(cells, level, out List<Vector2[]> triangles);
|
||||
body.CollisionCategories = Physics.CollisionLevel;
|
||||
|
||||
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
|
||||
Body.CollisionCategories = Physics.CollisionLevel;
|
||||
#if CLIENT
|
||||
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
|
||||
SetBodyVertices(bodyVertices.ToArray(), color);
|
||||
SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color);
|
||||
GenerateVertices();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (body.BodyType == BodyType.Static) return;
|
||||
if (Body.BodyType == BodyType.Static) { return; }
|
||||
|
||||
Vector2 bodyPos = ConvertUnits.ToDisplayUnits(body.Position);
|
||||
Vector2 bodyPos = ConvertUnits.ToDisplayUnits(Body.Position);
|
||||
Cells.ForEach(c => c.Translation = bodyPos);
|
||||
|
||||
if (!originalPos.HasValue) originalPos = bodyPos;
|
||||
if (!originalPos.HasValue) { originalPos = bodyPos; }
|
||||
|
||||
if (moveLength > 0.0f && MoveSpeed > 0.0f)
|
||||
{
|
||||
@@ -126,10 +134,15 @@ namespace Barotrauma
|
||||
moveState %= MathHelper.TwoPi;
|
||||
|
||||
Vector2 targetPos = ConvertUnits.ToSimUnits(originalPos.Value + moveAmount * (float)Math.Sin(moveState));
|
||||
body.ApplyForce((targetPos - body.Position).ClampLength(1.0f) * body.Mass);
|
||||
Body.ApplyForce((targetPos - Body.Position).ClampLength(1.0f) * Body.Mass);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPointInside(Vector2 point)
|
||||
{
|
||||
return Cells.Any(c => c.IsPointInside(point));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
@@ -139,16 +152,8 @@ namespace Barotrauma
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
#if CLIENT
|
||||
if (wallVertices != null)
|
||||
{
|
||||
wallVertices.Dispose();
|
||||
wallVertices = null;
|
||||
}
|
||||
if (bodyVertices != null)
|
||||
{
|
||||
BodyVertices.Dispose();
|
||||
bodyVertices = null;
|
||||
}
|
||||
VertexBuffer?.Dispose();
|
||||
VertexBuffer = null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
private string filePath;
|
||||
|
||||
private List<RuinRoom> roomTypeList;
|
||||
private readonly List<RuinRoom> roomTypeList;
|
||||
|
||||
public string Name => "RuinGenerationParams";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user