Merge branch 'master' of https://github.com/Regalis11/Barotrauma.git into Regalis11-master

This commit is contained in:
Evil Factory
2021-12-15 14:45:31 -03:00
388 changed files with 12646 additions and 8136 deletions
@@ -7,7 +7,7 @@ using System.Text;
namespace Barotrauma
{
class Entity : ISpatialEntity
abstract class Entity : ISpatialEntity
{
public const ushort NullEntityID = 0;
public const ushort EntitySpawnerID = ushort.MaxValue;
@@ -16,8 +16,10 @@ namespace Barotrauma
public const ushort ReservedIDStart = ushort.MaxValue - 3;
public const ushort MaxEntityCount = ushort.MaxValue - 2; //ushort.MaxValue - 2 because 0 and ushort.MaxValue are reserved values
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
public static IEnumerable<Entity> GetEntities()
public static IReadOnlyCollection<Entity> GetEntities()
{
return dictionary.Values;
}
@@ -28,11 +30,9 @@ namespace Barotrauma
protected AITarget aiTarget;
private bool idFreed;
public bool Removed { get; private set; }
public virtual bool Removed { get; private set; }
public bool IdFreed => idFreed;
public bool IdFreed { get; private set; }
public readonly ushort ID;
@@ -75,43 +75,65 @@ namespace Barotrauma
this.Submarine = submarine;
spawnTime = Timing.TotalTime;
if (id != NullEntityID && dictionary.ContainsKey(id))
{
throw new Exception($"ID {id} is taken by {dictionary[id]}");
}
//give a unique ID
ID = DetermineID(id, submarine);
if (dictionary.ContainsKey(ID))
{
throw new Exception($"ID {ID} is taken by {dictionary[ID]}");
}
dictionary.Add(ID, this);
}
protected virtual ushort DetermineID(ushort id, Submarine submarine)
{
return id != NullEntityID ?
id :
FindFreeID(submarine == null ? (ushort)1 : submarine.IdOffset);
return id != NullEntityID
? id
: FindFreeId(submarine == null ? (ushort)1 : submarine.IdOffset);
}
public static ushort FindFreeID(ushort idOffset = 0)
private static ushort FindFreeId(ushort idOffset)
{
//ushort.MaxValue - 2 because 0 and ushort.MaxValue are reserved values
if (dictionary.Count >= ushort.MaxValue - 2)
if (dictionary.Count >= MaxEntityCount)
{
throw new Exception("Maximum amount of entities (" + (ushort.MaxValue - 1) + ") reached!");
throw new Exception($"Maximum amount of entities ({MaxEntityCount}) reached!");
}
idOffset = Math.Max(idOffset, (ushort)1);
bool IDfound;
ushort id = idOffset;
do
while (id < ReservedIDStart)
{
id += 1;
IDfound = dictionary.ContainsKey(id);
} while (IDfound || id == NullEntityID || id > ReservedIDStart);
if (!dictionary.ContainsKey(id)) { break; }
id++;
};
return id;
}
/// <summary>
/// Finds a contiguous block of free IDs of at least the given size
/// </summary>
/// <returns>The first ID in the found block, or zero if none are found</returns>
public static int FindFreeIdBlock(int minBlockSize)
{
int currentBlockSize = 0;
for (int i = 1; i < ReservedIDStart; i++)
{
if (dictionary.ContainsKey((ushort)i))
{
currentBlockSize = 0;
}
else
{
currentBlockSize++;
if (currentBlockSize >= minBlockSize)
{
return i - (currentBlockSize-1);
}
}
}
return 0;
}
/// <summary>
/// Find an entity based on the ID
/// </summary>
@@ -134,11 +156,11 @@ namespace Barotrauma
}
catch (Exception exception)
{
DebugConsole.ThrowError("Error while removing entity \"" + e.ToString() + "\"", exception);
DebugConsole.ThrowError($"Error while removing entity \"{e}\"", exception);
GameAnalyticsManager.AddErrorEventOnce(
"Entity.RemoveAll:Exception" + e.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Error while removing entity \"" + e.ToString() + " (" + exception.Message + ")\n" + exception.StackTrace.CleanupStackTrace());
$"Entity.RemoveAll:Exception{e}",
GameAnalyticsManager.ErrorSeverity.Error,
$"Error while removing entity \"{e} ({exception.Message})\n{exception.StackTrace.CleanupStackTrace()}");
}
}
StringBuilder errorMsg = new StringBuilder();
@@ -167,7 +189,7 @@ namespace Barotrauma
}
catch (Exception exception)
{
DebugConsole.ThrowError("Error while removing item \"" + item.ToString() + "\"", exception);
DebugConsole.ThrowError($"Error while removing item \"{item}\"", exception);
}
}
Item.ItemList.Clear();
@@ -189,7 +211,7 @@ namespace Barotrauma
}
catch (Exception exception)
{
DebugConsole.ThrowError("Error while removing character \"" + character.ToString() + "\"", exception);
DebugConsole.ThrowError($"Error while removing character \"{character}\"", exception);
}
}
Character.CharacterList.Clear();
@@ -201,7 +223,7 @@ namespace Barotrauma
{
DebugConsole.ThrowError(errorLine);
}
GameAnalyticsManager.AddErrorEventOnce("Entity.RemoveAll", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg.ToString());
GameAnalyticsManager.AddErrorEventOnce("Entity.RemoveAll", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.ToString());
}
dictionary.Clear();
@@ -214,35 +236,33 @@ namespace Barotrauma
/// </summary>
public void FreeID()
{
DebugConsole.Log("Removing entity " + ToString() + " (" + ID + ") from entity dictionary.");
if (IdFreed) { return; }
DebugConsole.Log($"Removing entity {ToString()} ({ID}) from entity dictionary.");
if (!dictionary.TryGetValue(ID, out Entity existingEntity))
{
DebugConsole.Log("Entity " + ToString() + " (" + ID + ") not present in entity dictionary.");
DebugConsole.ThrowError($"Entity {ToString()} ({ID}) not present in entity dictionary.");
GameAnalyticsManager.AddErrorEventOnce(
"Entity.FreeID:EntityNotFound" + ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Entity " + ToString() + " (" + ID + ") not present in entity dictionary.\n" + Environment.StackTrace.CleanupStackTrace());
$"Entity.FreeID:EntityNotFound{ID}",
GameAnalyticsManager.ErrorSeverity.Error,
$"Entity {ToString()} ({ID}) not present in entity dictionary.\n{Environment.StackTrace.CleanupStackTrace()}");
}
else if (existingEntity != this)
{
DebugConsole.Log("Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")");
DebugConsole.ThrowError($"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})");
GameAnalyticsManager.AddErrorEventOnce("Entity.FreeID:EntityMismatch" + ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")");
foreach (var keyValuePair in dictionary.Where(kvp => kvp.Value == this).ToList())
{
dictionary.Remove(keyValuePair.Key);
}
GameAnalyticsManager.ErrorSeverity.Error,
$"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})");
}
dictionary.Remove(ID);
idFreed = true;
else
{
dictionary.Remove(ID);
}
IdFreed = true;
}
public virtual void Remove()
{
if (!idFreed) FreeID();
FreeID();
Removed = true;
}
@@ -255,8 +275,8 @@ namespace Barotrauma
List<string> lines = new List<string>();
for (int i = 0; i < count; i++)
{
lines.Add(entities[i].ID + ": " + entities[i].ToString());
DebugConsole.ThrowError(entities[i].ID + ": " + entities[i].ToString());
lines.Add($"{entities[i].ID}: {entities[i]}");
DebugConsole.ThrowError($"{entities[i].ID}: {entities[i]}");
}
if (!string.IsNullOrWhiteSpace(filename))
@@ -39,6 +39,8 @@ namespace Barotrauma
private readonly float itemRepairStrength;
public readonly HashSet<Submarine> IgnoredSubmarines = new HashSet<Submarine>();
public float EmpStrength { get; set; }
public float BallastFloraDamage { get; set; }
@@ -149,7 +151,7 @@ namespace Barotrauma
if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f))
{
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker);
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker, IgnoredSubmarines);
}
if (BallastFloraDamage > 0.0f)
@@ -397,13 +399,14 @@ namespace Barotrauma
/// <summary>
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
/// </summary>
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null)
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable<Submarine> ignoredSubmarines = null)
{
List<Structure> structureList = new List<Structure>();
float dist = 600.0f;
foreach (MapEntity entity in MapEntity.mapEntityList)
{
if (!(entity is Structure structure)) { continue; }
if (ignoredSubmarines != null && entity.Submarine != null && ignoredSubmarines.Contains(entity.Submarine)) { continue; }
if (structure.HasBody &&
!structure.IsPlatform &&
@@ -27,25 +27,25 @@ namespace Barotrauma
}
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
private float open;
private float open;
//the force of the water flow which is exerted on physics bodies
private Vector2 flowForce;
private Hull flowTargetHull;
private float openedTimer = 1.0f;
private float higherSurface;
private float lowerSurface;
private Vector2 lerpedFlowForce;
//if set to true, hull connections of this gap won't be updated when changes are being done to hulls
public bool DisableHullRechecks;
//can ambient light get through the gap even if it's not open
public bool PassAmbientLight;
//a collider outside the gap (for example an ice wall next to the sub)
//used by ragdolls to prevent them from ending up inside colliders when teleporting out of the sub
@@ -55,11 +55,11 @@ namespace Barotrauma
public float Open
{
get { return open; }
set
set
{
if (float.IsNaN(value)) { return; }
if (value > open) { openedTimer = 1.0f; }
open = MathHelper.Clamp(value, 0.0f, 1.0f);
open = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
@@ -320,7 +320,7 @@ namespace Barotrauma
if (openedTimer > 0.0f && flowForce.Length() > lerpedFlowForce.Length())
{
//if the gap has just been opened/created, allow it to exert a large force instantly without any smoothing
lerpedFlowForce = flowForce;
lerpedFlowForce = flowForce;
}
else
{
@@ -376,7 +376,7 @@ namespace Barotrauma
//make sure not to move more than what the room contains
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 5.0f * sizeModifier, Math.Min(hull2.WaterVolume, hull2.Volume));
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - (hull1.WaterVolume));
hull1.WaterVolume += delta;
@@ -406,7 +406,7 @@ namespace Barotrauma
{
hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure-subOffset.Y) + hull2.Pressure) / 2);
}
flowForce = new Vector2(delta, 0.0f);
}
@@ -449,7 +449,7 @@ namespace Barotrauma
hull2.WaterVolume -= delta;
flowForce = new Vector2(
0.0f,
0.0f,
Math.Min(Math.Min((hull2.Pressure + subOffset.Y) - hull1.Pressure, 200.0f), delta));
flowTargetHull = hull1;
@@ -457,7 +457,7 @@ namespace Barotrauma
if (hull1.WaterVolume > hull1.Volume)
{
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + (hull2.Pressure + subOffset.Y)) / 2);
}
}
}
//there's water in the upper room, drop to lower
@@ -495,7 +495,7 @@ namespace Barotrauma
hull1.LethalPressure = avgLethality;
hull2.LethalPressure = avgLethality;
}
else
else
{
hull1.LethalPressure = 0.0f;
hull2.LethalPressure = 0.0f;
@@ -512,7 +512,7 @@ namespace Barotrauma
float sizeModifier = size * open * open;
float delta = 500.0f * sizeModifier * deltaTime;
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
hull1.WaterVolume += delta;
@@ -527,7 +527,7 @@ namespace Barotrauma
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
{
flowForce = new Vector2(-delta, 0.0f);
}
else
{
@@ -555,7 +555,7 @@ namespace Barotrauma
hull1.WaveVel[0] += vel;
hull1.WaveVel[1] += vel;
}
}
}
else
{
@@ -657,12 +657,12 @@ namespace Barotrauma
float totalOxygen = hull1.Oxygen + hull2.Oxygen;
float totalVolume = (hull1.Volume + hull2.Volume);
float deltaOxygen = (totalOxygen * hull1.Volume / totalVolume) - hull1.Oxygen;
deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed, Hull.OxygenDistributionSpeed);
hull1.Oxygen += deltaOxygen;
hull2.Oxygen -= deltaOxygen;
hull2.Oxygen -= deltaOxygen;
}
public static Gap FindAdjacent(IEnumerable<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist)
@@ -730,7 +730,7 @@ namespace Barotrauma
{
if (!DisableHullRechecks) FindHulls();
}
public static Gap Load(XElement element, Submarine submarine, IdRemap idRemap)
{
Rectangle rect = Rectangle.Empty;
@@ -761,6 +761,8 @@ namespace Barotrauma
{
linkedToID = new List<ushort>(),
};
g.HiddenInGame = element.GetAttributeBool(nameof(HiddenInGame).ToLower(), g.HiddenInGame);
return g;
}
@@ -770,7 +772,8 @@ namespace Barotrauma
element.Add(
new XAttribute("ID", ID),
new XAttribute("horizontal", IsHorizontal ? "true" : "false"));
new XAttribute("horizontal", IsHorizontal ? "true" : "false"),
new XAttribute(nameof(HiddenInGame).ToLower(), HiddenInGame));
element.Add(new XAttribute("rect",
(int)(rect.X - Submarine.HiddenSubPosition.X) + "," +
@@ -1058,11 +1058,17 @@ namespace Barotrauma
/// <param name="inclusive">Does being exactly at the edge of the hull count as being inside?</param>
public static Hull FindHull(Vector2 position, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
{
if (EntityGrids == null) return null;
if (EntityGrids == null)
{
return null;
}
if (guess != null)
{
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive)) return guess;
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive))
{
return guess;
}
}
foreach (EntityGrid entityGrid in EntityGrids)
@@ -1088,15 +1094,19 @@ namespace Barotrauma
continue;
}
}
Vector2 transformedPosition = position;
if (useWorldCoordinates && entityGrid.Submarine != null) transformedPosition -= entityGrid.Submarine.Position;
if (useWorldCoordinates && entityGrid.Submarine != null)
{
transformedPosition -= entityGrid.Submarine.Position;
}
var entities = entityGrid.GetEntities(transformedPosition);
if (entities == null) continue;
if (entities == null) { continue; }
foreach (Hull hull in entities)
{
if (Submarine.RectContains(hull.rect, transformedPosition, inclusive)) return hull;
if (Submarine.RectContains(hull.rect, transformedPosition, inclusive))
{
return hull;
}
}
}
@@ -1565,7 +1575,7 @@ namespace Barotrauma
{
string errorMsg = "Error - tried to save a hull that's not a part of any submarine.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Hull.Save:WorldHull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Hull.Save:WorldHull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return null;
}
@@ -145,8 +145,7 @@ namespace Barotrauma
public static List<MapEntity> PasteEntities(Vector2 position, Submarine sub, XElement configElement, string filePath = null, bool selectInstance = false)
{
int idOffset = Entity.FindFreeID(1);
if (MapEntity.mapEntityList.Any()) { idOffset = MapEntity.mapEntityList.Max(e => e.ID); }
int idOffset = Entity.FindFreeIdBlock(configElement.Elements().Count());
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, filePath, idOffset);
if (entities.Count == 0) { return entities; }
@@ -440,7 +440,7 @@ namespace Barotrauma
DebugConsole.ThrowError("Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + ")");
GameAnalyticsManager.AddErrorEventOnce(
"CaveGenerator.GeneratePolygons:InvalidTriangle",
GameAnalyticsSDK.Net.EGAErrorSeverity.Warning,
GameAnalyticsManager.ErrorSeverity.Warning,
"Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + "). Seed: " + level.Seed);
}
}
@@ -202,10 +202,10 @@ namespace Barotrauma
get { return startPosition.ToVector2(); }
}
private Vector2 startExitPosition;
private Point startExitPosition;
public Vector2 StartExitPosition
{
get { return startExitPosition; }
get { return startExitPosition.ToVector2(); }
}
public Point Size
@@ -218,10 +218,10 @@ namespace Barotrauma
get { return endPosition.ToVector2(); }
}
private Vector2 endExitPosition;
private Point endExitPosition;
public Vector2 EndExitPosition
{
get { return endExitPosition; }
get { return endExitPosition.ToVector2(); }
}
public int BottomPos
@@ -366,9 +366,6 @@ namespace Barotrauma
{
this.LevelData = levelData;
borders = new Rectangle(Point.Zero, levelData.Size);
//remove from entity dictionary
//base.Remove();
}
public static Level Generate(LevelData levelData, bool mirror, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
@@ -462,12 +459,12 @@ namespace Barotrauma
startPosition = new Point(
(int)MathHelper.Lerp(minMainPathWidth, borders.Width - minMainPathWidth, GenerationParams.StartPosition.X),
(int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.StartPosition.Y));
startExitPosition = new Vector2(startPosition.X, borders.Bottom);
startExitPosition = new Point(startPosition.X, borders.Bottom);
endPosition = new Point(
(int)MathHelper.Lerp(minMainPathWidth, borders.Width - minMainPathWidth, GenerationParams.EndPosition.X),
(int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.EndPosition.Y));
endExitPosition = new Vector2(endPosition.X, borders.Bottom);
endExitPosition = new Point(endPosition.X, borders.Bottom);
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
@@ -486,25 +483,25 @@ namespace Barotrauma
{
startPath = new Tunnel(
TunnelType.SidePath,
new List<Point>() { startExitPosition.ToPoint(), startPosition },
new List<Point>() { startExitPosition, startPosition },
minWidth / 2, parentTunnel: mainPath);
Tunnels.Add(startPath);
}
else
{
startExitPosition = StartPosition;
startExitPosition = startPosition;
}
if (GenerationParams.EndPosition.Y < 0.5f && (Mirrored ? !HasStartOutpost() : !HasEndOutpost()))
{
endPath = new Tunnel(
TunnelType.SidePath,
new List<Point>() { endPosition, endExitPosition.ToPoint() },
new List<Point>() { endPosition, endExitPosition },
minWidth / 2, parentTunnel: mainPath);
Tunnels.Add(endPath);
}
else
{
endExitPosition = EndPosition;
endExitPosition = endPosition;
}
if (GenerationParams.CreateHoleNextToEnd)
@@ -513,14 +510,14 @@ namespace Barotrauma
{
endHole = new Tunnel(
TunnelType.SidePath,
new List<Point>() { startPosition, startExitPosition.ToPoint(), new Point(0, Size.Y) },
new List<Point>() { startPosition, startExitPosition, new Point(0, Size.Y) },
minWidth / 2, parentTunnel: mainPath);
}
else
{
endHole = new Tunnel(
TunnelType.SidePath,
new List<Point>() { endPosition, endExitPosition.ToPoint(), Size },
new List<Point>() { endPosition, endExitPosition, Size },
minWidth / 2, parentTunnel: mainPath);
}
Tunnels.Add(endHole);
@@ -799,8 +796,12 @@ namespace Barotrauma
for (int i = 0; i < GenerationParams.RuinCount; i++)
{
Point ruinSize = new Point(5000);
ruinPositions.Add(FindPosAwayFromMainPath((Math.Max(ruinSize.X, ruinSize.Y) + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true,
limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize)));
int limitLeft = Math.Max(startPosition.X, ruinSize.X / 2);
int limitRight = Math.Min(endPosition.X, Size.X - ruinSize.X / 2);
Rectangle limits = new Rectangle(limitLeft, ruinSize.Y, limitRight - limitLeft, Size.Y - ruinSize.Y);
Debug.Assert(limits.Width > 0);
Debug.Assert(limits.Height > 0);
ruinPositions.Add(FindPosAwayFromMainPath((Math.Max(ruinSize.X, ruinSize.Y) + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true, limits: limits));
CalculateTunnelDistanceField(ruinPositions);
}
@@ -953,6 +954,13 @@ namespace Barotrauma
{
for (int i = 0; i < cavePathCells.Count; i++)
{
var connectingEdge = i > 0 ? cavePathCells[i].Edges.Find(e => e.AdjacentCell(cavePathCells[i]) == cavePathCells[i - 1]) : null;
if (connectingEdge != null)
{
var edgeWayPoint = new WayPoint(connectingEdge.Center, SpawnType.Path, submarine: null);
ConnectWaypoints(prevWp, edgeWayPoint, 500.0f);
prevWp = edgeWayPoint;
}
var newWaypoint = new WayPoint(cavePathCells[i].Center, SpawnType.Path, submarine: null);
ConnectWaypoints(prevWp, newWaypoint, 500.0f);
prevWp = newWaypoint;
@@ -1004,7 +1012,7 @@ namespace Barotrauma
{
if (pos.PositionType != PositionType.MainPath && pos.PositionType != PositionType.SidePath) { continue; }
if (pos.Position.X < 5000 || pos.Position.X > Size.X - 5000) { continue; }
if (Math.Abs(pos.Position.X - StartPosition.X) < minMainPathWidth * 2 || Math.Abs(pos.Position.X - EndPosition.X) < minMainPathWidth * 2) { continue; }
if (Math.Abs(pos.Position.X - startPosition.X) < minMainPathWidth * 2 || Math.Abs(pos.Position.X - endPosition.X) < minMainPathWidth * 2) { continue; }
if (GetTooCloseCells(pos.Position.ToVector2(), minMainPathWidth * 0.7f).Count > 0) { continue; }
iceChunkPositions.Add(pos.Position);
}
@@ -1187,19 +1195,19 @@ namespace Barotrauma
startPosition = endPosition;
endPosition = tempP;
Vector2 tempV = startExitPosition;
tempP = startExitPosition;
startExitPosition = endExitPosition;
endExitPosition = tempV;
endExitPosition = tempP;
}
if (StartOutpost != null)
{
startExitPosition = StartOutpost.WorldPosition;
startPosition = startExitPosition.ToPoint();
startExitPosition = StartOutpost.WorldPosition.ToPoint();
startPosition = startExitPosition;
}
if (EndOutpost != null)
{
endExitPosition = EndOutpost.WorldPosition;
endPosition = endExitPosition.ToPoint();
endExitPosition = EndOutpost.WorldPosition.ToPoint();
endPosition = endExitPosition;
}
CreateWrecks();
@@ -1381,6 +1389,7 @@ namespace Barotrauma
if (tunnel.Cells.Count == 0) { return; }
List<WayPoint> wayPoints = new List<WayPoint>();
WayPoint prevWayPoint = null;
for (int i = 0; i < tunnel.Cells.Count; i++)
{
tunnel.Cells[i].CellType = CellType.Path;
@@ -1390,10 +1399,58 @@ namespace Barotrauma
};
wayPoints.Add(newWaypoint);
if (wayPoints.Count > 1)
if (prevWayPoint != null)
{
wayPoints[wayPoints.Count - 2].ConnectTo(newWaypoint);
bool solidCellBetween = false;
foreach (GraphEdge edge in tunnel.Cells[i].Edges)
{
if (edge.AdjacentCell(tunnel.Cells[i])?.CellType == CellType.Solid &&
MathUtils.LinesIntersect(newWaypoint.WorldPosition, prevWayPoint.WorldPosition, edge.Point1, edge.Point2))
{
solidCellBetween = true;
break;
}
}
if (solidCellBetween)
{
//something between the previous waypoint and this one
// -> find the edge that connects the cells and place a waypoint there, instead of connecting the centers of the cells directly
var edgeBetweenCells = tunnel.Cells[i].Edges.Find(e => e.AdjacentCell(tunnel.Cells[i]) == tunnel.Cells[i - 1]);
if (edgeBetweenCells != null)
{
var edgeWaypoint = new WayPoint(new Rectangle((int)edgeBetweenCells.Center.X, (int)edgeBetweenCells.Center.Y, 10, 10), null)
{
Tunnel = tunnel
};
prevWayPoint.ConnectTo(edgeWaypoint);
prevWayPoint = edgeWaypoint;
}
}
prevWayPoint.ConnectTo(newWaypoint);
//look back at the tunnel cells before the previous one, and see if the current cell shares edges with them
//= if we can "skip" from cell #1 to cell #3, create a waypoint between them.
//Fixes there sometimes not being a path past a destructible ice chunk even if there's space to go past it.
for (int j = i - 2; j > 0 && j > i - 5; j--)
{
foreach (GraphEdge edge in tunnel.Cells[i].Edges)
{
if (Vector2.DistanceSquared(edge.Point1, edge.Point2) < 30.0f * 30.0f) { continue; }
if (!edge.IsSolid && edge.AdjacentCell(tunnel.Cells[i]) == tunnel.Cells[j])
{
var edgeWaypoint = new WayPoint(new Rectangle((int)edge.Center.X, (int)edge.Center.Y, 10, 10), null)
{
Tunnel = tunnel
};
wayPoints[j].ConnectTo(edgeWaypoint);
edgeWaypoint.ConnectTo(newWaypoint);
break;
}
}
}
}
prevWayPoint = newWaypoint;
}
tunnel.WayPoints.AddRange(wayPoints);
@@ -1953,6 +2010,8 @@ namespace Barotrauma
wayPoints.RemoveAt(i);
}
Debug.Assert(wayPoints.Any(), "Couldn't generate waypoints around ruins.");
//connect ruin entrances to the outside waypoints
foreach (Gap g in Gap.GapList)
{
@@ -1996,6 +2055,13 @@ namespace Barotrauma
{
for (int i = 0; i < ruin.PathCells.Count; i++)
{
var connectingEdge = i > 0 ? ruin.PathCells[i].Edges.Find(e => e.AdjacentCell(ruin.PathCells[i]) == ruin.PathCells[i - 1]) : null;
if (connectingEdge != null)
{
var edgeWayPoint = new WayPoint(connectingEdge.Center, SpawnType.Path, submarine: null);
ConnectWaypoints(prevWp, edgeWayPoint, outSideWaypointInterval);
prevWp = edgeWayPoint;
}
var newWaypoint = new WayPoint(ruin.PathCells[i].Center, SpawnType.Path, submarine: null);
ConnectWaypoints(prevWp, newWaypoint, outSideWaypointInterval);
prevWp = newWaypoint;
@@ -2321,7 +2387,7 @@ namespace Barotrauma
{
if (l.Cell == null || l.Edge == null) { return false; }
if (resourceInfo.IsIslandSpecifc && !l.Cell.Island) { return false; }
if (!resourceInfo.AllowAtStart && l.EdgeCenter.Y > StartPosition.Y && l.EdgeCenter.X < Size.X * 0.25f) { return false; }
if (!resourceInfo.AllowAtStart && l.EdgeCenter.Y > startPosition.Y && l.EdgeCenter.X < Size.X * 0.25f) { return false; }
if (l.EdgeCenter.Y < AbyssArea.Bottom) { return false; }
return resourceInfo.ClusterSize <= GetMaxResourcesOnEdge(itemPrefab, l, out _);
@@ -2738,10 +2804,26 @@ namespace Barotrauma
allValidLocations.Sort((x, y) => Vector2.DistanceSquared(poiPos, x.EdgeCenter)
.CompareTo(Vector2.DistanceSquared(poiPos, y.EdgeCenter)));
var maxResourceOverlap = 0.4f;
// TODO: Find multiple locations if there's too many resources to fit on a sigle edge
var selectedLocation = allValidLocations.FirstOrDefault(l =>
Vector2.Distance(l.Edge.Point1, l.Edge.Point2) is float edgeLength &&
requiredAmount <= (int)Math.Floor(edgeLength / ((1.0f - maxResourceOverlap) * prefab.Size.X)));
if (selectedLocation.Edge == null)
{
//couldn't find a long enough edge, find the largest one
float longestEdge = 0.0f;
foreach (var validLocation in allValidLocations)
{
if (Vector2.Distance(validLocation.Edge.Point1, validLocation.Edge.Point2) is float edgeLength && edgeLength > longestEdge)
{
selectedLocation = validLocation;
longestEdge = edgeLength;
}
}
}
if (selectedLocation.Edge == null)
{
throw new Exception("Failed to find a suitable level wall edge to place level resources on.");
}
PlaceResources(prefab, requiredAmount, selectedLocation, out placedResources);
var edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell);
rotation = MathHelper.ToDegrees(-MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2);
@@ -2930,7 +3012,7 @@ namespace Barotrauma
if (!suitablePositions.Any())
{
string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
@@ -2955,7 +3037,7 @@ namespace Barotrauma
if (!farEnoughPositions.Any())
{
string errorMsg = "Could not find a position of interest far enough from the submarines. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:TooCloseToSubs", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:TooCloseToSubs", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
@@ -3067,7 +3149,25 @@ namespace Barotrauma
foreach (LevelWall wall in ExtraWalls)
{
if (wall is DestructibleLevelWall destructibleWall && destructibleWall.Destroyed) { continue; }
if (wall == SeaFloor)
{
if (SeaFloorTopPos < worldPos.Y - searchDepth * GridCellSize) { continue; }
}
else
{
if (wall is DestructibleLevelWall destructibleWall && destructibleWall.Destroyed) { continue; }
bool closeEnough = false;
foreach (VoronoiCell cell in wall.Cells)
{
if (Math.Abs(cell.Center.X - worldPos.X) < (searchDepth + 1) * GridCellSize &&
Math.Abs(cell.Center.Y - worldPos.Y) < (searchDepth + 1) * GridCellSize)
{
closeEnough = true;
break;
}
}
if (!closeEnough) { continue; }
}
foreach (VoronoiCell cell in wall.Cells)
{
tempCells.Add(cell);
@@ -3197,12 +3297,12 @@ namespace Barotrauma
public bool IsCloseToStart(Point position, float minDist)
{
return MathUtils.LineSegmentToPointDistanceSquared(StartPosition.ToPoint(), StartExitPosition.ToPoint(), position) < minDist * minDist;
return MathUtils.LineSegmentToPointDistanceSquared(startPosition, startExitPosition, position) < minDist * minDist;
}
public bool IsCloseToEnd(Point position, float minDist)
{
return MathUtils.LineSegmentToPointDistanceSquared(EndPosition.ToPoint(), EndExitPosition.ToPoint(), position) < minDist * minDist;
return MathUtils.LineSegmentToPointDistanceSquared(endPosition, endExitPosition, position) < minDist * minDist;
}
private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type)
@@ -3214,6 +3314,7 @@ namespace Barotrauma
var waypoints = WayPoint.WayPointList.Where(wp =>
wp.Submarine == null &&
wp.SpawnType == SpawnType.Path &&
wp.WorldPosition.X < EndExitPosition.X &&
!IsCloseToStart(wp.WorldPosition, minDistance) &&
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
@@ -3721,7 +3822,7 @@ namespace Barotrauma
subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Info.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
}
float outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
@@ -3731,7 +3832,7 @@ namespace Barotrauma
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
}
Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset, verticalMoveDir: 1);
@@ -3971,7 +4072,7 @@ namespace Barotrauma
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job, randSync: Rand.RandSync.Server);
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
corpse.AnimController.FindHull(worldPos, true);
corpse.AnimController.FindHull(worldPos, setSubmarine: true);
corpse.TeamID = CharacterTeamType.None;
corpse.EnableDespawn = false;
selectedPrefab.GiveItems(corpse, wreck);
@@ -143,14 +143,20 @@ namespace Barotrauma
var rand = new MTRandom(ToolBox.StringToInt(Seed));
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
//minimum difficulty of the level before hunting grounds can appear
float huntingGroundsDifficultyThreshold = 25;
//probability of hunting grounds appearing in 100% difficulty levels
float maxHuntingGroundsProbability = 0.3f;
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
if (Biome.IsEndBiome)
{
HasHuntingGrounds = false;
HasBeaconStation = false;
}
else
{
//minimum difficulty of the level before hunting grounds can appear
float huntingGroundsDifficultyThreshold = 25;
//probability of hunting grounds appearing in 100% difficulty levels
float maxHuntingGroundsProbability = 0.3f;
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
}
IsBeaconActive = false;
}
@@ -35,9 +35,6 @@ namespace Barotrauma.RuinGeneration
private static List<RuinGenerationParams> paramsList;
private readonly string filePath;
public override string Name => "RuinGenerationParams";
private RuinGenerationParams(XElement element, string filePath) : base(element, filePath)
{
@@ -1,7 +1,7 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Voronoi2;
namespace Barotrauma.RuinGeneration
@@ -41,6 +41,9 @@ namespace Barotrauma.RuinGeneration
Submarine.Info.Name = $"Ruin ({level.Seed})";
Submarine.Info.Type = SubmarineType.Ruin;
Submarine.TeamID = CharacterTeamType.None;
//prevent the ruin from extending above the level "ceiling"
position.Y = Math.Min(level.Size.Y - (Submarine.Borders.Height / 2) - 100, position.Y);
Submarine.SetPosition(position.ToVector2());
if (mirror)
@@ -52,9 +55,9 @@ namespace Barotrauma.RuinGeneration
worldBorders.Location += Submarine.WorldPosition.ToPoint();
Area = new Rectangle(worldBorders.X, worldBorders.Y - worldBorders.Height, worldBorders.Width, worldBorders.Height);
List<WayPoint> subWaypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == Submarine);
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Ruin == this || wp.Submarine == Submarine);
int interestingPosCount = 0;
foreach (WayPoint wp in subWaypoints)
foreach (WayPoint wp in waypoints)
{
if (wp.SpawnType != SpawnType.Enemy) { continue; }
level.PositionsOfInterest.Add(new Level.InterestingPosition(wp.WorldPosition.ToPoint(), Level.PositionType.Ruin, this));
@@ -63,8 +66,8 @@ namespace Barotrauma.RuinGeneration
if (interestingPosCount == 0)
{
//make sure there's at least on PositionsOfInterest in the ruins
level.PositionsOfInterest.Add(new Level.InterestingPosition(subWaypoints.GetRandom(Rand.RandSync.Server).WorldPosition.ToPoint(), Level.PositionType.Ruin, this));
//make sure there's at least one PositionsOfInterest in the ruins
level.PositionsOfInterest.Add(new Level.InterestingPosition(waypoints.GetRandom(Rand.RandSync.Server).WorldPosition.ToPoint(), Level.PositionType.Ruin, this));
}
}
}
@@ -175,9 +175,10 @@ namespace Barotrauma
{
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
LinkedSubmarine linkedSub;
idRemap.AssignMaxId(out ushort id);
if (Screen.Selected == GameMain.SubEditorScreen)
{
linkedSub = CreateDummy(submarine, element, pos, idRemap.AssignMaxId());
linkedSub = CreateDummy(submarine, element, pos, id);
linkedSub.saveElement = element;
linkedSub.purchasedLostShuttles = false;
}
@@ -185,7 +186,7 @@ namespace Barotrauma
{
string levelSeed = element.GetAttributeString("location", "");
LevelData levelData = GameMain.GameSession?.Campaign?.NextLevel ?? GameMain.GameSession?.LevelData;
linkedSub = new LinkedSubmarine(submarine, idRemap.AssignMaxId())
linkedSub = new LinkedSubmarine(submarine, id)
{
purchasedLostShuttles = GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.PurchasedLostShuttles,
saveElement = element
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using StoreBalanceStatus = Barotrauma.LocationType.StoreBalanceStatus;
namespace Barotrauma
{
@@ -87,16 +88,12 @@ namespace Barotrauma
public int TurnsInRadiation { get; set; }
#region Store
private const float StoreMaxReputationModifier = 0.1f;
private const float StoreSellPriceModifier = 0.8f;
private const float DailySpecialPriceModifier = 0.5f;
private const float RequestGoodPriceModifier = 1.5f;
public const int StoreInitialBalance = 5000;
/// <summary>
/// In percentages
/// </summary>
private const int StorePriceModifierRange = 5;
private float StoreMaxReputationModifier => Type.StoreMaxReputationModifier;
private float StoreSellPriceModifier => Type.StoreSellPriceModifier;
private float DailySpecialPriceModifier => Type.DailySpecialPriceModifier;
private float RequestGoodPriceModifier => Type.RequestGoodPriceModifier;
public int StoreInitialBalance => Type.StoreInitialBalance;
private int StorePriceModifierRange => Type.StorePriceModifierRange;
/// <summary>
/// In percentages. Larger values make buying more expensive and selling less profitable, and vice versa.
/// </summary>
@@ -104,26 +101,7 @@ namespace Barotrauma
public Color BalanceColor => ActiveStoreBalanceStatus.Color;
public StoreBalanceStatus ActiveStoreBalanceStatus { get; private set; }
private static StoreBalanceStatus DefaultBalanceStatus { get; } = new StoreBalanceStatus(1.0f, 1.0f, Color.White);
private static List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>
{
new StoreBalanceStatus(0.5f, 0.75f, Color.Orange),
new StoreBalanceStatus(0.25f, 0.2f, Color.Red),
};
public struct StoreBalanceStatus
{
public float PercentageOfInitialBalance { get; }
public float SellPriceModifier { get; }
public Color Color { get; }
public StoreBalanceStatus(float percentage, float sellPriceModifier, Color color)
{
PercentageOfInitialBalance = percentage;
SellPriceModifier = sellPriceModifier;
Color = color;
}
}
private List<StoreBalanceStatus> StoreBalanceStatuses => Type.StoreBalanceStatuses;
private int storeCurrentBalance;
public int StoreCurrentBalance
@@ -1111,15 +1089,16 @@ namespace Barotrauma
}
}
public static StoreBalanceStatus GetStoreBalanceStatus(int balance)
public StoreBalanceStatus GetStoreBalanceStatus(int balance)
{
StoreBalanceStatus nextStatus = DefaultBalanceStatus;
foreach (var balanceStatus in StoreBalanceStatuses)
StoreBalanceStatus nextStatus = StoreBalanceStatuses[0];
for (int i = 1; i < StoreBalanceStatuses.Count; i++)
{
if (balanceStatus.PercentageOfInitialBalance < nextStatus.PercentageOfInitialBalance &&
((float)balance / StoreInitialBalance) < balanceStatus.PercentageOfInitialBalance)
var status = StoreBalanceStatuses[i];
if (status.PercentageOfInitialBalance < nextStatus.PercentageOfInitialBalance &&
((float)balance / StoreInitialBalance) < status.PercentageOfInitialBalance)
{
nextStatus = balanceStatus;
nextStatus = status;
}
}
return nextStatus;
@@ -68,6 +68,37 @@ namespace Barotrauma
private set;
}
public float StoreMaxReputationModifier { get; } = 0.1f;
public float StoreSellPriceModifier { get; } = 0.8f;
public float DailySpecialPriceModifier { get; } = 0.5f;
public float RequestGoodPriceModifier { get; } = 1.5f;
public int StoreInitialBalance { get; } = 5000;
/// <summary>
/// In percentages
/// </summary>
public int StorePriceModifierRange { get; } = 5;
public List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>()
{
new StoreBalanceStatus(1.0f, 1.0f, Color.White),
new StoreBalanceStatus(0.5f, 0.75f, Color.Orange),
new StoreBalanceStatus(0.25f, 0.2f, Color.Red)
};
public struct StoreBalanceStatus
{
public float PercentageOfInitialBalance { get; }
public float SellPriceModifier { get; }
public Color Color { get; }
public StoreBalanceStatus(float percentage, float sellPriceModifier, Color color)
{
PercentageOfInitialBalance = percentage;
SellPriceModifier = sellPriceModifier;
Color = color;
}
}
public override string ToString()
{
return $"LocationType (" + Identifier + ")";
@@ -163,6 +194,26 @@ namespace Barotrauma
portraits.Add(portrait);
}
break;
case "store":
StoreMaxReputationModifier = subElement.GetAttributeFloat("maxreputationmodifier", StoreMaxReputationModifier);
StoreSellPriceModifier = subElement.GetAttributeFloat("sellpricemodifier", StoreSellPriceModifier);
DailySpecialPriceModifier = subElement.GetAttributeFloat("dailyspecialpricemodifier", DailySpecialPriceModifier);
RequestGoodPriceModifier = subElement.GetAttributeFloat("requestgoodpricemodifier", RequestGoodPriceModifier);
StoreInitialBalance = subElement.GetAttributeInt("initialbalance", StoreInitialBalance);
StorePriceModifierRange = subElement.GetAttributeInt("pricemodifierrange", StorePriceModifierRange);
var balanceStatusElements = subElement.GetChildElements("balancestatus");
if (balanceStatusElements.Any())
{
StoreBalanceStatuses.Clear();
foreach (var balanceStatusElement in balanceStatusElements)
{
float percentage = balanceStatusElement.GetAttributeFloat("percentage", 1.0f);
float modifier = balanceStatusElement.GetAttributeFloat("sellpricemodifier", 1.0f);
Color color = balanceStatusElement.GetAttributeColor("color", Color.White);
StoreBalanceStatuses.Add(new StoreBalanceStatus(percentage, modifier, color));
}
}
break;
}
}
}
@@ -472,8 +472,11 @@ namespace Barotrauma
foreach (LocationConnection connection in Connections)
{
float difficulty = GetLevelDifficulty(connection.CenterPos.X / Width);
connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
//float difficulty = GetLevelDifficulty(connection.CenterPos.X / Width);
//connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
float difficulty = connection.CenterPos.X / Width * 100;
float random = difficulty > 10 ? 5 : 0;
connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-random, random, Rand.RandSync.Server), 1.0f, 100.0f);
}
AssignBiomes();
@@ -761,7 +764,7 @@ namespace Barotrauma
{
string errorMsg = "Failed to select a location. " + (location?.Name ?? "null") + " not found in the map.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
@@ -780,7 +783,7 @@ namespace Barotrauma
{
string errorMsg = "Failed to select a mission (current location not set).";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
@@ -22,6 +23,8 @@ namespace Barotrauma
public readonly Map Map;
public readonly RadiationParams Params;
private Affliction radiationAffliction;
private float radiationTimer;
private float increasedAmount;
@@ -93,6 +96,8 @@ namespace Barotrauma
increasedAmount = lastIncrease = amount;
}
public void UpdateRadiation(float deltaTime)
{
if (!(GameMain.GameSession?.IsCurrentLocationRadiated() ?? false)) { return; }
@@ -105,6 +110,8 @@ namespace Barotrauma
return;
}
radiationAffliction ??= new Affliction(AfflictionPrefab.RadiationSickness, Params.RadiationDamageAmount);
radiationTimer = Params.RadiationDamageDelay;
foreach (Character character in Character.CharacterList)
@@ -113,7 +120,11 @@ namespace Barotrauma
if (IsEntityRadiated(character))
{
health.ApplyAffliction(null, new Affliction(AfflictionPrefab.RadiationSickness, Params.RadiationDamageAmount));
foreach (Limb limb in character.AnimController.Limbs)
{
AttackResult attackResult = limb.AddDamage(limb.SimPosition, radiationAffliction.ToEnumerable(), playSound: false);
character.CharacterHealth.ApplyDamage(limb, attackResult);
}
}
}
}
@@ -386,7 +386,7 @@ namespace Barotrauma
DebugConsole.ThrowError("Cloning entity \"" + e.Name + "\" failed.", ex);
GameAnalyticsManager.AddErrorEventOnce(
"MapEntity.Clone:" + e.Name,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
"Cloning entity \"" + e.Name + "\" failed (" + ex.Message + ").\n" + ex.StackTrace.CleanupStackTrace());
return clones;
}
@@ -447,7 +447,7 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone.");
GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectedNotFound" + connectedItem.ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
"Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone.");
continue;
}
@@ -458,7 +458,7 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\".");
GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectionNotFound" + connectedItem.ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
"Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\".");
continue;
}
@@ -262,7 +262,7 @@ namespace Barotrauma
item.GetComponent<ConnectionPanel>()?.InitializeLinks();
item.GetComponent<ItemContainer>()?.OnMapLoaded();
}
idOffset = moduleEntities.Max(e => e.ID);
idOffset = moduleEntities.Max(e => e.ID) + 1;
var wallEntities = moduleEntities.Where(e => e is Structure).Cast<Structure>();
var hullEntities = moduleEntities.Where(e => e is Hull).Cast<Hull>();
@@ -1483,7 +1483,7 @@ namespace Barotrauma
}
characterInfo.TeamID = CharacterTeamType.FriendlyNPC;
var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
npc.AnimController.FindHull(gotoTarget.WorldPosition, true);
npc.AnimController.FindHull(gotoTarget.WorldPosition, setSubmarine: true);
npc.TeamID = CharacterTeamType.FriendlyNPC;
npc.Prefab = humanPrefab;
if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier))
@@ -1503,7 +1503,7 @@ namespace Barotrauma
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
{
item.AllowStealing = outpost.Info.OutpostGenerationParams.AllowStealing;
item.SpawnedInOutpost = true;
item.SpawnedInCurrentOutpost = true;
}
npc.GiveIdCardTags(gotoTarget as WayPoint);
humanPrefab.InitializeCharacter(npc, gotoTarget);
@@ -44,7 +44,7 @@ namespace Barotrauma
#endif
}
private IEnumerable<object> Update(List<Submarine> subs, Camera cam)
private IEnumerable<CoroutineStatus> Update(List<Submarine> subs, Camera cam)
{
if (!subs.Any()) yield return CoroutineStatus.Success;
@@ -1407,7 +1407,7 @@ namespace Barotrauma
{
string errorMsg = $"Error while loading structure \"{s.Name}\". Section damage index out of bounds. Index: {index}, section count: {s.SectionCount}.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Structure.Load:SectionIndexOutOfBounds", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Structure.Load:SectionIndexOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
else
{
@@ -137,6 +137,9 @@ namespace Barotrauma
get { return subBody?.Body; }
}
/// <summary>
/// Extents of the solid items/structures (ones with a physics body) and hulls
/// </summary>
public Rectangle Borders
{
get
@@ -145,6 +148,17 @@ namespace Barotrauma
}
}
/// <summary>
/// Extents of all the visible items/structures/hulls (including ones without a physics body)
/// </summary>
public Rectangle VisibleBorders
{
get
{
return subBody == null ? Rectangle.Empty : subBody.VisibleBorders;
}
}
public override Vector2 Position
{
get { return subBody == null ? Vector2.Zero : subBody.Position - HiddenSubPosition; }
@@ -274,14 +288,6 @@ namespace Barotrauma
return "Barotrauma.Submarine (" + (Info?.Name ?? "[NULL INFO]") + ", " + IdOffset + ")";
}
public override bool Removed
{
get
{
return !loaded.Contains(this);
}
}
public int CalculateBasePrice()
{
int minPrice = 1000;
@@ -1126,22 +1132,6 @@ namespace Barotrauma
}
}
/// <summary>
/// Run the power logic so the sub is already powered up at the start of the round (as long as the reactor was on)
/// </summary>
public void WarmStartPower()
{
for (int i = 0; i < 600; i++)
{
Powered.UpdatePower((float)Timing.Step);
foreach (Entity e in Item.ItemList)
{
if (!(e is Item item) || item.GetComponent<Powered>() == null || e.Submarine != this) { continue; }
item.Update((float)Timing.Step, GameMain.GameScreen.Cam);
}
}
}
public void SetPrevTransform(Vector2 position)
{
prevPosition = position;
@@ -1398,7 +1388,7 @@ namespace Barotrauma
if (me.Submarine != this) { continue; }
if (me is Item item)
{
item.SpawnedInOutpost = info.OutpostGenerationParams != null;
item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null;
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
if (item.GetComponent<Repairable>() != null && indestructible)
{
@@ -68,12 +68,24 @@ namespace Barotrauma
}
}
/// <summary>
/// Extents of the solid items/structures (ones with a physics body) and hulls
/// </summary>
public Rectangle Borders
{
get;
private set;
}
/// <summary>
/// Extents of all the visible items/structures/hulls (including ones without a physics body)
/// </summary>
public Rectangle VisibleBorders
{
get;
private set;
}
public Vector2 Velocity
{
get { return Body.LinearVelocity; }
@@ -122,14 +134,21 @@ namespace Barotrauma
HullVertices = convexHull;
Vector2 minExtents = Vector2.Zero, maxExtents = Vector2.Zero;
Vector2 visibleMinExtents = Vector2.Zero, visibleMaxExtents = Vector2.Zero;
farseerBody = GameMain.World.CreateBody();
farseerBody.UserData = this;
foreach (Structure wall in Structure.WallList)
foreach (var mapEntity in MapEntity.mapEntityList)
{
if (wall.Submarine != submarine || wall.IsPlatform) { continue; }
if (mapEntity.Submarine != submarine || !(mapEntity is Structure wall)) { continue; }
Rectangle rect = wall.Rect;
visibleMinExtents.X = Math.Min(rect.X, visibleMinExtents.X);
visibleMinExtents.Y = Math.Min(rect.Y - rect.Height, visibleMinExtents.Y);
visibleMaxExtents.X = Math.Max(rect.Right, visibleMaxExtents.X);
visibleMaxExtents.Y = Math.Max(rect.Y, visibleMaxExtents.Y);
if (!wall.HasBody || wall.IsPlatform || wall.StairDirection != Direction.None) { continue; }
farseerBody.CreateRectangle(
ConvertUnits.ToSimUnits(wall.BodyWidth),
@@ -138,10 +157,10 @@ namespace Barotrauma
-wall.BodyRotation,
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset)).UserData = wall;
minExtents.X = Math.Min(rect.X, minExtents.X);
minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y);
maxExtents.X = Math.Max(rect.Right, maxExtents.X);
maxExtents.Y = Math.Max(rect.Y, maxExtents.Y);
minExtents.X = Math.Min(visibleMinExtents.X, minExtents.X);
minExtents.Y = Math.Min(visibleMinExtents.Y, minExtents.Y);
maxExtents.X = Math.Max(visibleMaxExtents.X, maxExtents.X);
maxExtents.Y = Math.Max(visibleMaxExtents.Y, maxExtents.Y);
}
foreach (Hull hull in Hull.hullList)
@@ -155,14 +174,20 @@ namespace Barotrauma
100.0f,
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2))).UserData = hull;
minExtents.X = Math.Min(rect.X, minExtents.X);
minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y);
maxExtents.X = Math.Max(rect.Right, maxExtents.X);
maxExtents.Y = Math.Max(rect.Y, maxExtents.Y);
visibleMinExtents.X = Math.Min(rect.X, visibleMinExtents.X);
visibleMinExtents.Y = Math.Min(rect.Y - rect.Height, visibleMinExtents.Y);
visibleMaxExtents.X = Math.Max(rect.Right, visibleMaxExtents.X);
visibleMaxExtents.Y = Math.Max(rect.Y, visibleMaxExtents.Y);
minExtents.X = Math.Min(visibleMinExtents.X, minExtents.X);
minExtents.Y = Math.Min(visibleMinExtents.Y, minExtents.Y);
maxExtents.X = Math.Max(visibleMaxExtents.X, maxExtents.X);
maxExtents.Y = Math.Max(visibleMaxExtents.Y, maxExtents.Y);
}
foreach (Item item in Item.ItemList)
{
if (item.Submarine != submarine) { continue; }
if (item.StaticBodyConfig == null || item.Submarine != submarine) { continue; }
float radius = item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f) * item.Scale;
@@ -183,43 +208,48 @@ namespace Barotrauma
{
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos));
minExtents.X = Math.Min(item.Position.X - width / 2, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - height / 2, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + width / 2, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + height / 2, maxExtents.Y);
visibleMinExtents.X = Math.Min(item.Position.X - width / 2, visibleMinExtents.X);
visibleMinExtents.Y = Math.Min(item.Position.Y - height / 2, visibleMinExtents.Y);
visibleMaxExtents.X = Math.Max(item.Position.X + width / 2, visibleMaxExtents.X);
visibleMaxExtents.Y = Math.Max(item.Position.Y + height / 2, visibleMaxExtents.Y);
}
else if (radius > 0.0f && width > 0.0f)
{
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2));
minExtents.X = Math.Min(item.Position.X - width / 2 - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + width / 2 + radius, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y);
visibleMinExtents.X = Math.Min(item.Position.X - width / 2 - radius, visibleMinExtents.X);
visibleMinExtents.Y = Math.Min(item.Position.Y - radius, visibleMinExtents.Y);
visibleMaxExtents.X = Math.Max(item.Position.X + width / 2 + radius, visibleMaxExtents.X);
visibleMaxExtents.Y = Math.Max(item.Position.Y + radius, visibleMaxExtents.Y);
}
else if (radius > 0.0f && height > 0.0f)
{
item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2));
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - height / 2 - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + height / 2 + radius, maxExtents.Y);
visibleMinExtents.X = Math.Min(item.Position.X - radius, visibleMinExtents.X);
visibleMinExtents.Y = Math.Min(item.Position.Y - height / 2 - radius, visibleMinExtents.Y);
visibleMaxExtents.X = Math.Max(item.Position.X + radius, visibleMaxExtents.X);
visibleMaxExtents.Y = Math.Max(item.Position.Y + height / 2 + radius, visibleMaxExtents.Y);
}
else if (radius > 0.0f)
{
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos));
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y);
visibleMinExtents.X = Math.Min(item.Position.X - radius, visibleMinExtents.X);
visibleMinExtents.Y = Math.Min(item.Position.Y - radius, visibleMinExtents.Y);
visibleMaxExtents.X = Math.Max(item.Position.X + radius, visibleMaxExtents.X);
visibleMaxExtents.Y = Math.Max(item.Position.Y + radius, visibleMaxExtents.Y);
}
item.StaticFixtures.ForEach(f => f.UserData = item);
minExtents.X = Math.Min(visibleMinExtents.X, minExtents.X);
minExtents.Y = Math.Min(visibleMinExtents.Y, minExtents.Y);
maxExtents.X = Math.Max(visibleMaxExtents.X, maxExtents.X);
maxExtents.Y = Math.Max(visibleMaxExtents.Y, maxExtents.Y);
}
Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y));
VisibleBorders = new Rectangle((int)visibleMinExtents.X, (int)visibleMaxExtents.Y, (int)(visibleMaxExtents.X - visibleMinExtents.X), (int)(visibleMaxExtents.Y - visibleMinExtents.Y));
}
farseerBody.BodyType = BodyType.Dynamic;
@@ -575,7 +605,7 @@ namespace Barotrauma
if (newHull != null)
{
CoroutineManager.Invoke(() =>
character.AnimController.FindHull(newHull.WorldPosition, true));
character.AnimController.FindHull(newHull.WorldPosition, setSubmarine: true));
}
return false;
@@ -660,7 +690,7 @@ namespace Barotrauma
{
GameAnalyticsManager.AddErrorEventOnce(
"SubmarineBody.HandleLimbCollision:" + submarine.ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
"Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity
+ ", avgContactNormal: " + avgContactNormal
+ ", contactDot: " + contactDot
@@ -835,7 +865,7 @@ namespace Barotrauma
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"SubmarineBody.ApplyImpact:InvalidImpulse",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
errorMsg);
return;
}
@@ -194,7 +194,7 @@ namespace Barotrauma
float minDist = 100.0f;
float heightFromFloor = 110.0f;
float hullMinHeight = 100;
var removals = new List<WayPoint>();
var removals = new HashSet<WayPoint>();
foreach (Hull hull in Hull.hullList)
{
if (isFlooded)
@@ -492,17 +492,18 @@ namespace Barotrauma
{
outsideWaypoints.RemoveAll(w => w.Item1 == wp);
}
removals.ForEach(wp => wp.Remove());
for (int i = 0; i < outsideWaypoints.Count; i++)
{
WayPoint current = outsideWaypoints[i].Item1;
if (current.linkedTo.Count > 1) { continue; }
if (current.linkedTo.Count(l => !removals.Contains(l)) > 1) { continue; }
WayPoint next = null;
int maxConnections = 2;
float tooFar = outSideWaypointInterval * 5;
for (int j = 0; j < maxConnections; j++)
{
if (current.linkedTo.Count >= maxConnections) { break; }
tooFar /= current.linkedTo.Count;
tooFar /= current.linkedTo.Count(l => !removals.Contains(l));
next = current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp.Item1 != next && wp.Item1.linkedTo.None(e => current.linkedTo.Contains(e)) && wp.Item1.linkedTo.Count < 2 && wp.Item2 < i);
if (next != null)
{
@@ -511,18 +512,19 @@ namespace Barotrauma
}
}
}
foreach (Structure wall in Structure.WallList)
foreach (MapEntity mapEntity in mapEntityList.ToList())
{
if (wall.StairDirection == Direction.None) { continue; }
if (!(mapEntity is Structure structure)) { continue; }
if (structure.StairDirection == Direction.None) { continue; }
WayPoint[] stairPoints = new WayPoint[3];
stairPoints[0] = new WayPoint(
new Vector2(wall.Rect.X - 32.0f,
wall.Rect.Y - (wall.StairDirection == Direction.Left ? 80 : wall.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
new Vector2(structure.Rect.X - 32.0f,
structure.Rect.Y - (structure.StairDirection == Direction.Left ? 80 : structure.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
stairPoints[1] = new WayPoint(
new Vector2(wall.Rect.Right + 32.0f,
wall.Rect.Y - (wall.StairDirection == Direction.Left ? wall.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
new Vector2(structure.Rect.Right + 32.0f,
structure.Rect.Y - (structure.StairDirection == Direction.Left ? structure.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
for (int i = 0; i < 2; i++)
{
@@ -859,20 +861,20 @@ namespace Barotrauma
System.Diagnostics.Debug.Assert(this != wayPoint2);
if (!linkedTo.Contains(wayPoint2))
{
linkedTo.Add(wayPoint2);
OnLinksChanged?.Invoke(this);
linkedTo.Add(wayPoint2);
}
if (!wayPoint2.linkedTo.Contains(this))
{
wayPoint2.linkedTo.Add(this);
wayPoint2.OnLinksChanged?.Invoke(wayPoint2);
wayPoint2.linkedTo.Add(this);
}
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null)
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null, bool ignoreSubmarine = false)
{
return WayPointList.GetRandom(wp =>
wp.Submarine == sub &&
(ignoreSubmarine || wp.Submarine == sub) &&
wp.spawnType == spawnType &&
(string.IsNullOrEmpty(spawnPointTag) || wp.Tags.Any(t => t.Equals(spawnPointTag, StringComparison.OrdinalIgnoreCase))) &&
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
@@ -1103,7 +1105,6 @@ namespace Barotrauma
Ladders = null;
OnLinksChanged = null;
WayPointList.Remove(this);
}
}
}
}