mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 18:17:09 +02:00
157 lines
5.5 KiB
C#
157 lines
5.5 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using Robust.Shared.ComponentTrees;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Physics;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.GameObjects;
|
|
public abstract class OccluderSystem : ComponentTreeSystem<OccluderTreeComponent, OccluderComponent>
|
|
{
|
|
public const float MaxRaycastRange = 100f;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<OccluderComponent, ComponentGetState>(OnGetState);
|
|
SubscribeLocalEvent<OccluderComponent, ComponentHandleState>(OnHandleState);
|
|
}
|
|
|
|
private void OnGetState(EntityUid uid, OccluderComponent comp, ref ComponentGetState args)
|
|
{
|
|
args.State = new OccluderComponent.OccluderComponentState(comp.Enabled, comp.BoundingBox);
|
|
}
|
|
private void OnHandleState(EntityUid uid, OccluderComponent comp, ref ComponentHandleState args)
|
|
{
|
|
if (args.Current is not OccluderComponent.OccluderComponentState state)
|
|
return;
|
|
|
|
SetEnabled(uid, state.Enabled, comp);
|
|
SetBoundingBox(uid, state.BoundingBox, comp);
|
|
}
|
|
|
|
#region Component Tree Overrides
|
|
protected override bool DoFrameUpdate => true;
|
|
protected override bool DoTickUpdate => true;
|
|
|
|
// this system relies on the assumption that all occluders are parented directly to a grid or map.
|
|
// if this ever changes, this will make server move events very expensive.
|
|
protected override bool Recursive => false;
|
|
|
|
protected override Box2 ExtractAabb(in ComponentTreeEntry<OccluderComponent> entry)
|
|
{
|
|
DebugTools.Assert(entry.Transform.ParentUid == entry.Component.TreeUid);
|
|
return entry.Component.BoundingBox.Translated(entry.Transform.LocalPosition);
|
|
}
|
|
|
|
protected override Box2 ExtractAabb(in ComponentTreeEntry<OccluderComponent> entry, Vector2 pos, Angle rot)
|
|
=> ExtractAabb(in entry);
|
|
#endregion
|
|
|
|
#region Setters
|
|
public void SetBoundingBox(EntityUid uid, Box2 box, OccluderComponent? comp = null)
|
|
{
|
|
if (!Resolve(uid, ref comp))
|
|
return;
|
|
|
|
comp.BoundingBox = box;
|
|
Dirty(uid, comp);
|
|
|
|
if (comp.TreeUid != null)
|
|
QueueTreeUpdate(uid, comp);
|
|
}
|
|
|
|
public virtual void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null, MetaDataComponent? meta = null)
|
|
{
|
|
if (!Resolve(uid, ref comp, false) || enabled == comp.Enabled)
|
|
return;
|
|
|
|
comp.Enabled = enabled;
|
|
Dirty(uid, comp, meta);
|
|
QueueTreeUpdate(uid, comp);
|
|
}
|
|
#endregion
|
|
|
|
#region InRangeUnoccluded
|
|
|
|
/// <summary>
|
|
/// Returns true if two points are within the specified range and there are no occluders between them that aren't
|
|
/// ignored by the predicate.
|
|
/// </summary>
|
|
public bool InRangeUnoccluded<TState>(
|
|
MapCoordinates origin,
|
|
MapCoordinates other,
|
|
float range,
|
|
TState state,
|
|
Func<Entity<OccluderComponent, TransformComponent>, TState, bool> ignore)
|
|
{
|
|
if (!GetRay(origin, other, range, out var length, out var ray, out var result))
|
|
return result;
|
|
|
|
return IntersectRay(origin.MapId, ray, length, state, ignore) == null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if two points are within the specified range and there are no occluders between them.
|
|
/// </summary>
|
|
/// <param name="ignoreTouching">If true, this will use <see cref="IsTouchingEndpoint"/> as a predicate to ignore \
|
|
/// occluders that are touching the start or end point.</param>
|
|
public bool InRangeUnoccluded(MapCoordinates origin, MapCoordinates other, float range, bool ignoreTouching)
|
|
{
|
|
if (!GetRay(origin, other, range, out var length, out var ray, out var result))
|
|
return result;
|
|
|
|
if (!ignoreTouching)
|
|
return IntersectRay(origin.MapId, ray, length) == null;
|
|
|
|
var state = (XformSystem, origin.Position, other.Position);
|
|
return IntersectRay(origin.MapId, ray, length, state, IsTouchingEndpoint) == null;
|
|
}
|
|
|
|
private bool GetRay(MapCoordinates origin, MapCoordinates other, float range, out float length, out Ray ray, out bool result)
|
|
{
|
|
ray = default;
|
|
length = default;
|
|
result = false;
|
|
if (other.MapId != origin.MapId || other.MapId == MapId.Nullspace)
|
|
return false;
|
|
|
|
var dir = other.Position - origin.Position;
|
|
length = dir.Length();
|
|
if (MathHelper.CloseTo(length, 0))
|
|
{
|
|
result = true;
|
|
return false;
|
|
}
|
|
|
|
var normalized = dir / length;
|
|
|
|
if (range > 0f && length > range + 0.01f)
|
|
return false;
|
|
|
|
if (length > MaxRaycastRange)
|
|
{
|
|
Log.Warning($"{nameof(InRangeUnoccluded)} check performed over extreme range. Limiting range.");
|
|
length = MaxRaycastRange;
|
|
}
|
|
|
|
ray = new Ray(origin.Position, normalized);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simple predicate for use with <see cref="InRangeUnoccluded"/> that will ignore any occluders that intersect the
|
|
/// start and end points.
|
|
/// </summary>
|
|
public static bool IsTouchingEndpoint(Entity<OccluderComponent, TransformComponent> ent, (SharedTransformSystem Sys, Vector2 Start, Vector2 End) state)
|
|
{
|
|
var occluderBox = ent.Comp1.BoundingBox;
|
|
occluderBox = occluderBox.Translated(state.Sys.GetWorldPosition(ent.Comp2));
|
|
return occluderBox.Contains(state.Start) || occluderBox.Contains(state.End);
|
|
}
|
|
|
|
#endregion
|
|
}
|