mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-06-09 10:06:34 +02:00
83c2a1be11
* [Dependency] source generator No more reflection, no more codegen at runtime Also various changes to Roslyn helpers to make this easier to write. Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback. No fallback is available for [field: Dependency] properties, due to a Roslyn bug. Code Fixes exist. We love Roslyn * Apply dependencies generator changes to all code * Release notes * Preprocessor got hands * Handle nullable dependencies These are bad but gotta deal with it. * Apply suggestions from code review Co-authored-by: Moony <moony@hellomouse.net> * Fine, let's not use collection expressions --------- Co-authored-by: Moony <moony@hellomouse.net>
251 lines
8.8 KiB
C#
251 lines
8.8 KiB
C#
using JetBrains.Annotations;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Map.Enumerators;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Utility;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Robust.Shared.IoC;
|
|
using static Robust.Shared.GameObjects.OccluderComponent;
|
|
|
|
namespace Robust.Client.GameObjects;
|
|
|
|
// NOTE: this class handles both snap grid updates of occluders, as well as occluder tree updates (via its parent).
|
|
// This seems like it's doing somewhat double work because it already has an update queue for occluders but...
|
|
// See the thing is the snap grid stuff was coded earlier
|
|
// and technically it only cares about changes in the entity's SNAP GRID position.
|
|
// Whereas the tree stuff is precise.
|
|
// Also I just realized this and I cba to refactor this again.
|
|
[UsedImplicitly]
|
|
internal sealed partial class ClientOccluderSystem : OccluderSystem
|
|
{
|
|
private readonly HashSet<EntityUid> _dirtyEntities = new();
|
|
[Dependency] private SharedMapSystem _mapSystem = default!;
|
|
|
|
/// <inheritdoc />
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<OccluderComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
|
SubscribeLocalEvent<OccluderComponent, ComponentShutdown>(OnShutdown);
|
|
}
|
|
|
|
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null, MetaDataComponent? meta = null)
|
|
{
|
|
if (!Resolve(uid, ref comp, false) || enabled == comp.Enabled)
|
|
return;
|
|
|
|
base.SetEnabled(uid, enabled, comp, meta);
|
|
|
|
var xform = Transform(uid);
|
|
QueueTreeUpdate(uid, comp, xform);
|
|
QueueOccludedDirectionUpdate(uid, comp, xform);
|
|
}
|
|
|
|
private void OnShutdown(EntityUid uid, OccluderComponent comp, ComponentShutdown args)
|
|
{
|
|
if (!Terminating(uid))
|
|
QueueOccludedDirectionUpdate(uid, comp);
|
|
}
|
|
|
|
protected override void OnCompStartup(EntityUid uid, OccluderComponent comp, ComponentStartup args)
|
|
{
|
|
base.OnCompStartup(uid, comp, args);
|
|
AnchorStateChanged(uid, comp, Transform(uid));
|
|
}
|
|
|
|
public void AnchorStateChanged(EntityUid uid, OccluderComponent comp, TransformComponent xform)
|
|
{
|
|
QueueOccludedDirectionUpdate(uid, comp, xform);
|
|
}
|
|
|
|
public override void FrameUpdate(float frameTime)
|
|
{
|
|
base.FrameUpdate(frameTime);
|
|
|
|
if (_dirtyEntities.Count == 0)
|
|
return;
|
|
|
|
var query = GetEntityQuery<OccluderComponent>();
|
|
var xforms = GetEntityQuery<TransformComponent>();
|
|
var grids = GetEntityQuery<MapGridComponent>();
|
|
|
|
try
|
|
{
|
|
foreach (var entity in _dirtyEntities)
|
|
{
|
|
if (query.TryGetComponent(entity, out var occluder))
|
|
UpdateOccluder(entity, occluder, query, xforms, grids);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_dirtyEntities.Clear();
|
|
}
|
|
}
|
|
|
|
private void OnAnchorChanged(EntityUid uid, OccluderComponent comp, ref AnchorStateChangedEvent args)
|
|
{
|
|
AnchorStateChanged(uid, comp, args.Transform);
|
|
}
|
|
|
|
private void QueueOccludedDirectionUpdate(EntityUid sender, OccluderComponent occluder, TransformComponent? xform = null)
|
|
{
|
|
if (!Resolve(sender, ref xform))
|
|
return;
|
|
|
|
occluder.Occluding = OccluderDir.None;
|
|
var query = GetEntityQuery<OccluderComponent>();
|
|
Vector2i pos;
|
|
EntityUid gridId;
|
|
MapGridComponent? grid;
|
|
|
|
if (occluder.Enabled && xform.Anchored && TryComp(xform.GridUid, out grid))
|
|
{
|
|
gridId = xform.GridUid.Value;
|
|
pos = _mapSystem.TileIndicesFor(gridId, grid, xform.Coordinates);
|
|
_dirtyEntities.Add(sender);
|
|
}
|
|
else if (occluder.LastPosition != null)
|
|
{
|
|
(gridId, pos) = occluder.LastPosition.Value;
|
|
occluder.LastPosition = null;
|
|
if (!TryComp(gridId, out grid))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(0, 1)), query);
|
|
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(0, -1)), query);
|
|
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(1, 0)), query);
|
|
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(-1, 0)), query);
|
|
}
|
|
|
|
private void DirtyNeighbours(AnchoredEntitiesEnumerator enumerator, EntityQuery<OccluderComponent> occluderQuery)
|
|
{
|
|
while (enumerator.MoveNext(out var entity))
|
|
{
|
|
if (occluderQuery.TryGetComponent(entity.Value, out var occluder))
|
|
{
|
|
_dirtyEntities.Add(entity.Value);
|
|
occluder.Occluding = OccluderDir.None;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateOccluder(EntityUid uid,
|
|
OccluderComponent occluder,
|
|
EntityQuery<OccluderComponent> occluders,
|
|
EntityQuery<TransformComponent> xforms,
|
|
EntityQuery<MapGridComponent> grids)
|
|
{
|
|
// Content may want to override the default behavior for occlusion.
|
|
// Apparently OD needs this?
|
|
{
|
|
var ev = new OccluderDirectionsEvent(uid, occluder);
|
|
RaiseLocalEvent(uid, ref ev, true);
|
|
|
|
if (ev.Handled)
|
|
return;
|
|
}
|
|
|
|
if (!occluder.Enabled)
|
|
{
|
|
DebugTools.Assert(occluder.Occluding == OccluderDir.None);
|
|
DebugTools.Assert(occluder.LastPosition == null);
|
|
return;
|
|
}
|
|
|
|
var xform = xforms.GetComponent(uid);
|
|
if (!xform.Anchored || !grids.TryGetComponent(xform.GridUid, out var grid))
|
|
{
|
|
DebugTools.Assert(occluder.Occluding == OccluderDir.None);
|
|
DebugTools.Assert(occluder.LastPosition == null);
|
|
return;
|
|
}
|
|
|
|
var tile = _mapSystem.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
|
|
|
|
// TODO: Sub to parent changes instead or something.
|
|
// DebugTools.Assert(occluder.LastPosition == null
|
|
// || occluder.LastPosition.Value.Grid == xform.GridUid && occluder.LastPosition.Value.Tile == tile);
|
|
occluder.LastPosition = (xform.GridUid.Value, tile);
|
|
|
|
// dir starts at the relative effective south direction;
|
|
var dir = xform.LocalRotation.GetCardinalDir();
|
|
CheckDir(dir, OccluderDir.South, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
|
|
|
dir = dir.GetClockwise90Degrees();
|
|
CheckDir(dir, OccluderDir.West, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
|
|
|
dir = dir.GetClockwise90Degrees();
|
|
CheckDir(dir, OccluderDir.North, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
|
|
|
dir = dir.GetClockwise90Degrees();
|
|
CheckDir(dir, OccluderDir.East, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
|
}
|
|
|
|
private void CheckDir(
|
|
Direction dir,
|
|
OccluderDir occDir,
|
|
Vector2i tile,
|
|
OccluderComponent occluder,
|
|
EntityUid gridUid,
|
|
MapGridComponent grid,
|
|
EntityQuery<OccluderComponent> query,
|
|
EntityQuery<TransformComponent> xforms)
|
|
{
|
|
if ((occluder.Occluding & occDir) != 0)
|
|
return;
|
|
|
|
foreach (var neighbor in _mapSystem.GetAnchoredEntities(gridUid, grid, tile.Offset(dir)))
|
|
{
|
|
if (!query.TryGetComponent(neighbor, out var otherOccluder) || !otherOccluder.Enabled)
|
|
continue;
|
|
|
|
occluder.Occluding |= occDir;
|
|
|
|
// while we are here, also set the occluder flag for the other entity;
|
|
var otherXform = xforms.GetComponent(neighbor);
|
|
DebugTools.Assert(otherXform.Anchored);
|
|
var rot = -otherXform.LocalRotation;
|
|
var otherOcDir = FromDirection(rot.RotateDir(dir.GetOpposite()));
|
|
otherOccluder.Occluding |= otherOcDir;
|
|
}
|
|
}
|
|
|
|
public static OccluderDir FromDirection(Direction dir)
|
|
{
|
|
return dir switch
|
|
{
|
|
Direction.South => OccluderDir.South,
|
|
Direction.North => OccluderDir.North,
|
|
Direction.East => OccluderDir.East,
|
|
Direction.West => OccluderDir.West,
|
|
_ => throw new ArgumentException($"Invalid dir: {dir}.")
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised by occluders when trying to get occlusion directions.
|
|
/// </summary>
|
|
[ByRefEvent]
|
|
public struct OccluderDirectionsEvent
|
|
{
|
|
public bool Handled = false;
|
|
public readonly EntityUid Sender = default!;
|
|
public readonly OccluderComponent Occluder = default!;
|
|
|
|
public OccluderDirectionsEvent(EntityUid sender, OccluderComponent occluder)
|
|
{
|
|
Sender = sender;
|
|
Occluder = occluder;
|
|
}
|
|
}
|
|
}
|