v0.14.6.0

This commit is contained in:
Joonas Rikkonen
2021-06-17 17:54:52 +03:00
parent 3f324b14e8
commit c27e2ea5ab
348 changed files with 13156 additions and 4266 deletions
@@ -25,12 +25,12 @@ namespace Barotrauma
private readonly float screenColorRange, screenColorDuration;
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
private bool playTinnitus;
private bool applyFireEffects;
private string[] ignoreFireEffectsForTags;
private bool ignoreCover;
private bool onlyInside;
private bool onlyOutside;
private readonly Color flashColor;
private readonly bool playTinnitus;
private readonly bool applyFireEffects;
private readonly string[] ignoreFireEffectsForTags;
private readonly bool ignoreCover;
private readonly bool onlyInside,onlyOutside;
private readonly float flashDuration;
private readonly float? flashRange;
private readonly string decal;
@@ -81,6 +81,7 @@ namespace Barotrauma
flash = element.GetAttributeBool("flash", true);
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow);
EmpStrength = element.GetAttributeFloat("empstrength", 0.0f);
BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f);
@@ -129,7 +130,7 @@ namespace Barotrauma
float displayRange = Attack.Range;
Vector2 cameraPos = Character.Controlled != null ? Character.Controlled.WorldPosition : GameMain.GameScreen.Cam.Position;
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
GameMain.GameScreen.Cam.Shake = cameraShake * Math.Max((cameraShakeRange - cameraDist) / cameraShakeRange, 0.0f);
#if CLIENT
@@ -395,7 +396,7 @@ namespace Barotrauma
for (int i = 0; i < structure.SectionCount; i++)
{
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
if (distFactor <= 0.0f) continue;
if (distFactor <= 0.0f) { continue; }
structure.AddDamage(i, damage * distFactor, attacker);
@@ -412,6 +413,19 @@ namespace Barotrauma
if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
{
if (Level.Loaded?.LevelObjectManager != null)
{
foreach (var levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(worldPosition, worldRange))
{
if (levelObject.Prefab.TakeLevelWallDamage)
{
float distFactor = 1.0f - (Vector2.Distance(levelObject.WorldPosition, worldPosition) / worldRange);
if (distFactor <= 0.0f) { continue; }
levelObject.AddDamage(levelWallDamage * distFactor, 1.0f, null);
}
}
}
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
{
if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; }
@@ -94,7 +94,7 @@ namespace Barotrauma
public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false)
{
hull = Hull.FindHull(worldPosition, spawningHull);
if (hull == null || worldPosition.Y < hull.WorldSurface) return;
if (hull == null || worldPosition.Y < hull.WorldSurface) { return; }
#if CLIENT
if (!isNetworkMessage && GameMain.Client != null) { return; }
@@ -189,6 +189,10 @@ namespace Barotrauma
if (roomName == value) { return; }
roomName = value;
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
if (!IsWetRoom && ForceAsWetRoom)
{
IsWetRoom = true;
}
}
}
@@ -329,6 +333,42 @@ namespace Barotrauma
}
}
private bool ForceAsWetRoom =>
roomName != null && (
roomName.Contains("ballast", StringComparison.OrdinalIgnoreCase) ||
roomName.Contains("bilge", StringComparison.OrdinalIgnoreCase) ||
roomName.Contains("airlock", StringComparison.OrdinalIgnoreCase));
private bool isWetRoom;
[Editable, Serialize(false, true, description: "It's normal for this hull to be filled with water. If the room name contains 'ballast', 'bilge', or 'airlock', you can't disable this setting.")]
public bool IsWetRoom
{
get { return isWetRoom; }
set
{
isWetRoom = value;
if (ForceAsWetRoom)
{
isWetRoom = true;
}
}
}
private bool avoidStaying;
[Editable, Serialize(false, true, description: "Bots avoid staying here, but they are still allowed to access the room when needed and go through it. Forced true for wet rooms.")]
public bool AvoidStaying
{
get { return avoidStaying || IsWetRoom; }
set
{
avoidStaying = value;
if (IsWetRoom)
{
avoidStaying = true;
}
}
}
public float WaterPercentage => MathUtils.Percentage(WaterVolume, Volume);
public float OxygenPercentage
@@ -534,6 +574,9 @@ namespace Barotrauma
Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
BallastFlora?.OnMapLoaded();
#if CLIENT
lastAmbientLightEditTime = 0.0;
#endif
}
public void AddToGrid(Submarine submarine)
@@ -643,6 +686,11 @@ namespace Barotrauma
public void AddFireSource(FireSource fireSource)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{
//clients aren't allowed to create fire sources in hulls whose IDs have been freed (dynamic hulls between docking ports), because they can't be synced
if (IdFreed) { return; }
}
if (fireSource is DummyFireSource dummyFire)
{
FakeFireSources.Add(dummyFire);
@@ -705,19 +753,22 @@ namespace Barotrauma
Oxygen -= OxygenDeteriorationSpeed * deltaTime;
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
if (FakeFireSources.Count > 0)
{
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
{
if (FakeFireSources[i].CausedByPsychosis)
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
{
FakeFireSources[i].Remove();
if (FakeFireSources[i].CausedByPsychosis)
{
FakeFireSources[i].Remove();
}
}
}
FireSource.UpdateAll(FakeFireSources, deltaTime);
}
FireSource.UpdateAll(FireSources, deltaTime);
FireSource.UpdateAll(FakeFireSources, deltaTime);
foreach (Decal decal in decals)
{
@@ -12,7 +12,7 @@ namespace Barotrauma
interface IIgnorable : ISpatialEntity
{
bool IgnoreByAI { get; }
bool IgnoreByAI(Character character);
bool OrderedToBeIgnored { get; set; }
}
}
@@ -36,7 +36,7 @@ namespace Barotrauma
public Rectangle Bounds;
public ItemAssemblyPrefab(string filePath)
public ItemAssemblyPrefab(string filePath, bool allowOverwrite = false)
{
FilePath = filePath;
XDocument doc = XMLExtensions.TryLoadXml(filePath);
@@ -113,6 +113,10 @@ namespace Barotrauma
new Rectangle(0, 0, 1, 1) :
new Rectangle(minX, minY, maxX - minX, maxY - minY);
if (allowOverwrite && Prefabs.ContainsKey(identifier))
{
Prefabs.Remove(Prefabs[identifier]);
}
Prefabs.Add(this, doc.Root.IsOverride());
}
@@ -35,7 +35,7 @@ namespace Barotrauma
Cave = 0x4,
Ruin = 0x8,
Wreck = 0x10,
BeaconStation = 0x20,
BeaconStation = 0x20, // Not used anywhere
Abyss = 0x40,
AbyssCave = 0x80
}
@@ -325,9 +325,11 @@ namespace Barotrauma
get { return LevelData.Seed; }
}
public static float? ForcedDifficulty;
public float Difficulty
{
get { return LevelData.Difficulty; }
get { return ForcedDifficulty ?? LevelData.Difficulty; }
}
public LevelData.LevelType Type
@@ -387,7 +389,7 @@ namespace Barotrauma
private void Generate(bool mirror)
{
if (Loaded != null) { Loaded.Remove(); }
Loaded?.Remove();
Loaded = this;
Generating = true;
@@ -1152,7 +1154,7 @@ namespace Barotrauma
}
CreateWrecks();
CreateBeaconStation(cells);
CreateBeaconStation();
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
@@ -1892,26 +1894,45 @@ namespace Barotrauma
private void CalculateTunnelDistanceField(int density)
{
distanceField = new List<(Point point, double distance)>();
for (int x = 0; x < Size.X; x += density)
if (Mirrored)
{
for (int y = 0; y < Size.Y; y += density)
for (int x = Size.X - 1; x >= 0; x -= density)
{
Point point = new Point(x, y);
double shortestDistSqr = double.PositiveInfinity;
foreach (Tunnel tunnel in Tunnels)
for (int y = 0; y < Size.Y; y += density)
{
for (int i = 1; i < tunnel.Nodes.Count; i++)
{
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
}
addPoint(x, y);
}
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endExitPosition.X, (double)borders.Bottom));
distanceField.Add((point, Math.Sqrt(shortestDistSqr)));
}
}
else
{
for (int x = 0; x < Size.X; x += density)
{
for (int y = 0; y < Size.Y; y += density)
{
addPoint(x, y);
}
}
}
void addPoint(int x, int y)
{
Point point = new Point(x, y);
double shortestDistSqr = double.PositiveInfinity;
foreach (Tunnel tunnel in Tunnels)
{
for (int i = 1; i < tunnel.Nodes.Count; i++)
{
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
}
}
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endExitPosition.X, (double)borders.Bottom));
distanceField.Add((point, Math.Sqrt(shortestDistSqr)));
}
}
private double GetDistToTunnel(Vector2 position, Tunnel tunnel)
@@ -2710,14 +2731,21 @@ namespace Barotrauma
return position;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null)
public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Vector2 awayPoint, float minDistFromPoint, Func<InterestingPosition, bool> filter = null)
{
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, filter);
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, awayPoint, minDistFromPoint, filter);
position = pos.ToVector2();
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Func<InterestingPosition, bool> filter = null)
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null)
{
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter);
position = pos.ToVector2();
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null)
{
if (!PositionsOfInterest.Any())
{
@@ -2755,6 +2783,11 @@ namespace Barotrauma
farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), sub.WorldPosition) < minDistFromSubs * minDistFromSubs);
}
}
if (minDistFromPoint > 0.0f)
{
farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), awayPoint) < minDistFromPoint * minDistFromPoint);
}
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();
@@ -2826,7 +2859,7 @@ namespace Barotrauma
if (index < 0 || index >= bottomPositions.Count - 1) { return new Vector2(xPosition, BottomPos); }
float t = (xPosition - bottomPositions[index].X) / (bottomPositions[index + 1].X - bottomPositions[index].X);
Debug.Assert(t < 1.0f);
Debug.Assert(t <= 1.0f);
t = MathHelper.Clamp(t, 0.0f, 1.0f);
float yPos = MathHelper.Lerp(bottomPositions[index].Y, bottomPositions[index + 1].Y, t);
@@ -2993,20 +3026,30 @@ namespace Barotrauma
return originalTag + "_" + shortSeed;
}
public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist);
public bool IsCloseToEnd(Vector2 position, float minDist) => IsCloseToEnd(position.ToPoint(), minDist);
public bool IsCloseToStart(Point position, float minDist)
{
return MathUtils.LineSegmentToPointDistanceSquared(StartPosition.ToPoint(), StartExitPosition.ToPoint(), position) < minDist * minDist;
}
public bool IsCloseToEnd(Point position, float minDist)
{
return MathUtils.LineSegmentToPointDistanceSquared(EndPosition.ToPoint(), EndExitPosition.ToPoint(), position) < minDist * minDist;
}
private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type)
{
var tempSW = new Stopwatch();
// Min distance between a sub and the start/end/other sub.
float minDistance = Sonar.DefaultSonarRange;
float squaredMinDistance = minDistance * minDistance;
Vector2 start = startPosition.ToVector2();
Vector2 end = endPosition.ToVector2();
var waypoints = WayPoint.WayPointList.Where(wp =>
wp.Submarine == null &&
wp.SpawnType == SpawnType.Path &&
Vector2.DistanceSquared(wp.WorldPosition, start) > squaredMinDistance &&
Vector2.DistanceSquared(wp.WorldPosition, end) > squaredMinDistance).ToList();
!IsCloseToStart(wp.WorldPosition, minDistance) &&
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
@@ -3094,12 +3137,11 @@ namespace Barotrauma
sub.SetPosition(spawnPoint);
wreckPositions.Add(sub, positions);
blockedRects.Add(sub, rects);
return sub;
}
else
{
DebugConsole.NewMessage($"Failed to position wreck {subName}. Used {tempSW.ElapsedMilliseconds.ToString()} (ms).", Color.Red);
DebugConsole.NewMessage($"Failed to position wreck {subName}. Used {tempSW.ElapsedMilliseconds} (ms).", Color.Red);
return null;
}
@@ -3150,7 +3192,7 @@ namespace Barotrauma
else
{
var sp = spawnPoint;
if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance))
if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < minDistance * minDistance))
{
Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s).");
return false;
@@ -3306,18 +3348,25 @@ namespace Barotrauma
}
wreckFiles.Shuffle(Rand.RandSync.Server);
int wreckCount = Math.Min(Loaded.GenerationParams.WreckCount, wreckFiles.Count);
int minWreckCount = Math.Min(Loaded.GenerationParams.MinWreckCount, wreckFiles.Count);
int maxWreckCount = Math.Min(Loaded.GenerationParams.MaxWreckCount, wreckFiles.Count);
int wreckCount = Rand.Range(minWreckCount, maxWreckCount + 1, Rand.RandSync.Server);
if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireWreck) ?? false)
{
wreckCount = Math.Max(wreckCount, 1);
}
Wrecks = new List<Submarine>(wreckCount);
for (int i = 0; i < wreckCount; i++)
{
ContentFile contentFile = wreckFiles[i];
if (contentFile == null) { continue; }
string wreckName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path);
// For storing the translations. Used only for debugging.
SpawnSubOnPath(wreckName, contentFile, SubmarineType.Wreck);
}
totalSW.Stop();
Debug.WriteLine($"{Wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds.ToString()} (ms)");
Debug.WriteLine($"{Wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds} (ms)");
}
private bool HasStartOutpost()
@@ -3365,11 +3414,8 @@ namespace Barotrauma
for (int i = 0; i < 2; i++)
{
if (Submarine.MainSubs.Length > 1 && Submarine.MainSubs[0] != null && Submarine.MainSubs[1] != null)
{
continue;
}
if (GameMain.GameSession?.GameMode is PvPMode) { continue; }
bool isStart = (i == 0) == !Mirrored;
if (isStart)
{
@@ -3527,7 +3573,7 @@ namespace Barotrauma
{
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
}
outpost.SetPosition(spawnPos);
outpost.SetPosition(spawnPos, forceUndockFromStaticSubmarines: false);
if ((i == 0) == !Mirrored)
{
StartOutpost = outpost;
@@ -3550,7 +3596,7 @@ namespace Barotrauma
}
}
private void CreateBeaconStation(List<VoronoiCell> mainPath)
private void CreateBeaconStation()
{
if (!LevelData.HasBeaconStation) { return; }
var beaconStationFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.BeaconStation).ToList();
@@ -496,8 +496,11 @@ namespace Barotrauma
[Serialize(1, true, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
public int RuinCount { get; set; }
[Serialize(1, true, description: "The minimum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
public int MinWreckCount { get; set; }
[Serialize(1, true, description: "The maximum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
public int WreckCount { get; set; }
public int MaxWreckCount { get; set; }
// TODO: Move the wreck parameters under a separate class?
#region Wreck parameters
@@ -8,7 +8,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelObject : ISpatialEntity
partial class LevelObject : ISpatialEntity, IDamageable, ISerializableEntity
{
public readonly LevelObjectPrefab Prefab;
public Vector3 Position;
@@ -21,6 +21,8 @@ namespace Barotrauma
private int spriteIndex;
protected bool tookDamage;
public LevelObjectPrefab ActivePrefab;
public PhysicsBody PhysicsBody
@@ -37,11 +39,15 @@ namespace Barotrauma
public bool NeedsNetworkSyncing
{
get { return Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing); }
get
{
return tookDamage || (Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing));
}
set
{
if (Triggers == null) { return; }
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
tookDamage = false;
}
}
@@ -50,6 +56,12 @@ namespace Barotrauma
get; private set;
}
public float Health
{
get;
private set;
}
public Sprite Sprite
{
get
@@ -67,6 +79,10 @@ namespace Barotrauma
public Submarine Submarine => null;
public string Name => Prefab?.Name ?? "LevelObject (null)";
public Dictionary<string, SerializableProperty> SerializableProperties { get; } = new Dictionary<string, SerializableProperty>();
public Level.Cave ParentCave;
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
@@ -75,6 +91,7 @@ namespace Barotrauma
Position = position;
Scale = scale;
Rotation = rotation;
Health = prefab.Health;
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
@@ -89,10 +106,13 @@ namespace Barotrauma
if (PhysicsBody != null)
{
PhysicsBody.UserData = this;
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition, -Rotation);
PhysicsBody.BodyType = BodyType.Static;
PhysicsBody.CollisionCategories = Physics.CollisionLevel;
PhysicsBody.CollidesWith = Physics.CollisionWall | Physics.CollisionCharacter;
PhysicsBody.CollidesWith = Prefab.TakeLevelWallDamage?
Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionProjectile :
Physics.CollisionWall | Physics.CollisionCharacter;
}
foreach (XElement triggerElement in prefab.LevelTriggerElements)
@@ -111,6 +131,10 @@ namespace Barotrauma
}
var newTrigger = new LevelTrigger(triggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale, prefab.Name);
if (newTrigger.PhysicsBody != null)
{
newTrigger.PhysicsBody.UserData = this;
}
int parentTriggerIndex = prefab.LevelTriggerElements.IndexOf(triggerElement.Parent);
if (parentTriggerIndex > -1) { newTrigger.ParentTrigger = Triggers[parentTriggerIndex]; }
Triggers.Add(newTrigger);
@@ -135,7 +159,54 @@ namespace Barotrauma
}
partial void InitProjSpecific();
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
{
if (Health <= 0.0f) { return new AttackResult(0.0f); }
float damage = 0.0f;
if (Prefab.TakeLevelWallDamage)
{
damage += attack.GetLevelWallDamage(deltaTime);
}
damage = Math.Max(Health, damage);
AddDamage(damage, deltaTime, attacker);
return new AttackResult(damage);
}
public void AddDamage(float damage, float deltaTime, Entity attacker, bool isNetworkEvent = false)
{
if (Health <= 0.0f) { return; }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkEvent)
{
return;
}
tookDamage |= !MathUtils.NearlyEqual(damage, 0.0f);
Health -= damage;
if (Health <= 0.0f)
{
#if CLIENT
if (GameMain.GameSession?.Level?.LevelObjectManager != null)
{
GameMain.GameSession.Level.LevelObjectManager.ForceRefreshVisibleObjects = true;
}
#endif
if (PhysicsBody != null)
{
PhysicsBody.Enabled = false;
}
foreach (LevelTrigger trigger in Triggers)
{
trigger.PhysicsBody.Enabled = false;
foreach (StatusEffect effect in trigger.StatusEffects)
{
if (effect.type != ActionType.OnBroken) { continue; }
effect.Apply(effect.type, deltaTime, attacker, this, worldPosition: WorldPosition);
}
}
}
}
public Vector2 LocalToWorld(Vector2 localPosition, float swingState = 0.0f)
{
Vector2 emitterPos = localPosition * Scale;
@@ -169,6 +240,10 @@ namespace Barotrauma
public void ServerWrite(IWriteMessage msg, Client c)
{
if (Triggers == null) { return; }
if (Prefab.TakeLevelWallDamage)
{
msg.WriteRangedSingle(MathHelper.Clamp(Health, 0.0f, Prefab.Health), 0.0f, Prefab.Health, 8);
}
for (int j = 0; j < Triggers.Count; j++)
{
if (!Triggers[j].UseNetworkSyncing) { continue; }
@@ -21,6 +21,12 @@ namespace Barotrauma
private List<LevelObject> updateableObjects;
private List<LevelObject>[,] objectGrid;
public float GlobalForceDecreaseTimer
{
get;
private set;
}
public LevelObjectManager() : base(null, Entity.NullEntityID)
{
}
@@ -130,11 +136,16 @@ namespace Barotrauma
if (prefab == null) { continue; }
if (!suitableSpawnPositions.ContainsKey(prefab))
{
float minDistance = level.Size.X * 0.2f;
suitableSpawnPositions.Add(prefab,
availableSpawnPositions.Where(sp =>
sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) &&
sp.Length >= prefab.MinSurfaceWidth &&
sp.Length >= prefab.MinSurfaceWidth &&
(prefab.AllowAtStart || !level.IsCloseToStart(sp.GraphEdge.Center, minDistance)) &&
(prefab.AllowAtEnd || !level.IsCloseToEnd(sp.GraphEdge.Center, minDistance)) &&
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
spawnPositionWeights.Add(prefab,
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
}
@@ -422,10 +433,10 @@ namespace Barotrauma
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
{
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) return Enumerable.Empty<LevelObject>();
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) { return Enumerable.Empty<LevelObject>(); }
var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius);
if (maxIndices.X < 0 || maxIndices.Y < 0) return Enumerable.Empty<LevelObject>();
if (maxIndices.X < 0 || maxIndices.Y < 0) { return Enumerable.Empty<LevelObject>(); }
minIndices.X = Math.Max(0, minIndices.X);
minIndices.Y = Math.Max(0, minIndices.Y);
@@ -440,6 +451,7 @@ namespace Barotrauma
if (objectGrid[x, y] == null) { continue; }
foreach (LevelObject obj in objectGrid[x, y])
{
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
objectsInRange.Add(obj);
}
}
@@ -484,6 +496,12 @@ namespace Barotrauma
public void Update(float deltaTime)
{
GlobalForceDecreaseTimer += deltaTime;
if (GlobalForceDecreaseTimer > 1000000.0f)
{
GlobalForceDecreaseTimer = 0.0f;
}
foreach (LevelObject obj in updateableObjects)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
@@ -496,6 +514,7 @@ namespace Barotrauma
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
}
}
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
if (obj.Triggers != null)
{
@@ -176,6 +176,20 @@ namespace Barotrauma
private set;
}
[Editable, Serialize(true, true, description: "Can the object be placed near the start of the level.")]
public bool AllowAtStart
{
get;
private set;
}
[Editable, Serialize(true, true, description: "Can the object be placed near the end of the level.")]
public bool AllowAtEnd
{
get;
private set;
}
[Serialize(0.0f, true, description: "Minimum length of a graph edge the object can spawn on."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
/// <summary>
/// Minimum length of a graph edge the object can spawn on.
@@ -250,6 +264,27 @@ namespace Barotrauma
private set;
}
[Serialize(false, true, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable]
public bool TakeLevelWallDamage
{
get;
private set;
}
[Serialize(false, true), Editable]
public bool HideWhenBroken
{
get;
private set;
}
[Serialize(100.0f, true), Editable]
public float Health
{
get;
private set;
}
public string Identifier
{
get;
@@ -38,6 +38,10 @@ namespace Barotrauma
/// Effects applied to entities that are inside the trigger
/// </summary>
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
public IEnumerable<StatusEffect> StatusEffects
{
get { return statusEffects; }
}
/// <summary>
/// Attacks applied to entities that are inside the trigger
@@ -140,6 +144,11 @@ namespace Barotrauma
get;
private set;
}
public float GlobalForceDecreaseInterval
{
get;
private set;
}
private readonly TriggerForceMode forceMode;
public TriggerForceMode ForceMode
@@ -200,7 +209,7 @@ namespace Barotrauma
PhysicsBody = new PhysicsBody(element, scale)
{
CollisionCategories = Physics.CollisionLevel,
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall,
};
PhysicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
PhysicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
@@ -234,6 +243,7 @@ namespace Barotrauma
ForceFluctuationInterval = element.GetAttributeFloat("forcefluctuationinterval", 0.01f);
ForceFluctuationStrength = Math.Max(element.GetAttributeFloat("forcefluctuationstrength", 0.0f), 0.0f);
ForceFalloff = element.GetAttributeBool("forcefalloff", true);
GlobalForceDecreaseInterval = element.GetAttributeFloat("globalforcedecreaseinterval", 0.0f);
ForceVelocityLimit = ConvertUnits.ToSimUnits(element.GetAttributeFloat("forcevelocitylimit", float.MaxValue));
string forceModeStr = element.GetAttributeString("forcemode", "Force");
@@ -434,6 +444,8 @@ namespace Barotrauma
}
}
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public void Update(float deltaTime)
{
if (ParentTrigger != null && !ParentTrigger.IsTriggered) { return; }
@@ -457,7 +469,13 @@ namespace Barotrauma
if (!UseNetworkSyncing || isNotClient)
{
if (ForceFluctuationStrength > 0.0f)
if (GlobalForceDecreaseInterval > 0.0f && Level.Loaded?.LevelObjectManager != null &&
Level.Loaded.LevelObjectManager.GlobalForceDecreaseTimer % (GlobalForceDecreaseInterval * 2) < GlobalForceDecreaseInterval)
{
NeedsNetworkSyncing |= currentForceFluctuation > 0.0f;
currentForceFluctuation = 0.0f;
}
else if (ForceFluctuationStrength > 0.0f)
{
//no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers
if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any())
@@ -509,6 +527,7 @@ namespace Barotrauma
{
foreach (StatusEffect effect in statusEffects)
{
if (effect.type == ActionType.OnBroken) { continue; }
Vector2? position = null;
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; }
if (triggerer is Character character)
@@ -533,8 +552,8 @@ namespace Barotrauma
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
var targets = new List<ISerializableEntity>();
effect.GetNearbyTargets(worldPosition, targets);
targets.Clear();
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
effect.Apply(effect.type, deltaTime, triggerer, targets);
}
}
@@ -354,7 +354,7 @@ namespace Barotrauma
}
}
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition);
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition, forceUndockFromStaticSubmarines: false);
sub.Submarine = Submarine;
}
@@ -13,14 +13,14 @@ namespace Barotrauma
public class TakenItem
{
public readonly ushort OriginalID;
public readonly ushort OriginalContainerID;
public readonly ushort ModuleIndex;
public readonly string Identifier;
public readonly int OriginalContainerIndex;
public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerID, ushort moduleIndex)
public TakenItem(string identifier, UInt16 originalID, int originalContainerIndex, ushort moduleIndex)
{
OriginalID = originalID;
OriginalContainerID = originalContainerID;
OriginalContainerIndex = originalContainerIndex;
ModuleIndex = moduleIndex;
Identifier = identifier;
}
@@ -29,11 +29,7 @@ namespace Barotrauma
{
System.Diagnostics.Debug.Assert(item.OriginalModuleIndex >= 0, "Trying to add a non-outpost item to a location's taken items");
if (item.OriginalContainerID != Entity.NullEntityID)
{
OriginalContainerID = item.OriginalContainerID;
}
OriginalContainerIndex = item.OriginalContainerIndex;
OriginalID = item.ID;
ModuleIndex = (ushort) item.OriginalModuleIndex;
Identifier = item.prefab.Identifier;
@@ -41,14 +37,14 @@ namespace Barotrauma
public bool IsEqual(TakenItem obj)
{
return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
return obj.OriginalID == OriginalID && obj.OriginalContainerIndex == OriginalContainerIndex && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
}
public bool Matches(Item item)
{
if (item.OriginalContainerID != Entity.NullEntityID)
if (item.OriginalContainerIndex != Entity.NullEntityID)
{
return item.OriginalContainerID == OriginalContainerID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
return item.OriginalContainerIndex == OriginalContainerIndex && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
}
else
{
@@ -181,27 +177,54 @@ namespace Barotrauma
}
}
public Mission SelectedMission
private readonly List<Mission> selectedMissions = new List<Mission>();
public IEnumerable<Mission> SelectedMissions
{
get;
set;
get
{
selectedMissions.RemoveAll(m => !availableMissions.Contains(m));
return selectedMissions;
}
}
public int SelectedMissionIndex
public void SelectMission(Mission mission)
{
get
if (!SelectedMissions.Contains(mission) && mission != null)
{
if (SelectedMission == null) { return -1; }
return availableMissions.IndexOf(SelectedMission);
selectedMissions.Add(mission);
}
set
}
public void DeselectMission(Mission mission)
{
selectedMissions.Remove(mission);
}
public List<int> GetSelectedMissionIndices()
{
List<int> selectedMissionIndices = new List<int>();
foreach (Mission mission in SelectedMissions)
{
if (value < 0 || value >= AvailableMissions.Count())
if (availableMissions.Contains(mission))
{
SelectedMission = null;
return;
selectedMissionIndices.Add(availableMissions.IndexOf(mission));
}
SelectedMission = availableMissions[value];
}
return selectedMissionIndices;
}
public void SetSelectedMissionIndices(IEnumerable<int> missionIndices)
{
selectedMissions.Clear();
foreach (int missionIndex in missionIndices)
{
if (missionIndex < 0 || missionIndex >= availableMissions.Count)
{
DebugConsole.ThrowError($"Failed to select a mission in location \"{Name}\". Mission index out of bounds ({missionIndex}, available missions: {availableMissions.Count})");
break;
}
selectedMissions.Add(availableMissions[missionIndex]);
}
}
@@ -322,9 +345,9 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\"");
continue;
}
if (!ushort.TryParse(takenItemSplit[2], out ushort containerId))
if (!int.TryParse(takenItemSplit[2], out int containerIndex))
{
DebugConsole.ThrowError($"Error in saved location: could not parse taken container id \"{takenItemSplit[2]}\"");
DebugConsole.ThrowError($"Error in saved location: could not parse taken container index \"{takenItemSplit[2]}\"");
continue;
}
if (!ushort.TryParse(takenItemSplit[3], out ushort moduleIndex))
@@ -332,7 +355,7 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error in saved location: could not parse taken item module index \"{takenItemSplit[3]}\"");
continue;
}
takenItems.Add(new TakenItem(takenItemSplit[0], id, containerId, moduleIndex));
takenItems.Add(new TakenItem(takenItemSplit[0], id, containerIndex, moduleIndex));
}
killedCharacterIdentifiers = element.GetAttributeIntArray("killedcharacters", new int[0]).ToHashSet();
@@ -415,6 +438,12 @@ namespace Barotrauma
{
if (newType == Type) { return; }
if (newType == null)
{
DebugConsole.ThrowError($"Failed to change the type of the location \"{Name}\" to null.\n" + Environment.StackTrace.CleanupStackTrace());
return;
}
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
Type = newType;
@@ -533,8 +562,28 @@ namespace Barotrauma
//prefer connections that haven't been passed through, and connections with fewer available missions
connection = ToolBox.SelectWeightedRandom(
suitableConnections.ToList(),
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
Rand.RandSync.Unsynced);
suitableConnections.Select(c => GetConnectionWeight(this, c)).ToList(),
Rand.RandSync.Unsynced);
static float GetConnectionWeight(Location location, LocationConnection c)
{
float weight = c.Passed ? 1.0f : 5.0f;
Location destination = c.OtherLocation(location);
if (destination != null)
{
if (destination.MapPosition.X > location.MapPosition.X) { weight *= 2.0f; }
int missionCount = location.availableMissions.Count(m => m.Locations.Contains(destination));
if (missionCount > 0)
{
weight /= missionCount * 2;
}
if (destination.IsRadiated())
{
weight *= 0.001f;
}
}
return weight;
}
return InstantiateMission(prefab, connection);
}
@@ -542,14 +591,14 @@ namespace Barotrauma
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection)
{
Location destination = connection.OtherLocation(this);
var mission = prefab.Instantiate(new Location[] { this, destination });
var mission = prefab.Instantiate(new Location[] { this, destination }, Submarine.MainSub);
mission.AdjustLevelData(connection.LevelData);
return mission;
}
private Mission InstantiateMission(MissionPrefab prefab)
{
var mission = prefab.Instantiate(new Location[] { this, this });
var mission = prefab.Instantiate(new Location[] { this, this }, Submarine.MainSub);
mission.AdjustLevelData(LevelData);
return mission;
}
@@ -557,6 +606,7 @@ namespace Barotrauma
public void InstantiateLoadedMissions(Map map)
{
availableMissions.Clear();
selectedMissions.Clear();
if (loadedMissions != null && loadedMissions.Any())
{
foreach (LoadedMission loadedMission in loadedMissions)
@@ -570,9 +620,9 @@ namespace Barotrauma
{
destination = Connections.First().OtherLocation(this);
}
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination });
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination }, Submarine.MainSub);
availableMissions.Add(mission);
if (loadedMission.SelectedMission) { SelectedMission = mission; }
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
}
loadedMissions = null;
}
@@ -596,7 +646,7 @@ namespace Barotrauma
public void ClearMissions()
{
availableMissions.Clear();
SelectedMissionIndex = -1;
selectedMissions.Clear();
}
public bool HasOutpost()
@@ -1107,7 +1157,7 @@ namespace Barotrauma
{
locationElement.Add(new XAttribute(
"takenitems",
string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerID + ";" + it.ModuleIndex))));
string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerIndex + ";" + it.ModuleIndex))));
}
if (killedCharacterIdentifiers.Any())
{
@@ -1164,7 +1214,7 @@ namespace Barotrauma
missionsElement.Add(new XElement("mission",
new XAttribute("prefabid", mission.Prefab.Identifier),
new XAttribute("destinationindex", i),
new XAttribute("selected", mission == SelectedMission)));
new XAttribute("selected", selectedMissions.Contains(mission))));
}
locationElement.Add(missionsElement);
}
@@ -24,7 +24,7 @@ namespace Barotrauma
/// From -> To
/// </summary>
public Action<Location, Location> OnLocationChanged;
public Action<LocationConnection, Mission> OnMissionSelected;
public Action<LocationConnection, IEnumerable<Mission>> OnMissionsSelected;
public Location EndLocation { get; private set; }
@@ -44,9 +44,9 @@ namespace Barotrauma
get { return Locations.IndexOf(SelectedLocation); }
}
public int SelectedMissionIndex
public IEnumerable<int> GetSelectedMissionIndices()
{
get { return SelectedConnection == null ? -1 : CurrentLocation.SelectedMissionIndex; }
return SelectedConnection == null ? Enumerable.Empty<int>() : CurrentLocation.GetSelectedMissionIndices();
}
public LocationConnection SelectedConnection { get; private set; }
@@ -388,8 +388,14 @@ namespace Barotrauma
}
}
LocationConnection[] connectionsBetweenZones = new LocationConnection[generationParams.DifficultyZones];
foreach (var connection in Connections)
List<LocationConnection>[] connectionsBetweenZones = new List<LocationConnection>[generationParams.DifficultyZones];
for (int i = 0; i < generationParams.DifficultyZones; i++)
{
connectionsBetweenZones[i] = new List<LocationConnection>();
}
var shuffledConnections = Connections.ToList();
shuffledConnections.Shuffle(Rand.RandSync.Server);
foreach (var connection in shuffledConnections)
{
int zone1 = GetZoneIndex(connection.Locations[0].MapPosition.X);
int zone2 = GetZoneIndex(connection.Locations[1].MapPosition.X);
@@ -401,17 +407,25 @@ namespace Barotrauma
zone1 = temp;
}
if (connectionsBetweenZones[zone1] == null)
if (generationParams.GateCount[zone1] == 0) { continue; }
if (!connectionsBetweenZones[zone1].Any())
{
connectionsBetweenZones[zone1] = connection;
connectionsBetweenZones[zone1].Add(connection);
}
else
else if (generationParams.GateCount[zone1] == 1)
{
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].CenterPos.Y - Height / 2))
//if there's only one connection, place it at the center of the map
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].First().CenterPos.Y - Height / 2))
{
connectionsBetweenZones[zone1] = connection;
connectionsBetweenZones[zone1].Clear();
connectionsBetweenZones[zone1].Add(connection);
}
}
else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1])
{
connectionsBetweenZones[zone1].Add(connection);
}
}
for (int i = Connections.Count - 1; i >= 0; i--)
@@ -421,7 +435,9 @@ namespace Barotrauma
if (zone1 == zone2) { continue; }
if (zone1 == generationParams.DifficultyZones || zone2 == generationParams.DifficultyZones) { continue; }
if (!connectionsBetweenZones.Contains(Connections[i]))
if (generationParams.GateCount[Math.Min(zone1, zone2)] == 0) { continue; }
if (!connectionsBetweenZones[Math.Min(zone1, zone2)].Contains(Connections[i]))
{
Connections.RemoveAt(i);
}
@@ -756,7 +772,7 @@ namespace Barotrauma
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
}
public void SelectMission(int missionIndex)
public void SelectMission(IEnumerable<int> missionIndices)
{
if (CurrentLocation == null)
{
@@ -765,23 +781,24 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
CurrentLocation.SelectedMissionIndex = missionIndex;
if (CurrentLocation.SelectedMission == null) { return; }
CurrentLocation.SetSelectedMissionIndices(missionIndices);
if (CurrentLocation.SelectedMission.Locations[0] != CurrentLocation ||
CurrentLocation.SelectedMission.Locations[1] != CurrentLocation)
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
{
if (SelectedConnection == null) { return; }
//the destination must be the same as the destination of the mission
if (CurrentLocation.SelectedMission != null &&
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
if (selectedMission.Locations[0] != CurrentLocation ||
selectedMission.Locations[1] != CurrentLocation)
{
CurrentLocation.SelectedMissionIndex = -1;
if (SelectedConnection == null) { return; }
//the destination must be the same as the destination of the mission
if (selectedMission.Locations[1] != SelectedLocation)
{
CurrentLocation.DeselectMission(selectedMission);
}
}
}
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
}
public void SelectRandomLocation(bool preferUndiscovered)
@@ -977,6 +994,12 @@ namespace Barotrauma
string prevName = location.Name;
var newType = LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase));
if (newType == null)
{
DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found.");
return;
}
if (newType.OutpostTeam != location.Type.OutpostTeam ||
newType.HasOutpost != location.Type.HasOutpost)
{
@@ -67,6 +67,8 @@ namespace Barotrauma
[Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
public float ConnectionIndicatorDisplacementMultiplier { get; set; }
public int[] GateCount { get; private set; }
#if CLIENT
[Serialize(0.75f, true), Editable(DecimalCount = 2)]
@@ -201,6 +203,16 @@ namespace Barotrauma
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
GateCount = element.GetAttributeIntArray("gatecount", null) ?? element.GetAttributeIntArray("GateCount", null);
if (GateCount == null)
{
GateCount = new int[DifficultyZones];
for (int i = 0; i < DifficultyZones; i++)
{
GateCount[i] = 1;
}
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -81,6 +81,7 @@ namespace Barotrauma
if (location.Type.HasOutpost && !wasCritical && location.IsCriticallyRadiated())
{
location.ClearMissions();
amountOfOutposts--;
}
}
@@ -243,7 +243,7 @@ namespace Barotrauma
/// </summary>
public int OriginalModuleIndex = -1;
public UInt16 OriginalContainerID;
public int OriginalContainerIndex = -1;
public virtual string Name
{
@@ -280,7 +280,7 @@ namespace Barotrauma
public void ResolveLinks(IdRemap childRemap)
{
if (unresolvedLinkedToID == null) { return; }
for (int i=0;i<unresolvedLinkedToID.Count;i++)
for (int i = 0; i < unresolvedLinkedToID.Count; i++)
{
int srcId = unresolvedLinkedToID[i];
int targetId = childRemap.GetOffsetId(srcId);
@@ -420,7 +420,7 @@ namespace Barotrauma
if (cloneItem == null) { continue; }
var door = cloneItem.GetComponent<Door>();
if (door != null) { door.RefreshLinkedGap(); }
door?.RefreshLinkedGap();
var cloneWire = cloneItem.GetComponent<Wire>();
if (cloneWire == null) continue;
@@ -509,9 +509,9 @@ namespace Barotrauma
mapEntityList.Remove(this);
#if CLIENT
if (selectedList.Contains(this))
if (SelectedList.Contains(this))
{
selectedList = selectedList.FindAll(e => e != this);
SelectedList = SelectedList.Where(e => e != this).ToHashSet();
}
#endif
@@ -649,7 +649,10 @@ namespace Barotrauma
else
{
object newEntity = loadMethod.Invoke(t, new object[] { element, submarine, idRemap });
if (newEntity != null) entities.Add((MapEntity)newEntity);
if (newEntity != null)
{
entities.Add((MapEntity)newEntity);
}
}
}
catch (TargetInvocationException e)
@@ -186,7 +186,8 @@ namespace Barotrauma
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != sub) { continue; }
if (hull.RoomName.Contains("RoomName.", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(hull.RoomName) ||
hull.RoomName.Contains("RoomName.", StringComparison.OrdinalIgnoreCase))
{
hull.RoomName = hull.CreateRoomName();
}
@@ -1436,6 +1437,7 @@ namespace Barotrauma
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.TeamID = CharacterTeamType.FriendlyNPC;
npc.Prefab = humanPrefab;
if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier))
{
outpost.Info.OutpostNPCs.Add(humanPrefab.Identifier, new List<Character>());
@@ -1447,7 +1449,7 @@ namespace Barotrauma
}
else
{
npc.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier);
}
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server);
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
@@ -27,7 +27,7 @@ namespace Barotrauma
public Submarine Submarine => Wall.Submarine;
public Rectangle WorldRect => Submarine == null ? rect :
new Rectangle((int)(rect.X + Submarine.Position.X), (int)(rect.Y + Submarine.Position.Y), rect.Width, rect.Height);
public bool IgnoreByAI => OrderedToBeIgnored;
public bool IgnoreByAI(Character character) => OrderedToBeIgnored && character.IsOnPlayerTeam;
public bool OrderedToBeIgnored { get; set; }
public WallSection(Rectangle rect, Structure wall, float damage = 0.0f)
@@ -53,7 +53,16 @@ namespace Barotrauma
//dimensions of the wall sections' physics bodies (only used for debug rendering)
private readonly List<Vector2> bodyDebugDimensions = new List<Vector2>();
public bool Indestructible;
#if DEBUG
[Serialize(false, true), Editable]
#else
[Serialize(false, true)]
#endif
public bool Indestructible
{
get;
set;
}
//sections of the wall that are supposed to be rendered
public WallSection[] Sections
@@ -353,10 +362,7 @@ namespace Barotrauma
}
#if CLIENT
if (convexHulls!=null)
{
convexHulls.ForEach(x => x.Move(amount));
}
convexHulls?.ForEach(x => x.Move(amount));
#endif
}
@@ -801,7 +807,7 @@ namespace Barotrauma
public void AddDamage(int sectionIndex, float damage, Character attacker = null)
{
if (!Prefab.Body || Prefab.Platform || Indestructible ) { return; }
if (!Prefab.Body || Prefab.Platform || Indestructible) { return; }
if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) { return; }
@@ -875,13 +881,17 @@ namespace Barotrauma
public Vector2 SectionPosition(int sectionIndex, bool world = false)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return Vector2.Zero;
if (sectionIndex < 0 || sectionIndex >= Sections.Length)
{
return Vector2.Zero;
}
if (Prefab.BodyRotation == 0.0f)
{
Vector2 sectionPos = new Vector2(
Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f,
Sections[sectionIndex].rect.Y - Sections[sectionIndex].rect.Height / 2.0f);
if (world && Submarine != null)
{
sectionPos += Submarine.Position;
@@ -900,8 +910,11 @@ namespace Barotrauma
{
diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (rect.Y - rect.Height / 2)) / (float)rect.Height * BodyHeight;
}
if (FlippedX) diffFromCenter = -diffFromCenter;
if (FlippedX)
{
diffFromCenter = -diffFromCenter;
}
Vector2 sectionPos = Position + new Vector2(
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
@@ -250,17 +250,21 @@ namespace Barotrauma
var parentType = element.Parent?.GetAttributeString("prefabtype", "") ?? string.Empty;
string nameIdentifier = element.GetAttributeString("nameidentifier", "");
//only used if the item doesn't have a name/description defined in the currently selected language
string fallbackNameIdentifier = element.GetAttributeString("fallbacknameidentifier", "");
string descriptionIdentifier = element.GetAttributeString("descriptionidentifier", "");
if (string.IsNullOrEmpty(sp.originalName))
{
if (string.IsNullOrEmpty(nameIdentifier))
{
sp.name = TextManager.Get("EntityName." + sp.identifier, true) ?? string.Empty;
sp.name = TextManager.Get("EntityName." + sp.identifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
}
else
{
sp.name = TextManager.Get("EntityName." + nameIdentifier, true) ?? string.Empty;
sp.name = TextManager.Get("EntityName." + nameIdentifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
}
}
@@ -77,6 +77,7 @@ namespace Barotrauma
private static Vector2 lastPickedPosition;
private static float lastPickedFraction;
private static Fixture lastPickedFixture;
private static Vector2 lastPickedNormal;
private Vector2 prevPosition;
@@ -99,6 +100,11 @@ namespace Barotrauma
get { return lastPickedFraction; }
}
public static Fixture LastPickedFixture
{
get { return lastPickedFixture; }
}
public static Vector2 LastPickedNormal
{
get { return lastPickedNormal; }
@@ -368,7 +374,7 @@ namespace Barotrauma
public WreckAI WreckAI { get; private set; }
public bool CreateWreckAI()
{
WreckAI = new WreckAI(this);
WreckAI = WreckAI.Create(this);
return WreckAI != null;
}
@@ -669,6 +675,7 @@ namespace Barotrauma
float closestFraction = 1.0f;
Vector2 closestNormal = Vector2.Zero;
Fixture closestFixture = null;
Body closestBody = null;
if (allowInsideFixture)
{
@@ -682,13 +689,15 @@ namespace Barotrauma
closestFraction = 0.0f;
closestNormal = Vector2.Normalize(rayEnd - rayStart);
if (fixture.Body != null) closestBody = fixture.Body;
closestFixture = fixture;
if (fixture.Body != null) { closestBody = fixture.Body; }
return false;
}, ref aabb);
if (closestFraction <= 0.0f)
{
lastPickedPosition = rayStart;
lastPickedFraction = closestFraction;
lastPickedFixture = closestFixture;
lastPickedNormal = closestNormal;
return closestBody;
}
@@ -702,6 +711,7 @@ namespace Barotrauma
{
closestFraction = fraction;
closestNormal = normal;
closestFixture = fixture;
if (fixture.Body != null) closestBody = fixture.Body;
}
return fraction;
@@ -709,6 +719,7 @@ namespace Barotrauma
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
lastPickedFixture = closestFixture;
lastPickedNormal = closestNormal;
return closestBody;
@@ -752,6 +763,7 @@ namespace Barotrauma
lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction;
lastPickedFraction = fraction;
lastPickedNormal = normal;
lastPickedFixture = fixture;
}
//continue
return -1;
@@ -772,6 +784,7 @@ namespace Barotrauma
lastPickedPosition = rayStart;
lastPickedFraction = 0.0f;
lastPickedNormal = Vector2.Normalize(rayEnd - rayStart);
lastPickedFixture = fixture;
bodies.Add(fixture.Body);
bodyDist[fixture.Body] = 0.0f;
return false;
@@ -828,6 +841,7 @@ namespace Barotrauma
{
Body closestBody = null;
float closestFraction = 1.0f;
Fixture closestFixture = null;
Vector2 closestNormal = Vector2.Zero;
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
@@ -847,6 +861,8 @@ namespace Barotrauma
if (ignoreSubs && fixture.Body.UserData is Submarine) { return -1; }
if (ignoreBranches && fixture.Body.UserData is VineTile) { return -1; }
if (fixture.Body.UserData as string == "ruinroom") { return -1; }
//the hulls have solid fixtures in the submarine's world space collider, ignore them
if (fixture.UserData is Hull) { return -1; }
if (fixture.Body.UserData is Structure structure)
{
if (structure.IsPlatform || structure.StairDirection != Direction.None) { return -1; }
@@ -861,6 +877,7 @@ namespace Barotrauma
{
closestBody = fixture.Body;
closestFraction = fraction;
closestFixture = fixture;
closestNormal = normal;
}
return closestFraction;
@@ -870,6 +887,7 @@ namespace Barotrauma
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
lastPickedFixture = closestFixture;
lastPickedNormal = closestNormal;
return closestBody;
}
@@ -944,19 +962,23 @@ namespace Barotrauma
mapEntity.Move(HiddenSubPosition);
}
foreach (Item item in Item.ItemList)
for (int i = 0; i < 2; i++)
{
if (bodyItems.Contains(item))
foreach (Item item in Item.ItemList)
{
item.Submarine = this;
if (Position == Vector2.Zero) item.Move(-HiddenSubPosition);
//two passes: flip docking ports on the 2nd pass because the doors need to be correctly flipped for the port's orientation to be determined correctly
if ((item.GetComponent<DockingPort>() != null) == (i == 0)) { continue; }
if (bodyItems.Contains(item))
{
item.Submarine = this;
if (Position == Vector2.Zero) { item.Move(-HiddenSubPosition); }
}
else if (item.Submarine != this)
{
continue;
}
item.FlipX(true);
}
else if (item.Submarine != this)
{
continue;
}
item.FlipX(true);
}
Item.UpdateHulls();
@@ -1152,7 +1174,7 @@ namespace Barotrauma
prevPosition = position;
}
public void SetPosition(Vector2 position, List<Submarine> checkd = null)
public void SetPosition(Vector2 position, List<Submarine> checkd = null, bool forceUndockFromStaticSubmarines = true)
{
if (!MathUtils.IsValid(position)) { return; }
@@ -1166,7 +1188,7 @@ namespace Barotrauma
foreach (Submarine dockedSub in DockedTo)
{
if (dockedSub.PhysicsBody.BodyType == BodyType.Static)
if (dockedSub.PhysicsBody.BodyType == BodyType.Static && forceUndockFromStaticSubmarines)
{
if (ConnectedDockingPorts.TryGetValue(dockedSub, out DockingPort port))
{
@@ -1176,7 +1198,7 @@ namespace Barotrauma
}
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
if (expectedLocation == null) { continue; }
dockedSub.SetPosition(position + expectedLocation.Value, checkd);
dockedSub.SetPosition(position + expectedLocation.Value, checkd, forceUndockFromStaticSubmarines);
dockedSub.UpdateTransform(interpolate: false);
}
}
@@ -1238,6 +1260,26 @@ namespace Barotrauma
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
}
public List<(ItemContainer container, int freeSlots)> GetCargoContainers()
{
List<(ItemContainer container, int freeSlots)> containers = new List<(ItemContainer container, int freeSlots)>();
var connectedSubs = GetConnectedSubs();
foreach (Item item in Item.ItemList)
{
if (!connectedSubs.Contains(item.Submarine)) { continue; }
if (!item.HasTag("cargocontainer")) { continue; }
var itemContainer = item.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
int emptySlots = 0;
for (int i = 0; i < itemContainer.Inventory.Capacity; i++)
{
if (itemContainer.Inventory.GetItemAt(i) == null) { emptySlots++; }
}
containers.Add((itemContainer, emptySlots));
}
return containers;
}
public IEnumerable<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
{
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
@@ -1527,6 +1569,8 @@ namespace Barotrauma
Rectangle dimensions = CalculateDimensions();
element.Add(new XAttribute("dimensions", XMLExtensions.Vector2ToString(dimensions.Size.ToVector2())));
var cargoContainers = GetCargoContainers();
element.Add(new XAttribute("cargocapacity", cargoContainers.Sum(c => c.container.Capacity)));
element.Add(new XAttribute("recommendedcrewsizemin", Info.RecommendedCrewSizeMin));
element.Add(new XAttribute("recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? ""));
@@ -1537,6 +1581,41 @@ namespace Barotrauma
Info.OutpostModuleInfo?.Save(element);
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap?.SwappableItem?.ConnectedItemsToSwap == null) { continue; }
foreach (var (requiredTag, swapTo) in item.PendingItemSwap.SwappableItem.ConnectedItemsToSwap)
{
List<Item> itemsToSwap = new List<Item>();
itemsToSwap.AddRange(item.linkedTo.Where(lt => (lt as Item)?.HasTag(requiredTag) ?? false).Cast<Item>());
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null)
{
foreach (Connection c in connectionPanel.Connections)
{
foreach (var connectedComponent in item.GetConnectedComponentsRecursive<ItemComponent>(c))
{
if (!itemsToSwap.Contains(connectedComponent.Item) && connectedComponent.Item.HasTag(requiredTag))
{
itemsToSwap.Add(connectedComponent.Item);
}
}
}
}
ItemPrefab itemPrefab = ItemPrefab.Find("", swapTo);
if (itemPrefab == null)
{
DebugConsole.ThrowError($"Failed to swap an item connected to \"{item.Name}\" into \"{swapTo}\".");
continue;
}
foreach (Item itemToSwap in itemsToSwap)
{
itemToSwap.PurchasedNewSwap = item.PurchasedNewSwap;
if (itemPrefab != itemToSwap.Prefab) { itemToSwap.PendingItemSwap = itemPrefab; }
}
}
}
foreach (MapEntity e in MapEntity.mapEntityList.OrderBy(e => e.ID))
{
if (!e.ShouldBeSaved) { continue; }
@@ -174,6 +174,11 @@ namespace Barotrauma
float simWidth = ConvertUnits.ToSimUnits(width);
float simHeight = ConvertUnits.ToSimUnits(height);
if (sub.FlippedX)
{
simPos.X = -simPos.X;
}
if (width > 0.0f && height > 0.0f)
{
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos));
@@ -317,19 +322,30 @@ namespace Barotrauma
Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height))));
}
if (Position.X < 0)
//hard limit for how far outside the level the sub can go
float maxDist = 200000.0f;
//the force of the current starts to increase exponentially after this point
float exponentialForceIncreaseDist = 150000.0f;
float distance = Position.X < 0 ? Math.Abs(Position.X) : Position.X - Level.Loaded.Size.X;
if (distance > 0)
{
float force = Math.Abs(Position.X * 0.5f);
totalForce += Vector2.UnitX * force;
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
if (distance > maxDist)
{
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f));
if (Position.X < 0)
{
Body.LinearVelocity = new Vector2(Math.Max(0, Body.LinearVelocity.X), Body.LinearVelocity.Y);
}
else
{
Body.LinearVelocity = new Vector2(Math.Min(0, Body.LinearVelocity.X), Body.LinearVelocity.Y);
}
}
}
else
{
float force = (Position.X - Level.Loaded.Size.X) * 0.5f;
totalForce -= Vector2.UnitX * force;
if (distance > exponentialForceIncreaseDist)
{
distance += (float)Math.Pow((distance - exponentialForceIncreaseDist) * 0.01f, 2.0f);
}
float force = distance * 0.5f;
totalForce += (Position.X < 0 ? Vector2.UnitX : -Vector2.UnitX) * force;
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f));
@@ -513,7 +529,7 @@ namespace Barotrauma
{
return CheckCharacterCollision(contact, character);
}
else if (f2.UserData is Items.Components.DockingPort)
else if (f1.UserData is Items.Components.DockingPort || f2.UserData is Items.Components.DockingPort)
{
return false;
}
@@ -823,9 +839,9 @@ namespace Barotrauma
}
#if CLIENT
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
if (Character.Controlled != null && Character.Controlled.Submarine == submarine && Character.Controlled.KnockbackCooldownTimer <= 0.0f)
{
GameMain.GameScreen.Cam.Shake = impact * 10.0f;
GameMain.GameScreen.Cam.Shake = Math.Max(impact * 10.0f, GameMain.GameScreen.Cam.Shake);
if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player))
{
float angularVelocity =
@@ -23,7 +23,7 @@ namespace Barotrauma
HideInMenus = 2
}
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation }
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine }
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
partial class SubmarineInfo : IDisposable
@@ -133,6 +133,12 @@ namespace Barotrauma
private set;
}
public int CargoCapacity
{
get;
private set;
}
public string FilePath
{
get;
@@ -261,6 +267,7 @@ namespace Barotrauma
SubmarineClass = original.SubmarineClass;
hash = !string.IsNullOrEmpty(original.FilePath) ? original.MD5Hash : null;
Dimensions = original.Dimensions;
CargoCapacity = original.CargoCapacity;
FilePath = original.FilePath;
RequiredContentPackages = new HashSet<string>(original.RequiredContentPackages);
IsFileCorrupted = original.IsFileCorrupted;
@@ -323,6 +330,7 @@ namespace Barotrauma
Tags = tags;
}
Dimensions = SubmarineElement.GetAttributeVector2("dimensions", Vector2.Zero);
CargoCapacity = SubmarineElement.GetAttributeInt("cargocapacity", -1);
RecommendedCrewSizeMin = SubmarineElement.GetAttributeInt("recommendedcrewsizemin", 0);
RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0);
RecommendedCrewExperience = SubmarineElement.GetAttributeString("recommendedcrewexperience", "Unknown");
@@ -511,10 +519,14 @@ namespace Barotrauma
//saving/loading ----------------------------------------------------
public bool SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
{
var newElement = new XElement(SubmarineElement.Name,
SubmarineElement.Attributes().Where(a => !string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase) &&
!string.Equals(a.Name.LocalName, "name", StringComparison.InvariantCultureIgnoreCase)),
var newElement = new XElement(
SubmarineElement.Name,
SubmarineElement.Attributes()
.Where(a =>
!string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase) &&
!string.Equals(a.Name.LocalName, "name", StringComparison.InvariantCultureIgnoreCase)),
SubmarineElement.Elements());
if (Type == SubmarineType.OutpostModule)
{
OutpostModuleInfo.Save(newElement);
@@ -523,7 +535,6 @@ namespace Barotrauma
XDocument doc = new XDocument(newElement);
doc.Root.Add(new XAttribute("name", Name));
if (previewImage != null)
{
doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray())));
@@ -574,7 +585,7 @@ namespace Barotrauma
var contentPackageSubs = ContentPackage.GetFilesOfType(
GameMain.Config.AllEnabledPackages,
ContentType.Submarine, ContentType.Outpost, ContentType.OutpostModule,
ContentType.Wreck, ContentType.BeaconStation);
ContentType.Wreck, ContentType.BeaconStation, ContentType.EnemySubmarine);
for (int i = savedSubmarines.Count - 1; i >= 0; i--)
{
@@ -680,8 +691,6 @@ namespace Barotrauma
}
}
static readonly string TempFolder = Path.Combine("Submarine", "Temp");
public static XDocument OpenFile(string file)
{
return OpenFile(file, out _);
@@ -711,7 +720,7 @@ namespace Barotrauma
if (extension == ".sub")
{
System.IO.Stream stream = null;
System.IO.Stream stream;
try
{
stream = SaveUtil.DecompressFiletoStream(file);
@@ -192,17 +192,37 @@ namespace Barotrauma
float minDist = 100.0f;
float heightFromFloor = 110.0f;
float hullMinHeight = 100;
foreach (Hull hull in Hull.hullList)
{
// Ignore hulls that a human couldn't fit in.
// Doesn't take multi-hull rooms into account, but it's probably best to leave them to be setup manually.
if (hull.Rect.Height < hullMinHeight) { continue; }
// Don't create waypoints if there's no floor.
Vector2 floorPos = new Vector2(hull.SimPosition.X, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
Body floor = Submarine.PickBody(hull.SimPosition, floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
// Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor.
Body floor = null;
for (int i = 0; i < 5; i++)
{
float horizontalOffset = 0;
switch (i)
{
case 1:
horizontalOffset = hull.RectWidth * 0.2f;
break;
case 2:
horizontalOffset = hull.RectWidth * 0.4f;
break;
case 3:
horizontalOffset = -hull.RectWidth * 0.2f;
break;
case 4:
horizontalOffset = -hull.RectWidth * 0.4f;
break;
}
horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset);
Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
if (floor != null) { break; }
}
if (floor == null) { continue; }
// Make sure that the waypoints don't go higher than the halfway of the room.
float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
if (hull.Rect.Width < diffFromHullEdge * 3.0f)
{
@@ -223,12 +243,42 @@ namespace Barotrauma
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
}
}
}
}
// Platforms
foreach (Structure platform in Structure.WallList)
{
if (!platform.IsPlatform) { continue; }
float waypointHeight = heightFromFloor;
WayPoint prevWaypoint = null;
for (float x = platform.Rect.X + diffFromHullEdge; x <= platform.Rect.Right - diffFromHullEdge; x += minDist)
{
WayPoint wayPoint = new WayPoint(new Vector2(x, platform.Rect.Y + waypointHeight), SpawnType.Path, submarine);
if (prevWaypoint != null)
{
wayPoint.ConnectTo(prevWaypoint);
}
// If the waypoint is close to hull waypoints, remove it.
if (wayPoint != null)
{
for (int dir = -1; dir <= 1; dir += 2)
{
if (wayPoint.FindClosest(dir, horizontalSearch: true, tolerance: new Vector2(minDist, heightFromFloor), ignored: prevWaypoint.ToEnumerable()) != null)
{
wayPoint.Remove();
wayPoint = null;
break;
}
}
}
prevWaypoint = wayPoint;
}
}
float outSideWaypointInterval = 100.0f;
if (submarine.Info.Type != SubmarineType.OutpostModule)
{
List<WayPoint> outsideWaypoints = new List<WayPoint>();
List<(WayPoint, int)> outsideWaypoints = new List<(WayPoint, int)>();
Rectangle borders = Hull.GetBorders();
int originalWidth = borders.Width;
@@ -260,7 +310,7 @@ namespace Barotrauma
new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition,
SpawnType.Path, submarine);
outsideWaypoints.Add(wayPoint);
outsideWaypoints.Add((wayPoint, i));
if (x == borders.X + outSideWaypointInterval)
{
@@ -284,7 +334,7 @@ namespace Barotrauma
new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition,
SpawnType.Path, submarine);
outsideWaypoints.Add(wayPoint);
outsideWaypoints.Add((wayPoint, i));
if (y == borders.Y - borders.Height)
{
@@ -302,8 +352,9 @@ namespace Barotrauma
Vector2 center = ConvertUnits.ToSimUnits(submarine.HiddenSubPosition);
float halfHeight = ConvertUnits.ToSimUnits(borders.Height / 2);
// Try to move the waypoints so that they are near the walls, roughly following the shape of the sub.
foreach (WayPoint wp in outsideWaypoints)
foreach (var wayPoint in outsideWaypoints)
{
WayPoint wp = wayPoint.Item1;
float xDiff = center.X - wp.SimPosition.X;
Vector2 targetPos = new Vector2(center.X - xDiff * 0.5f, center.Y);
Body wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine));
@@ -331,8 +382,9 @@ namespace Barotrauma
var removals = new List<WayPoint>();
WayPoint previous = null;
float tooClose = outSideWaypointInterval / 2;
foreach (WayPoint wp in outsideWaypoints)
foreach (var wayPoint in outsideWaypoints)
{
WayPoint wp = wayPoint.Item1;
if (wp.CurrentHull != null ||
Submarine.PickBody(wp.SimPosition, wp.SimPosition + Vector2.Normalize(center - wp.SimPosition) * 0.1f, collisionCategory: Physics.CollisionWall | Physics.CollisionItem, customPredicate: f => !(f.Body.UserData is Submarine), allowInsideFixture: true) != null)
{
@@ -341,8 +393,9 @@ namespace Barotrauma
previous = wp;
continue;
}
foreach (WayPoint otherWp in outsideWaypoints)
foreach (var otherWayPoint in outsideWaypoints)
{
WayPoint otherWp = otherWayPoint.Item1;
if (otherWp == wp) { continue; }
if (removals.Contains(otherWp)) { continue; }
float sqrDist = Vector2.DistanceSquared(wp.Position, otherWp.Position);
@@ -356,13 +409,12 @@ namespace Barotrauma
}
foreach (WayPoint wp in removals)
{
outsideWaypoints.Remove(wp);
outsideWaypoints.RemoveAll(w => w.Item1 == wp);
wp.Remove();
}
// Connect loose ends (TODO: this sometimes fails, creating the connection to a wrong node)
for (int i = 0; i < outsideWaypoints.Count; i++)
{
WayPoint current = outsideWaypoints[i];
WayPoint current = outsideWaypoints[i].Item1;
if (current.linkedTo.Count > 1) { continue; }
WayPoint next = null;
int maxConnections = 2;
@@ -371,19 +423,12 @@ namespace Barotrauma
{
if (current.linkedTo.Count >= maxConnections) { break; }
tooFar /= current.linkedTo.Count;
// First try to find a loose end
next = current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp != next && wp.linkedTo.None(e => current.linkedTo.Contains(e)) && wp.linkedTo.Count < 2);
// Then accept any connection that not connected to the existing connection
next ??= current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp != next && wp.linkedTo.None(e => current.linkedTo.Contains(e)));
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)
{
current.ConnectTo(next);
}
}
if (current.linkedTo.Count == 1)
{
DebugConsole.ThrowError($"Couldn't automatically link waypoint {current.ID}. You should do it manually.");
}
}
}
@@ -532,7 +577,7 @@ namespace Barotrauma
}
else
{
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 70), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints);
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints);
}
if (closest == null) { continue; }
ladderPoint.ConnectTo(closest);
@@ -601,13 +646,20 @@ namespace Barotrauma
}
}
var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any());
var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && w.linkedTo.None());
foreach (WayPoint wp in orphans)
{
wp.Remove();
}
foreach (WayPoint wp in WayPointList)
{
if (wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2)
{
DebugConsole.ThrowError($"Couldn't automatically link the waypoint {wp.ID} outside of the submarine. You should do it manually. The waypoint ID is shown in red color.");
}
}
//re-disable the bodies of the doors that are supposed to be open
foreach (Door door in openDoors)
{
@@ -617,18 +669,20 @@ namespace Barotrauma
return true;
}
private WayPoint FindClosestOutside(IEnumerable<WayPoint> waypointList, float tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<WayPoint, bool> filter = null)
private WayPoint FindClosestOutside(IEnumerable<(WayPoint, int)> waypointList, float tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<(WayPoint, int), bool> filter = null)
{
float closestDist = 0;
WayPoint closest = null;
foreach (WayPoint wp in waypointList)
foreach (var wayPoint in waypointList)
{
WayPoint wp = wayPoint.Item1;
if (wp.SpawnType != SpawnType.Path || wp == this) { continue; }
// Ignore if already linked
if (linkedTo.Contains(wp)) { continue; }
if (ignored != null && ignored.Contains(wp)) { continue; }
if (filter != null && !filter(wp)) { continue; }
if (filter != null && !filter(wayPoint)) { continue; }
float sqrDist = Vector2.DistanceSquared(Position, wp.Position);
if (sqrDist > tolerance * tolerance) { continue; }
if (closest == null || sqrDist < closestDist)
{
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false);
@@ -712,14 +766,14 @@ namespace Barotrauma
if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); }
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
{
return WayPointList.GetRandom(wp =>
wp.Submarine == sub &&
wp.ParentRuin == ruin &&
wp.spawnType == spawnType &&
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob.Prefab))
, useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
}
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
@@ -775,7 +829,7 @@ namespace Barotrauma
{
if (assignedWayPoints[i] == null)
{
DebugConsole.ThrowError("Couldn't find a waypoint for " + crew[i].Name + "!");
DebugConsole.AddWarning("Couldn't find a waypoint for " + crew[i].Name + "!");
assignedWayPoints[i] = WayPointList[0];
}
}