diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index e276965d43..856614d596 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -27,8 +27,5 @@ - - - diff --git a/Content.Server/_EE/Atmos/AtmosphereSystem.EE.API.cs b/Content.Server/_EE/Atmos/AtmosphereSystem.EE.API.cs new file mode 100644 index 0000000000..5f2fe4e926 --- /dev/null +++ b/Content.Server/_EE/Atmos/AtmosphereSystem.EE.API.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.Atmos.Components; +using Content.Shared.Atmos; + + +namespace Content.Server.Atmos.EntitySystems; + +/// +/// This handles... +/// +public partial class AtmosphereSystem +{ + public TileAtmosphere? GetTileAtmosphere(Entity grid, Vector2i tile) + { + if (!_atmosQuery.Resolve(grid, ref grid.Comp, false)) + return null; + + return grid.Comp.Tiles.TryGetValue(tile, out var atmosTile) ? atmosTile : null; + } + + public bool TryGetTileAtmosphere(Entity grid, Vector2i tile, [NotNullWhen(true)] out TileAtmosphere? tileAtmosphere) + { + if (!_atmosQuery.Resolve(grid, ref grid.Comp, false)) + { + tileAtmosphere = null; + return false; + } + + var success = grid.Comp.Tiles.TryGetValue(tile, out var atmosTile); + tileAtmosphere = success ? atmosTile : null; + return success; + } + + public TileEnumerator GetAdjacentTileAtmospheres(Entity grid, Vector2i tile, bool includeBlocked = false, bool excite = false) + { + if (!_atmosQuery.Resolve(grid, ref grid.Comp, false)) + return TileEnumerator.Empty; + + return !grid.Comp.Tiles.TryGetValue(tile, out var atmosTile) + ? TileEnumerator.Empty + : new(atmosTile.AdjacentTiles); + } +} diff --git a/Content.Server/_EE/Atmos/TileEnumerator.cs b/Content.Server/_EE/Atmos/TileEnumerator.cs new file mode 100644 index 0000000000..34f0cf1482 --- /dev/null +++ b/Content.Server/_EE/Atmos/TileEnumerator.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Atmos; + +namespace Content.Server.Atmos; + +public struct TileEnumerator +{ + public readonly TileAtmosphere?[] Tiles; + public int Index = 0; + + public static readonly TileEnumerator Empty = new([]); + + internal TileEnumerator(TileAtmosphere?[] tiles) + { + Tiles = tiles; + } + + public bool MoveNext([NotNullWhen(true)] out TileAtmosphere? tileAtmosphere) + { + while (Index < Tiles.Length) + { + tileAtmosphere = Tiles[Index++]; + + if (tileAtmosphere != null) + return true; + } + + tileAtmosphere = null; + return false; + } +} diff --git a/Content.Server/_EE/PressureDestructible/Components/PressureDestructibleComponent.cs b/Content.Server/_EE/PressureDestructible/Components/PressureDestructibleComponent.cs new file mode 100644 index 0000000000..944ea3e7ff --- /dev/null +++ b/Content.Server/_EE/PressureDestructible/Components/PressureDestructibleComponent.cs @@ -0,0 +1,30 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + + +namespace Content.Server._EE.PressureDestructible.Components; + + +/// +/// This is used in making pressure destroy structures. +/// +[RegisterComponent] +public sealed partial class PressureDestructibleComponent : Component +{ + /// + /// How much pressure could this entity reasonably withstand? + /// + [DataField] + public float MaxPressureDifferential { get; set; } + + /// + /// How much damage, as a percentage, will the entity take? + /// + [DataField] + public int Damage { get; set; } = 20; + + [DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextUpdate { get; set; } + + [DataField] + public TimeSpan CheckInterval { get; set; } = TimeSpan.FromSeconds(5); +} diff --git a/Content.Server/_EE/PressureDestructible/Components/PressureDestructibleImmuneComponent.cs b/Content.Server/_EE/PressureDestructible/Components/PressureDestructibleImmuneComponent.cs new file mode 100644 index 0000000000..7889dd3096 --- /dev/null +++ b/Content.Server/_EE/PressureDestructible/Components/PressureDestructibleImmuneComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server._EE.PressureDestructible.Components; + + +/// +/// This is used for setting an entity as immune to pressure destructible. +/// +[RegisterComponent] +public sealed partial class PressureDestructibleImmuneComponent : Component; diff --git a/Content.Server/_EE/PressureDestructible/EntitySystems/PressureDestructibleSystem.cs b/Content.Server/_EE/PressureDestructible/EntitySystems/PressureDestructibleSystem.cs new file mode 100644 index 0000000000..2502eac9d5 --- /dev/null +++ b/Content.Server/_EE/PressureDestructible/EntitySystems/PressureDestructibleSystem.cs @@ -0,0 +1,96 @@ +using Content.Server._EE.PressureDestructible.Components; +using Content.Server.Atmos; +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Server.GameObjects; +using Robust.Shared.Timing; + +namespace Content.Server._EE.PressureDestructible.EntitySystems; + + +/// +/// This handles destroying entities based on pressure if it has a PressureDestructible component +/// +public sealed class PressureDestructibleSystem : EntitySystem +{ + [Dependency] private TransformSystem _transform = default!; + [Dependency] private AtmosphereSystem _atmosphere = default!; + [Dependency] private DamageableSystem _damageable = default!; + [Dependency] private IGameTiming _gameTiming = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityManager.EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var pressureDestructible, out var damageable)) + { + if (_gameTiming.CurTime < pressureDestructible.NextUpdate || pressureDestructible.MaxPressureDifferential == 0) + continue; + + pressureDestructible.NextUpdate = _gameTiming.CurTime + pressureDestructible.CheckInterval; + + if (uid == EntityUid.Invalid || !Exists(uid)) + continue; + + var grid = _transform.GetGrid(uid); + var currentTile = _transform.GetGridOrMapTilePosition(uid); + + if (grid == null || grid == EntityUid.Invalid || !Exists(grid)) + continue; + + var hasAtmos = _atmosphere.TryGetTileAtmosphere((grid.Value, null), currentTile, out _); + + if (!hasAtmos) + continue; + + var adjacentTiles = new HashSet(); + var directionsToCheck = new[] { AtmosDirection.North, AtmosDirection.East, AtmosDirection.South, AtmosDirection.West }; + + foreach (var direction in directionsToCheck) + { + var adjacentTile = currentTile.Offset(direction); + + if (!_atmosphere.TryGetTileAtmosphere(grid.Value, adjacentTile, out var adjacentAtmos)) + continue; + + adjacentTiles.Add(adjacentAtmos); + } + + var greatestDifference = 0f; + TileAtmosphere? largestPressureTile = null; + + foreach (var tileAtmos in adjacentTiles) + { + var largestPressure = largestPressureTile?.Air?.Pressure ?? 0; + var tileMix = _atmosphere.GetTileMixture(grid.Value, null, tileAtmos.GridIndices, true); + var tilePressure = tileMix?.Pressure!; + + if (tilePressure == null) + return; + + var difference = MathF.Abs(largestPressure - (float) tilePressure); + + if (tilePressure == 0) + continue; + + if (difference > greatestDifference) + greatestDifference = difference; + + if (tilePressure > largestPressure) + largestPressureTile = tileAtmos; + } + + if (greatestDifference < pressureDestructible.MaxPressureDifferential || HasComp(uid)) + continue; + + var damageSpecifier = damageable.Damage; + var currentDamage = damageSpecifier["Blunt"]; + + damageSpecifier.DamageDict["Blunt"] = currentDamage + pressureDestructible.Damage; + _damageable.SetDamage(uid, damageable, damageSpecifier); + } + } +} diff --git a/Resources/Prototypes/Entities/Objects/Misc/inflatable_wall.yml b/Resources/Prototypes/Entities/Objects/Misc/inflatable_wall.yml index d11aa714cb..9b77cc689d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/inflatable_wall.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/inflatable_wall.yml @@ -5,6 +5,8 @@ components: - type: Clickable - type: InteractionOutline + - type: PressureDestructible + maxPressureDifferential: 12500.0 - type: Sprite sprite: Objects/Misc/inflatable_wall.rsi state: inflatable_wall @@ -47,6 +49,8 @@ components: - type: Clickable - type: InteractionOutline + - type: PressureDestructible + maxPressureDifferential: 12500.0 - type: Sprite sprite: Objects/Misc/inflatable_door.rsi state: closed diff --git a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml index 1bd0356e95..8afe869344 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml @@ -52,6 +52,8 @@ price: 60 - type: RadiationBlocker resistance: 4 + - type: PressureDestructible + maxPressureDifferential: 3000 - type: entity id: PlasmaWindowDirectional diff --git a/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml b/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml index 9914ad8399..0201a9cca3 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml @@ -55,6 +55,8 @@ trackAllDamage: true damageOverlay: sprite: Structures/Windows/cracks.rsi + - type: PressureDestructible + maxPressureDifferential: 100000.0 - type: entity parent: ReinforcedWindow @@ -134,6 +136,8 @@ acts: [ "Destruction" ] - type: StaticPrice price: 22 + - type: PressureDestructible + maxPressureDifferential: 5000.0 - type: entity parent: ReinforcedWindow diff --git a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml index 3c35d03a43..35c103c7f0 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml @@ -55,6 +55,8 @@ sprite: Structures/Windows/cracks.rsi - type: StaticPrice price: 132 + - type: PressureDestructible + maxPressureDifferential: 0.0 - type: entity id: PlasmaReinforcedWindowDirectional diff --git a/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml b/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml index 2796c89c1a..f06120d45f 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml @@ -52,6 +52,8 @@ price: 140 - type: RadiationBlocker resistance: 10 + - type: PressureDestructible + maxPressureDifferential: 0.0 - type: entity id: UraniumReinforcedWindowDirectional @@ -100,6 +102,8 @@ acts: [ "Destruction" ] - type: StaticPrice price: 70 + - type: PressureDestructible + maxPressureDifferential: 7500.0 - type: entity parent: ReinforcedUraniumWindow diff --git a/Resources/Prototypes/Entities/Structures/Windows/window.yml b/Resources/Prototypes/Entities/Structures/Windows/window.yml index 224e14e4b2..87e1bb24a4 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/window.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/window.yml @@ -91,6 +91,8 @@ allowedVerbs: - KnockOn - LickObject + - type: PressureDestructible + maxPressureDifferential: 2000.0 - type: entity id: WindowRCDResistant @@ -194,6 +196,8 @@ allowedVerbs: - KnockOn - LickObject + - type: PressureDestructible + maxPressureDifferential: 3000.0 - type: entity id: WindowDirectionalRCDResistant