155 lines
5.5 KiB
C#
155 lines
5.5 KiB
C#
// SPDX-FileCopyrightText: 2025 Ark
|
|
// SPDX-FileCopyrightText: 2025 Ilya246
|
|
// SPDX-FileCopyrightText: 2025 Redrover1760
|
|
// SPDX-FileCopyrightText: 2025 ark1368
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
using System.Numerics;
|
|
using Content.Shared._Mono.Radar;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Timing;
|
|
using System.Linq;
|
|
|
|
namespace Content.Client._Mono.Radar;
|
|
|
|
public sealed partial class RadarBlipsSystem : EntitySystem
|
|
{
|
|
private const double BlipStaleSeconds = 1.5;
|
|
private static readonly List<(Vector2, float, Color, RadarBlipShape)> EmptyBlipList = new();
|
|
private static readonly List<(NetEntity netUid, NetCoordinates Position, Vector2 Vel, float Scale, Color Color, RadarBlipShape Shape)> EmptyRawBlipList = new();
|
|
private static readonly List<(Vector2 Start, Vector2 End, float Thickness, Color Color)> EmptyHitscanList = new();
|
|
private TimeSpan _lastRequestTime = TimeSpan.Zero;
|
|
private static readonly TimeSpan RequestThrottle = TimeSpan.FromMilliseconds(250);
|
|
|
|
// Maximum distance for blips to be considered visible
|
|
private const float MaxBlipRenderDistance = 1000f;
|
|
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
|
|
|
private TimeSpan _lastUpdatedTime;
|
|
private List<(NetEntity netUid, NetCoordinates Position, Vector2 Vel, float Scale, Color Color, RadarBlipShape Shape)> _blips = new();
|
|
private List<(Vector2 Start, Vector2 End, float Thickness, Color Color)> _hitscans = new();
|
|
private Vector2 _radarWorldPosition;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeNetworkEvent<GiveBlipsEvent>(HandleReceiveBlips);
|
|
SubscribeNetworkEvent<BlipRemovalEvent>(RemoveBlip);
|
|
}
|
|
|
|
private void HandleReceiveBlips(GiveBlipsEvent ev, EntitySessionEventArgs args)
|
|
{
|
|
if (ev?.Blips == null)
|
|
{
|
|
_blips = EmptyRawBlipList;
|
|
}
|
|
else
|
|
{
|
|
_blips = ev.Blips;
|
|
}
|
|
|
|
if (ev?.HitscanLines == null)
|
|
{
|
|
_hitscans = EmptyHitscanList;
|
|
}
|
|
else
|
|
{
|
|
_hitscans = ev.HitscanLines;
|
|
}
|
|
|
|
_lastUpdatedTime = _timing.CurTime;
|
|
}
|
|
|
|
private void RemoveBlip(BlipRemovalEvent args)
|
|
{
|
|
var blipid = _blips.FirstOrDefault(x => x.netUid == args.NetBlipUid);
|
|
_blips.Remove(blipid);
|
|
}
|
|
|
|
public void RequestBlips(EntityUid console)
|
|
{
|
|
// Only request if we have a valid console
|
|
if (!Exists(console))
|
|
return;
|
|
|
|
// Add request throttling to avoid network spam
|
|
if (_timing.CurTime - _lastRequestTime < RequestThrottle)
|
|
return;
|
|
|
|
_lastRequestTime = _timing.CurTime;
|
|
|
|
// Cache the radar position for distance culling
|
|
if (TryComp<TransformComponent>(console, out var xform))
|
|
{
|
|
_radarWorldPosition = _xform.GetWorldPosition(console);
|
|
}
|
|
|
|
var netConsole = GetNetEntity(console);
|
|
var ev = new RequestBlipsEvent(netConsole);
|
|
RaiseNetworkEvent(ev);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current blips as world positions with their scale, color and shape.
|
|
/// </summary>
|
|
public List<(EntityCoordinates Position, float Scale, Color Color, RadarBlipShape Shape)> GetCurrentBlips()
|
|
{
|
|
// If it's been more than the stale threshold since our last update,
|
|
// the data is considered stale - return an empty list
|
|
if (_timing.CurTime.TotalSeconds - _lastUpdatedTime.TotalSeconds > BlipStaleSeconds)
|
|
return new List<(EntityCoordinates, float, Color, RadarBlipShape)>();
|
|
|
|
var result = new List<(EntityCoordinates, float, Color, RadarBlipShape)>(_blips.Count);
|
|
|
|
foreach (var blip in _blips)
|
|
{
|
|
var coord = GetCoordinates(blip.Position);
|
|
|
|
if (!coord.IsValid(EntityManager))
|
|
continue;
|
|
|
|
var predictedPos = new EntityCoordinates(coord.EntityId, coord.Position + blip.Vel * (float)(_timing.CurTime - _lastUpdatedTime).TotalSeconds);
|
|
|
|
// Distance culling for world position blips
|
|
if (Vector2.DistanceSquared(_xform.ToMapCoordinates(predictedPos).Position, _radarWorldPosition) > MaxBlipRenderDistance * MaxBlipRenderDistance)
|
|
continue;
|
|
|
|
result.Add((predictedPos, blip.Scale, blip.Color, blip.Shape));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the hitscan lines to be rendered on the radar
|
|
/// </summary>
|
|
public List<(Vector2 Start, Vector2 End, float Thickness, Color Color)> GetHitscanLines()
|
|
{
|
|
if (_timing.CurTime.TotalSeconds - _lastUpdatedTime.TotalSeconds > BlipStaleSeconds)
|
|
return new List<(Vector2, Vector2, float, Color)>();
|
|
|
|
var result = new List<(Vector2, Vector2, float, Color)>(_hitscans.Count);
|
|
|
|
foreach (var hitscan in _hitscans)
|
|
{
|
|
var worldStart = hitscan.Start;
|
|
var worldEnd = hitscan.End;
|
|
|
|
// Distance culling - check if either end of the line is in range
|
|
var startDist = Vector2.DistanceSquared(worldStart, _radarWorldPosition);
|
|
var endDist = Vector2.DistanceSquared(worldEnd, _radarWorldPosition);
|
|
|
|
if (startDist > MaxBlipRenderDistance * MaxBlipRenderDistance &&
|
|
endDist > MaxBlipRenderDistance * MaxBlipRenderDistance)
|
|
continue;
|
|
|
|
result.Add((worldStart, worldEnd, hitscan.Thickness, hitscan.Color));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|