Atmos Delta-Pressure Window Shattering (#39238)
This PR adds delta-pressure damage. In short, airtight structures can now take damage proportional to the difference in pressures between the sides of the structure.
This commit is contained in:
139
Content.Server/Atmos/Components/DeltaPressureComponent.cs
Normal file
139
Content.Server/Atmos/Components/DeltaPressureComponent.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Server.Atmos.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Entities that have this component will have damage done to them depending on the local pressure
|
||||
/// environment that they reside in.
|
||||
///
|
||||
/// Atmospherics.DeltaPressure batch-processes entities with this component in a list on
|
||||
/// the grid's <see cref="GridAtmosphereComponent"/>.
|
||||
/// The entities are automatically added and removed from this list, and automatically
|
||||
/// added on initialization.
|
||||
/// </summary>
|
||||
/// <remarks> Note that the entity should have an <see cref="AirtightComponent"/> and be a grid structure.</remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class DeltaPressureComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the entity is currently in the processing list of the grid's <see cref="GridAtmosphereComponent"/>.
|
||||
/// </summary>
|
||||
[DataField(readOnly: true)]
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[Access(typeof(DeltaPressureSystem), typeof(AtmosphereSystem))]
|
||||
public bool InProcessingList;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this entity is currently taking damage from pressure.
|
||||
/// </summary>
|
||||
[DataField(readOnly: true)]
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[Access(typeof(DeltaPressureSystem), typeof(AtmosphereSystem))]
|
||||
public bool IsTakingDamage;
|
||||
|
||||
/// <summary>
|
||||
/// The current cached position of this entity on the grid.
|
||||
/// Updated via MoveEvent.
|
||||
/// </summary>
|
||||
[DataField(readOnly: true)]
|
||||
public Vector2i CurrentPosition = Vector2i.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The grid this entity is currently joined to for processing.
|
||||
/// Required for proper deletion, as we cannot reference the grid
|
||||
/// for removal while the entity is being deleted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? GridUid;
|
||||
|
||||
/// <summary>
|
||||
/// The percent chance that the entity will take damage each atmos tick,
|
||||
/// when the entity is above the damage threshold.
|
||||
/// Makes it so that windows don't all break in one go.
|
||||
/// Float is from 0 to 1, where 1 means 100% chance.
|
||||
/// If this is set to 0, the entity will never take damage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float RandomDamageChance = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// The base damage applied to the entity per atmos tick when it is above the damage threshold.
|
||||
/// This damage will be scaled as defined by the <see cref="DeltaPressureDamageScalingType"/> enum
|
||||
/// depending on the current effective pressure this entity is experiencing.
|
||||
/// Note that this damage will scale depending on the pressure above the minimum pressure,
|
||||
/// not at the current pressure.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier BaseDamage = new()
|
||||
{
|
||||
DamageDict = new Dictionary<string, FixedPoint2>
|
||||
{
|
||||
{ "Structural", 10 },
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The minimum pressure in kPa at which the entity will start taking damage.
|
||||
/// This doesn't depend on the difference in pressure.
|
||||
/// The entity will start to take damage if it is exposed to this pressure.
|
||||
/// This is needed because we don't correctly handle 2-layer windows yet.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MinPressure = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum difference in pressure between any side required for the entity to start taking damage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MinPressureDelta = 7500;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum pressure at which damage will no longer scale.
|
||||
/// If the effective pressure goes beyond this, the damage will be considered at this pressure.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MaxEffectivePressure = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// Simple constant to affect the scaling behavior.
|
||||
/// See comments in the <see cref="DeltaPressureDamageScalingType"/> types to see how this affects scaling.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ScalingPower = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the scaling behavior for the damage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DeltaPressureDamageScalingType ScalingType = DeltaPressureDamageScalingType.Threshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enum that defines how the damage dealt by the <see cref="DeltaPressureComponent"/> scales
|
||||
/// depending on the pressure experienced by the entity.
|
||||
/// The scaling is done on the effective pressure, which is the pressure above the minimum pressure.
|
||||
/// See https://www.desmos.com/calculator/9ctlq3zpnt for a visual representation of the scaling types.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public enum DeltaPressureDamageScalingType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Damage dealt will be constant as long as the minimum values are met.
|
||||
/// Scaling power is ignored.
|
||||
/// </summary>
|
||||
Threshold,
|
||||
|
||||
/// <summary>
|
||||
/// Damage dealt will be a linear function.
|
||||
/// Scaling power determines the slope of the function.
|
||||
/// </summary>
|
||||
Linear,
|
||||
|
||||
/// <summary>
|
||||
/// Damage dealt will be a logarithmic function.
|
||||
/// Scaling power determines the base of the log.
|
||||
/// </summary>
|
||||
Log,
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Serialization;
|
||||
@@ -61,6 +62,39 @@ namespace Content.Server.Atmos.Components
|
||||
[ViewVariables]
|
||||
public int HighPressureDeltaCount => HighPressureDelta.Count;
|
||||
|
||||
/// <summary>
|
||||
/// A list of entities that have a <see cref="DeltaPressureComponent"/> and are to
|
||||
/// be processed by the <see cref="DeltaPressureSystem"/>, if enabled.
|
||||
///
|
||||
/// To prevent massive bookkeeping overhead, this list is processed in-place,
|
||||
/// with add/remove/find operations helped via a dict.
|
||||
/// </summary>
|
||||
/// <remarks>If you want to add/remove/find entities in this list,
|
||||
/// use the API methods in the Atmospherics API.</remarks>
|
||||
[ViewVariables]
|
||||
public readonly List<Entity<DeltaPressureComponent>> DeltaPressureEntities =
|
||||
new(AtmosphereSystem.DeltaPressurePreAllocateLength);
|
||||
|
||||
/// <summary>
|
||||
/// An index lookup for the <see cref="DeltaPressureEntities"/> list.
|
||||
/// Used for add/remove/find operations to speed up processing.
|
||||
/// </summary>
|
||||
public readonly Dictionary<EntityUid, int> DeltaPressureEntityLookup =
|
||||
new(AtmosphereSystem.DeltaPressurePreAllocateLength);
|
||||
|
||||
/// <summary>
|
||||
/// Integer that indicates the current position in the
|
||||
/// <see cref="DeltaPressureEntities"/> list that is being processed.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public int DeltaPressureCursor;
|
||||
|
||||
/// <summary>
|
||||
/// Queue of entities that need to have damage applied to them.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly ConcurrentQueue<AtmosphereSystem.DeltaPressureDamageResult> DeltaPressureDamageResults = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly HashSet<IPipeNet> PipeNets = new();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
@@ -5,6 +6,7 @@ using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -319,6 +321,107 @@ public partial class AtmosphereSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entity with a DeltaPressureComponent to the DeltaPressure processing list.
|
||||
/// Also fills in important information on the component itself.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to add the entity to.</param>
|
||||
/// <param name="ent">The entity to add.</param>
|
||||
/// <returns>True if the entity was added to the list, false if it could not be added or
|
||||
/// if the entity was already present in the list.</returns>
|
||||
[PublicAPI]
|
||||
public bool TryAddDeltaPressureEntity(Entity<GridAtmosphereComponent?> grid, Entity<DeltaPressureComponent> ent)
|
||||
{
|
||||
// The entity needs to be part of a grid, and it should be the right one :)
|
||||
var xform = Transform(ent);
|
||||
|
||||
// The entity is not on a grid, so it cannot possibly have an atmosphere that affects it.
|
||||
if (xform.GridUid == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Entity should be on the grid it's being added to.
|
||||
Debug.Assert(xform.GridUid == grid.Owner);
|
||||
|
||||
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
|
||||
return false;
|
||||
|
||||
if (grid.Comp.DeltaPressureEntityLookup.ContainsKey(ent.Owner))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
grid.Comp.DeltaPressureEntityLookup[ent.Owner] = grid.Comp.DeltaPressureEntities.Count;
|
||||
grid.Comp.DeltaPressureEntities.Add(ent);
|
||||
|
||||
ent.Comp.CurrentPosition = _map.CoordinatesToTile(grid,
|
||||
Comp<MapGridComponent>(grid),
|
||||
xform.Coordinates);
|
||||
|
||||
ent.Comp.GridUid = grid.Owner;
|
||||
ent.Comp.InProcessingList = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity with a DeltaPressureComponent from the DeltaPressure processing list.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to remove the entity from.</param>
|
||||
/// <param name="ent">The entity to remove.</param>
|
||||
/// <returns>True if the entity was removed from the list, false if it could not be removed or
|
||||
/// if the entity was not present in the list.</returns>
|
||||
[PublicAPI]
|
||||
public bool TryRemoveDeltaPressureEntity(Entity<GridAtmosphereComponent?> grid, Entity<DeltaPressureComponent> ent)
|
||||
{
|
||||
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!grid.Comp.DeltaPressureEntityLookup.TryGetValue(ent.Owner, out var index))
|
||||
return false;
|
||||
|
||||
var lastIndex = grid.Comp.DeltaPressureEntities.Count - 1;
|
||||
if (lastIndex < 0)
|
||||
return false;
|
||||
|
||||
if (index != lastIndex)
|
||||
{
|
||||
var lastEnt = grid.Comp.DeltaPressureEntities[lastIndex];
|
||||
grid.Comp.DeltaPressureEntities[index] = lastEnt;
|
||||
grid.Comp.DeltaPressureEntityLookup[lastEnt.Owner] = index;
|
||||
}
|
||||
|
||||
grid.Comp.DeltaPressureEntities.RemoveAt(lastIndex);
|
||||
grid.Comp.DeltaPressureEntityLookup.Remove(ent.Owner);
|
||||
|
||||
if (grid.Comp.DeltaPressureCursor > grid.Comp.DeltaPressureEntities.Count)
|
||||
grid.Comp.DeltaPressureCursor = grid.Comp.DeltaPressureEntities.Count;
|
||||
|
||||
ent.Comp.InProcessingList = false;
|
||||
ent.Comp.GridUid = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a DeltaPressureComponent is currently considered for processing on a grid.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid that the entity may belong to.</param>
|
||||
/// <param name="ent">The entity to check.</param>
|
||||
/// <returns>True if the entity is part of the processing list, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool IsDeltaPressureEntityInList(Entity<GridAtmosphereComponent?> grid, Entity<DeltaPressureComponent> ent)
|
||||
{
|
||||
// Dict and list must be in sync - deep-fried if we aren't.
|
||||
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
|
||||
return false;
|
||||
|
||||
var contains = grid.Comp.DeltaPressureEntityLookup.ContainsKey(ent.Owner);
|
||||
Debug.Assert(contains == grid.Comp.DeltaPressureEntities.Contains(ent));
|
||||
|
||||
return contains;
|
||||
}
|
||||
|
||||
[ByRefEvent] private record struct SetSimulatedGridMethodEvent
|
||||
(EntityUid Grid, bool Simulated, bool Handled = false);
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
/*
|
||||
Helper methods to assist in getting very low overhead profiling of individual stages of the atmospherics simulation.
|
||||
Ideal for benchmarking and performance testing.
|
||||
These methods obviously aren't to be used in production code. Don't call them. They know my voice.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Runs the grid entity through a single processing stage of the atmosphere simulation.
|
||||
/// Ideal for benchmarking single stages of the simulation.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity to profile Atmospherics with.</param>
|
||||
/// <param name="state">The state to profile on the entity.</param>
|
||||
/// <param name="mapEnt">The optional mapEntity to provide when benchmarking ProcessAtmosDevices.</param>
|
||||
/// <returns>True if the processing stage completed, false if the processing stage had to pause processing due to time constraints.</returns>
|
||||
public bool RunProcessingStage(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
AtmosphereProcessingState state,
|
||||
Entity<MapAtmosphereComponent?>? mapEnt = null)
|
||||
{
|
||||
var processingPaused = state switch
|
||||
{
|
||||
AtmosphereProcessingState.Revalidate => ProcessRevalidate(ent),
|
||||
AtmosphereProcessingState.TileEqualize => ProcessTileEqualize(ent),
|
||||
AtmosphereProcessingState.ActiveTiles => ProcessActiveTiles(ent),
|
||||
AtmosphereProcessingState.ExcitedGroups => ProcessExcitedGroups(ent),
|
||||
AtmosphereProcessingState.HighPressureDelta => ProcessHighPressureDelta(ent),
|
||||
AtmosphereProcessingState.DeltaPressure => ProcessDeltaPressure(ent),
|
||||
AtmosphereProcessingState.Hotspots => ProcessHotspots(ent),
|
||||
AtmosphereProcessingState.Superconductivity => ProcessSuperconductivity(ent),
|
||||
AtmosphereProcessingState.PipeNet => ProcessPipeNets(ent),
|
||||
AtmosphereProcessingState.AtmosDevices => mapEnt is not null
|
||||
? ProcessAtmosDevices(ent, mapEnt.Value)
|
||||
: throw new ArgumentException(
|
||||
"An Entity<MapAtmosphereComponent> must be provided when benchmarking ProcessAtmosDevices."),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
ent.Comp1.ProcessingPaused = !processingPaused;
|
||||
|
||||
return processingPaused;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public float AtmosTickRate { get; private set; }
|
||||
public float Speedup { get; private set; }
|
||||
public float HeatScale { get; private set; }
|
||||
public bool DeltaPressureDamage { get; private set; }
|
||||
public int DeltaPressureParallelProcessPerIteration { get; private set; }
|
||||
public int DeltaPressureParallelBatchSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time between each atmos sub-update. If you are writing an atmos device, use AtmosDeviceUpdateEvent.dt
|
||||
@@ -55,6 +58,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
Subs.CVar(_cfg, CCVars.AtmosHeatScale, value => { HeatScale = value; InitializeGases(); }, true);
|
||||
Subs.CVar(_cfg, CCVars.ExcitedGroups, value => ExcitedGroups = value, true);
|
||||
Subs.CVar(_cfg, CCVars.ExcitedGroupsSpaceIsAllConsuming, value => ExcitedGroupsSpaceIsAllConsuming = value, true);
|
||||
Subs.CVar(_cfg, CCVars.DeltaPressureDamage, value => DeltaPressureDamage = value, true);
|
||||
Subs.CVar(_cfg, CCVars.DeltaPressureParallelToProcessPerIteration, value => DeltaPressureParallelProcessPerIteration = value, true);
|
||||
Subs.CVar(_cfg, CCVars.DeltaPressureParallelBatchSize, value => DeltaPressureParallelBatchSize = value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Threading;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of pairs of opposing directions we can have.
|
||||
/// This is Atmospherics.Directions / 2, since we always compare opposing directions
|
||||
/// (e.g. North vs South, East vs West, etc.).
|
||||
/// Used to determine the size of the opposing groups when processing delta pressure entities.
|
||||
/// </summary>
|
||||
private const int DeltaPressurePairCount = Atmospherics.Directions / 2;
|
||||
|
||||
/// <summary>
|
||||
/// The length to pre-allocate list/dicts of delta pressure entities on a <see cref="GridAtmosphereComponent"/>.
|
||||
/// </summary>
|
||||
public const int DeltaPressurePreAllocateLength = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Processes a singular entity, determining the pressures it's experiencing and applying damage based on that.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity to process.</param>
|
||||
/// <param name="gridAtmosComp">The <see cref="GridAtmosphereComponent"/> that belongs to the entity's GridUid.</param>
|
||||
private void ProcessDeltaPressureEntity(Entity<DeltaPressureComponent> ent, GridAtmosphereComponent gridAtmosComp)
|
||||
{
|
||||
if (!_random.Prob(ent.Comp.RandomDamageChance))
|
||||
return;
|
||||
|
||||
/*
|
||||
To make our comparisons a little bit faster, we take advantage of SIMD-accelerated methods
|
||||
in the NumericsHelpers class.
|
||||
|
||||
This involves loading our values into a span in the form of opposing pairs,
|
||||
so simple vector operations like min/max/abs can be performed on them.
|
||||
*/
|
||||
|
||||
var tiles = new TileAtmosphere?[Atmospherics.Directions];
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection)(1 << i);
|
||||
var offset = ent.Comp.CurrentPosition.Offset(direction);
|
||||
tiles[i] = gridAtmosComp.Tiles.GetValueOrDefault(offset);
|
||||
}
|
||||
|
||||
Span<float> pressures = stackalloc float[Atmospherics.Directions];
|
||||
|
||||
GetBulkTileAtmospherePressures(tiles, pressures);
|
||||
|
||||
Span<float> opposingGroupA = stackalloc float[DeltaPressurePairCount];
|
||||
Span<float> opposingGroupB = stackalloc float[DeltaPressurePairCount];
|
||||
Span<float> opposingGroupMax = stackalloc float[DeltaPressurePairCount];
|
||||
|
||||
// Directions are always in pairs: the number of directions is always even
|
||||
// (we must consider the future where Multi-Z is real)
|
||||
// Load values into opposing pairs.
|
||||
for (var i = 0; i < DeltaPressurePairCount; i++)
|
||||
{
|
||||
opposingGroupA[i] = pressures[i];
|
||||
opposingGroupB[i] = pressures[i + DeltaPressurePairCount];
|
||||
}
|
||||
|
||||
// TODO ATMOS: Needs to be changed to batch operations so that more operations can actually be done in parallel.
|
||||
|
||||
// Need to determine max pressure in opposing directions for absolute pressure calcs.
|
||||
NumericsHelpers.Max(opposingGroupA, opposingGroupB, opposingGroupMax);
|
||||
|
||||
// Calculate pressure differences between opposing directions.
|
||||
NumericsHelpers.Sub(opposingGroupA, opposingGroupB);
|
||||
NumericsHelpers.Abs(opposingGroupA);
|
||||
|
||||
var maxPressure = 0f;
|
||||
var maxDelta = 0f;
|
||||
for (var i = 0; i < DeltaPressurePairCount; i++)
|
||||
{
|
||||
maxPressure = MathF.Max(maxPressure, opposingGroupMax[i]);
|
||||
maxDelta = MathF.Max(maxDelta, opposingGroupA[i]);
|
||||
}
|
||||
|
||||
EnqueueDeltaPressureDamage(ent,
|
||||
gridAtmosComp,
|
||||
maxPressure,
|
||||
maxDelta);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A DeltaPressure helper method that retrieves the pressures of all gas mixtures
|
||||
/// in the given array of <see cref="TileAtmosphere"/>s, and stores the results in the
|
||||
/// provided <paramref name="pressures"/> span.
|
||||
/// The tiles array length is limited to Atmosphereics.Directions.
|
||||
/// </summary>
|
||||
/// <param name="tiles">The tiles array to find the pressures of.</param>
|
||||
/// <param name="pressures">The span to store the pressures to - this should be the same length
|
||||
/// as the tile array.</param>
|
||||
/// <remarks>This is for internal use of the DeltaPressure system -
|
||||
/// it may not be a good idea to use this generically.</remarks>
|
||||
private static void GetBulkTileAtmospherePressures(TileAtmosphere?[] tiles, Span<float> pressures)
|
||||
{
|
||||
#if DEBUG
|
||||
// Just in case someone tries to use this method incorrectly.
|
||||
if (tiles.Length != pressures.Length || tiles.Length != Atmospherics.Directions)
|
||||
throw new ArgumentException("Length of arrays must be the same and of Atmospherics.Directions length.");
|
||||
#endif
|
||||
|
||||
// This hardcoded direction limit is stopping goobers from
|
||||
// overflowing the stack with massive arrays.
|
||||
// If this method is pulled into a more generic place,
|
||||
// it should be replaced with method params.
|
||||
Span<float> mixtVol = stackalloc float[Atmospherics.Directions];
|
||||
Span<float> mixtTemp = stackalloc float[Atmospherics.Directions];
|
||||
Span<float> mixtMoles = stackalloc float[Atmospherics.Directions];
|
||||
Span<float> atmosR = stackalloc float[Atmospherics.Directions];
|
||||
|
||||
for (var i = 0; i < tiles.Length; i++)
|
||||
{
|
||||
if (tiles[i] is not { Air: { } mixture })
|
||||
{
|
||||
pressures[i] = 0f;
|
||||
|
||||
// To prevent any NaN/Div/0 errors, we just bite the bullet
|
||||
// and set everything to the lowest possible value.
|
||||
mixtVol[i] = 1;
|
||||
mixtTemp[i] = 1;
|
||||
mixtMoles[i] = float.Epsilon;
|
||||
atmosR[i] = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
mixtVol[i] = mixture.Volume;
|
||||
mixtTemp[i] = mixture.Temperature;
|
||||
mixtMoles[i] = mixture.TotalMoles;
|
||||
atmosR[i] = Atmospherics.R;
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieval of single tile pressures requires calling a get method for each tile,
|
||||
which does a bunch of scalar operations.
|
||||
|
||||
So we go ahead and batch-retrieve the pressures of all tiles
|
||||
and process them in bulk.
|
||||
*/
|
||||
NumericsHelpers.Multiply(mixtMoles, atmosR);
|
||||
NumericsHelpers.Multiply(mixtMoles, mixtTemp);
|
||||
NumericsHelpers.Divide(mixtMoles, mixtVol, pressures);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs data into a <see cref="DeltaPressureDamageResult"/> data struct and enqueues it
|
||||
/// into the <see cref="GridAtmosphereComponent.DeltaPressureDamageResults"/> queue for
|
||||
/// later processing.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity to enqueue if necessary.</param>
|
||||
/// <param name="gridAtmosComp">The <see cref="GridAtmosphereComponent"/>
|
||||
/// containing the queue.</param>
|
||||
/// <param name="pressure">The current absolute pressure being experienced by the entity.</param>
|
||||
/// <param name="delta">The current delta pressure being experienced by the entity.</param>
|
||||
private static void EnqueueDeltaPressureDamage(Entity<DeltaPressureComponent> ent,
|
||||
GridAtmosphereComponent gridAtmosComp,
|
||||
float pressure,
|
||||
float delta)
|
||||
{
|
||||
var aboveMinPressure = pressure > ent.Comp.MinPressure;
|
||||
var aboveMinDeltaPressure = delta > ent.Comp.MinPressureDelta;
|
||||
if (!aboveMinPressure && !aboveMinDeltaPressure)
|
||||
{
|
||||
ent.Comp.IsTakingDamage = false;
|
||||
return;
|
||||
}
|
||||
|
||||
gridAtmosComp.DeltaPressureDamageResults.Enqueue(new DeltaPressureDamageResult(ent,
|
||||
pressure,
|
||||
delta));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Job for solving DeltaPressure entities in parallel.
|
||||
/// Batches are given some index to start from, so each thread can simply just start at that index
|
||||
/// and process the next n entities in the list.
|
||||
/// </summary>
|
||||
/// <param name="system">The AtmosphereSystem instance.</param>
|
||||
/// <param name="atmosphere">The GridAtmosphereComponent to work with.</param>
|
||||
/// <param name="startIndex">The index in the DeltaPressureEntities list to start from.</param>
|
||||
/// <param name="cvarBatchSize">The batch size to use for this job.</param>
|
||||
private sealed class DeltaPressureParallelJob(
|
||||
AtmosphereSystem system,
|
||||
GridAtmosphereComponent atmosphere,
|
||||
int startIndex,
|
||||
int cvarBatchSize)
|
||||
: IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => cvarBatchSize;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
// The index is relative to the startIndex (because we can pause and resume computation),
|
||||
// so we need to add it to the startIndex.
|
||||
var actualIndex = startIndex + index;
|
||||
|
||||
if (actualIndex >= atmosphere.DeltaPressureEntities.Count)
|
||||
return;
|
||||
|
||||
var ent = atmosphere.DeltaPressureEntities[actualIndex];
|
||||
system.ProcessDeltaPressureEntity(ent, atmosphere);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct that holds the result of delta pressure damage processing for an entity.
|
||||
/// This is only created and enqueued when the entity needs to take damage.
|
||||
/// </summary>
|
||||
/// <param name="Ent">The entity to deal damage to.</param>
|
||||
/// <param name="Pressure">The current absolute pressure the entity is experiencing.</param>
|
||||
/// <param name="DeltaPressure">The current delta pressure the entity is experiencing.</param>
|
||||
public readonly record struct DeltaPressureDamageResult(
|
||||
Entity<DeltaPressureComponent> Ent,
|
||||
float Pressure,
|
||||
float DeltaPressure);
|
||||
|
||||
/// <summary>
|
||||
/// Does damage to an entity depending on the pressure experienced by it, based on the
|
||||
/// entity's <see cref="DeltaPressureComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity to apply damage to.</param>
|
||||
/// <param name="pressure">The absolute pressure being exerted on the entity.</param>
|
||||
/// <param name="deltaPressure">The delta pressure being exerted on the entity.</param>
|
||||
private void PerformDamage(Entity<DeltaPressureComponent> ent, float pressure, float deltaPressure)
|
||||
{
|
||||
var maxPressure = Math.Max(pressure - ent.Comp.MinPressure, deltaPressure - ent.Comp.MinPressureDelta);
|
||||
var appliedDamage = ScaleDamage(ent, ent.Comp.BaseDamage, maxPressure);
|
||||
|
||||
_damage.TryChangeDamage(ent, appliedDamage, ignoreResistances: true, interruptsDoAfters: false);
|
||||
ent.Comp.IsTakingDamage = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new DamageSpecifier scaled based on values on an entity with a DeltaPressureComponent.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity to base the manipulations off of (pull scaling type)</param>
|
||||
/// <param name="damage">The base damage specifier to scale.</param>
|
||||
/// <param name="pressure">The pressure being exerted on the entity.</param>
|
||||
/// <returns>A scaled DamageSpecifier.</returns>
|
||||
private static DamageSpecifier ScaleDamage(Entity<DeltaPressureComponent> ent, DamageSpecifier damage, float pressure)
|
||||
{
|
||||
var factor = ent.Comp.ScalingType switch
|
||||
{
|
||||
DeltaPressureDamageScalingType.Threshold => 1f,
|
||||
DeltaPressureDamageScalingType.Linear => pressure * ent.Comp.ScalingPower,
|
||||
DeltaPressureDamageScalingType.Log =>
|
||||
(float) Math.Log(pressure, ent.Comp.ScalingPower),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ent), "Invalid damage scaling type!"),
|
||||
};
|
||||
|
||||
return damage * factor;
|
||||
}
|
||||
}
|
||||
@@ -467,6 +467,66 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes all entities with a <see cref="DeltaPressureComponent"/>, doing damage to them
|
||||
/// depending on certain pressure differential conditions.
|
||||
/// </summary>
|
||||
/// <returns>True if we've finished processing all entities that required processing this run,
|
||||
/// otherwise, false.</returns>
|
||||
private bool ProcessDeltaPressure(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
var atmosphere = ent.Comp1;
|
||||
var count = atmosphere.DeltaPressureEntities.Count;
|
||||
if (!atmosphere.ProcessingPaused)
|
||||
{
|
||||
atmosphere.DeltaPressureCursor = 0;
|
||||
atmosphere.DeltaPressureDamageResults.Clear();
|
||||
}
|
||||
|
||||
var remaining = count - atmosphere.DeltaPressureCursor;
|
||||
var batchSize = Math.Max(50, DeltaPressureParallelProcessPerIteration);
|
||||
var toProcess = Math.Min(batchSize, remaining);
|
||||
|
||||
var timeCheck1 = 0;
|
||||
while (atmosphere.DeltaPressureCursor < count)
|
||||
{
|
||||
var job = new DeltaPressureParallelJob(this,
|
||||
atmosphere,
|
||||
atmosphere.DeltaPressureCursor,
|
||||
DeltaPressureParallelBatchSize);
|
||||
_parallel.ProcessNow(job, toProcess);
|
||||
|
||||
atmosphere.DeltaPressureCursor += toProcess;
|
||||
|
||||
if (timeCheck1++ < LagCheckIterations)
|
||||
continue;
|
||||
|
||||
timeCheck1 = 0;
|
||||
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
|
||||
return false;
|
||||
}
|
||||
|
||||
var timeCheck2 = 0;
|
||||
while (atmosphere.DeltaPressureDamageResults.TryDequeue(out var result))
|
||||
{
|
||||
PerformDamage(result.Ent,
|
||||
result.Pressure,
|
||||
result.DeltaPressure);
|
||||
|
||||
if (timeCheck2++ < LagCheckIterations)
|
||||
continue;
|
||||
|
||||
timeCheck2 = 0;
|
||||
// Process the rest next time.
|
||||
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessPipeNets(GridAtmosphereComponent atmosphere)
|
||||
{
|
||||
if (!atmosphere.ProcessingPaused)
|
||||
@@ -510,6 +570,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
num--;
|
||||
if (!ExcitedGroups)
|
||||
num--;
|
||||
if (!DeltaPressureDamage)
|
||||
num--;
|
||||
if (!Superconduction)
|
||||
num--;
|
||||
return num * AtmosTime;
|
||||
@@ -653,6 +715,18 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
atmosphere.ProcessingPaused = false;
|
||||
atmosphere.State = DeltaPressureDamage
|
||||
? AtmosphereProcessingState.DeltaPressure
|
||||
: AtmosphereProcessingState.Hotspots;
|
||||
continue;
|
||||
case AtmosphereProcessingState.DeltaPressure:
|
||||
if (!ProcessDeltaPressure(ent))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
atmosphere.ProcessingPaused = false;
|
||||
atmosphere.State = AtmosphereProcessingState.Hotspots;
|
||||
continue;
|
||||
@@ -721,6 +795,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
ActiveTiles,
|
||||
ExcitedGroups,
|
||||
HighPressureDelta,
|
||||
DeltaPressure,
|
||||
Hotspots,
|
||||
Superconductivity,
|
||||
PipeNet,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
@@ -15,6 +14,8 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Threading;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
@@ -27,6 +28,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
@@ -37,6 +39,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
[Dependency] public readonly PuddleSystem Puddle = default!;
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
|
||||
private const float ExposedUpdateDelay = 1f;
|
||||
private float _exposedTimer = 0f;
|
||||
|
||||
82
Content.Server/Atmos/EntitySystems/DeltaPressureSystem.cs
Normal file
82
Content.Server/Atmos/EntitySystems/DeltaPressureSystem.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// <para>System that handles <see cref="DeltaPressureComponent"/>.</para>
|
||||
///
|
||||
/// <para>Entities with a <see cref="DeltaPressureComponent"/> will take damage per atmostick
|
||||
/// depending on the pressure they experience.</para>
|
||||
///
|
||||
/// <para>DeltaPressure logic is mostly handled in a partial class in Atmospherics.
|
||||
/// This system handles the adding and removing of entities to a processing list,
|
||||
/// as well as any field changes via the API.</para>
|
||||
/// </summary>
|
||||
public sealed class DeltaPressureSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DeltaPressureComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<DeltaPressureComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
SubscribeLocalEvent<DeltaPressureComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<DeltaPressureComponent, MoveEvent>(OnMoveEvent);
|
||||
|
||||
SubscribeLocalEvent<DeltaPressureComponent, GridUidChangedEvent>(OnGridChanged);
|
||||
}
|
||||
|
||||
private void OnMoveEvent(Entity<DeltaPressureComponent> ent, ref MoveEvent args)
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
// May move off-grid, so, might as well protect against that.
|
||||
if (!TryComp<MapGridComponent>(xform.GridUid, out var mapGridComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ent.Comp.CurrentPosition = _map.CoordinatesToTile(xform.GridUid.Value, mapGridComponent, args.NewPosition);
|
||||
}
|
||||
|
||||
private void OnComponentInit(Entity<DeltaPressureComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
if (xform.GridUid == null)
|
||||
return;
|
||||
|
||||
_atmosphereSystem.TryAddDeltaPressureEntity(xform.GridUid.Value, ent);
|
||||
}
|
||||
|
||||
private void OnComponentShutdown(Entity<DeltaPressureComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
// Wasn't part of a list, so nothing to clean up.
|
||||
if (ent.Comp.GridUid == null)
|
||||
return;
|
||||
|
||||
_atmosphereSystem.TryRemoveDeltaPressureEntity(ent.Comp.GridUid.Value, ent);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<DeltaPressureComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (ent.Comp.IsTakingDamage)
|
||||
args.PushMarkup(Loc.GetString("window-taking-damage"));
|
||||
}
|
||||
|
||||
private void OnGridChanged(Entity<DeltaPressureComponent> ent, ref GridUidChangedEvent args)
|
||||
{
|
||||
if (args.OldGrid != null)
|
||||
{
|
||||
_atmosphereSystem.TryRemoveDeltaPressureEntity(args.OldGrid.Value, ent);
|
||||
}
|
||||
|
||||
if (args.NewGrid != null)
|
||||
{
|
||||
_atmosphereSystem.TryAddDeltaPressureEntity(args.NewGrid.Value, ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user