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;
private readonly EntityLookupSystem _lookup;
private readonly SharedMapSystem _mapSystem;
+ private readonly SharedRoofSystem _roof = default!;
private readonly SharedTransformSystem _xformSystem;
- private readonly HashSet<Entity<OccluderComponent>> _occluders = new();
private List<Entity<MapGridComponent>> _grids = new();
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
_lookup = _entManager.System<EntityLookupSystem>();
_mapSystem = _entManager.System<SharedMapSystem>();
+ _roof = _entManager.System<SharedRoofSystem>();
_xformSystem = _entManager.System<SharedTransformSystem>();
ZIndex = ContentZIndex;
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);
using System.Numerics;
+using Content.Shared.Light.Components;
using Content.Shared.Weather;
using Robust.Client.Graphics;
using Robust.Shared.Map.Components;
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;
}
using System.Numerics;
+using Content.Shared.Light.Components;
using Content.Shared.Weather;
using Robust.Client.Audio;
using Robust.Client.GameObjects;
// Work out tiles nearby to determine volume.
if (TryComp<MapGridComponent>(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);
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
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Light.Components;
+
+/// <summary>
+/// Counts the tile this entity on as being rooved.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class IsRoofComponent : Component
+{
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
+}
namespace Content.Shared.Light.Components;
/// <summary>
-/// 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.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class RoofComponent : Component
{
+ public const int ChunkSize = 8;
+
[DataField, AutoNetworkedField]
public Color Color = Color.Black;
+
+ /// <summary>
+ /// Chunk origin and bitmask of value in chunk.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public Dictionary<Vector2i, ulong> Data = new();
}
/// </summary>
public abstract class SharedRoofSystem : EntitySystem
{
- [Dependency] private readonly SharedMapSystem _maps = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+ private HashSet<Entity<IsRoofComponent>> _roofSet = new();
+
+ /// <summary>
+ /// Returns whether the specified tile is roof-occupied.
+ /// </summary>
+ /// <returns>Returns false if no data or not rooved.</returns>
+ public bool IsRooved(Entity<MapGridComponent, RoofComponent> 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<MapGridComponent?, RoofComponent?> 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);
}
}
TileId = id;
}
}
-
- [Flags]
- public enum TileFlag : byte
- {
- None = 0,
- Roof = 1 << 0,
- }
}
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile = string.Empty;
-
- // TODO: Need some good engine solution to this, see FlagSerializer for what needs changing.
- /// <summary>
- /// Flags to set on the tile when placed.
- /// </summary>
- [DataField]
- public byte Flags = 0;
}
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;
}
/// <summary>
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
/// </summary>
- private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, byte tileFlags, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
+ private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
{
var found = noise.GetNoise(indices.X, indices.Y);
found = invert ? found * -1 : found;
variant = _tile.PickVariant(tileDef, (int) variantValue);
}
- tile = new Tile(tileDef.TileId, flags: tileFlags, variant);
+ tile = new Tile(tileDef.TileId, variant);
return true;
}
+using Content.Shared.Light.Components;
+using Content.Shared.Light.EntitySystems;
using Content.Shared.Maps;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
[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<BlockWeatherComponent> _blockQuery;
}
}
- 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];
- type: Tag
tags:
- Wall
+ - type: IsRoof
- type: Sprite
drawdepth: Walls
- type: Icon
- !type:BiomeTileLayer
threshold: -1.0
tile: FloorAsteroidSand
- flags: 1
# Asteroid
- type: biomeTemplate