Unstable 1.2.1.0

This commit is contained in:
Markus Isberg
2023-11-10 17:45:19 +02:00
parent 2ea58c58a7
commit 8a2e2ea0ae
268 changed files with 4076 additions and 1843 deletions
@@ -9,7 +9,8 @@ namespace Barotrauma
public bool CausedByPsychosis;
public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) : base(worldPosition, spawningHull, isNetworkMessage)
public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) :
base(worldPosition, spawningHull, sourceCharacter: null, isNetworkMessage: isNetworkMessage)
{
this.maxSize = maxSize;
DamagesItems = false;
@@ -130,10 +130,17 @@ namespace Barotrauma
/// <summary>
/// When set to true, the explosion don't deal less damage when the target is behind a solid object.
/// </summary>
public bool IgnoreCover
{
get; set;
}
public bool IgnoreCover { get; set; }
/// <summary>
/// Does the damage from the explosion decrease with distance from the origin of the explosion?
/// </summary>
public bool DistanceFalloff { get; set; } = true;
/// <summary>
/// Structures that don't count as "cover" that reduces damage from the explosion. Only relevant if IgnoreCover is set to false.
/// </summary>
public IEnumerable<Structure> IgnoredCover;
/// <summary>
/// How long the light source created by the explosion lasts.
@@ -311,12 +318,15 @@ 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, IgnoredSubmarines, Attack.EmitStructureDamageParticles);
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker,
IgnoredSubmarines,
Attack.EmitStructureDamageParticles,
DistanceFalloff);
}
if (BallastFloraDamage > 0.0f)
{
RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker);
RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker, DistanceFalloff);
}
if (EmpStrength > 0.0f)
@@ -326,7 +336,7 @@ namespace Barotrauma
{
float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
if (distSqr > displayRangeSqr) { continue; }
float distFactor = CalculateDistanceFactor(distSqr, displayRange);
float distFactor = DistanceFalloff ? CalculateDistanceFactor(distSqr, displayRange) : 1.0f;
//damage repairable power-consuming items
var powered = item.GetComponent<Powered>();
@@ -362,7 +372,10 @@ namespace Barotrauma
float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
if (distSqr > displayRangeSqr) { continue; }
float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange;
float distFactor =
DistanceFalloff ?
1.0f - (float)Math.Sqrt(distSqr) / displayRange :
1.0f;
//repair repairable items
if (item.Repairables.Any())
{
@@ -415,13 +428,16 @@ namespace Barotrauma
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
{
float distFactor = 1.0f - dist / displayRange;
float distFactor =
DistanceFalloff ?
1.0f - dist / displayRange :
1.0f;
float damageAmount = Attack.GetItemDamage(1.0f, item.Prefab.ExplosionDamageMultiplier);
Vector2 explosionPos = worldPosition;
if (item.Submarine != null) { explosionPos -= item.Submarine.Position; }
damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition);
damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition, IgnoredCover);
item.Condition -= damageAmount * distFactor;
}
}
@@ -482,12 +498,15 @@ namespace Barotrauma
if (dist > attack.Range) { continue; }
float distFactor = 1.0f - dist / attack.Range;
float distFactor =
DistanceFalloff ?
1.0f - dist / attack.Range :
1.0f;
//solid obstacles between the explosion and the limb reduce the effect of the explosion
if (!IgnoreCover)
{
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition, IgnoredCover);
}
if (distFactor > 0)
{
@@ -602,7 +621,8 @@ 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, IEnumerable<Submarine> ignoredSubmarines = null, bool emitWallDamageParticles = true)
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable<Submarine> ignoredSubmarines = null,
bool emitWallDamageParticles = true, bool distanceFalloff = true)
{
float dist = 600.0f;
damagedStructures.Clear();
@@ -616,7 +636,10 @@ namespace Barotrauma
{
for (int i = 0; i < structure.SectionCount; i++)
{
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
float distFactor =
distanceFalloff ?
1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange) :
1.0f;
if (distFactor <= 0.0f) { continue; }
structure.AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles);
@@ -680,7 +703,7 @@ namespace Barotrauma
return damagedStructures;
}
public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null, bool distanceFalloff = true)
{
List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>();
@@ -698,7 +721,10 @@ namespace Barotrauma
float branchDist = Vector2.Distance(branchWorldPos, worldPosition);
if (branchDist < worldRange)
{
float distFactor = 1.0f - (branchDist / worldRange);
float distFactor =
distanceFalloff ?
1.0f - (branchDist / worldRange) :
1.0f;
if (distFactor <= 0.0f) { return; }
Vector2 explosionPos = worldPosition;
@@ -715,7 +741,7 @@ namespace Barotrauma
}
}
private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos)
private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos, IEnumerable<Structure> ignoredCover = null)
{
float damageMultiplier = 1.0f;
var obstacles = Submarine.PickBodies(targetSimPos, explosionSimPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
@@ -728,6 +754,10 @@ namespace Barotrauma
}
else if (body.UserData is Structure structure)
{
if (ignoredCover != null)
{
if (ignoredCover.Contains(structure)) { continue; }
}
int sectionIndex = structure.FindSectionIndex(explosionWorldPos, world: true, clamp: true);
if (structure.SectionBodyDisabled(sectionIndex))
{
@@ -91,7 +91,12 @@ namespace Barotrauma
get { return hull; }
}
public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false)
/// <summary>
/// Which character caused this fire (if any)?
/// </summary>
public readonly Character SourceCharacter;
public FireSource(Vector2 worldPosition, Hull spawningHull = null, Character sourceCharacter = null, bool isNetworkMessage = false)
{
hull = Hull.FindHull(worldPosition, spawningHull);
if (hull == null || worldPosition.Y < hull.WorldSurface) { return; }
@@ -109,6 +114,8 @@ namespace Barotrauma
position -= Submarine.Position;
}
SourceCharacter = sourceCharacter;
#if CLIENT
lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull?.Submarine);
#endif
@@ -306,8 +313,12 @@ namespace Barotrauma
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
c.LastDamageSource = null;
c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(), 0.0f, false, 0.0f);
c.LastDamageSource = SourceCharacter;
c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(),
stun: 0.0f,
playSound: false,
attackImpulse: Vector2.Zero,
attacker: SourceCharacter);
}
#if CLIENT
//let clients display the client-side damage immediately, otherwise they may not be able to react to the damage fast enough
@@ -109,6 +109,8 @@ namespace Barotrauma
public float Size => IsHorizontal ? Rect.Height : Rect.Width;
public float PressureDistributionSpeed => Size / 100.0f * open;
private Door connectedDoor;
public Door ConnectedDoor
{
@@ -427,11 +429,9 @@ namespace Barotrauma
if (hull1.WaterVolume <= 0.0 && hull2.WaterVolume <= 0.0) { return; }
float size = IsHorizontal ? rect.Height : rect.Width;
//a variable affecting the water flow through the gap
//the larger the gap is, the faster the water flows
float sizeModifier = size / 100.0f * open;
float sizeModifier = Size / 100.0f * open;
//horizontal gap (such as a regular door)
if (IsHorizontal)
@@ -440,7 +440,7 @@ namespace Barotrauma
float delta = 0.0f;
//water level is above the lower boundary of the gap
if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - size)
if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - Size)
{
int dir = (hull1.Pressure > hull2.Pressure + subOffset.Y) ? 1 : -1;
@@ -569,27 +569,35 @@ namespace Barotrauma
if (open > 0.0f)
{
if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress && hull2.WaterVolume > hull2.Volume / Hull.MaxCompress)
if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress &&
hull2.WaterVolume > hull2.Volume / Hull.MaxCompress)
{
//both hulls full -> distribute pressure
float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f;
hull1.LethalPressure = avgLethality;
hull2.LethalPressure = avgLethality;
changePressure(hull1, avgLethality, PressureDistributionSpeed, deltaTime);
changePressure(hull2, avgLethality, PressureDistributionSpeed, deltaTime);
static void changePressure(Hull hull, float target, float speed, float deltaTime)
{
float diff = target - hull.LethalPressure;
float maxChange = Hull.PressureBuildUpSpeed * speed * deltaTime;
hull.LethalPressure += MathHelper.Clamp(diff, -maxChange, maxChange);
}
}
else
{
hull1.LethalPressure -= Hull.PressureDropSpeed * deltaTime;
hull2.LethalPressure -= Hull.PressureDropSpeed * deltaTime;
//either hull not full -> pressure drops
hull1.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime;
hull2.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime;
}
}
}
void UpdateRoomToOut(float deltaTime, Hull hull1)
{
float size = IsHorizontal ? rect.Height : rect.Width;
//a variable affecting the water flow through the gap
//the larger the gap is, the faster the water flows
float sizeModifier = size * open * open;
float sizeModifier = Size * open * open;
float delta = 500.0f * sizeModifier * deltaTime;
@@ -642,7 +650,7 @@ namespace Barotrauma
}
else
{
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime;
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime;
}
}
else
@@ -657,7 +665,7 @@ namespace Barotrauma
}
if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress)
{
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime;
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime;
}
}
}
@@ -1016,7 +1016,11 @@ namespace Barotrauma
if (waterVolume < Volume)
{
LethalPressure -= PressureDropSpeed * deltaTime;
//pressure drop speed is inversely proportionate to water percentage
//= pressure drops very fast if the hull is nowhere near full
float waterVolumeFactor = Math.Max((100.0f - WaterPercentage) / 10.0f, 1.0f);
LethalPressure -=
PressureDropSpeed * waterVolumeFactor * deltaTime;
if (WaterVolume <= 0.0f)
{
#if CLIENT
@@ -9,7 +9,7 @@ namespace Barotrauma
Vector2 WorldPosition { get; }
float Health { get; }
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true);
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true);
public readonly struct AttackEventData
@@ -89,7 +89,7 @@ namespace Barotrauma
partial void AddDamageProjSpecific(float damage, Vector2 worldPosition);
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true)
{
AddDamage(attack.StructureDamage, worldPosition);
return new AttackResult(attack.StructureDamage);
@@ -115,6 +115,20 @@ namespace Barotrauma
Ruin = null;
Cave = cave;
}
/// <summary>
/// Caves, ruins, outposts and similar enclosed areas
/// </summary>
/// <returns></returns>
public bool IsEnclosedArea()
{
return
PositionType == PositionType.Cave ||
PositionType == PositionType.Ruin ||
PositionType == PositionType.Outpost ||
PositionType == PositionType.BeaconStation ||
PositionType == PositionType.AbyssCave;
}
}
public enum TunnelType
@@ -930,6 +944,7 @@ namespace Barotrauma
foreach (AbyssIsland abyssIsland in AbyssIslands)
{
abyssIsland.Cells.RemoveAll(c => c.CellType == CellType.Path);
cells.AddRange(abyssIsland.Cells);
}
@@ -1726,7 +1741,9 @@ namespace Barotrauma
{
bool tooClose = false;
if (cell.IsPointInsideAABB(position, margin: minDistance))
//if the cell is very large, the position can be far away from the edges while being inside the cell
//so we need to check that here too
if (cell.IsPointInside(position))
{
tooClose = true;
}
@@ -3257,13 +3274,13 @@ namespace Barotrauma
}
Vector2 position = Vector2.Zero;
int tries = 0;
do
{
TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out Vector2 startPos, filter);
TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out InterestingPosition potentialPos, filter);
Vector2 offset = Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.ServerAndClient), Rand.RandSync.ServerAndClient);
Vector2 startPos = potentialPos.Position.ToVector2();
if (!IsPositionInsideWall(startPos + offset))
{
startPos += offset;
@@ -3271,14 +3288,18 @@ namespace Barotrauma
Vector2 endPos = startPos - Vector2.UnitY * Size.Y;
if (Submarine.PickBody(
ConvertUnits.ToSimUnits(startPos),
ConvertUnits.ToSimUnits(endPos),
ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)),
Physics.CollisionLevel | Physics.CollisionWall) != null)
//try to find a level wall below the position unless the position is indoors
if (!potentialPos.IsEnclosedArea())
{
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall;
break;
if (Submarine.PickBody(
ConvertUnits.ToSimUnits(startPos),
ConvertUnits.ToSimUnits(endPos),
ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)),
Physics.CollisionLevel | Physics.CollisionWall)?.UserData is VoronoiCell)
{
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall;
break;
}
}
tries++;
@@ -3293,25 +3314,25 @@ namespace Barotrauma
return position;
}
public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Vector2 awayPoint, float minDistFromPoint, Func<InterestingPosition, bool> filter = null)
public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Vector2 awayPoint, float minDistFromPoint, Func<InterestingPosition, bool> filter = null)
{
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, awayPoint, minDistFromPoint, filter);
position = pos.ToVector2();
position = default;
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out position, awayPoint, minDistFromPoint, filter);
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
{
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning);
position = pos.ToVector2();
position = default;
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out position, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning);
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
{
if (!PositionsOfInterest.Any())
{
position = new Point(Size.X / 2, Size.Y / 2);
position = default;
return false;
}
@@ -3323,7 +3344,20 @@ namespace Barotrauma
if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath) || positionType.HasFlag(PositionType.Abyss) ||
positionType.HasFlag(PositionType.Cave) || positionType.HasFlag(PositionType.AbyssCave))
{
suitablePositions.RemoveAll(p => IsPositionInsideWall(p.Position.ToVector2()));
#if DEBUG
for (int i = 0; i < PositionsOfInterest.Count; i++)
{
var pos = PositionsOfInterest[i];
if (!suitablePositions.Contains(pos)) { continue; }
if (IsInvalid(pos))
{
pos.IsValid = false;
PositionsOfInterest[i] = pos;
}
}
#endif
suitablePositions.RemoveAll(p => IsInvalid(p));
bool IsInvalid(InterestingPosition p) => IsPositionInsideWall(p.Position.ToVector2());
}
if (!suitablePositions.Any())
{
@@ -3335,7 +3369,7 @@ namespace Barotrauma
DebugConsole.ThrowError(errorMsg);
#endif
}
position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))].Position;
position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))];
return false;
}
@@ -3361,14 +3395,14 @@ namespace Barotrauma
DebugConsole.ThrowError(errorMsg);
#endif
float maxDist = 0.0f;
position = suitablePositions.First().Position;
position = suitablePositions.First();
foreach (InterestingPosition pos in suitablePositions)
{
float dist = Submarine.Loaded.Sum(s =>
Submarine.MainSubs.Contains(s) ? Vector2.DistanceSquared(s.WorldPosition, pos.Position.ToVector2()) : 0.0f);
if (dist > maxDist)
{
position = pos.Position;
position = pos;
maxDist = dist;
}
}
@@ -3376,7 +3410,7 @@ namespace Barotrauma
return false;
}
position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced)].Position;
position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced)];
return true;
}
@@ -3933,12 +3967,28 @@ namespace Barotrauma
{
var totalSW = new Stopwatch();
totalSW.Start();
var wreckFiles = ContentPackageManager.EnabledPackages.All
.SelectMany(p => p.GetFiles<WreckFile>())
.OrderBy(f => f.UintIdentifier).ToList();
for (int i = wreckFiles.Count - 1; i >= 0; i--)
{
var wreckFile = wreckFiles[i];
var wreckInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsWreck);
var matchingInfo = wreckInfos.SingleOrDefault(info => info.FilePath == wreckFile.Path.Value);
Debug.Assert(matchingInfo != null);
if (matchingInfo?.WreckInfo is WreckInfo wreckInfo)
{
if (Difficulty < wreckInfo.MinLevelDifficulty || Difficulty > wreckInfo.MaxLevelDifficulty)
{
wreckFiles.RemoveAt(i);
}
}
}
if (wreckFiles.None())
{
DebugConsole.ThrowError("No wreck files found in the selected content packages!");
DebugConsole.ThrowError($"No wreck files found for the level difficulty {LevelData.Difficulty}!");
Wrecks = new List<Submarine>();
return;
}
@@ -4235,7 +4285,8 @@ namespace Barotrauma
ContentFile contentFile = null;
if (!string.IsNullOrEmpty(GenerationParams.ForceBeaconStation))
{
contentFile = beaconStationFiles.OrderBy(b => b.UintIdentifier).FirstOrDefault(f => f.Path == GenerationParams.ForceBeaconStation);
var contentPath = ContentPath.FromRaw(GenerationParams.ContentPackage, GenerationParams.ForceBeaconStation);
contentFile = beaconStationFiles.OrderBy(b => b.UintIdentifier).FirstOrDefault(f => f.Path == contentPath);
if (contentFile == null)
{
DebugConsole.ThrowError($"Failed to find the beacon station \"{GenerationParams.ForceBeaconStation}\". Using a random one instead...");
@@ -160,7 +160,7 @@ namespace Barotrauma
partial void InitProjSpecific();
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true)
{
if (Health <= 0.0f) { return new AttackResult(0.0f); }
@@ -526,12 +526,13 @@ namespace Barotrauma
{
List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
bool requireCaveSpawnPos = spawnPosType == LevelObjectPrefab.SpawnPosType.CaveWall;
foreach (var cell in cells)
{
foreach (var edge in cell.Edges)
{
if (!edge.IsSolid || edge.OutsideLevel) { continue; }
if (spawnPosType != LevelObjectPrefab.SpawnPosType.CaveWall && edge.NextToCave) { continue; }
if (requireCaveSpawnPos != edge.NextToCave) { continue; }
Vector2 normal = edge.GetNormal(cell);
Alignment edgeAlignment = 0;
@@ -639,7 +639,7 @@ namespace Barotrauma
System.Diagnostics.Debug.Assert(Type != null, $"Could not find the location type \"{locationTypeId}\"!");
Type ??= LocationType.Prefabs.First();
LevelData = new LevelData(element.Element("Level"), clampDifficultyToBiome: true);
LevelData = new LevelData(element.GetChildElement("Level"), clampDifficultyToBiome: true);
PortraitId = ToolBox.StringToInt(Name);
@@ -659,15 +659,12 @@ namespace Barotrauma
if (type == null)
{
DebugConsole.AddWarning($"Could not find location type \"{identifier}\". Using location type \"None\" instead.");
LocationType.Prefabs.TryGet("None".ToIdentifier(), out type);
if (type == null)
{
type = LocationType.Prefabs.First();
}
LocationType.Prefabs.TryGet("None".ToIdentifier(), out type);
type ??= LocationType.Prefabs.First();
}
if (type != null)
{
element.SetAttributeValue("type", type.Identifier);
element.SetAttributeValue("type", type.Identifier.ToString());
}
return false;
}
@@ -776,11 +773,11 @@ namespace Barotrauma
{
if (Type.MissionIdentifiers.Any())
{
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync));
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync), invokingContentPackage: Type.ContentPackage);
}
if (Type.MissionTags.Any())
{
UnlockMissionByTag(Type.MissionTags.GetRandom(randSync));
UnlockMissionByTag(Type.MissionTags.GetRandom(randSync), invokingContentPackage: Type.ContentPackage);
}
}
@@ -798,7 +795,7 @@ namespace Barotrauma
AddMission(InstantiateMission(missionPrefab));
}
public Mission UnlockMissionByIdentifier(Identifier identifier)
public Mission UnlockMissionByIdentifier(Identifier identifier, ContentPackage invokingContentPackage = null)
{
if (AvailableMissions.Any(m => m.Prefab.Identifier == identifier)) { return null; }
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
@@ -806,7 +803,8 @@ namespace Barotrauma
var missionPrefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == identifier);
if (missionPrefab == null)
{
DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found.");
DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found.",
contentPackage: invokingContentPackage);
}
else
{
@@ -823,13 +821,13 @@ namespace Barotrauma
return null;
}
public Mission UnlockMissionByTag(Identifier tag, Random random = null)
public Mission UnlockMissionByTag(Identifier tag, Random random = null, ContentPackage invokingContentPackage = null)
{
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Contains(tag));
if (matchingMissions.None())
{
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.");
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.", contentPackage: invokingContentPackage);
}
else
{
@@ -841,7 +839,16 @@ namespace Barotrauma
{
suitableMissions = unusedMissions;
}
var filteredMissions = suitableMissions.Where(m => LevelData.Difficulty >= m.MinLevelDifficulty && LevelData.Difficulty <= m.MaxLevelDifficulty);
if (filteredMissions.None())
{
DebugConsole.AddWarning($"No suitable mission matching the level difficulty {LevelData.Difficulty} found with the tag \"{tag}\". Ignoring the restriction.",
contentPackage: invokingContentPackage);
}
else
{
suitableMissions = filteredMissions;
}
MissionPrefab missionPrefab =
random != null ?
ToolBox.SelectWeightedRandom(suitableMissions.OrderBy(m => m.Identifier), m => m.Commonness, random) :
@@ -854,12 +861,13 @@ namespace Barotrauma
return null;
}
AddMission(mission);
DebugConsole.NewMessage($"Unlocked a random mission by \"{tag}\".", debugOnly: true);
DebugConsole.NewMessage($"Unlocked a random mission by \"{tag}\": {mission.Prefab.Identifier} (difficulty level: {LevelData.Difficulty})", debugOnly: true);
return mission;
}
else
{
DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked.");
DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked.",
contentPackage: invokingContentPackage);
}
}
@@ -988,11 +996,11 @@ namespace Barotrauma
{
if (addInitialMissionsForType.MissionIdentifiers.Any())
{
UnlockMissionByIdentifier(addInitialMissionsForType.MissionIdentifiers.GetRandomUnsynced());
UnlockMissionByIdentifier(addInitialMissionsForType.MissionIdentifiers.GetRandomUnsynced(), invokingContentPackage: Type.ContentPackage);
}
if (addInitialMissionsForType.MissionTags.Any())
{
UnlockMissionByTag(addInitialMissionsForType.MissionTags.GetRandomUnsynced());
UnlockMissionByTag(addInitialMissionsForType.MissionTags.GetRandomUnsynced(), invokingContentPackage: Type.ContentPackage);
}
addInitialMissionsForType = null;
}
@@ -55,7 +55,7 @@ namespace Barotrauma
/// </summary>
public readonly bool RequireHuntingGrounds;
public Requirement(XElement element, LocationTypeChange change)
public Requirement(ContentXElement element, LocationTypeChange change)
{
RequiredLocations = element.GetAttributeIdentifierArray("requiredlocations", element.GetAttributeIdentifierArray("requiredadjacentlocations", Array.Empty<Identifier>())).ToImmutableArray();
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 0);
@@ -80,13 +80,15 @@ namespace Barotrauma
{
DebugConsole.AddWarning(
$"Invalid location type change in location type \"{change.CurrentType}\". " +
"Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.");
"Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.",
element.ContentPackage);
}
if (Probability >= 1.0f)
{
DebugConsole.AddWarning(
$"Invalid location type change in location type \"{change.CurrentType}\". " +
"Probability is configured to increase when near some other type of location, but the base probability is already 100%");
"Probability is configured to increase when near some other type of location, but the base probability is already 100%",
element.ContentPackage);
}
}
}
@@ -173,7 +175,7 @@ namespace Barotrauma
public readonly Point RequiredDurationRange;
public LocationTypeChange(Identifier currentType, XElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
public LocationTypeChange(Identifier currentType, ContentXElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
{
CurrentType = currentType;
ChangeToType = element.GetAttributeIdentifier("type", element.GetAttributeIdentifier("to", ""));
@@ -190,13 +192,13 @@ namespace Barotrauma
CooldownAfterChange = Math.Max(element.GetAttributeInt("cooldownafterchange", 0), 0);
//backwards compatibility
if (element.Attribute("requiredlocations") != null)
if (element.GetAttribute("requiredlocations") != null)
{
Requirements.Add(new Requirement(element, this));
}
//backwards compatibility
if (element.Attribute("requiredduration") != null)
if (element.GetAttribute("requiredduration") != null)
{
RequiredDurationRange = new Point(element.GetAttributeInt("requiredduration", 0));
}
@@ -3,13 +3,11 @@ using System.Xml.Linq;
namespace Barotrauma
{
class BeaconStationInfo : ISerializableEntity
abstract class ExtraSubmarineInfo : ISerializableEntity
{
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool AllowDamagedWalls { get; set; }
public string Name { get; protected set; }
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool AllowDisconnectedWires { get; set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; protected set; }
[Serialize(0.0f, IsPropertySaveable.Yes), Editable]
public float MinLevelDifficulty { get; set; }
@@ -17,26 +15,19 @@ namespace Barotrauma
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
public float MaxLevelDifficulty { get; set; }
[Serialize(Level.PlacementType.Bottom, IsPropertySaveable.Yes), Editable]
public Level.PlacementType Placement { get; set; }
public string Name { get; private set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element)
public ExtraSubmarineInfo(SubmarineInfo submarineInfo, XElement element)
{
Name = $"BeaconStationInfo ({submarineInfo.Name})";
Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})";
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
}
public BeaconStationInfo(SubmarineInfo submarineInfo)
public ExtraSubmarineInfo(SubmarineInfo submarineInfo)
{
Name = $"BeaconStationInfo ({submarineInfo.Name})";
Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})";
SerializableProperties = SerializableProperty.DeserializeProperties(this);
}
public BeaconStationInfo(BeaconStationInfo original)
public ExtraSubmarineInfo(ExtraSubmarineInfo original)
{
Name = original.Name;
SerializableProperties = new Dictionary<Identifier, SerializableProperty>();
@@ -55,4 +46,43 @@ namespace Barotrauma
SerializableProperty.SerializeProperties(this, element);
}
}
class BeaconStationInfo : ExtraSubmarineInfo
{
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool AllowDamagedWalls { get; set; }
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool AllowDisconnectedWires { get; set; }
[Serialize(Level.PlacementType.Bottom, IsPropertySaveable.Yes), Editable]
public Level.PlacementType Placement { get; set; }
public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element)
{
Name = $"{nameof(BeaconStationInfo)} ({submarineInfo.Name})";
}
public BeaconStationInfo(SubmarineInfo submarineInfo) : base(submarineInfo)
{
Name = $"{nameof(BeaconStationInfo)} ({submarineInfo.Name})";
}
public BeaconStationInfo(BeaconStationInfo original) : base(original) { }
}
class WreckInfo : ExtraSubmarineInfo
{
public WreckInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element)
{
Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})";
}
public WreckInfo(SubmarineInfo submarineInfo) : base(submarineInfo)
{
Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})";
}
public WreckInfo(WreckInfo original) : base(original) { }
}
}
@@ -249,7 +249,7 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.");
DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.", contentPackage: element.ContentPackage);
}
}
@@ -111,7 +111,7 @@ namespace Barotrauma
set;
}
public bool IsHorizontal { get; private set; }
public bool IsHorizontal { get; }
public int SectionCount
{
@@ -240,6 +240,25 @@ namespace Barotrauma
}
}
protected float rotationRad = 0f;
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowRotating, DecimalCount = 3, ForceShowPlusMinusButtons = true, ValueStep = 0.1f), Serialize(0.0f, IsPropertySaveable.Yes)]
public float Rotation
{
get => MathHelper.ToDegrees(rotationRad);
set
{
rotationRad = MathHelper.WrapAngle(MathHelper.ToRadians(value));
if (StairDirection != Direction.None)
{
CreateStairBodies();
}
else if (Prefab.Body)
{
CreateSections();
UpdateSections();
}
}
}
protected Vector2 textureScale = Vector2.One;
@@ -336,9 +355,18 @@ namespace Barotrauma
{
get
{
float rotation = MathHelper.ToRadians(Prefab.BodyRotation);
if (FlippedX) rotation = -MathHelper.Pi - rotation;
if (FlippedY) rotation = -rotation;
float rotation = MathHelper.ToRadians(Prefab.BodyRotation) + this.rotationRad;
if (IsHorizontal)
{
if (FlippedX) { rotation = -MathHelper.Pi - rotation; }
if (FlippedY) { rotation = -rotation; }
}
else
{
if (FlippedX) { rotation = -rotation; }
if (FlippedY) { rotation = -MathHelper.Pi -rotation; }
}
rotation = MathHelper.WrapAngle(rotation);
return rotation;
}
}
@@ -350,6 +378,10 @@ namespace Barotrauma
get
{
Vector2 bodyOffset = Prefab.BodyOffset;
if (rotationRad != 0f)
{
bodyOffset = MathUtils.RotatePoint(bodyOffset, -rotationRad);
}
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
return bodyOffset;
@@ -567,9 +599,14 @@ namespace Barotrauma
Body newBody = GameMain.World.CreateRectangle(bodyWidth, bodyHeight, 1.5f);
var rotationWithFlip = FlippedX ^ FlippedY ? -rotationRad : rotationRad;
newBody.BodyType = BodyType.Static;
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + stairHeight / 2.0f);
newBody.Rotation = (StairDirection == Direction.Right) ? stairAngle : -stairAngle;
Vector2 stairRectHeightDiff = new Vector2(0f, stairHeight / 2.0f - rect.Height / 2.0f);
stairRectHeightDiff = MathUtils.RotatePoint(stairRectHeightDiff, -rotationWithFlip);
if (FlippedY) { stairRectHeightDiff = -stairRectHeightDiff; }
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height / 2.0f) + stairRectHeightDiff;
newBody.Rotation = ((StairDirection == Direction.Right) ? stairAngle : -stairAngle) - rotationWithFlip;
newBody.CollisionCategories = Physics.CollisionStairs;
newBody.Friction = 0.8f;
newBody.UserData = this;
@@ -696,17 +733,6 @@ namespace Barotrauma
}
}
private static Vector2[] CalculateExtremes(Rectangle sectionRect)
{
Vector2[] corners = new Vector2[4];
corners[0] = new Vector2(sectionRect.X, sectionRect.Y - sectionRect.Height);
corners[1] = new Vector2(sectionRect.X, sectionRect.Y);
corners[2] = new Vector2(sectionRect.Right, sectionRect.Y);
corners[3] = new Vector2(sectionRect.Right, sectionRect.Y - sectionRect.Height);
return corners;
}
/// <summary>
/// Checks if there's a structure items can be attached to at the given position and returns it.
/// </summary>
@@ -727,8 +753,6 @@ namespace Barotrauma
public override bool IsMouseOn(Vector2 position)
{
if (!base.IsMouseOn(position)) { return false; }
if (StairDirection == Direction.None)
{
Vector2 rectSize = rect.Size.ToVector2();
@@ -745,14 +769,19 @@ namespace Barotrauma
}
else
{
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(
position,
WorldRect.Location.ToVector2() + WorldRect.Size.ToVector2().FlipY() * 0.5f,
BodyRotation);
if (!Submarine.RectContains(WorldRect, position)) { return false; }
if (StairDirection == Direction.Left)
{
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), position) < 1600.0f;
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), transformedMousePos) < 1600.0f;
}
else
{
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), position) < 1600.0f;
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), transformedMousePos) < 1600.0f;
}
}
}
@@ -934,11 +963,19 @@ namespace Barotrauma
for (int i = 1; i <= particleAmount; i++)
{
var worldRect = section.WorldRect;
var directionUnitX = MathUtils.RotatedUnitXRadians(BodyRotation);
var directionUnitY = directionUnitX.YX().FlipX();
Vector2 particlePos = new Vector2(
Rand.Range(worldRect.X, worldRect.Right + 1),
Rand.Range(worldRect.Y - worldRect.Height, worldRect.Y + 1));
Rand.Range(0, worldRect.Width + 1),
Rand.Range(-worldRect.Height, 1));
particlePos -= worldRect.Size.ToVector2().FlipY() * 0.5f;
var particle = GameMain.ParticleManager.CreateParticle(Prefab.DamageParticle, particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
var particlePosFinal = SectionPosition(sectionIndex, world: true);
particlePosFinal += particlePos.X * directionUnitX + particlePos.Y * directionUnitY;
var particle = GameMain.ParticleManager.CreateParticle(Prefab.DamageParticle,
position: particlePosFinal,
velocity: Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
if (particle == null) break;
}
}
@@ -960,14 +997,23 @@ namespace Barotrauma
//if the sub has been flipped horizontally, the first section may be smaller than wallSectionSize
//and we need to adjust the position accordingly
if (Sections[0].rect.Width < WallSectionSize)
if (IsHorizontal)
{
displayPos.X += WallSectionSize - Sections[0].rect.Width;
if (Sections[0].rect.Width < WallSectionSize)
{
displayPos += DirectionUnit * (WallSectionSize - Sections[0].rect.Width);
}
}
else
{
if (Sections[0].rect.Height < WallSectionSize)
{
displayPos += DirectionUnit * (WallSectionSize - Sections[0].rect.Height);
}
}
int index = IsHorizontal ?
(int)Math.Floor((displayPos.X - rect.X) / WallSectionSize) :
(int)Math.Floor((rect.Y - displayPos.Y) / WallSectionSize);
var leftmostPos = Position - DirectionUnit * (IsHorizontal ? Rect.Width : Rect.Height) * 0.5f;
int index = (int)Math.Floor(Vector2.Dot(DirectionUnit, displayPos - leftmostPos) / WallSectionSize);
if (clamp)
{
@@ -987,6 +1033,17 @@ namespace Barotrauma
return Sections[sectionIndex].damage;
}
protected Vector2 DirectionUnit
{
get
{
var rotation = IsHorizontal ? -BodyRotation : -MathHelper.PiOver2 - BodyRotation;
if (IsHorizontal && FlippedX) { rotation += MathF.PI; }
if (!IsHorizontal && FlippedY) { rotation += MathF.PI; }
return MathUtils.RotatedUnitXRadians(rotation);
}
}
public Vector2 SectionPosition(int sectionIndex, bool world = false)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length)
@@ -994,7 +1051,7 @@ namespace Barotrauma
return Vector2.Zero;
}
if (Prefab.BodyRotation == 0.0f)
if (MathUtils.NearlyEqual(BodyRotation, 0f))
{
Vector2 sectionPos = new Vector2(
Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f,
@@ -1017,15 +1074,10 @@ namespace Barotrauma
else
{
diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (rect.Y - rect.Height / 2)) / (float)rect.Height * BodyHeight;
}
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;
Vector2 sectionPos = Position + DirectionUnit * diffFromCenter;
if (world && Submarine != null)
{
@@ -1035,13 +1087,22 @@ namespace Barotrauma
}
}
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = false)
{
if (Submarine != null && Submarine.GodMode) { return new AttackResult(0.0f, null); }
if (!Prefab.Body || Prefab.Platform || Indestructible) { return new AttackResult(0.0f, null); }
Vector2 transformedPos = worldPosition;
if (Submarine != null) transformedPos -= Submarine.Position;
if (Submarine != null) { transformedPos -= Submarine.Position; }
if (!MathUtils.NearlyEqual(BodyRotation, 0f))
{
var center = Rect.Location.ToVector2() + Rect.Size.ToVector2().FlipY() * 0.5f;
var rotation = BodyRotation;
if (IsHorizontal && FlippedX) { rotation += MathF.PI; }
if (!IsHorizontal && FlippedY) { rotation += MathF.PI; }
transformedPos = MathUtils.RotatePointAroundTarget(transformedPos, center, rotation);
}
float damageAmount = 0.0f;
for (int i = 0; i < SectionCount; i++)
@@ -1143,6 +1204,7 @@ namespace Barotrauma
gapRect.Y = (gapRect.Y - gapRect.Height / 2) + (int)(BodyHeight / 2 + BodyOffset.Y * scale);
gapRect.Height = (int)BodyHeight;
}
if (FlippedX) { diffFromCenter = -diffFromCenter; }
}
else
{
@@ -1153,8 +1215,8 @@ namespace Barotrauma
gapRect.Width = (int)BodyWidth;
}
if (BodyHeight > 0.0f) { gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); }
if (FlippedY) { diffFromCenter = -diffFromCenter; }
}
if (FlippedX) { diffFromCenter = -diffFromCenter; }
if (Math.Abs(BodyRotation) > 0.01f)
{
@@ -1170,14 +1232,26 @@ namespace Barotrauma
gapRect.Width += 20;
gapRect.Height += 20;
bool horizontalGap = !IsHorizontal;
bool rotatedEnoughToChangeOrientation = (MathUtils.WrapAngleTwoPi(rotationRad - MathHelper.PiOver4) % MathHelper.Pi < MathHelper.PiOver2);
if (rotatedEnoughToChangeOrientation)
{
var center = gapRect.Location + gapRect.Size.FlipY() / new Point(2);
var topLeft = gapRect.Location;
var diff = topLeft - center;
diff = diff.FlipY().YX().FlipY();
var newTopLeft = diff + center;
gapRect = new Rectangle(newTopLeft, gapRect.Size.YX());
}
bool horizontalGap = rotatedEnoughToChangeOrientation
? IsHorizontal
: !IsHorizontal;
bool diagonalGap = false;
if (Prefab.BodyRotation != 0.0f)
if (!MathUtils.NearlyEqual(BodyRotation, 0f))
{
//rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80)
float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2;
//diagonal if 30 < angle < 60
diagonalGap = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3;
diagonalGap = sectorizedRotation is > MathHelper.Pi / 6 and < MathHelper.Pi / 3;
//gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical
if (diagonalGap)
{
@@ -1245,7 +1319,7 @@ namespace Barotrauma
private static void CreateWallDamageExplosion(Gap gap, Character attacker)
{
const float explosionRange = 750.0f;
const float explosionRange = 500.0f;
float explosionStrength = gap.Open;
var linkedHull = gap.linkedTo.FirstOrDefault() as Hull;
@@ -1264,20 +1338,22 @@ namespace Barotrauma
if (explosionOnBroken == null)
{
explosionOnBroken = new Explosion(explosionRange, force: 10.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f);
explosionOnBroken = new Explosion(explosionRange, force: 5.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f);
if (AfflictionPrefab.Prefabs.TryGet("lacerations".ToIdentifier(), out AfflictionPrefab lacerations))
{
explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(3.0f), null);
explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(5.0f), null);
}
else
{
explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(3.0f), null);
explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(5.0f), null);
}
explosionOnBroken.IgnoreCover = true;
explosionOnBroken.IgnoreCover = false;
explosionOnBroken.OnlyInside = true;
explosionOnBroken.DistanceFalloff = false;
explosionOnBroken.DisableParticles();
}
explosionOnBroken.IgnoredCover = gap.ConnectedWall?.ToEnumerable();
explosionOnBroken.Attack.Range = explosionRange * gap.Open;
explosionOnBroken.Attack.DamageMultiplier = explosionStrength;
explosionOnBroken.Attack.Stun = MathHelper.Clamp(explosionStrength, 0.5f, 1.0f);
@@ -1335,7 +1411,7 @@ namespace Barotrauma
{
hasHoles = true;
if (!mergedSections.Any()) continue;
if (!mergedSections.Any()) { continue; }
var mergedRect = GenerateMergedRect(mergedSections);
mergedSections.Clear();
CreateRectBody(mergedRect, createConvexHull: true);
@@ -1371,18 +1447,17 @@ namespace Barotrauma
diffFromCenter = (rect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth;
if (BodyWidth > 0.0f) rect.Width = Math.Max((int)Math.Round(BodyWidth * (rect.Width / (float)this.rect.Width)), 1);
if (BodyHeight > 0.0f) rect.Height = (int)BodyHeight;
if (FlippedX) { diffFromCenter = -diffFromCenter; }
}
else
{
diffFromCenter = ((rect.Y - rect.Height / 2) - (this.rect.Y - this.rect.Height / 2)) / (float)this.rect.Height * BodyHeight;
if (BodyWidth > 0.0f) rect.Width = (int)BodyWidth;
if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1);
if (FlippedY) { diffFromCenter = -diffFromCenter; }
}
if (FlippedX) { diffFromCenter = -diffFromCenter; }
Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset) * scale;
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
Vector2 bodyOffset = ConvertUnits.ToSimUnits(BodyOffset) * scale;
Body newBody = GameMain.World.CreateRectangle(
ConvertUnits.ToSimUnits(rect.Width),
@@ -1396,7 +1471,7 @@ namespace Barotrauma
newBody.UserData = this;
Vector2 structureCenter = ConvertUnits.ToSimUnits(Position);
if (BodyRotation != 0.0f)
if (!MathUtils.NearlyEqual(BodyRotation, 0f))
{
Vector2 pos = structureCenter + bodyOffset + new Vector2(
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
@@ -67,6 +67,9 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.No, description: "Can items like signal components be attached on this structure? Should be enabled on structures like decorative background walls.")]
public bool AllowAttachItems { get; private set; }
[Serialize(true, IsPropertySaveable.No, description: "Can the structure be rotated in the submarine editor?")]
public bool AllowRotatingInEditor { get; set; }
[Serialize(0.0f, IsPropertySaveable.No)]
public float MinHealth { get; private set; }
@@ -297,14 +300,16 @@ namespace Barotrauma
if (Identifier == Identifier.Empty)
{
DebugConsole.ThrowError(
"Structure prefab \"" + Name + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.");
"Structure prefab \"" + Name.Value + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.",
contentPackage: ContentPackage);
}
#if DEBUG
if (!Category.HasFlag(MapEntityCategory.Legacy) && !HideInMenus)
{
if (!string.IsNullOrEmpty(OriginalName))
{
DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.");
DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.",
ContentPackage);
}
}
#endif
@@ -624,7 +624,7 @@ namespace Barotrauma
//math/physics stuff ----------------------------------------------------
public static Vector2 VectorToWorldGrid(Vector2 position, bool round = false)
public static Vector2 VectorToWorldGrid(Vector2 position, Submarine sub = null, bool round = false)
{
if (round)
{
@@ -636,6 +636,12 @@ namespace Barotrauma
position.X = MathF.Floor(position.X / GridSize.X) * GridSize.X;
position.Y = MathF.Ceiling(position.Y / GridSize.Y) * GridSize.Y;
}
if (sub != null)
{
position.X += sub.Position.X % GridSize.X;
position.Y += sub.Position.Y % GridSize.Y;
}
return position;
}
@@ -1840,6 +1846,7 @@ namespace Barotrauma
FilePath = filePath,
OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null,
BeaconStationInfo = Info.BeaconStationInfo != null ? new BeaconStationInfo(Info.BeaconStationInfo) : null,
WreckInfo = Info.WreckInfo != null ? new WreckInfo(Info.WreckInfo) : null,
Name = Path.GetFileNameWithoutExtension(filePath)
};
#if CLIENT
@@ -800,7 +800,10 @@ namespace Barotrauma
float damageAmount = contactDot * Body.Mass / limb.character.Mass;
limb.character.LastDamageSource = submarine;
limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb,
AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f);
AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(),
stun: 0.0f,
playSound: true,
attackImpulse: Vector2.Zero);
if (limb.character.IsDead)
{
@@ -122,6 +122,9 @@ namespace Barotrauma
public OutpostModuleInfo OutpostModuleInfo { get; set; }
public BeaconStationInfo BeaconStationInfo { get; set; }
public WreckInfo WreckInfo { get; set; }
public ExtraSubmarineInfo GetExtraSubmarineInfo => BeaconStationInfo ?? WreckInfo as ExtraSubmarineInfo;
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
@@ -320,10 +323,14 @@ namespace Barotrauma
{
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
}
if (original.BeaconStationInfo != null)
else if (original.BeaconStationInfo != null)
{
BeaconStationInfo = new BeaconStationInfo(original.BeaconStationInfo);
}
else if (original.WreckInfo != null)
{
WreckInfo = new WreckInfo(original.WreckInfo);
}
#if CLIENT
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null;
#endif
@@ -410,6 +417,10 @@ namespace Barotrauma
{
BeaconStationInfo = new BeaconStationInfo(this, SubmarineElement);
}
else if (Type == SubmarineType.Wreck)
{
WreckInfo = new WreckInfo(this, SubmarineElement);
}
}
}
@@ -589,6 +600,11 @@ namespace Barotrauma
BeaconStationInfo.Save(newElement);
BeaconStationInfo = new BeaconStationInfo(this, newElement);
}
else if (Type == SubmarineType.Wreck)
{
WreckInfo.Save(newElement);
WreckInfo = new WreckInfo(this, newElement);
}
XDocument doc = new XDocument(newElement);
doc.Root.Add(new XAttribute("name", Name));