From a4575511084c1472e677b2dfbd44360de90710ef Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 23 Feb 2025 18:23:00 +1100 Subject: [PATCH] Roof data rework (#35388) --- Content.Client/Light/RoofOverlay.cs | 26 ++----- .../Overlays/StencilOverlay.Weather.cs | 4 +- Content.Client/Weather/WeatherSystem.cs | 4 +- .../Light/Components/IsRoofComponent.cs | 13 ++++ .../Light/Components/RoofComponent.cs | 10 ++- .../Light/EntitySystems/SharedRoofSystem.cs | 74 ++++++++++++++++--- Content.Shared/Maps/ContentTileDefinition.cs | 7 -- .../Parallax/Biomes/Layers/BiomeTileLayer.cs | 7 -- .../Parallax/Biomes/SharedBiomeSystem.cs | 6 +- Content.Shared/Weather/SharedWeatherSystem.cs | 7 +- .../Entities/Structures/Walls/walls.yml | 1 + .../Prototypes/Procedural/biome_templates.yml | 1 - 12 files changed, 107 insertions(+), 53 deletions(-) create mode 100644 Content.Shared/Light/Components/IsRoofComponent.cs diff --git a/Content.Client/Light/RoofOverlay.cs b/Content.Client/Light/RoofOverlay.cs index 5543103cdc..0648f8624f 100644 --- a/Content.Client/Light/RoofOverlay.cs +++ b/Content.Client/Light/RoofOverlay.cs @@ -1,10 +1,12 @@ using System.Numerics; using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Content.Shared.Maps; using Robust.Client.Graphics; using Robust.Shared.Enums; using Robust.Shared.Map; using Robust.Shared.Map.Components; +using Robust.Shared.Map.Enumerators; using Robust.Shared.Physics; namespace Content.Client.Light; @@ -17,9 +19,9 @@ public sealed class RoofOverlay : Overlay private readonly EntityLookupSystem _lookup; private readonly SharedMapSystem _mapSystem; + private readonly SharedRoofSystem _roof = default!; private readonly SharedTransformSystem _xformSystem; - private readonly HashSet> _occluders = new(); private List> _grids = new(); public override OverlaySpace Space => OverlaySpace.BeforeLighting; @@ -33,6 +35,7 @@ public sealed class RoofOverlay : Overlay _lookup = _entManager.System(); _mapSystem = _entManager.System(); + _roof = _entManager.System(); _xformSystem = _entManager.System(); ZIndex = ContentZIndex; @@ -86,29 +89,14 @@ public sealed class RoofOverlay : Overlay worldHandle.SetTransform(matty); var tileEnumerator = _mapSystem.GetTilesEnumerator(grid.Owner, grid, bounds); + var roofEnt = (grid.Owner, grid.Comp, roof); // Due to stencilling we essentially draw on unrooved tiles while (tileEnumerator.MoveNext(out var tileRef)) { - if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == 0x0) + if (!_roof.IsRooved(roofEnt, tileRef.GridIndices)) { - // Check if the tile is occluded in which case hide it anyway. - // This is to avoid lit walls bleeding over to unlit tiles. - _occluders.Clear(); - _lookup.GetLocalEntitiesIntersecting(grid.Owner, tileRef.GridIndices, _occluders); - var found = false; - - foreach (var occluder in _occluders) - { - if (!occluder.Comp.Enabled) - continue; - - found = true; - break; - } - - if (!found) - continue; + continue; } var local = _lookup.GetLocalBounds(tileRef, grid.Comp.TileSize); diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs index ad69522dfd..b1a521433a 100644 --- a/Content.Client/Overlays/StencilOverlay.Weather.cs +++ b/Content.Client/Overlays/StencilOverlay.Weather.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Shared.Light.Components; using Content.Shared.Weather; using Robust.Client.Graphics; using Robust.Shared.Map.Components; @@ -34,11 +35,12 @@ public sealed partial class StencilOverlay var matrix = _transform.GetWorldMatrix(grid, xformQuery); var matty = Matrix3x2.Multiply(matrix, invMatrix); worldHandle.SetTransform(matty); + _entManager.TryGetComponent(grid.Owner, out RoofComponent? roofComp); foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB)) { // Ignored tiles for stencil - if (_weather.CanWeatherAffect(grid.Owner, grid, tile)) + if (_weather.CanWeatherAffect(grid.Owner, grid, tile, roofComp)) { continue; } diff --git a/Content.Client/Weather/WeatherSystem.cs b/Content.Client/Weather/WeatherSystem.cs index 975831392c..26def25a15 100644 --- a/Content.Client/Weather/WeatherSystem.cs +++ b/Content.Client/Weather/WeatherSystem.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Shared.Light.Components; using Content.Shared.Weather; using Robust.Client.Audio; using Robust.Client.GameObjects; @@ -57,6 +58,7 @@ public sealed class WeatherSystem : SharedWeatherSystem // Work out tiles nearby to determine volume. if (TryComp(entXform.GridUid, out var grid)) { + TryComp(entXform.GridUid, out RoofComponent? roofComp); var gridId = entXform.GridUid.Value; // FloodFill to the nearest tile and use that for audio. var seed = _mapSystem.GetTileRef(gridId, grid, entXform.Coordinates); @@ -71,7 +73,7 @@ public sealed class WeatherSystem : SharedWeatherSystem if (!visited.Add(node.GridIndices)) continue; - if (!CanWeatherAffect(entXform.GridUid.Value, grid, node)) + if (!CanWeatherAffect(entXform.GridUid.Value, grid, node, roofComp)) { // Add neighbors // TODO: Ideally we pick some deterministically random direction and use that diff --git a/Content.Shared/Light/Components/IsRoofComponent.cs b/Content.Shared/Light/Components/IsRoofComponent.cs new file mode 100644 index 0000000000..d64793f358 --- /dev/null +++ b/Content.Shared/Light/Components/IsRoofComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +/// +/// Counts the tile this entity on as being rooved. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class IsRoofComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Enabled = true; +} diff --git a/Content.Shared/Light/Components/RoofComponent.cs b/Content.Shared/Light/Components/RoofComponent.cs index 0e2adf527c..47de333af0 100644 --- a/Content.Shared/Light/Components/RoofComponent.cs +++ b/Content.Shared/Light/Components/RoofComponent.cs @@ -3,11 +3,19 @@ using Robust.Shared.GameStates; namespace Content.Shared.Light.Components; /// -/// Will draw shadows over tiles flagged as roof tiles on the attached map. +/// Will draw shadows over tiles flagged as roof tiles on the attached grid. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class RoofComponent : Component { + public const int ChunkSize = 8; + [DataField, AutoNetworkedField] public Color Color = Color.Black; + + /// + /// Chunk origin and bitmask of value in chunk. + /// + [DataField, AutoNetworkedField] + public Dictionary Data = new(); } diff --git a/Content.Shared/Light/EntitySystems/SharedRoofSystem.cs b/Content.Shared/Light/EntitySystems/SharedRoofSystem.cs index d06b5bcb0c..2d19b8ba87 100644 --- a/Content.Shared/Light/EntitySystems/SharedRoofSystem.cs +++ b/Content.Shared/Light/EntitySystems/SharedRoofSystem.cs @@ -10,33 +10,85 @@ namespace Content.Shared.Light.EntitySystems; /// public abstract class SharedRoofSystem : EntitySystem { - [Dependency] private readonly SharedMapSystem _maps = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + + private HashSet> _roofSet = new(); + + /// + /// Returns whether the specified tile is roof-occupied. + /// + /// Returns false if no data or not rooved. + public bool IsRooved(Entity grid, Vector2i index) + { + var roof = grid.Comp2; + var chunkOrigin = SharedMapSystem.GetChunkIndices(index, RoofComponent.ChunkSize); + + if (roof.Data.TryGetValue(chunkOrigin, out var bitMask)) + { + var chunkRelative = SharedMapSystem.GetChunkRelative(index, RoofComponent.ChunkSize); + var bitFlag = (ulong) 1 << (chunkRelative.X + chunkRelative.Y * RoofComponent.ChunkSize); + + var isRoof = (bitMask & bitFlag) == bitFlag; + + // Early out, otherwise check for components on tile. + if (isRoof) + return true; + } + + _roofSet.Clear(); + _lookup.GetLocalEntitiesIntersecting(grid.Owner, index, _roofSet); + + foreach (var isRoofEnt in _roofSet) + { + if (!isRoofEnt.Comp.Enabled) + continue; + + return true; + } + + return false; + } public void SetRoof(Entity grid, Vector2i index, bool value) { if (!Resolve(grid, ref grid.Comp1, ref grid.Comp2, false)) return; - if (!_maps.TryGetTile(grid.Comp1, index, out var tile)) - return; + var chunkOrigin = SharedMapSystem.GetChunkIndices(index, RoofComponent.ChunkSize); + var roof = grid.Comp2; - var mask = (tile.Flags & (byte)TileFlag.Roof); - var rooved = mask != 0x0; + if (!roof.Data.TryGetValue(chunkOrigin, out var chunkData)) + { + // No value to remove so leave it. + if (!value) + { + return; + } - if (rooved == value) - return; + chunkData = 0; + } - Tile newTile; + var chunkRelative = SharedMapSystem.GetChunkRelative(index, RoofComponent.ChunkSize); + var bitFlag = (ulong) 1 << (chunkRelative.X + chunkRelative.Y * RoofComponent.ChunkSize); if (value) { - newTile = tile.WithFlag((byte)(tile.Flags | (ushort)TileFlag.Roof)); + // Already set + if ((chunkData & bitFlag) == bitFlag) + return; + + chunkData |= bitFlag; } else { - newTile = tile.WithFlag((byte)(tile.Flags & ~(ushort)TileFlag.Roof)); + // Not already set + if ((chunkData & bitFlag) == 0x0) + return; + + chunkData &= ~bitFlag; } - _maps.SetTile((grid.Owner, grid.Comp1), index, newTile); + roof.Data[chunkOrigin] = chunkData; + Dirty(grid.Owner, roof); } } diff --git a/Content.Shared/Maps/ContentTileDefinition.cs b/Content.Shared/Maps/ContentTileDefinition.cs index 86ceac77be..a9ad016b87 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -120,11 +120,4 @@ namespace Content.Shared.Maps TileId = id; } } - - [Flags] - public enum TileFlag : byte - { - None = 0, - Roof = 1 << 0, - } } diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs index 114b6b20b9..9dee35da4e 100644 --- a/Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs +++ b/Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs @@ -26,11 +26,4 @@ public sealed partial class BiomeTileLayer : IBiomeLayer [DataField(required: true)] public ProtoId Tile = string.Empty; - - // TODO: Need some good engine solution to this, see FlagSerializer for what needs changing. - /// - /// Flags to set on the tile when placed. - /// - [DataField] - public byte Flags = 0; } diff --git a/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs b/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs index 32a7823273..b06574eb5b 100644 --- a/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs +++ b/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs @@ -129,7 +129,7 @@ public abstract class SharedBiomeSystem : EntitySystem if (layer is not BiomeTileLayer tileLayer) continue; - if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index(tileLayer.Tile), tileLayer.Flags, tileLayer.Variants, out tile)) + if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index(tileLayer.Tile), tileLayer.Variants, out tile)) { return true; } @@ -142,7 +142,7 @@ public abstract class SharedBiomeSystem : EntitySystem /// /// Gets the underlying biome tile, ignoring any existing tile that may be there. /// - private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, byte tileFlags, List? variants, [NotNullWhen(true)] out Tile? tile) + private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List? variants, [NotNullWhen(true)] out Tile? tile) { var found = noise.GetNoise(indices.X, indices.Y); found = invert ? found * -1 : found; @@ -163,7 +163,7 @@ public abstract class SharedBiomeSystem : EntitySystem variant = _tile.PickVariant(tileDef, (int) variantValue); } - tile = new Tile(tileDef.TileId, flags: tileFlags, variant); + tile = new Tile(tileDef.TileId, variant); return true; } diff --git a/Content.Shared/Weather/SharedWeatherSystem.cs b/Content.Shared/Weather/SharedWeatherSystem.cs index ca870afc9e..382af64565 100644 --- a/Content.Shared/Weather/SharedWeatherSystem.cs +++ b/Content.Shared/Weather/SharedWeatherSystem.cs @@ -1,3 +1,5 @@ +using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Content.Shared.Maps; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; @@ -17,6 +19,7 @@ public abstract class SharedWeatherSystem : EntitySystem [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMapSystem _mapSystem = default!; + [Dependency] private readonly SharedRoofSystem _roof = default!; private EntityQuery _blockQuery; @@ -38,12 +41,12 @@ public abstract class SharedWeatherSystem : EntitySystem } } - public bool CanWeatherAffect(EntityUid uid, MapGridComponent grid, TileRef tileRef) + public bool CanWeatherAffect(EntityUid uid, MapGridComponent grid, TileRef tileRef, RoofComponent? roofComp = null) { if (tileRef.Tile.IsEmpty) return true; - if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == (byte) TileFlag.Roof) + if (Resolve(uid, ref roofComp, false) && _roof.IsRooved((uid, grid, roofComp), tileRef.GridIndices)) return false; var tileDef = (ContentTileDefinition) _tileDefManager[tileRef.Tile.TypeId]; diff --git a/Resources/Prototypes/Entities/Structures/Walls/walls.yml b/Resources/Prototypes/Entities/Structures/Walls/walls.yml index 278517e0e6..dd6b3d36bc 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/walls.yml @@ -21,6 +21,7 @@ - type: Tag tags: - Wall + - type: IsRoof - type: Sprite drawdepth: Walls - type: Icon diff --git a/Resources/Prototypes/Procedural/biome_templates.yml b/Resources/Prototypes/Procedural/biome_templates.yml index 45293f582f..588d95f40d 100644 --- a/Resources/Prototypes/Procedural/biome_templates.yml +++ b/Resources/Prototypes/Procedural/biome_templates.yml @@ -544,7 +544,6 @@ - !type:BiomeTileLayer threshold: -1.0 tile: FloorAsteroidSand - flags: 1 # Asteroid - type: biomeTemplate -- 2.51.2