v0.11.0.9

This commit is contained in:
Joonas Rikkonen
2020-12-09 16:34:16 +02:00
parent bbf06f0984
commit f433a7ba10
325 changed files with 13947 additions and 3652 deletions
@@ -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);
}
}
@@ -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();
@@ -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";