mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-15 03:31:30 +01:00
Reduce explosion airtight cache memory usage (#40912)
* Reduce explosion airtight cache memory usage This means you can happily add explosion prototypes again New approach has the tolerance value data in a shared storage with reference counting. * Oops fix index removal * Remove debug code and fix merge conflicts * Also address my other review * Oh it's in two places lmao --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d0a784b9e6
commit
42b33ddd93
@@ -0,0 +1,100 @@
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores data for airtight explosion traversal on a <see cref="MapGridComponent"/> entity.
|
||||
/// </summary>
|
||||
/// <seealso cref="ExplosionSystem"/>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(ExplosionSystem), Other = AccessPermissions.None)]
|
||||
public sealed partial class ExplosionAirtightGridComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for every tile on the current grid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Intentionally not saved.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<Vector2i, TileData> Tiles = new();
|
||||
|
||||
/// <summary>
|
||||
/// Data struct that describes the explosion-blocking airtight entities on a tile.
|
||||
/// </summary>
|
||||
public struct TileData
|
||||
{
|
||||
/// <summary>
|
||||
/// Which index into the tolerance cache of <see cref="ExplosionSystem"/> this tile is using.
|
||||
/// </summary>
|
||||
public required int ToleranceCacheIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Which directions this tile is blocking explosions in. Bitflag field.
|
||||
/// </summary>
|
||||
public required AtmosDirection BlockedDirections;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A set of tolerance values
|
||||
/// </summary>
|
||||
public struct ToleranceValues : IEquatable<ToleranceValues>
|
||||
{
|
||||
/// <summary>
|
||||
/// Special value that indicates the entity is "invulnerable" against a specific explosion type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Here to deal with the limited range of <see cref="FixedPoint2"/> over typical floats.
|
||||
/// </remarks>
|
||||
public static readonly FixedPoint2 Invulnerable = FixedPoint2.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// The intensities at which explosions of each type can instantly break through an entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is an array, with the index of each value corresponding to the "explosion type ID" cached by
|
||||
/// <see cref="ExplosionSystem"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Values are stored as <see cref="FixedPoint2"/> to avoid possible precision issues resulting in
|
||||
/// different-but-almost-identical tolerance values wasting memory.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If a value is <see cref="Invulnerable"/>, that indicates the tile is invulnerable.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public required FixedPoint2[] Values;
|
||||
|
||||
public bool Equals(ToleranceValues other)
|
||||
{
|
||||
return Values.AsSpan().SequenceEqual(other.Values);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ToleranceValues other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hc = new HashCode();
|
||||
hc.AddArray(Values);
|
||||
return hc.ToHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(ToleranceValues left, ToleranceValues right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ToleranceValues left, ToleranceValues right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Map.Components;
|
||||
using static Content.Server.Explosion.Components.ExplosionAirtightGridComponent;
|
||||
using static Content.Server.Explosion.EntitySystems.ExplosionSystem;
|
||||
|
||||
namespace Content.Server.Explosion.EntitySystems;
|
||||
@@ -11,6 +12,8 @@ namespace Content.Server.Explosion.EntitySystems;
|
||||
/// </summary>
|
||||
public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
{
|
||||
private readonly ExplosionSystem _explosionSystem;
|
||||
|
||||
public Entity<MapGridComponent> Grid;
|
||||
private bool _needToTransform = false;
|
||||
|
||||
@@ -45,7 +48,8 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
Dictionary<Vector2i, NeighborFlag> edgeTiles,
|
||||
EntityUid? referenceGrid,
|
||||
Matrix3x2 spaceMatrix,
|
||||
Angle spaceAngle)
|
||||
Angle spaceAngle,
|
||||
ExplosionSystem explosionSystem)
|
||||
{
|
||||
Grid = grid;
|
||||
_airtightMap = airtightMap;
|
||||
@@ -53,6 +57,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
_intensityStepSize = intensityStepSize;
|
||||
_typeIndex = typeIndex;
|
||||
_edgeTiles = edgeTiles;
|
||||
_explosionSystem = explosionSystem;
|
||||
|
||||
// initialise SpaceTiles
|
||||
foreach (var (tile, spaceNeighbors) in _edgeTiles)
|
||||
@@ -193,11 +198,11 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
NewBlockedTiles.Add(tile);
|
||||
|
||||
// At what explosion iteration would this blocker be destroyed?
|
||||
var required = tileData.ExplosionTolerance[_typeIndex];
|
||||
var required = _explosionSystem.GetToleranceValues(tileData.ToleranceCacheIndex).Values[_typeIndex];
|
||||
if (required > _maxIntensity)
|
||||
return; // blocker is never destroyed.
|
||||
|
||||
var clearIteration = iteration + (int) MathF.Ceiling(required / _intensityStepSize);
|
||||
var clearIteration = iteration + (int) MathF.Ceiling((float)required / _intensityStepSize);
|
||||
if (FreedTileLists.TryGetValue(clearIteration, out var list))
|
||||
list.Add(tile);
|
||||
else
|
||||
@@ -261,13 +266,13 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
var blockedDirections = AtmosDirection.Invalid;
|
||||
float sealIntegrity = 0;
|
||||
FixedPoint2 sealIntegrity = 0;
|
||||
|
||||
// Note that if (grid, tile) is not a valid key, then airtight.BlockedDirections will default to 0 (no blocked directions)
|
||||
if (_airtightMap.TryGetValue(tile, out var tileData))
|
||||
{
|
||||
blockedDirections = tileData.BlockedDirections;
|
||||
sealIntegrity = tileData.ExplosionTolerance[_typeIndex];
|
||||
sealIntegrity = _explosionSystem.GetToleranceValues(tileData.ToleranceCacheIndex).Values[_typeIndex];
|
||||
}
|
||||
|
||||
// First, yield any neighboring tiles that are not blocked by airtight entities on this tile
|
||||
@@ -290,7 +295,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
continue;
|
||||
|
||||
// At what explosion iteration would this blocker be destroyed?
|
||||
var clearIteration = iteration + (int) MathF.Ceiling(sealIntegrity / _intensityStepSize);
|
||||
var clearIteration = iteration + (int) MathF.Ceiling((float) sealIntegrity / _intensityStepSize);
|
||||
|
||||
// Get the delayed neighbours list
|
||||
if (!_delayedNeighbors.TryGetValue(clearIteration, out var list))
|
||||
|
||||
@@ -1,44 +1,59 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Explosion;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Server.Explosion.Components.ExplosionAirtightGridComponent;
|
||||
|
||||
namespace Content.Server.Explosion.EntitySystems;
|
||||
|
||||
public sealed partial class ExplosionSystem
|
||||
{
|
||||
private readonly Dictionary<string, int> _explosionTypes = new();
|
||||
// We keep track of which tiles are airtight, and how much damage from explosions those airtight blockers can take.
|
||||
// This is quite complicated, as the data effectively needs to be tracked *per tile*, *per explosion type*.
|
||||
// To avoid wasting significant memory, we calculate the values and share the actual backing storage of it.
|
||||
// Stored values are reference counted so they can be evicted when no longer needed.
|
||||
// At the time of writing, this compacts the storage for Box Station from ~5500 tolerance value sets to 13,
|
||||
// at round start.
|
||||
|
||||
// Use integers instead of prototype IDs for storage of explosion data.
|
||||
// This allows us to replace a Dictionary<string, FixedPoint2> with just a FixedPoint2[].
|
||||
private readonly Dictionary<ProtoId<ExplosionPrototype>, int> _explosionTypes = new();
|
||||
// Index to look up if we already have an existing set of tolerance values stored, so the data can be shared.
|
||||
private readonly Dictionary<ToleranceValues, int> _toleranceIndex = new();
|
||||
// Storage for tolerance values. Entries form a free linked list when not occupied by a set of real values.
|
||||
private ValueList<CacheEntry> _toleranceData;
|
||||
// First free position in _toleranceData.
|
||||
// -1 indicates there are no free slots left and the storage must be expanded.
|
||||
private int _freeListHead = -1;
|
||||
|
||||
private void InitAirtightMap()
|
||||
{
|
||||
// Currently explosion prototype hot-reload isn't supported, as it would involve completely re-computing the
|
||||
// airtight map. Could be done, just not yet implemented.
|
||||
_explosionTypes.Clear();
|
||||
|
||||
// for storing airtight entity damage thresholds for all anchored airtight entities, we will use integers in
|
||||
// place of id-strings. This initializes the string <--> id association.
|
||||
// This allows us to replace a Dictionary<string, float> with just a float[].
|
||||
int index = 0;
|
||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ExplosionPrototype>())
|
||||
{
|
||||
// TODO EXPLOSION
|
||||
// just make this a field on the prototype
|
||||
_explosionTypes.Add(prototype.ID, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// The explosion intensity required to break an entity depends on the explosion type. So it is stored in a
|
||||
// Dictionary<string, float>
|
||||
//
|
||||
// Hence, each tile has a tuple (Dictionary<string, float>, AtmosDirection). This specifies what directions are
|
||||
// blocked, and how intense a given explosion type needs to be in order to destroy ALL airtight entities on that
|
||||
// tile. This is the TileData struct.
|
||||
//
|
||||
// We then need this data for every tile on a grid. So this mess of a variable maps the Grid ID and Vector2i grid
|
||||
// indices to this tile-data struct.
|
||||
private Dictionary<EntityUid, Dictionary<Vector2i, TileData>> _airtightMap = new();
|
||||
private void ReloadExplosionPrototypes(PrototypesReloadedEventArgs prototypesReloadedEventArgs)
|
||||
{
|
||||
if (!prototypesReloadedEventArgs.Modified.Contains(typeof(ExplosionPrototype)))
|
||||
return;
|
||||
|
||||
InitAirtightMap();
|
||||
ReloadMap();
|
||||
}
|
||||
|
||||
public void UpdateAirtightMap(EntityUid gridId, Vector2i tile, MapGridComponent? grid = null)
|
||||
{
|
||||
@@ -46,6 +61,12 @@ public sealed partial class ExplosionSystem
|
||||
UpdateAirtightMap(gridId, grid, tile);
|
||||
}
|
||||
|
||||
[Access(typeof(ExplosionGridTileFlood))]
|
||||
public ToleranceValues GetToleranceValues(int idx)
|
||||
{
|
||||
return _toleranceData[idx].Values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the map of explosion blockers.
|
||||
/// </summary>
|
||||
@@ -58,11 +79,12 @@ public sealed partial class ExplosionSystem
|
||||
/// </remarks>
|
||||
public void UpdateAirtightMap(EntityUid gridId, MapGridComponent grid, Vector2i tile)
|
||||
{
|
||||
var tolerance = new float[_explosionTypes.Count];
|
||||
var blockedDirections = AtmosDirection.Invalid;
|
||||
var airtightGrid = EnsureComp<ExplosionAirtightGridComponent>(gridId);
|
||||
|
||||
if (!_airtightMap.ContainsKey(gridId))
|
||||
_airtightMap[gridId] = new();
|
||||
// Calculate tile new airtight state.
|
||||
|
||||
var tolerance = new FixedPoint2[_explosionTypes.Count];
|
||||
var blockedDirections = AtmosDirection.Invalid;
|
||||
|
||||
var anchoredEnumerator = _map.GetAnchoredEntitiesEnumerator(gridId, grid, tile);
|
||||
|
||||
@@ -72,17 +94,97 @@ public sealed partial class ExplosionSystem
|
||||
continue;
|
||||
|
||||
blockedDirections |= airtight.AirBlockedDirection;
|
||||
var entityTolerances = GetExplosionTolerance(uid.Value);
|
||||
for (var i = 0; i < tolerance.Length; i++)
|
||||
{
|
||||
tolerance[i] = Math.Max(tolerance[i], entityTolerances[i]);
|
||||
}
|
||||
GetExplosionTolerance(uid.Value, tolerance);
|
||||
}
|
||||
|
||||
if (blockedDirections != AtmosDirection.Invalid)
|
||||
_airtightMap[gridId][tile] = new(tolerance, blockedDirections);
|
||||
// Log.Info($"UPDATE {gridId}/{tile}: {blockedDirections}");
|
||||
|
||||
if (blockedDirections == AtmosDirection.Invalid)
|
||||
{
|
||||
// No longer airtight
|
||||
|
||||
if (!airtightGrid.Tiles.Remove(tile, out var tileData))
|
||||
{
|
||||
// Did not have this tile before and after, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Removing tile data.
|
||||
DecrementRefCount(tileData.ToleranceCacheIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
ref var tileEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(airtightGrid.Tiles, tile, out var existed);
|
||||
var cacheKey = new ToleranceValues { Values = tolerance };
|
||||
|
||||
// Remove previous tolerance reference if necessary.
|
||||
if (existed)
|
||||
{
|
||||
ref var prevEntry = ref _toleranceData[tileEntry.ToleranceCacheIndex];
|
||||
if (prevEntry.Values == cacheKey)
|
||||
{
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
|
||||
DecrementRefCount(tileEntry.ToleranceCacheIndex);
|
||||
}
|
||||
|
||||
ref var newCacheIndex = ref CollectionsMarshal.GetValueRefOrAddDefault(_toleranceIndex, cacheKey, out existed);
|
||||
if (existed)
|
||||
{
|
||||
_toleranceData[newCacheIndex].RefCount += 1;
|
||||
}
|
||||
else
|
||||
_airtightMap[gridId].Remove(tile);
|
||||
{
|
||||
if (_freeListHead < 0)
|
||||
ExpandCache();
|
||||
|
||||
newCacheIndex = _freeListHead;
|
||||
ref var newCacheEntry = ref _toleranceData[newCacheIndex];
|
||||
_freeListHead = newCacheEntry.RefCount;
|
||||
|
||||
newCacheEntry.Values = cacheKey;
|
||||
newCacheEntry.RefCount = 1;
|
||||
}
|
||||
|
||||
tileEntry = new TileData
|
||||
{
|
||||
BlockedDirections = blockedDirections,
|
||||
ToleranceCacheIndex = newCacheIndex,
|
||||
};
|
||||
}
|
||||
|
||||
private void ExpandCache()
|
||||
{
|
||||
var newCacheSize = Math.Max(8, _toleranceData.Count * 2);
|
||||
var curSize = _toleranceData.Count;
|
||||
|
||||
_toleranceData.EnsureLength(newCacheSize);
|
||||
for (var i = curSize; i < newCacheSize; i++)
|
||||
{
|
||||
_toleranceData[i].RefCount = _freeListHead;
|
||||
_freeListHead = i;
|
||||
}
|
||||
}
|
||||
|
||||
private void DecrementRefCount(int index)
|
||||
{
|
||||
ref var cacheEntry = ref _toleranceData[index];
|
||||
|
||||
DebugTools.Assert(cacheEntry.RefCount > 0);
|
||||
cacheEntry.RefCount -= 1;
|
||||
|
||||
if (cacheEntry.RefCount == 0)
|
||||
{
|
||||
var prevValue = cacheEntry.Values;
|
||||
cacheEntry.Values = default;
|
||||
cacheEntry.RefCount = _freeListHead;
|
||||
_freeListHead = index;
|
||||
|
||||
var result = _toleranceIndex.Remove(prevValue);
|
||||
DebugTools.Assert(result, "Failed to removed 0 refcounted index!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,7 +208,7 @@ public sealed partial class ExplosionSystem
|
||||
/// <summary>
|
||||
/// Return a dictionary that specifies how intense a given explosion type needs to be in order to destroy an entity.
|
||||
/// </summary>
|
||||
public float[] GetExplosionTolerance(EntityUid uid)
|
||||
private void GetExplosionTolerance(EntityUid uid, Span<FixedPoint2> explosionTolerance)
|
||||
{
|
||||
// How much total damage is needed to destroy this entity? This also includes "break" behaviors. This ASSUMES
|
||||
// that this will result in a non-airtight entity.Entities that ONLY break via construction graph node changes
|
||||
@@ -117,14 +219,14 @@ public sealed partial class ExplosionSystem
|
||||
totalDamageTarget = _destructibleSystem.DestroyedAt(uid, destructible);
|
||||
}
|
||||
|
||||
var explosionTolerance = new float[_explosionTypes.Count];
|
||||
if (totalDamageTarget == FixedPoint2.MaxValue || !_damageableQuery.TryGetComponent(uid, out var damageable))
|
||||
{
|
||||
for (var i = 0; i < explosionTolerance.Length; i++)
|
||||
{
|
||||
explosionTolerance[i] = float.MaxValue;
|
||||
explosionTolerance[i] = ToleranceValues.Invulnerable;
|
||||
}
|
||||
return explosionTolerance;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// What multiple of each explosion type damage set will result in the damage exceeding the required amount? This
|
||||
@@ -157,38 +259,43 @@ public sealed partial class ExplosionSystem
|
||||
damagePerIntensity += value * mod * Math.Max(0, ev.DamageCoefficient);
|
||||
}
|
||||
|
||||
explosionTolerance[index] = damagePerIntensity > 0
|
||||
var toleranceValue = damagePerIntensity > 0
|
||||
? (float) ((totalDamageTarget - damageable.TotalDamage) / damagePerIntensity)
|
||||
: float.MaxValue;
|
||||
}
|
||||
: ToleranceValues.Invulnerable;
|
||||
|
||||
return explosionTolerance;
|
||||
explosionTolerance[index] = toleranceValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data struct that describes the explosion-blocking airtight entities on a tile.
|
||||
/// </summary>
|
||||
public struct TileData
|
||||
private void OnAirtightGridRemoved(EntityUid entity)
|
||||
{
|
||||
public TileData(float[] explosionTolerance, AtmosDirection blockedDirections)
|
||||
if (!TryComp(entity, out ExplosionAirtightGridComponent? airtightGrid))
|
||||
return;
|
||||
|
||||
foreach (var tile in airtightGrid.Tiles.Values)
|
||||
{
|
||||
ExplosionTolerance = explosionTolerance;
|
||||
BlockedDirections = blockedDirections;
|
||||
DecrementRefCount(tile.ToleranceCacheIndex);
|
||||
}
|
||||
|
||||
public float[] ExplosionTolerance;
|
||||
public AtmosDirection BlockedDirections = AtmosDirection.Invalid;
|
||||
RemComp<ExplosionAirtightGridComponent>(entity);
|
||||
}
|
||||
|
||||
public override void ReloadMap()
|
||||
{
|
||||
foreach (var(grid, dict) in _airtightMap)
|
||||
var enumerator = EntityQueryEnumerator<ExplosionAirtightGridComponent, MapGridComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var airtightComp, out var mapGrid))
|
||||
{
|
||||
var comp = Comp<MapGridComponent>(grid);
|
||||
foreach (var index in dict.Keys)
|
||||
foreach (var pos in airtightComp.Tiles.Keys)
|
||||
{
|
||||
UpdateAirtightMap(grid, comp, index);
|
||||
UpdateAirtightMap(uid, pos, mapGrid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct CacheEntry
|
||||
{
|
||||
public ToleranceValues Values;
|
||||
public int RefCount; // Doubles as freelist chain
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed partial class ExplosionSystem
|
||||
|
||||
private void OnGridRemoved(GridRemovalEvent ev)
|
||||
{
|
||||
_airtightMap.Remove(ev.EntityUid);
|
||||
OnAirtightGridRemoved(ev.EntityUid);
|
||||
_gridEdges.Remove(ev.EntityUid);
|
||||
|
||||
// this should be a small enough set that iterating all of them is fine
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Robust.Shared.Map;
|
||||
@@ -40,11 +41,7 @@ public sealed partial class ExplosionSystem
|
||||
if (totalIntensity <= 0 || slope <= 0)
|
||||
return null;
|
||||
|
||||
if (!_explosionTypes.TryGetValue(typeID, out var typeIndex))
|
||||
{
|
||||
Log.Error("Attempted to spawn explosion using a prototype that was not defined during initialization. Explosion prototype hot-reload is not currently supported.");
|
||||
return null;
|
||||
}
|
||||
var typeIndex = _explosionTypes[typeID];
|
||||
|
||||
Vector2i initialTile;
|
||||
EntityUid? epicentreGrid = null;
|
||||
@@ -103,8 +100,7 @@ public sealed partial class ExplosionSystem
|
||||
// set up the initial `gridData` instance
|
||||
encounteredGrids.Add(epicentreGrid.Value);
|
||||
|
||||
if (!_airtightMap.TryGetValue(epicentreGrid.Value, out var airtightMap))
|
||||
airtightMap = new();
|
||||
var airtightMap = CompOrNull<ExplosionAirtightGridComponent>(epicentreGrid)?.Tiles ?? new();
|
||||
|
||||
var initialGridData = new ExplosionGridTileFlood(
|
||||
(epicentreGrid.Value, Comp<MapGridComponent>(epicentreGrid.Value)),
|
||||
@@ -115,7 +111,8 @@ public sealed partial class ExplosionSystem
|
||||
_gridEdges[epicentreGrid.Value],
|
||||
referenceGrid,
|
||||
spaceMatrix,
|
||||
spaceAngle);
|
||||
spaceAngle,
|
||||
this);
|
||||
|
||||
gridData[epicentreGrid.Value] = initialGridData;
|
||||
|
||||
@@ -192,8 +189,7 @@ public sealed partial class ExplosionSystem
|
||||
// is this a new grid, for which we must create a new explosion data set
|
||||
if (!gridData.TryGetValue(grid, out var data))
|
||||
{
|
||||
if (!_airtightMap.TryGetValue(grid, out var airtightMap))
|
||||
airtightMap = new();
|
||||
var airtightMap = CompOrNull<ExplosionAirtightGridComponent>(grid)?.Tiles ?? new();
|
||||
|
||||
data = new ExplosionGridTileFlood(
|
||||
(grid, Comp<MapGridComponent>(grid)),
|
||||
@@ -204,7 +200,8 @@ public sealed partial class ExplosionSystem
|
||||
_gridEdges[grid],
|
||||
referenceGrid,
|
||||
spaceMatrix,
|
||||
spaceAngle);
|
||||
spaceAngle,
|
||||
this);
|
||||
|
||||
gridData[grid] = data;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
_destructibleQuery = GetEntityQuery<DestructibleComponent>();
|
||||
_damageableQuery = GetEntityQuery<DamageableComponent>();
|
||||
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
|
||||
_prototypeManager.PrototypesReloaded += ReloadExplosionPrototypes;
|
||||
}
|
||||
|
||||
private void OnReset(RoundRestartCleanupEvent ev)
|
||||
@@ -122,6 +124,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
base.Shutdown();
|
||||
_nodeGroupSystem.PauseUpdating = false;
|
||||
_pathfindingSystem.PauseUpdating = false;
|
||||
_prototypeManager.PrototypesReloaded -= ReloadExplosionPrototypes;
|
||||
}
|
||||
|
||||
private void RelayedResistance(EntityUid uid, ExplosionResistanceComponent component,
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
# Does not currently support prototype hot-reloading. See comments in c# file.
|
||||
|
||||
# Note that for every explosion type you define, explosions & nukes will start performing worse
|
||||
# You should only define a new explopsion type if you really need to
|
||||
#
|
||||
# If you just want to modify properties other than `damagePerIntensity`, it'd be better to
|
||||
# split off explosion damage & explosion visuals/effects into their own separate prototypes.
|
||||
|
||||
- type: explosion
|
||||
id: Default
|
||||
damagePerIntensity:
|
||||
@@ -135,7 +127,3 @@
|
||||
texturePath: /Textures/Effects/fire.rsi
|
||||
fireStates: 3
|
||||
fireStacks: 2
|
||||
|
||||
# STOP
|
||||
# BEFORE YOU ADD MORE EXPLOSION TYPES CONSIDER IF AN EXISTING ONE IS SUITABLE
|
||||
# ADDING NEW ONES IS PROHIBITIVELY EXPENSIVE
|
||||
|
||||
Reference in New Issue
Block a user