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:
174
Content.Benchmarks/DeltaPressureBenchmark.cs
Normal file
174
Content.Benchmarks/DeltaPressureBenchmark.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns N number of entities with a <see cref="DeltaPressureComponent"/> and
|
||||
/// simulates them for a number of ticks M.
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
[GcServer(true)]
|
||||
//[MemoryDiagnoser]
|
||||
//[ThreadingDiagnoser]
|
||||
public class DeltaPressureBenchmark
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of entities (windows, really) to spawn with a <see cref="DeltaPressureComponent"/>.
|
||||
/// </summary>
|
||||
[Params(1, 10, 100, 1000, 5000, 10000, 50000, 100000)]
|
||||
public int EntityCount;
|
||||
|
||||
/// <summary>
|
||||
/// Number of entities that each parallel processing job will handle.
|
||||
/// </summary>
|
||||
// [Params(1, 10, 100, 1000, 5000, 10000)] For testing how multithreading parameters affect performance (THESE TESTS TAKE 16+ HOURS TO RUN)
|
||||
[Params(10)]
|
||||
public int BatchSize;
|
||||
|
||||
/// <summary>
|
||||
/// Number of entities to process per iteration in the DeltaPressure
|
||||
/// processing loop.
|
||||
/// </summary>
|
||||
// [Params(100, 1000, 5000, 10000, 50000)]
|
||||
[Params(1000)]
|
||||
public int EntitiesPerIteration;
|
||||
|
||||
private readonly EntProtoId _windowProtoId = "Window";
|
||||
private readonly EntProtoId _wallProtoId = "WallPlastitaniumIndestructible";
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private SharedMapSystem _map = default!;
|
||||
private IRobustRandom _random = default!;
|
||||
private IConfigurationManager _cvar = default!;
|
||||
private ITileDefinitionManager _tileDefMan = default!;
|
||||
private AtmosphereSystem _atmospereSystem = default!;
|
||||
|
||||
private Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>
|
||||
_testEnt;
|
||||
|
||||
[GlobalSetup]
|
||||
public async Task SetupAsync()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
var mapdata = await _pair.CreateTestMap();
|
||||
|
||||
_entMan = server.ResolveDependency<IEntityManager>();
|
||||
_map = _entMan.System<SharedMapSystem>();
|
||||
_random = server.ResolveDependency<IRobustRandom>();
|
||||
_cvar = server.ResolveDependency<IConfigurationManager>();
|
||||
_tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
_atmospereSystem = _entMan.System<AtmosphereSystem>();
|
||||
|
||||
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
|
||||
|
||||
_cvar.SetCVar(CCVars.DeltaPressureParallelToProcessPerIteration, EntitiesPerIteration);
|
||||
_cvar.SetCVar(CCVars.DeltaPressureParallelBatchSize, BatchSize);
|
||||
|
||||
var plating = _tileDefMan["Plating"].TileId;
|
||||
|
||||
/*
|
||||
Basically, we want to have a 5-wide grid of tiles.
|
||||
Edges are walled, and the length of the grid is determined by N + 2.
|
||||
Windows should only touch the top and bottom walls, and each other.
|
||||
*/
|
||||
|
||||
var length = EntityCount + 2; // ensures we can spawn exactly N windows between side walls
|
||||
const int height = 5;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Fill required tiles (extend grid) with plating
|
||||
for (var x = 0; x < length; x++)
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn perimeter walls and windows row in the middle (y = 2)
|
||||
const int midY = height / 2;
|
||||
for (var x = 0; x < length; x++)
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
|
||||
|
||||
var isPerimeter = x == 0 || x == length - 1 || y == 0 || y == height - 1;
|
||||
if (isPerimeter)
|
||||
{
|
||||
_entMan.SpawnEntity(_wallProtoId, coords);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Spawn windows only on the middle row, spanning interior (excluding side walls)
|
||||
if (y == midY)
|
||||
{
|
||||
_entMan.SpawnEntity(_windowProtoId, coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Next we run the fixgridatmos command to ensure that we have some air on our grid.
|
||||
// Wait a little bit as well.
|
||||
// TODO: Unhardcode command magic string when fixgridatmos is an actual command we can ref and not just
|
||||
// a stamp-on in AtmosphereSystem.
|
||||
await _pair.WaitCommand("fixgridatmos " + mapdata.Grid.Owner, 1);
|
||||
|
||||
var uid = mapdata.Grid.Owner;
|
||||
_testEnt = new Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>(
|
||||
uid,
|
||||
_entMan.GetComponent<GridAtmosphereComponent>(uid),
|
||||
_entMan.GetComponent<GasTileOverlayComponent>(uid),
|
||||
_entMan.GetComponent<MapGridComponent>(uid),
|
||||
_entMan.GetComponent<TransformComponent>(uid));
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task PerformFullProcess()
|
||||
{
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
while (!_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure)) { }
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task PerformSingleRunProcess()
|
||||
{
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure);
|
||||
});
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public async Task CleanupAsync()
|
||||
{
|
||||
await _pair.DisposeAsync();
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
}
|
||||
417
Content.IntegrationTests/Tests/Atmos/DeltaPressureTest.cs
Normal file
417
Content.IntegrationTests/Tests/Atmos/DeltaPressureTest.cs
Normal file
@@ -0,0 +1,417 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Atmos;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for AtmosphereSystem.DeltaPressure and surrounding systems
|
||||
/// handling the DeltaPressureComponent.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DeltaPressureSystem))]
|
||||
public sealed class DeltaPressureTest
|
||||
{
|
||||
#region Prototypes
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
parent: BaseStructure
|
||||
id: DeltaPressureSolidTest
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
snap:
|
||||
- Wall
|
||||
components:
|
||||
- type: Physics
|
||||
bodyType: Static
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: ""-0.5,-0.5,0.5,0.5""
|
||||
mask:
|
||||
- FullTileMask
|
||||
layer:
|
||||
- WallLayer
|
||||
density: 1000
|
||||
- type: Airtight
|
||||
- type: DeltaPressure
|
||||
minPressure: 15000
|
||||
minPressureDelta: 10000
|
||||
scalingType: Threshold
|
||||
baseDamage:
|
||||
types:
|
||||
Structural: 1000
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 300
|
||||
behaviors:
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
Girder:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ ""Destruction"" ]
|
||||
|
||||
- type: entity
|
||||
parent: DeltaPressureSolidTest
|
||||
id: DeltaPressureSolidTestNoAutoJoin
|
||||
components:
|
||||
- type: DeltaPressure
|
||||
autoJoinProcessingList: false
|
||||
|
||||
- type: entity
|
||||
parent: DeltaPressureSolidTest
|
||||
id: DeltaPressureSolidTestAbsolute
|
||||
components:
|
||||
- type: DeltaPressure
|
||||
minPressure: 10000
|
||||
minPressureDelta: 15000
|
||||
scalingType: Threshold
|
||||
baseDamage:
|
||||
types:
|
||||
Structural: 1000
|
||||
";
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly ResPath _testMap = new("Maps/Test/Atmospherics/DeltaPressure/deltapressuretest.yml");
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that an entity with a DeltaPressureComponent with autoJoinProcessingList
|
||||
/// set to true is automatically added to the DeltaPressure processing list
|
||||
/// on the grid's GridAtmosphereComponent.
|
||||
///
|
||||
/// Also asserts that an entity with a DeltaPressureComponent with autoJoinProcessingList
|
||||
/// set to false is not automatically added to the DeltaPressure processing list.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ProcessingListAutoJoinTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.EntMan;
|
||||
var mapLoader = entMan.System<MapLoaderSystem>();
|
||||
var atmosphereSystem = entMan.System<AtmosphereSystem>();
|
||||
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
|
||||
|
||||
Entity<MapGridComponent> grid = default;
|
||||
Entity<DeltaPressureComponent> dpEnt;
|
||||
|
||||
// Load our test map in and assert that it exists.
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
|
||||
$"Failed to load map {_testMap}.");
|
||||
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
|
||||
#pragma warning restore NUnit2045
|
||||
|
||||
grid = gridSet.First();
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
|
||||
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
|
||||
|
||||
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have automatically joined!");
|
||||
entMan.DeleteEntity(uid);
|
||||
Assert.That(!atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was still in processing list after deletion!");
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that an entity that doesn't need to be damaged by DeltaPressure
|
||||
/// is not damaged by DeltaPressure.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ProcessingDeltaStandbyTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.EntMan;
|
||||
var mapLoader = entMan.System<MapLoaderSystem>();
|
||||
var atmosphereSystem = entMan.System<AtmosphereSystem>();
|
||||
var transformSystem = entMan.System<SharedTransformSystem>();
|
||||
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
|
||||
|
||||
Entity<MapGridComponent> grid = default;
|
||||
Entity<DeltaPressureComponent> dpEnt = default;
|
||||
TileAtmosphere tile = null!;
|
||||
AtmosDirection direction = default;
|
||||
|
||||
// Load our test map in and assert that it exists.
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
|
||||
$"Failed to load map {_testMap}.");
|
||||
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
|
||||
#pragma warning restore NUnit2045
|
||||
|
||||
grid = gridSet.First();
|
||||
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
|
||||
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
|
||||
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
|
||||
});
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
|
||||
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
|
||||
|
||||
direction = (AtmosDirection)(1 << i);
|
||||
var offsetIndices = indices.Offset(direction);
|
||||
tile = gridAtmosComp.Tiles[offsetIndices];
|
||||
|
||||
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
|
||||
|
||||
var toPressurize = dpEnt.Comp!.MinPressureDelta - 10;
|
||||
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
|
||||
|
||||
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
|
||||
// Entity should exist, if it took one tick of damage then it should be instantly destroyed.
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(!entMan.Deleted(dpEnt), $"{dpEnt} should still exist after experiencing non-threshold pressure from {direction} side!");
|
||||
tile.Air!.Clear();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that an entity that needs to be damaged by DeltaPressure
|
||||
/// is damaged by DeltaPressure when the pressure is above the threshold.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ProcessingDeltaDamageTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.EntMan;
|
||||
var mapLoader = entMan.System<MapLoaderSystem>();
|
||||
var atmosphereSystem = entMan.System<AtmosphereSystem>();
|
||||
var transformSystem = entMan.System<SharedTransformSystem>();
|
||||
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
|
||||
|
||||
Entity<MapGridComponent> grid = default;
|
||||
Entity<DeltaPressureComponent> dpEnt = default;
|
||||
TileAtmosphere tile = null!;
|
||||
AtmosDirection direction = default;
|
||||
|
||||
// Load our test map in and assert that it exists.
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
|
||||
$"Failed to load map {_testMap}.");
|
||||
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
|
||||
#pragma warning restore NUnit2045
|
||||
|
||||
grid = gridSet.First();
|
||||
});
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Need to spawn an entity each run to ensure it works for all directions.
|
||||
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
|
||||
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
|
||||
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
|
||||
|
||||
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
|
||||
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
|
||||
|
||||
direction = (AtmosDirection)(1 << i);
|
||||
var offsetIndices = indices.Offset(direction);
|
||||
tile = gridAtmosComp.Tiles[offsetIndices];
|
||||
|
||||
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
|
||||
|
||||
var toPressurize = dpEnt.Comp!.MinPressureDelta + 10;
|
||||
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
|
||||
|
||||
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
|
||||
// Entity should exist, if it took one tick of damage then it should be instantly destroyed.
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(entMan.Deleted(dpEnt), $"{dpEnt} still exists after experiencing threshold pressure from {direction} side!");
|
||||
tile.Air!.Clear();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that an entity that doesn't need to be damaged by DeltaPressure
|
||||
/// is not damaged by DeltaPressure when using absolute pressure thresholds.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ProcessingAbsoluteStandbyTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.EntMan;
|
||||
var mapLoader = entMan.System<MapLoaderSystem>();
|
||||
var atmosphereSystem = entMan.System<AtmosphereSystem>();
|
||||
var transformSystem = entMan.System<SharedTransformSystem>();
|
||||
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
|
||||
|
||||
Entity<MapGridComponent> grid = default;
|
||||
Entity<DeltaPressureComponent> dpEnt = default;
|
||||
TileAtmosphere tile = null!;
|
||||
AtmosDirection direction = default;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
|
||||
$"Failed to load map {_testMap}.");
|
||||
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
|
||||
#pragma warning restore NUnit2045
|
||||
grid = gridSet.First();
|
||||
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTestAbsolute", new EntityCoordinates(grid.Owner, Vector2.Zero));
|
||||
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
|
||||
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
|
||||
});
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
|
||||
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
|
||||
|
||||
direction = (AtmosDirection)(1 << i);
|
||||
var offsetIndices = indices.Offset(direction);
|
||||
tile = gridAtmosComp.Tiles[offsetIndices];
|
||||
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
|
||||
|
||||
var toPressurize = dpEnt.Comp!.MinPressure - 10; // just below absolute threshold
|
||||
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
|
||||
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(!entMan.Deleted(dpEnt), $"{dpEnt} should still exist after experiencing non-threshold absolute pressure from {direction} side!");
|
||||
tile.Air!.Clear();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that an entity that needs to be damaged by DeltaPressure
|
||||
/// is damaged by DeltaPressure when the pressure is above the absolute threshold.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ProcessingAbsoluteDamageTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.EntMan;
|
||||
var mapLoader = entMan.System<MapLoaderSystem>();
|
||||
var atmosphereSystem = entMan.System<AtmosphereSystem>();
|
||||
var transformSystem = entMan.System<SharedTransformSystem>();
|
||||
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
|
||||
|
||||
Entity<MapGridComponent> grid = default;
|
||||
Entity<DeltaPressureComponent> dpEnt = default;
|
||||
TileAtmosphere tile = null!;
|
||||
AtmosDirection direction = default;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
|
||||
$"Failed to load map {_testMap}.");
|
||||
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
|
||||
#pragma warning restore NUnit2045
|
||||
grid = gridSet.First();
|
||||
});
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Spawn fresh entity each iteration to verify all directions work
|
||||
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTestAbsolute", new EntityCoordinates(grid.Owner, Vector2.Zero));
|
||||
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
|
||||
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
|
||||
|
||||
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
|
||||
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
|
||||
|
||||
direction = (AtmosDirection)(1 << i);
|
||||
var offsetIndices = indices.Offset(direction);
|
||||
tile = gridAtmosComp.Tiles[offsetIndices];
|
||||
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
|
||||
|
||||
// Above absolute threshold but below delta threshold to ensure absolute alone causes damage
|
||||
var toPressurize = dpEnt.Comp!.MinPressure + 10;
|
||||
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
|
||||
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(entMan.Deleted(dpEnt), $"{dpEnt} still exists after experiencing threshold absolute pressure from {direction} side!");
|
||||
tile.Air!.Clear();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(30);
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,4 +150,31 @@ public sealed partial class CCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AtmosTankFragment =
|
||||
CVarDef.Create("atmos.max_explosion_range", 26f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether atmospherics will process delta-pressure damage on entities with a DeltaPressureComponent.
|
||||
/// Entities with this component will take damage if they are exposed to a pressure difference
|
||||
/// above the minimum pressure threshold defined in the component.
|
||||
/// </summary>
|
||||
// TODO: Needs CVARs for global configuration, like min pressure, max damage, etc.
|
||||
public static readonly CVarDef<bool> DeltaPressureDamage =
|
||||
CVarDef.Create("atmos.delta_pressure_damage", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Number of entities to submit for parallel processing per processing run.
|
||||
/// Low numbers may suffer from thinning out the work per job and leading to threads waiting,
|
||||
/// or seeing a lot of threading overhead.
|
||||
/// High numbers may cause Atmospherics to exceed its time budget per tick, as it will not
|
||||
/// check its time often enough to know if it's exceeding it.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DeltaPressureParallelToProcessPerIteration =
|
||||
CVarDef.Create("atmos.delta_pressure_parallel_process_per_iteration", 1000, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Number of entities to process per processing job.
|
||||
/// Low numbers may cause Atmospherics to see high threading overhead,
|
||||
/// high numbers may cause Atmospherics to distribute the work unevenly.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DeltaPressureParallelBatchSize =
|
||||
CVarDef.Create("atmos.delta_pressure_parallel_batch_size", 10, CVar.SERVERONLY);
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
window-taking-damage = [color=orange]It's straining under pressure![/color]
|
||||
@@ -0,0 +1,234 @@
|
||||
meta:
|
||||
format: 7
|
||||
category: Map
|
||||
engineVersion: 265.0.0
|
||||
forkId: ""
|
||||
forkVersion: ""
|
||||
time: 08/16/2025 22:09:01
|
||||
entityCount: 27
|
||||
maps:
|
||||
- 1
|
||||
grids:
|
||||
- 2
|
||||
orphans: []
|
||||
nullspace: []
|
||||
tilemap:
|
||||
1: Space
|
||||
0: Plating
|
||||
entities:
|
||||
- proto: ""
|
||||
entities:
|
||||
- uid: 1
|
||||
components:
|
||||
- type: MetaData
|
||||
name: Map Entity
|
||||
- type: Transform
|
||||
- type: Map
|
||||
mapPaused: True
|
||||
- type: GridTree
|
||||
- type: Broadphase
|
||||
- type: OccluderTree
|
||||
- uid: 2
|
||||
components:
|
||||
- type: MetaData
|
||||
name: grid
|
||||
- type: Transform
|
||||
pos: -0.33581543,-0.640625
|
||||
parent: 1
|
||||
- type: MapGrid
|
||||
chunks:
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAA==
|
||||
version: 7
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAA==
|
||||
version: 7
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
version: 7
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAA==
|
||||
version: 7
|
||||
- type: Broadphase
|
||||
- type: Physics
|
||||
bodyStatus: InAir
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
- type: Fixtures
|
||||
fixtures: {}
|
||||
- type: OccluderTree
|
||||
- type: SpreaderGrid
|
||||
- type: Shuttle
|
||||
dampingModifier: 0.25
|
||||
- type: ImplicitRoof
|
||||
- type: GridPathfinding
|
||||
- type: Gravity
|
||||
gravityShakeSound: !type:SoundPathSpecifier
|
||||
path: /Audio/Effects/alert.ogg
|
||||
- type: DecalGrid
|
||||
chunkCollection:
|
||||
version: 2
|
||||
nodes: []
|
||||
- type: GridAtmosphere
|
||||
version: 2
|
||||
data:
|
||||
tiles:
|
||||
0,0:
|
||||
0: 19
|
||||
0,-1:
|
||||
0: 4096
|
||||
-1,0:
|
||||
0: 8
|
||||
uniqueMixes:
|
||||
- volume: 2500
|
||||
temperature: 293.15
|
||||
moles:
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
chunkSize: 4
|
||||
- type: GasTileOverlay
|
||||
- type: RadiationGridResistance
|
||||
- proto: AtmosFixBlockerMarker
|
||||
entities:
|
||||
- uid: 23
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 0.5,1.5
|
||||
parent: 2
|
||||
- uid: 24
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 0.5,0.5
|
||||
parent: 2
|
||||
- uid: 25
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 0.5,-0.5
|
||||
parent: 2
|
||||
- uid: 26
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -0.5,0.5
|
||||
parent: 2
|
||||
- uid: 27
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 1.5,0.5
|
||||
parent: 2
|
||||
- proto: WallPlastitaniumIndestructible
|
||||
entities:
|
||||
- uid: 3
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -1.5,2.5
|
||||
parent: 2
|
||||
- uid: 4
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -0.5,2.5
|
||||
parent: 2
|
||||
- uid: 5
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 0.5,2.5
|
||||
parent: 2
|
||||
- uid: 6
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 1.5,2.5
|
||||
parent: 2
|
||||
- uid: 7
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 2.5,2.5
|
||||
parent: 2
|
||||
- uid: 8
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 2.5,1.5
|
||||
parent: 2
|
||||
- uid: 9
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 2.5,0.5
|
||||
parent: 2
|
||||
- uid: 10
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 2.5,-0.5
|
||||
parent: 2
|
||||
- uid: 11
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 2.5,-1.5
|
||||
parent: 2
|
||||
- uid: 12
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 1.5,-1.5
|
||||
parent: 2
|
||||
- uid: 13
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 0.5,-1.5
|
||||
parent: 2
|
||||
- uid: 14
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -0.5,-1.5
|
||||
parent: 2
|
||||
- uid: 15
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -1.5,-1.5
|
||||
parent: 2
|
||||
- uid: 16
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -1.5,-0.5
|
||||
parent: 2
|
||||
- uid: 17
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -1.5,0.5
|
||||
parent: 2
|
||||
- uid: 18
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -1.5,1.5
|
||||
parent: 2
|
||||
- uid: 19
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -0.5,1.5
|
||||
parent: 2
|
||||
- uid: 20
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 1.5,1.5
|
||||
parent: 2
|
||||
- uid: 21
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 1.5,-0.5
|
||||
parent: 2
|
||||
- uid: 22
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -0.5,-0.5
|
||||
parent: 2
|
||||
...
|
||||
@@ -163,6 +163,10 @@
|
||||
noAirWhenFullyAirBlocked: false
|
||||
airBlockedDirection:
|
||||
- South
|
||||
- type: DeltaPressure
|
||||
minPressure: 250
|
||||
minPressureDelta: 187.5
|
||||
scalingType: Threshold
|
||||
- type: Construction
|
||||
graph: Windoor
|
||||
node: windoor
|
||||
@@ -235,6 +239,10 @@
|
||||
- type: Construction
|
||||
graph: Windoor
|
||||
node: windoorSecure
|
||||
- type: DeltaPressure
|
||||
minPressure: 3750
|
||||
minPressureDelta: 2500
|
||||
scalingType: Threshold
|
||||
- type: StaticPrice
|
||||
price: 350
|
||||
- type: Tag
|
||||
@@ -304,6 +312,10 @@
|
||||
- type: Construction
|
||||
graph: Windoor
|
||||
node: pwindoor
|
||||
- type: DeltaPressure
|
||||
minPressure: 18750
|
||||
minPressureDelta: 12500
|
||||
scalingType: Threshold
|
||||
- type: StaticPrice
|
||||
price: 500
|
||||
- type: RadiationBlocker
|
||||
@@ -370,6 +382,10 @@
|
||||
- type: Construction
|
||||
graph: Windoor
|
||||
node: pwindoorSecure
|
||||
- type: DeltaPressure
|
||||
minPressure: 37500
|
||||
minPressureDelta: 25000
|
||||
scalingType: Threshold
|
||||
- type: StaticPrice
|
||||
price: 500
|
||||
- type: RadiationBlocker
|
||||
@@ -438,6 +454,10 @@
|
||||
max: 2
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: DeltaPressure
|
||||
minPressure: 18750
|
||||
minPressureDelta: 12500
|
||||
scalingType: Threshold
|
||||
- type: Construction
|
||||
graph: Windoor
|
||||
node: uwindoor
|
||||
@@ -504,6 +524,10 @@
|
||||
max: 2
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: DeltaPressure
|
||||
minPressure: 37500
|
||||
minPressureDelta: 25000
|
||||
scalingType: Threshold
|
||||
- type: Construction
|
||||
graph: Windoor
|
||||
node: uwindoorSecure
|
||||
|
||||
@@ -48,6 +48,11 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: DeltaPressure
|
||||
minPressure: 75000
|
||||
minPressureDelta: 50000
|
||||
scalingType: Linear
|
||||
scalingPower: 0.0005
|
||||
- type: StaticPrice
|
||||
price: 100
|
||||
- type: RadiationBlocker
|
||||
@@ -104,6 +109,10 @@
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: DeltaPressure
|
||||
minPressure: 18750
|
||||
minPressureDelta: 12500
|
||||
scalingType: Threshold
|
||||
- type: StaticPrice
|
||||
price: 50
|
||||
- type: RadiationBlocker
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: DeltaPressure
|
||||
minPressure: 15000
|
||||
minPressureDelta: 10000
|
||||
scalingType: Threshold
|
||||
|
||||
- type: entity
|
||||
id: WindowReinforcedDirectional
|
||||
@@ -113,6 +117,9 @@
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: DeltaPressure
|
||||
minPressure: 3750
|
||||
minPressureDelta: 2500
|
||||
- type: StaticPrice
|
||||
price: 22.5
|
||||
|
||||
|
||||
@@ -53,6 +53,11 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: DeltaPressure
|
||||
minPressure: 150000
|
||||
minPressureDelta: 100000
|
||||
scalingType: Linear
|
||||
scalingPower: 0.0001
|
||||
- type: StaticPrice
|
||||
price: 132
|
||||
|
||||
@@ -111,6 +116,10 @@
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: DeltaPressure
|
||||
minPressure: 37500
|
||||
minPressureDelta: 25000
|
||||
scalingType: Threshold
|
||||
- type: StaticPrice
|
||||
price: 66
|
||||
|
||||
|
||||
@@ -48,6 +48,11 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: DeltaPressure
|
||||
minPressure: 150000
|
||||
minPressureDelta: 100000
|
||||
scalingType: Linear
|
||||
scalingPower: 0.0001
|
||||
- type: StaticPrice
|
||||
price: 215
|
||||
- type: RadiationBlocker
|
||||
@@ -106,6 +111,10 @@
|
||||
max: 2
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: DeltaPressure
|
||||
minPressure: 37500
|
||||
minPressureDelta: 25000
|
||||
scalingType: Threshold
|
||||
- type: StaticPrice
|
||||
price: 110
|
||||
- type: RadiationBlocker
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: DeltaPressure
|
||||
minPressure: 15000
|
||||
minPressureDelta: 10000
|
||||
scalingType: Linear
|
||||
scalingPower: 0.0005
|
||||
- type: StaticPrice
|
||||
price: 150
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: DeltaPressure
|
||||
minPressure: 75000
|
||||
minPressureDelta: 50000
|
||||
scalingType: Linear
|
||||
scalingPower: 0.0005
|
||||
- type: StaticPrice
|
||||
price: 200
|
||||
- type: RadiationBlocker
|
||||
@@ -99,6 +104,10 @@
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: DeltaPressure
|
||||
minPressure: 18750
|
||||
minPressureDelta: 12500
|
||||
scalingType: Threshold
|
||||
- type: StaticPrice
|
||||
price: 100
|
||||
- type: RadiationBlocker
|
||||
|
||||
@@ -75,6 +75,11 @@
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: Airtight
|
||||
- type: DeltaPressure
|
||||
minPressure: 1000
|
||||
minPressureDelta: 750
|
||||
scalingType: Linear
|
||||
scalingPower: 0.0005
|
||||
- type: IconSmooth
|
||||
key: windows
|
||||
base: window
|
||||
@@ -206,6 +211,10 @@
|
||||
noAirWhenFullyAirBlocked: false
|
||||
airBlockedDirection:
|
||||
- South
|
||||
- type: DeltaPressure
|
||||
minPressure: 250
|
||||
minPressureDelta: 187.5
|
||||
scalingType: Threshold
|
||||
- type: Construction
|
||||
graph: WindowDirectional
|
||||
node: windowDirectional
|
||||
|
||||
Reference in New Issue
Block a user