Merge remote-tracking branch 'origin/master' into dev

# Conflicts:
#	Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
#	Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
#	Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
#	Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs
#	Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
#	Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs
#	Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
#	Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
#	Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
#	Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
#	Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs
#	Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen/ServerListScreen.cs
#	Barotrauma/BarotraumaClient/LinuxClient.csproj
#	Barotrauma/BarotraumaClient/MacClient.csproj
#	Barotrauma/BarotraumaClient/WindowsClient.csproj
#	Barotrauma/BarotraumaServer/LinuxServer.csproj
#	Barotrauma/BarotraumaServer/MacServer.csproj
#	Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs
#	Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs
#	Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs
#	Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs
#	Barotrauma/BarotraumaServer/ServerSource/Utils/DoSProtection.cs
#	Barotrauma/BarotraumaServer/WindowsServer.csproj
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorkerOperateWeapons.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
#	Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/Enums.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs
#	Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs
#	Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs
#	Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs
#	Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
#	Barotrauma/BarotraumaShared/changelog.txt
This commit is contained in:
Regalis11
2023-06-15 16:55:39 +03:00
238 changed files with 5395 additions and 3045 deletions
@@ -290,6 +290,7 @@ namespace Barotrauma
dictionary.Clear();
Hull.EntityGrids.Clear();
Spawner?.Reset();
Items.Components.Projectile.ResetSpreadCounter();
}
/// <summary>
@@ -10,37 +10,169 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Explosions are area of effect attacks that can damage characters, items and structures.
/// </summary>
/// <doc>
/// <Field Identifier="showEffects" Type="bool" DefaultValue="true">
/// Used to enable all particle effects without having to specify them one by one.
/// </Field>
/// </doc>
partial class Explosion
{
public readonly Attack Attack;
/// <summary>
/// How much force the explosion applies to the characters.
/// </summary>
private readonly float force;
private readonly float cameraShake, cameraShakeRange;
/// <summary>
/// Intensity of the screen shake effect.
/// </summary>
/// <doc>
/// <override type="DefaultValue">
/// 10% of the range if showEffects is true, 0 otherwise.
/// </override>
/// </doc>
private readonly float cameraShake;
/// <summary>
/// How far away does the camera shake effect reach.
/// </summary>
/// <doc>
/// <override type="DefaultValue">
/// Same as attack range if showEffects is true, 0 otherwise.
/// </override>
/// </doc>
private readonly float cameraShakeRange;
/// <summary>
/// Color tint to apply to the player's screen when in range of the explosion.
/// </summary>
private readonly Color screenColor;
private readonly float screenColorRange, screenColorDuration;
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
/// <summary>
/// How far away can the screen color effect be seen.
/// </summary>
/// <doc>
/// <override type="DefaultValue">
/// 10% of the range if showEffects is true, 0 otherwise.
/// </override>
/// </doc>
private readonly float screenColorRange;
/// <summary>
/// How long the screen color effect lasts.
/// </summary>
private readonly float screenColorDuration;
/// <summary>
/// Whether a spark particle effect is created when the explosion happens.
/// </summary>
private bool sparks;
/// <summary>
/// Whether a shockwave particle effect is created when the explosion happens.
/// </summary>
private bool shockwave;
/// <summary>
/// Whether a flame particle effect is created when the explosion happens.
/// </summary>
private bool flames;
/// <summary>
/// Whether a smoke particle effect is created when the explosion happens.
/// </summary>
private bool smoke;
/// <summary>
/// Whether a flash effect is created when the explosion happens.
/// </summary>
private bool flash;
/// <summary>
/// Whether a underwater bubble particle effect is created when the explosion happens.
/// </summary>
private bool underwaterBubble;
/// <summary>
/// Color of the light source created by the explosion.
/// </summary>
private readonly Color flashColor;
/// <summary>
/// Whether the explosion plays a tinnitus sound to players who get hit by it.
/// </summary>
private readonly bool playTinnitus;
/// <summary>
/// Whether the explosion executes 'OnFire' status effects on the items it hits.
/// </summary>
/// <doc>
/// <override type="DefaultValue">
/// true if showEffects is true and flames haven't been explicitly set to false, false otherwise.
/// </override>
/// </doc>
private readonly bool applyFireEffects;
/// <summary>
/// List of item tags that the explosion ignores when applying fire effects.
/// </summary>
private readonly string[] ignoreFireEffectsForTags;
/// <summary>
/// When set to true, the explosion don't deal less damage when the target is behind a solid object.
/// </summary>
private readonly bool ignoreCover;
/// <summary>
/// How long the light source created by the explosion lasts.
/// </summary>
private readonly float flashDuration;
/// <summary>
/// How large the light source created by the explosion is.
/// </summary>
private readonly float? flashRange;
/// <summary>
/// Identifier of the decal the explosion creates on the background structure it explodes over.
/// Set to empty string to disable.
/// </summary>
private readonly string decal;
/// <summary>
/// Relative size of the decal created by the explosion.
/// </summary>
private readonly float decalSize;
private readonly bool applyToSelf;
public bool OnlyInside, OnlyOutside;
/// <summary>
/// Whether the explosion only affects characters inside a submarine.
/// </summary>
public bool OnlyInside;
/// <summary>
/// Whether the explosion only affects characters outside a submarine.
/// </summary>
public bool OnlyOutside;
/// <summary>
/// How much the explosion repairs items.
/// </summary>
private readonly float itemRepairStrength;
public readonly HashSet<Submarine> IgnoredSubmarines = new HashSet<Submarine>();
/// <summary>
/// Strength of the EMP effect created by the explosion.
/// </summary>
public float EmpStrength { get; set; }
/// <summary>
/// How much damage the explosion does to ballast flora.
/// </summary>
public float BallastFloraDamage { get; set; }
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f)
@@ -66,8 +198,6 @@ namespace Barotrauma
force = element.GetAttributeFloat("force", 0.0f);
applyToSelf = element.GetAttributeBool("applytoself", true);
//the "abilityexplosion" field is kept for backwards compatibility (basically the opposite of "showeffects")
bool showEffects = !element.GetAttributeBool("abilityexplosion", false) && element.GetAttributeBool("showeffects", true);
sparks = element.GetAttributeBool("sparks", showEffects);
@@ -196,7 +326,9 @@ namespace Barotrauma
var lightComponent = item.GetComponent<LightComponent>();
if (lightComponent != null)
{
lightComponent.TemporaryFlickerTimer = Math.Min(EmpStrength * distFactor, 10.0f);
//multiply by 10 to make the effect more noticeable
//(a strength of 1 is already enough to kill power and shut down the lights, but we want weaker EMPs to make the lights flicker noticeably)
lightComponent.TemporaryFlickerTimer = Math.Min(EmpStrength * distFactor * 10.0f, 10.0f);
}
//discharge batteries
@@ -231,7 +363,7 @@ namespace Barotrauma
return;
}
DamageCharacters(worldPosition, Attack, force, damageSource, attacker, applyToSelf);
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
@@ -284,8 +416,8 @@ namespace Barotrauma
}
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker, bool applyToSelf)
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
{
if (attack.Range <= 0.0f) { return; }
@@ -300,7 +432,6 @@ namespace Barotrauma
{
continue;
}
//if (c == attacker && !applyToSelf) { continue; }
if (OnlyInside && c.Submarine == null)
{
@@ -513,21 +644,28 @@ namespace Barotrauma
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
{
if (Level.Loaded.ExtraWalls[i] is not DestructibleLevelWall destructibleWall) { continue; }
bool inRange = false;
foreach (var cell in destructibleWall.Cells)
{
if (cell.IsPointInside(worldPosition))
{
destructibleWall.AddDamage(levelWallDamage, worldPosition);
continue;
inRange = true;
break;
}
foreach (var edge in cell.Edges)
{
if (MathUtils.LineSegmentToPointDistanceSquared((edge.Point1 + cell.Translation).ToPoint(), (edge.Point2 + cell.Translation).ToPoint(), worldPosition.ToPoint()) < worldRange * worldRange)
{
destructibleWall.AddDamage(levelWallDamage, worldPosition);
inRange = true;
break;
}
}
if (inRange) { break; }
}
if (inRange)
{
destructibleWall.AddDamage(levelWallDamage, worldPosition);
}
}
}
@@ -254,7 +254,7 @@ namespace Barotrauma
d.ForceRefreshFadeTimer(Math.Min(d.FadeTimer, d.FadeInTime));
}
UpdateProjSpecific(growModifier);
UpdateProjSpecific(growModifier, deltaTime);
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
@@ -273,7 +273,7 @@ namespace Barotrauma
position.X -= GrowSpeed * growModifier * 0.5f * deltaTime;
}
partial void UpdateProjSpecific(float growModifier);
partial void UpdateProjSpecific(float growModifier, float deltaTime);
private void OnChangeHull(Vector2 pos, Hull particleHull)
{
@@ -59,6 +59,8 @@ namespace Barotrauma
private Body outsideCollisionBlocker;
private float outsideColliderRaycastTimer;
private bool wasRoomToRoom;
public float Open
{
get { return open; }
@@ -205,7 +207,10 @@ namespace Barotrauma
outsideCollisionBlocker.Enabled = false;
#if CLIENT
Resized += newRect => IsHorizontal = newRect.Width < newRect.Height;
#endif
# endif
wasRoomToRoom = IsRoomToRoom;
RefreshOutsideCollider();
DebugConsole.Log("Created gap (" + ID + ")");
}
@@ -214,7 +219,7 @@ namespace Barotrauma
return new Gap(rect, IsHorizontal, Submarine);
}
public override void Move(Vector2 amount, bool ignoreContacts = false)
public override void Move(Vector2 amount, bool ignoreContacts = true)
{
if (!MathUtils.IsValid(amount))
{
@@ -222,7 +227,7 @@ namespace Barotrauma
return;
}
base.Move(amount);
base.Move(amount, ignoreContacts);
if (!DisableHullRechecks) { FindHulls(); }
}
@@ -313,27 +318,26 @@ namespace Barotrauma
for (int i = 0; i < 2; i++)
{
hulls[i] = Hull.FindHullUnoptimized(searchPos[i], null, false);
if (hulls[i] == null) { hulls[i] = Hull.FindHullUnoptimized(searchPos[i], null, false, true); }
if (hulls[i] == null) hulls[i] = Hull.FindHullUnoptimized(searchPos[i], null, false, true);
}
if (hulls[0] != null || hulls[1] != null)
{
if (hulls[0] == null && hulls[1] != null)
{
(hulls[1], hulls[0]) = (hulls[0], hulls[1]);
}
if (hulls[0] == null && hulls[1] == null) { return; }
flowTargetHull = hulls[0];
for (int i = 0; i < 2; i++)
{
if (hulls[i] == null) { continue; }
linkedTo.Add(hulls[i]);
if (!hulls[i].ConnectedGaps.Contains(this)) { hulls[i].ConnectedGaps.Add(this); }
}
if (hulls[0] == null && hulls[1] != null)
{
Hull temp = hulls[0];
hulls[0] = hulls[1];
hulls[1] = temp;
}
RefreshOutsideCollider();
flowTargetHull = hulls[0];
for (int i = 0; i < 2; i++)
{
if (hulls[i] == null) { continue; }
linkedTo.Add(hulls[i]);
if (!hulls[i].ConnectedGaps.Contains(this)) hulls[i].ConnectedGaps.Add(this);
}
}
private int updateCount;
@@ -359,9 +363,14 @@ namespace Barotrauma
updateCount = 0;
flowForce = Vector2.Zero;
outsideColliderRaycastTimer -= deltaTime;
if (IsRoomToRoom != wasRoomToRoom)
{
RefreshOutsideCollider();
wasRoomToRoom = IsRoomToRoom;
}
if (open == 0.0f || linkedTo.Count == 0)
{
lerpedFlowForce = Vector2.Zero;
@@ -664,7 +673,7 @@ namespace Barotrauma
if (outsideColliderRaycastTimer <= 0.0f)
{
UpdateOutsideColliderPos((Hull)linkedTo[0]);
UpdateOutsideColliderState((Hull)linkedTo[0]);
outsideColliderRaycastTimer = outsideCollisionBlocker.Enabled ?
OutsideColliderRaycastIntervalHighPrio :
OutsideColliderRaycastIntervalLowPrio;
@@ -673,7 +682,7 @@ namespace Barotrauma
return outsideCollisionBlocker.Enabled;
}
private void UpdateOutsideColliderPos(Hull hull)
private void UpdateOutsideColliderState(Hull hull)
{
if (Submarine == null || IsRoomToRoom || Level.Loaded == null) { return; }
@@ -710,7 +719,7 @@ namespace Barotrauma
if (blockingBody.UserData == Submarine) { return; }
outsideCollisionBlocker.Enabled = true;
Vector2 colliderPos = Submarine.LastPickedPosition - Submarine.SimPosition;
float colliderRotation = MathUtils.VectorToAngle(rayDir) - MathHelper.PiOver2;
float colliderRotation = MathUtils.VectorToAngle(Submarine.LastPickedNormal) - MathHelper.PiOver2;
outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation);
}
else
@@ -590,7 +590,7 @@ namespace Barotrauma
return index;
}
public override void Move(Vector2 amount, bool ignoreContacts = false)
public override void Move(Vector2 amount, bool ignoreContacts = true)
{
if (!MathUtils.IsValid(amount))
{
@@ -851,7 +851,21 @@ namespace Barotrauma
{
decal.Update(deltaTime);
}
decals.RemoveAll(d => d.FadeTimer >= d.LifeTime || d.BaseAlpha <= 0.001f);
//clients don't remove decals unless the server says so
if (GameMain.NetworkMember is not { IsClient: true })
{
for (int i = decals.Count - 1; i >= 0; i--)
{
var decal = decals[i];
if (decal.FadeTimer >= decal.LifeTime || decal.BaseAlpha <= 0.001f)
{
decals.RemoveAt(i);
#if SERVER
decalUpdatePending = true;
#endif
}
}
}
if (aiTarget != null)
{
@@ -1509,9 +1523,8 @@ namespace Barotrauma
public void CleanSection(BackgroundSection section, float cleanVal, bool updateRequired)
{
bool decalsCleaned = false;
for (int i = 0; i < decals.Count; i++)
foreach (Decal decal in decals)
{
Decal decal = decals[i];
if (decal.AffectsSection(section))
{
decal.Clean(cleanVal);
@@ -1672,5 +1685,9 @@ namespace Barotrauma
return element;
}
public override string ToString()
{
return $"{base.ToString()} ({Name ?? "unnamed"})";
}
}
}
@@ -136,7 +136,7 @@ namespace Barotrauma
{
me.Move(position);
me.Submarine = sub;
if (!(me is Item item)) { continue; }
if (me is not Item item) { continue; }
Wire wire = item.GetComponent<Wire>();
//Vector2 subPosition = Submarine == null ? Vector2.Zero : Submarine.HiddenSubPosition;
if (wire != null)
@@ -296,9 +296,9 @@ namespace Barotrauma
Vector2 triangleCenter = (edge.Point1 + edge.Point2 + extrudedPoint) / 3;
foreach (GraphEdge nearbyEdge in nearbyCell.Edges)
{
if (!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) &&
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) &&
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2))
if (!MathUtils.LineSegmentsIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) &&
!MathUtils.LineSegmentsIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) &&
!MathUtils.LineSegmentsIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2))
{
isInside = true;
break;
@@ -429,7 +429,7 @@ namespace Barotrauma
public static bool IsLoadedOutpost => Loaded?.Type == LevelData.LevelType.Outpost;
/// <summary>
/// Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1)
/// Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1). Does not take reputation into account.
/// </summary>
public static bool IsLoadedFriendlyOutpost =>
loaded?.Type == LevelData.LevelType.Outpost &&
@@ -468,8 +468,7 @@ namespace Barotrauma
(StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) &&
StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player))
{
var reputation = GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation;
return reputation == null || reputation.NormalizedValue >= Reputation.HostileThreshold;
return GameMain.GameSession.Campaign?.CurrentLocation is not { IsFactionHostile: true };
}
return false;
}
@@ -508,6 +507,8 @@ namespace Barotrauma
StartLocation = startLocation;
EndLocation = endLocation;
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
GenerateEqualityCheckValue(LevelGenStage.GenStart);
SetEqualityCheckValue(LevelGenStage.LevelGenParams, unchecked((int)GenerationParams.UintIdentifier));
SetEqualityCheckValue(LevelGenStage.Size, borders.Width ^ borders.Height << 16);
@@ -535,7 +536,6 @@ namespace Barotrauma
List<Vector2> sites = new List<Vector2>();
Voronoi voronoi = new Voronoi(1.0);
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
#if CLIENT
renderer = new LevelRenderer(this);
@@ -838,14 +838,6 @@ namespace Barotrauma
foreach (var pathCell in tunnel.Cells)
{
MarkEdges(pathCell, tunnel.Type);
foreach (GraphEdge edge in pathCell.Edges)
{
var adjacent = edge.AdjacentCell(pathCell);
if (adjacent != null)
{
MarkEdges(adjacent, tunnel.Type);
}
}
if (!pathCells.Contains(pathCell))
{
pathCells.Add(pathCell);
@@ -1462,7 +1454,7 @@ namespace Barotrauma
if (node2.X <= pathNodes.Last().X) { continue; }
if (MathUtils.NearlyEqual(node1.X, pathNodes.Last().X)) { continue; }
if (Math.Abs(node1.Y - nodePos.Y) > tunnel.MinWidth && Math.Abs(node2.Y - nodePos.Y) > tunnel.MinWidth &&
!MathUtils.LinesIntersect(node1.ToVector2(), node2.ToVector2(), pathNodes.Last().ToVector2(), nodePos.ToVector2()))
!MathUtils.LineSegmentsIntersect(node1.ToVector2(), node2.ToVector2(), pathNodes.Last().ToVector2(), nodePos.ToVector2()))
{
continue;
}
@@ -1558,7 +1550,7 @@ namespace Barotrauma
foreach (GraphEdge edge in tunnel.Cells[i].Edges)
{
if (edge.AdjacentCell(tunnel.Cells[i])?.CellType == CellType.Solid &&
MathUtils.LinesIntersect(newWaypoint.WorldPosition, prevWayPoint.WorldPosition, edge.Point1, edge.Point2))
MathUtils.LineSegmentsIntersect(newWaypoint.WorldPosition, prevWayPoint.WorldPosition, edge.Point1, edge.Point2))
{
solidCellBetween = true;
break;
@@ -1846,9 +1838,10 @@ namespace Barotrauma
bool createCave =
//force at least one abyss cave
(i == islandCount - 1 && createdCaves == 0) ||
Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) > GenerationParams.AbyssIslandCaveProbability;
Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) < GenerationParams.AbyssIslandCaveProbability;
if (!createCave)
{
//just create a chunk with no cave
float radiusVariance = Math.Min(islandArea.Width, islandArea.Height) * 0.1f;
var vertices = CaveGenerator.CreateRandomChunk(islandArea.Width - (int)(radiusVariance * 2), islandArea.Height - (int)(radiusVariance * 2), 16, radiusVariance: radiusVariance);
Vector2 position = islandArea.Center.ToVector2();
@@ -2808,7 +2801,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(c.EdgeCenter, validLocation.EdgeCenter) > (intervalRange.X * intervalRange.X)) { return true; }
// If there is a line from a previous path point to one of its existing cluster locations
// which intersects with the line from this path point to the new possible cluster location
if (MathUtils.LinesIntersect(anotherPathPoint.Position, c.EdgeCenter, pathPoint.Position, validLocation.EdgeCenter)) { return true; }
if (MathUtils.LineSegmentsIntersect(anotherPathPoint.Position, c.EdgeCenter, pathPoint.Position, validLocation.EdgeCenter)) { return true; }
return false;
}
}
@@ -3188,7 +3181,7 @@ namespace Barotrauma
Vector2 edgeNormal = location.Edge.GetNormal(location.Cell);
float moveAmount = (item.body == null ? item.Rect.Height / 2 : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent() * 0.7f));
moveAmount += (item.GetComponent<LevelResource>()?.RandomOffsetFromWall ?? 0.0f) * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient);
item.Move(edgeNormal * moveAmount, ignoreContacts: true);
item.Move(edgeNormal * moveAmount);
if (item.GetComponent<Holdable>() is Holdable h)
{
h.AttachToWall();
@@ -3513,7 +3506,7 @@ namespace Barotrauma
{
foreach (GraphEdge e in cell.Edges)
{
if (!MathUtils.LinesIntersect(closestPathCell.Center, pos.ToVector2(), e.Point1, e.Point2)) { continue; }
if (!MathUtils.LineSegmentsIntersect(closestPathCell.Center, pos.ToVector2(), e.Point1, e.Point2)) { continue; }
cell.CellType = CellType.Removed;
for (int x = 0; x < cellGrid.GetLength(0); x++)
@@ -95,6 +95,8 @@ namespace Barotrauma
public Reputation Reputation => Faction?.Reputation;
public bool IsFactionHostile => Faction?.Reputation.NormalizedValue < Reputation.HostileThreshold;
public int TurnsInRadiation { get; set; }
#region Store
@@ -177,29 +179,30 @@ namespace Barotrauma
}
}
public static PurchasedItem CreateInitialStockItem(ItemPrefab itemPrefab, PriceInfo priceInfo)
{
int quantity = PriceInfo.DefaultAmount;
if (priceInfo.MaxAvailableAmount > 0)
{
quantity =
priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount ?
Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount + 1) :
priceInfo.MaxAvailableAmount;
}
else if (priceInfo.MinAvailableAmount > 0)
{
quantity = priceInfo.MinAvailableAmount;
}
return new PurchasedItem(itemPrefab, quantity, buyer: null);
}
public List<PurchasedItem> CreateStock()
{
var stock = new List<PurchasedItem>();
foreach (var prefab in ItemPrefab.Prefabs)
{
if (!prefab.CanBeBoughtFrom(this, out var priceInfo)) { continue; }
int quantity = PriceInfo.DefaultAmount;
if (priceInfo.MaxAvailableAmount > 0)
{
if (priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount)
{
quantity = Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount + 1);
}
else
{
quantity = priceInfo.MaxAvailableAmount;
}
}
else if (priceInfo.MinAvailableAmount > 0)
{
quantity = priceInfo.MinAvailableAmount;
}
stock.Add(new PurchasedItem(prefab, quantity, buyer: null));
stock.Add(CreateInitialStockItem(prefab, priceInfo));
}
return stock;
}
@@ -304,6 +307,7 @@ namespace Barotrauma
if (!faction.IsEmpty && GameMain.GameSession.Campaign.GetFactionAffiliation(faction) is FactionAffiliation.Positive)
{
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
price *= 1f - characters.Max(static c => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, new Identifier("all")));
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, tag)));
}
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
@@ -1277,25 +1281,31 @@ namespace Barotrauma
}
var stock = new List<PurchasedItem>(store.Stock);
var stockToRemove = new List<PurchasedItem>();
foreach (var item in stock)
foreach (var itemPrefab in ItemPrefab.Prefabs)
{
if (item.ItemPrefab.CanBeBoughtFrom(store, out PriceInfo priceInfo))
var existingStock = stock.FirstOrDefault(s => s.ItemPrefab == itemPrefab);
if (itemPrefab.CanBeBoughtFrom(store, out PriceInfo priceInfo))
{
item.Quantity += 1;
if (priceInfo.MaxAvailableAmount > 0)
if (existingStock == null)
{
item.Quantity = Math.Min(item.Quantity, priceInfo.MaxAvailableAmount);
//can be bought from the location, but not in stock - some new item added by an update or mod?
stock.Add(StoreInfo.CreateInitialStockItem(itemPrefab, priceInfo));
}
else
{
item.Quantity = Math.Min(item.Quantity, CargoManager.MaxQuantity);
existingStock.Quantity =
Math.Min(
existingStock.Quantity + 1,
priceInfo.MaxAvailableAmount > 0 ? priceInfo.MaxAvailableAmount : CargoManager.MaxQuantity);
}
}
else
else if (existingStock != null)
{
stockToRemove.Add(item);
stockToRemove.Add(existingStock);
}
}
stockToRemove.ForEach(i => stock.Remove(i));
store.Stock.Clear();
store.Stock.AddRange(stock);
@@ -294,40 +294,37 @@ namespace Barotrauma
throw new Exception($"Generating a campaign map failed (no locations created). Width: {Width}, height: {Height}");
}
foreach (Location location in Locations)
{
if (location.Type.Identifier != "outpost") { continue; }
SetStartLocation(location);
}
FindStartLocation(l => l.Type.Identifier == "outpost");
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
if (CurrentLocation == null)
{
FindStartLocation(l => l.Type.HasOutpost);
}
void FindStartLocation(Func<Location, bool> predicate)
{
foreach (Location location in Locations)
{
if (!location.Type.HasOutpost) { continue; }
SetStartLocation(location);
if (!predicate(location)) { continue; }
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
}
}
void SetStartLocation(Location location)
StartLocation.SecondaryFaction = null;
var startOutpostFaction = campaign?.Factions.FirstOrDefault(f => f.Prefab.StartOutpost);
if (startOutpostFaction != null)
{
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
StartLocation.Faction = startOutpostFaction;
foreach (var connection in StartLocation.Connections)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
StartLocation.SecondaryFaction = null;
var startOutpostFaction = campaign?.Factions.FirstOrDefault(f => f.Prefab.StartOutpost);
if (startOutpostFaction != null)
var otherLocation = connection.OtherLocation(StartLocation);
if (otherLocation.HasOutpost() && otherLocation.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
{
StartLocation.Faction = startOutpostFaction;
foreach (var connection in StartLocation.Connections)
{
var otherLocation = connection.OtherLocation(StartLocation);
if (otherLocation.HasOutpost() && otherLocation.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
{
otherLocation.Faction = startOutpostFaction;
}
}
}
otherLocation.Faction = startOutpostFaction;
}
}
}
@@ -312,7 +312,7 @@ namespace Barotrauma
}
}
public virtual void Move(Vector2 amount, bool ignoreContacts = false)
public virtual void Move(Vector2 amount, bool ignoreContacts = true)
{
rect.X += (int)amount.X;
rect.Y += (int)amount.Y;
@@ -449,7 +449,7 @@ namespace Barotrauma
List<Wire> orphanedWires = new List<Wire>();
for (int i = 0; i < clones.Count; i++)
{
if (!(clones[i] is Item cloneItem)) { continue; }
if (clones[i] is not Item cloneItem) { continue; }
var door = cloneItem.GetComponent<Door>();
door?.RefreshLinkedGap();
@@ -507,7 +507,9 @@ namespace Barotrauma
cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], n, addNode: false);
}
if ((cloneWire.Connections[0] == null || cloneWire.Connections[1] == null) && cloneItem.GetComponent<DockingPort>() == null)
if (originalWire.Connections.Any(c => c != null) &&
(cloneWire.Connections[0] == null || cloneWire.Connections[1] == null) &&
cloneItem.GetComponent<DockingPort>() == null)
{
if (!clones.Any(c => (c as Item)?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(cloneWire) ?? false))
{
@@ -786,7 +786,7 @@ namespace Barotrauma
//check if the connection overlaps with this module's connection
if (selfGapPos1.HasValue && selfGapPos2.HasValue &&
!gapPos1.NearlyEquals(gapPos2) && !selfGapPos1.Value.NearlyEquals(selfGapPos2.Value) &&
MathUtils.LinesIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
MathUtils.LineSegmentsIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
{
return true;
}
@@ -1374,11 +1374,6 @@ namespace Barotrauma
endWaypoint.linkedTo.Add(prevWayPoint);
}
}
else
{
startWaypoint.linkedTo.Add(endWaypoint);
endWaypoint.linkedTo.Add(startWaypoint);
}
WayPoint closestWaypoint = null;
float closestDistSqr = 30.0f * 30.0f;
@@ -1440,27 +1435,7 @@ namespace Barotrauma
private static void EnableFactionSpecificEntities(Submarine sub, Location location)
{
foreach (MapEntity me in MapEntity.mapEntityList)
{
if (string.IsNullOrEmpty(me.Layer) || me.Submarine != sub) { continue; }
var layerAsIdentifier = me.Layer.ToIdentifier();
if (FactionPrefab.Prefabs.ContainsKey(layerAsIdentifier))
{
me.HiddenInGame =
location?.Faction?.Prefab != FactionPrefab.Prefabs[layerAsIdentifier];
#if CLIENT
//normally this is handled in LightComponent.OnMapLoaded, but this method is called after that
if (me.HiddenInGame && me is Item item)
{
foreach (var lightComponent in item.GetComponents<LightComponent>())
{
lightComponent.Light.Enabled = false;
}
}
#endif
}
}
sub.EnableFactionSpecificEntities(location?.Faction?.Prefab.Identifier ?? Identifier.Empty);
}
private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities, bool removeUnusedGaps)
@@ -1615,10 +1590,11 @@ namespace Barotrauma
{
var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == bottomGap);
var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == topGap);
float margin = 100;
if (startWaypoint != null && endWaypoint != null)
{
WayPoint prevWaypoint = startWaypoint;
for (float y = startWaypoint.Position.Y + WayPoint.LadderWaypointInterval; y <= endWaypoint.Position.Y - WayPoint.LadderWaypointInterval; y += WayPoint.LadderWaypointInterval)
for (float y = bottomGap.Position.Y + margin; y <= topGap.Position.Y - margin; y += WayPoint.LadderWaypointInterval)
{
var wayPoint = new WayPoint(new Vector2(startWaypoint.Position.X, y), SpawnType.Path, ladder.Item.Submarine)
{
@@ -59,7 +59,7 @@ namespace Barotrauma
private static Explosion explosionOnBroken;
#if DEBUG
[Serialize(false, IsPropertySaveable.Yes), Editable]
[Serialize(false, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody)]
#else
[Serialize(false, IsPropertySaveable.Yes)]
#endif
@@ -106,9 +106,11 @@ namespace Barotrauma
public List<Body> Bodies { get; private set; }
[Serialize(false, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody)]
public bool CastShadow
{
get { return Prefab.CastShadow; }
get;
set;
}
public bool IsHorizontal { get; private set; }
@@ -120,7 +122,7 @@ namespace Barotrauma
private float? maxHealth;
[Serialize(100.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0)]
[Serialize(100.0f, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody, MinValueFloat = 0)]
public float MaxHealth
{
get => maxHealth ?? Prefab.Health;
@@ -191,14 +193,14 @@ namespace Barotrauma
set { spriteColor = value; }
}
[Editable, Serialize(false, IsPropertySaveable.Yes)]
[ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody), Serialize(false, IsPropertySaveable.Yes)]
public bool UseDropShadow
{
get;
private set;
}
[Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")]
[ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody), Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")]
public Vector2 DropShadowOffset
{
get;
@@ -369,7 +371,7 @@ namespace Barotrauma
private set;
}
public override void Move(Vector2 amount, bool ignoreContacts = false)
public override void Move(Vector2 amount, bool ignoreContacts = true)
{
if (!MathUtils.IsValid(amount))
{
@@ -377,7 +379,7 @@ namespace Barotrauma
return;
}
base.Move(amount);
base.Move(amount, ignoreContacts);
for (int i = 0; i < Sections.Length; i++)
{
@@ -444,13 +446,11 @@ namespace Barotrauma
}
else
{
float width = BodyWidth > 0.0f ? BodyWidth : rect.Width;
float height = BodyHeight > 0.0f ? BodyHeight : rect.Height;
if (BodyWidth > 0.0f && BodyHeight > 0.0f)
{
IsHorizontal = BodyWidth > BodyHeight;
}
else
{
IsHorizontal = (rect.Width > rect.Height);
IsHorizontal = width > height;
}
}
@@ -459,29 +459,28 @@ namespace Barotrauma
InitProjSpecific();
if (!HiddenInGame)
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
if (element?.GetAttribute(nameof(CastShadow)) == null)
{
if (Prefab.Body)
{
Bodies = new List<Body>();
WallList.Add(this);
CreateSections();
UpdateSections();
}
else
{
Sections = new WallSection[1];
Sections[0] = new WallSection(rect, this);
if (StairDirection != Direction.None)
{
CreateStairBodies();
}
}
CastShadow = Prefab.CastShadow;
}
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
if (Prefab.Body)
{
Bodies = new List<Body>();
WallList.Add(this);
CreateSections();
UpdateSections();
}
else if (StairDirection != Direction.None)
{
CreateStairBodies();
}
if (Sections == null)
{
Sections = new WallSection[1];
Sections[0] = new WallSection(rect, this);
}
#if CLIENT
foreach (var subElement in sp.ConfigElement.Elements())
@@ -1576,16 +1575,16 @@ namespace Barotrauma
}
}
if (element.GetAttributeBool("flippedx", false)) { s.FlipX(false); }
if (element.GetAttributeBool("flippedy", false)) { s.FlipY(false); }
if (element.GetAttributeBool(nameof(FlippedX), false)) { s.FlipX(false); }
if (element.GetAttributeBool(nameof(FlippedY), false)) { s.FlipY(false); }
//structures with a body drop a shadow by default
if (element.GetAttribute("usedropshadow") == null)
if (element.GetAttribute(nameof(UseDropShadow)) == null)
{
s.UseDropShadow = prefab.Body;
}
if (element.GetAttribute("noaitarget") == null)
if (element.GetAttribute(nameof(NoAITarget)) == null)
{
s.NoAITarget = prefab.NoAITarget;
}
@@ -1634,12 +1633,12 @@ namespace Barotrauma
(int)(rect.Y - Submarine.HiddenSubPosition.Y) + "," +
width + "," + height));
if (FlippedX) element.Add(new XAttribute("flippedx", true));
if (FlippedY) element.Add(new XAttribute("flippedy", true));
if (FlippedX) { element.Add(new XAttribute("flippedx", true)); }
if (FlippedY) { element.Add(new XAttribute("flippedy", true)); }
for (int i = 0; i < Sections.Length; i++)
{
if (Sections[i].damage == 0.0f) continue;
if (Sections[i].damage == 0.0f) { continue; }
var sectionElement =
new XElement("section",
new XAttribute("i", i),
@@ -1649,6 +1648,11 @@ namespace Barotrauma
SerializableProperty.SerializeProperties(this, element);
if (CastShadow == Prefab.CastShadow)
{
element.GetAttribute(nameof(CastShadow))?.Remove();
}
foreach (var upgrade in Upgrades)
{
upgrade.Save(element);
@@ -1,16 +1,14 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.Extensions;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Barotrauma.IO;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Voronoi2;
@@ -259,7 +257,7 @@ namespace Barotrauma
{
if (outpost.exitPoints.Any())
{
Rectangle worldBorders = Borders;
Rectangle worldBorders = GetDockedBorders();
worldBorders.Location += WorldPosition.ToPoint();
foreach (var exitPoint in outpost.exitPoints)
{
@@ -425,18 +423,25 @@ namespace Barotrauma
}
}
private static readonly HashSet<Submarine> checkSubmarineBorders = new HashSet<Submarine>();
/// <summary>
/// Returns a rect that contains the borders of this sub and all subs docked to it
/// Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
/// </summary>
public Rectangle GetDockedBorders(List<Submarine> checkd = null)
public Rectangle GetDockedBorders(bool allowDifferentTeam = true)
{
if (checkd == null) { checkd = new List<Submarine>(); }
checkd.Add(this);
checkSubmarineBorders.Clear();
return GetDockedBordersRecursive(allowDifferentTeam);
}
private Rectangle GetDockedBordersRecursive(bool allowDifferentTeam)
{
Rectangle dockedBorders = Borders;
var connectedSubs = DockedTo.Where(s => !checkd.Contains(s) && !s.Info.IsOutpost).ToList();
checkSubmarineBorders.Add(this);
var connectedSubs = DockedTo.Where(s =>
!checkSubmarineBorders.Contains(s) &&
!s.Info.IsOutpost &&
(allowDifferentTeam || s.TeamID == TeamID));
foreach (Submarine dockedSub in connectedSubs)
{
//use docking ports instead of world position to determine
@@ -445,7 +450,7 @@ namespace Barotrauma
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
if (expectedLocation == null) { continue; }
Rectangle dockedSubBorders = dockedSub.GetDockedBorders(checkd);
Rectangle dockedSubBorders = dockedSub.GetDockedBordersRecursive(allowDifferentTeam);
dockedSubBorders.Location += MathUtils.ToPoint(expectedLocation.Value);
dockedBorders.Y = -dockedBorders.Y;
@@ -457,28 +462,27 @@ namespace Barotrauma
return dockedBorders;
}
/// <summary>
/// Don't use this directly, because the list is updated only when GetConnectedSubs() is called. The method is called so frequently that we don't want to create new list here.
/// </summary>
private readonly List<Submarine> connectedSubs = new List<Submarine>(2);
private readonly HashSet<Submarine> connectedSubs;
/// <summary>
/// Returns a list of all submarines that are connected to this one via docking ports, including this sub.
/// </summary>
public List<Submarine> GetConnectedSubs()
public IEnumerable<Submarine> GetConnectedSubs()
{
return connectedSubs;
}
public void RefreshConnectedSubs()
{
connectedSubs.Clear();
connectedSubs.Add(this);
GetConnectedSubsRecursive(connectedSubs);
return connectedSubs;
}
private void GetConnectedSubsRecursive(List<Submarine> subs)
private void GetConnectedSubsRecursive(HashSet<Submarine> subs)
{
foreach (Submarine dockedSub in DockedTo)
{
if (subs.Contains(dockedSub)) continue;
if (subs.Contains(dockedSub)) { continue; }
subs.Add(dockedSub);
dockedSub.GetConnectedSubsRecursive(subs);
}
@@ -1041,8 +1045,34 @@ namespace Barotrauma
#endif
}
public void EnableFactionSpecificEntities(Identifier factionIdentifier)
{
foreach (MapEntity me in MapEntity.mapEntityList)
{
if (string.IsNullOrEmpty(me.Layer) || me.Submarine != this) { continue; }
var layerAsIdentifier = me.Layer.ToIdentifier();
if (FactionPrefab.Prefabs.ContainsKey(layerAsIdentifier))
{
me.HiddenInGame = factionIdentifier != layerAsIdentifier;
#if CLIENT
//normally this is handled in LightComponent.OnMapLoaded, but this method is called after that
if (me.HiddenInGame && me is Item item)
{
foreach (var lightComponent in item.GetComponents<LightComponent>())
{
lightComponent.Light.Enabled = false;
}
}
#endif
}
}
}
public void Update(float deltaTime)
{
RefreshConnectedSubs();
if (Info.IsWreck)
{
WreckAI?.Update(deltaTime);
@@ -1328,38 +1358,22 @@ namespace Barotrauma
}
/// <summary>
/// Finds the sub whose borders contain the position. Note that this method uses the "actual" position of the sub outside the level:
/// only use this if the position is in a submarine's local coordinate space!
/// Finds the sub whose borders contain the position
/// </summary>
public static Submarine FindContainingInLocalCoordinates(Vector2 position, float inflate = 500.0f)
public static Submarine FindContaining(Vector2 position)
{
foreach (Submarine sub in Loaded)
{
Rectangle subBorders = sub.Borders;
subBorders.Location += MathUtils.ToPoint(sub.HiddenSubPosition) - new Point(0, sub.Borders.Height);
subBorders.Inflate(inflate, inflate);
if (subBorders.Contains(position)) { return sub; }
subBorders.Location += MathUtils.ToPoint(sub.HiddenSubPosition) - new Microsoft.Xna.Framework.Point(0, sub.Borders.Height);
subBorders.Inflate(500.0f, 500.0f);
if (subBorders.Contains(position)) return sub;
}
return null;
}
/// <summary>
/// Finds the sub whose world borders contain the position.
/// </summary>
public static Submarine FindContaining(Vector2 worldPosition, float inflate = 500.0f)
{
foreach (Submarine sub in Loaded)
{
Rectangle worldBorders = sub.Borders;
worldBorders.Location += sub.WorldPosition.ToPoint();
worldBorders.Inflate(inflate, inflate);
if (RectContains(worldBorders, worldPosition)) { return sub; }
}
return null;
}
public static Rectangle GetBorders(XElement submarineElement)
{
Vector4 bounds = Vector4.Zero;
@@ -1385,6 +1399,13 @@ namespace Barotrauma
public Submarine(SubmarineInfo info, bool showErrorMessages = true, Func<Submarine, List<MapEntity>> loadEntities = null, IdRemap linkedRemap = null) : base(null, Entity.NullEntityID)
{
Stopwatch sw = Stopwatch.StartNew();
connectedSubs = new HashSet<Submarine>(2)
{
this
};
upgradeEventIdentifier = new Identifier($"Submarine{ID}");
Loading = true;
GameMain.World.Enabled = false;
@@ -1481,8 +1502,14 @@ namespace Barotrauma
if (me.Submarine != this) { continue; }
if (me is Item item)
{
item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null;
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
item.AllowStealing = true;
if (info.OutpostGenerationParams != null)
{
item.SpawnedInCurrentOutpost = true;
item.AllowStealing =
info.OutpostGenerationParams.AllowStealing ||
item.RootContainer is { Prefab: { AllowStealingContainedItems: true } };
}
if (item.GetComponent<Repairable>() != null && indestructible)
{
item.Indestructible = true;
@@ -1589,6 +1616,10 @@ namespace Barotrauma
Loading = false;
GameMain.World.Enabled = true;
}
sw.Stop();
string debugMsg = $"Loading {Info?.Name ?? "unknown"} took {sw.ElapsedMilliseconds} ms.";
DebugConsole.Log(debugMsg);
System.Diagnostics.Debug.WriteLine(debugMsg);
}
protected override ushort DetermineID(ushort id, Submarine submarine)
@@ -1704,9 +1735,30 @@ namespace Barotrauma
}
}
Dictionary<int, MapEntity> savedEntities = new Dictionary<int, MapEntity>();
foreach (MapEntity e in MapEntity.mapEntityList.OrderBy(e => e.ID))
{
if (!e.ShouldBeSaved) { continue; }
if (e.Removed)
{
GameAnalyticsManager.AddErrorEventOnce(
"Submarine.SaveToXElement:Removed" + e.Name,
GameAnalyticsManager.ErrorSeverity.Error,
$"Attempted to save a removed entity (\"{e.Name}\"). Duplicate ID: {savedEntities.ContainsKey(e.ID)}");
DebugConsole.ThrowError($"Error while saving the submarine. Attempted to save a removed entity (\"{e.Name} ({e.ID})\"). The entity will not be saved to avoid corrupting the submarine file.");
continue;
}
if (savedEntities.TryGetValue(e.ID, out MapEntity duplicateEntity))
{
GameAnalyticsManager.AddErrorEventOnce(
"Submarine.SaveToXElement:DuplicateId" + e.Name,
GameAnalyticsManager.ErrorSeverity.Error,
$"Attempted to save an entity with a duplicate ID ({e.Name}, {duplicateEntity.Name}).");
DebugConsole.ThrowError($"Error while saving the submarine. The entity \"{e.Name}\" has the same ID as \"{duplicateEntity.Name}\" ({e.ID}). The entity will not be saved to avoid corrupting the submarine file.");
continue;
}
if (e is Item item)
{
if (item.FindParentInventory(inv => inv is CharacterInventory) != null) { continue; }
@@ -1717,8 +1769,7 @@ namespace Barotrauma
}
#endif
if (e.Submarine != this) { continue; }
var rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.Submarine != this) { continue; }
if (item.RootContainer != null && item.RootContainer.Submarine != this) { continue; }
}
else
{
@@ -1726,6 +1777,7 @@ namespace Barotrauma
}
e.Save(element);
savedEntities.Add(e.ID, e);
}
Info.CheckSubsLeftBehind(element);
}
@@ -204,6 +204,7 @@ namespace Barotrauma
if (item.Submarine != submarine) { continue; }
Vector2 simPos = ConvertUnits.ToSimUnits(item.Position);
if (sub.FlippedX) { simPos.X = -simPos.X; }
if (item.GetComponent<Door>() is Door door)
{
door.OutsideSubmarineFixture = farseerBody.CreateRectangle(door.Body.Width, door.Body.Height, 5.0f, simPos, collisionCategory, collidesWith);
@@ -220,11 +221,6 @@ 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, collisionCategory, collidesWith));
@@ -404,7 +400,7 @@ namespace Barotrauma
if (totalForce.Y > 0)
{
ContactEdge contactEdge = Body?.FarseerBody?.ContactList;
while (contactEdge?.Next != null)
while (contactEdge?.Contact != null)
{
if (contactEdge.Contact.Enabled &&
contactEdge.Other.UserData is Submarine otherSubmarine &&
@@ -412,10 +408,10 @@ namespace Barotrauma
contactEdge.Contact.IsTouching)
{
contactEdge.Contact.GetWorldManifold(out Vector2 _, out FixedArray2<Vector2> points);
if (points[0].Y > Body.SimPosition.Y &&
if (points[0].Y > Body.SimPosition.Y &&
!Character.CharacterList.Any(c => c.Submarine == otherSubmarine && !c.IsIncapacitated && c.TeamID == otherSubmarine.TeamID))
{
otherSubmarine.SubBody.forceUpwardsTimer += deltaTime;
otherSubmarine.GetConnectedSubs().ForEach(s => s.SubBody.forceUpwardsTimer += deltaTime);
break;
}
}
@@ -471,8 +467,6 @@ namespace Barotrauma
}
}
totalForcePerFrame = Vector2.Zero;
UpdateDepthDamage(deltaTime);
forceUpwardsTimer = MathHelper.Clamp(forceUpwardsTimer - deltaTime * 0.1f, 0.0f, ForceUpwardsDelay);
@@ -558,12 +552,9 @@ namespace Barotrauma
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f);
}
private Vector2 totalForcePerFrame;
public void ApplyForce(Vector2 force)
{
Body.ApplyForce(force);
totalForcePerFrame += force;
}
public void SetPosition(Vector2 position)
@@ -701,7 +692,7 @@ namespace Barotrauma
}
//if all the bodies of a wall have been disabled, we don't need to care about gaps (can always pass through)
if (!(contact.FixtureA.UserData is Structure wall) || !wall.AllSectionBodiesDisabled())
if (contact.FixtureA.UserData is not Structure wall || !wall.AllSectionBodiesDisabled())
{
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
@@ -820,7 +811,7 @@ namespace Barotrauma
}
}
private IEnumerable<Contact> GetLevelContacts(PhysicsBody body)
private static IEnumerable<Contact> GetLevelContacts(PhysicsBody body)
{
ContactEdge contactEdge = body.FarseerBody.ContactList;
while (contactEdge?.Contact != null)
@@ -19,7 +19,7 @@ namespace Barotrauma
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
public const float LadderWaypointInterval = 55.0f;
public const float LadderWaypointInterval = 75.0f;
protected SpawnType spawnType;
private string[] idCardTags;