Files
crystall-punk-14/Content.Client/_CP14/Religion/CP14ReligionVisionOverlay.cs
Red 3a4bbcb289 Area of influence depends on followers MobState (#1439)
* simple anti-religion zone shader

* observers refactor
2025-06-18 21:46:58 +03:00

182 lines
6.2 KiB
C#

using System.Numerics;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Religion;
public sealed class CP14ReligionVisionOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly SharedTransformSystem _transform;
/// <summary>
/// Maximum number of observers zones that can be shown on screen at a time.
/// If this value is changed, the shader itself also needs to be updated.
/// </summary>
public const int MaxCount = 64;
public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ProtoId<CP14ReligionPrototype>? _religion;
private readonly ShaderInstance _religionShader;
private readonly Vector2[] _positions = new Vector2[MaxCount];
private readonly float[] _radii = new float[MaxCount];
private int _count;
private readonly Vector2[] _antiPositions = new Vector2[MaxCount];
private readonly float[] _antiRadii = new float[MaxCount];
private int _antiCount;
public CP14ReligionVisionOverlay()
{
IoCManager.InjectDependencies(this);
_religionShader = _proto.Index<ShaderPrototype>("CP14ReligionVision").InstanceUnique();
_transform = _entManager.System<SharedTransformSystem>();
if (_entManager.TryGetComponent<CP14ReligionEntityComponent>(_player.LocalEntity, out var vision))
_religion = vision.Religion;
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (args.Viewport.Eye == null)
return false;
var clusters = new List<Cluster>();
var antiClusters = new List<Cluster>();
var religionQuery = _entManager.AllEntityQueryEnumerator<CP14ReligionObserverComponent, TransformComponent>();
while (religionQuery.MoveNext(out var uid, out var observer, out var xform))
{
if (_religion is null)
continue;
if (observer.Religion is null)
continue;
if (observer.Radius <= 0f)
continue;
if (!observer.Active || xform.MapID != args.MapId)
continue;
var mapPos = _transform.GetWorldPosition(uid);
// To be clear, this needs to use "inside-viewport" pixels.
// In other words, specifically NOT IViewportControl.WorldToScreen (which uses outer coordinates).
var tempCoords = args.Viewport.WorldToLocal(mapPos);
tempCoords.Y = args.Viewport.Size.Y - tempCoords.Y; // Local space to fragment space.
if (observer.Religion.Value == _religion.Value)
{
// try find cluster to merge with
bool merged = false;
foreach (var cluster in clusters)
{
if ((cluster.Position - tempCoords).Length() < 150f)
{
cluster.Add(tempCoords, observer.Radius);
merged = true;
break;
}
}
if (!merged && clusters.Count < MaxCount)
clusters.Add(new Cluster(tempCoords, observer.Radius));
}
else
{
// try find cluster to merge with
bool merged = false;
foreach (var antiCluster in antiClusters)
{
if ((antiCluster.Position - tempCoords).Length() < 150f)
{
antiCluster.Add(tempCoords, observer.Radius);
merged = true;
break;
}
}
if (!merged && antiClusters.Count < MaxCount)
antiClusters.Add(new Cluster(tempCoords, observer.Radius));
}
}
_count = 0;
foreach (var cluster in clusters)
{
_positions[_count] = cluster.Position;
_radii[_count] = cluster.Radius;
_count++;
}
_antiCount = 0;
foreach (var antiCluster in antiClusters)
{
_antiPositions[_antiCount] = antiCluster.Position;
_antiRadii[_antiCount] = antiCluster.Radius;
_antiCount++;
}
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null || args.Viewport.Eye == null)
return;
if (!_entManager.TryGetComponent<CP14ReligionVisionComponent>(_player.LocalEntity, out var visionComponent))
return;
_religionShader?.SetParameter("shaderColor", visionComponent.ShaderColor);
_religionShader?.SetParameter("renderScale", args.Viewport.RenderScale * args.Viewport.Eye.Scale);
_religionShader?.SetParameter("count", _count);
_religionShader?.SetParameter("position", _positions);
_religionShader?.SetParameter("radius", _radii);
_religionShader?.SetParameter("anticount", _antiCount);
_religionShader?.SetParameter("antiposition", _antiPositions);
_religionShader?.SetParameter("antiradius", _antiRadii);
_religionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
var worldHandle = args.WorldHandle;
worldHandle.UseShader(_religionShader);
worldHandle.DrawRect(args.WorldAABB, Color.White);
worldHandle.UseShader(null);
}
private sealed class Cluster
{
public Vector2 Position;
public float Radius;
public int Count;
public Cluster(Vector2 pos, float radius)
{
Position = pos;
Radius = radius;
Count = 1;
}
public void Add(Vector2 pos, float radius)
{
Position = (Position * Count + pos) / (Count + 1);
Radius = Math.Max(Radius, radius);
Count++;
}
}
}