Skip to content
  •  
  •  
  •  
14 changes: 9 additions & 5 deletions Content.Client/Overlays/EquipmentHudSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
{
[Dependency] private readonly IPlayerManager _player = default!;

[ViewVariables]
protected bool IsActive;
protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET;

Expand All @@ -32,8 +33,8 @@ public override void Initialize()

SubscribeLocalEvent<T, RefreshEquipmentHudEvent<T>>(OnRefreshComponentHud);
SubscribeLocalEvent<T, InventoryRelayedEvent<RefreshEquipmentHudEvent<T>>>(OnRefreshEquipmentHud);
/*
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart); */

SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
}

private void Update(RefreshEquipmentHudEvent<T> ev)
Expand Down Expand Up @@ -86,14 +87,17 @@ private void OnCompUnequip(Entity<T> ent, ref GotUnequippedEvent args)
RefreshOverlay();
}

/* private void OnRoundRestart(RoundRestartCleanupEvent args)
private void OnRoundRestart(RoundRestartCleanupEvent args)
{
Deactivate();
} */
}

protected virtual void OnRefreshEquipmentHud(Entity<T> ent, ref InventoryRelayedEvent<RefreshEquipmentHudEvent<T>> args)
{
OnRefreshComponentHud(ent, ref args.Args);
// Goob edit start
args.Args.Active = true;
args.Args.Components.Add(ent);
// Goob edit end
}

protected virtual void OnRefreshComponentHud(Entity<T> ent, ref RefreshEquipmentHudEvent<T> args)
Expand Down
48 changes: 48 additions & 0 deletions Content.Client/_White/Overlays/BaseSwitchableOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Numerics;
using Content.Shared._White.Overlays;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;

namespace Content.Client._White.Overlays;

public sealed class BaseSwitchableOverlay<TComp> : Overlay where TComp : SwitchableVisionOverlayComponent
{
[Dependency] private readonly IPrototypeManager _prototype = default!;

public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;

private readonly ShaderInstance _shader;

public TComp? Comp = null;

public bool IsActive = true;

public BaseSwitchableOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototype.Index<ShaderPrototype>("NVHud").InstanceUnique();
}

protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture is null || Comp is null || !IsActive)
return;

_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
_shader.SetParameter("tint", Comp.Tint);
_shader.SetParameter("luminance_threshold", Comp.Strength);
_shader.SetParameter("noise_amount", Comp.Noise);

var worldHandle = args.WorldHandle;

var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime);
var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime);

worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_shader);
worldHandle.DrawRect(args.WorldBounds, Comp.Color.WithAlpha(alpha));
worldHandle.UseShader(null);
}
}
166 changes: 166 additions & 0 deletions Content.Client/_White/Overlays/IRHudOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using System.Linq;
using System.Numerics;
using Content.Client.Stealth;
using Content.Shared._White.Overlays;
using Content.Shared.Body.Components;
using Content.Shared.Stealth.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Timing;

namespace Content.Client._White.Overlays;

public sealed class IRHudOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IGameTiming _timing = default!;

private readonly TransformSystem _transform;
private readonly StealthSystem _stealth;
private readonly ContainerSystem _container;
private readonly SharedPointLightSystem _light;

public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;

private readonly List<IRHudRenderEntry> _entries = [];

private EntityUid? _lightEntity;

public float LightRadius;

public IRHudComponent? Comp;

public IRHudOverlay()
{
IoCManager.InjectDependencies(this);

_container = _entity.System<ContainerSystem>();
_transform = _entity.System<TransformSystem>();
_stealth = _entity.System<StealthSystem>();
_light = _entity.System<SharedPointLightSystem>();

ZIndex = -1;
}

protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture is null || Comp is null)
return;

var worldHandle = args.WorldHandle;
var eye = args.Viewport.Eye;

if (eye == null)
return;

var player = _player.LocalEntity;

if (!_entity.TryGetComponent(player, out TransformComponent? playerXform))
return;

var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime);
var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime);

// Thermal vision grants some night vision (clientside light)
if (LightRadius > 0)
{
_lightEntity ??= _entity.SpawnAttachedTo(null, playerXform.Coordinates);
_transform.SetParent(_lightEntity.Value, player.Value);
var light = _entity.EnsureComponent<PointLightComponent>(_lightEntity.Value);
_light.SetRadius(_lightEntity.Value, LightRadius, light);
_light.SetEnergy(_lightEntity.Value, alpha, light);
_light.SetColor(_lightEntity.Value, Comp.Color, light);
}
else
ResetLight();

var mapId = eye.Position.MapId;
var eyeRot = eye.Rotation;

_entries.Clear();
var entities = _entity.EntityQueryEnumerator<BodyComponent, SpriteComponent, TransformComponent>();
while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform))
{
if (!CanSee(uid, sprite) || !body.ThermalVisibility)
continue;

var entity = uid;

if (_container.TryGetOuterContainer(uid, xform, out var container))
{
continue; // Mono

// Mono edit, Thermals don't reveal people in lockers
/*
var owner = container.Owner;
if (_entity.TryGetComponent<SpriteComponent>(owner, out var ownerSprite)
&& _entity.TryGetComponent<TransformComponent>(owner, out var ownerXform))
{
entity = owner;
sprite = ownerSprite;
xform = ownerXform;
}
*/
// Mono End
}

if (_entries.Any(e => e.Ent.Owner == entity))
continue;

_entries.Add(new IRHudRenderEntry((entity, sprite, xform), mapId, eyeRot));
}

foreach (var entry in _entries)
{
Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, alpha);
}

worldHandle.SetTransform(Matrix3x2.Identity);
}

private void Render(Entity<SpriteComponent, TransformComponent> ent,
MapId? map,
DrawingHandleWorld handle,
Angle eyeRot,
Color color,
float alpha)
{
var (uid, sprite, xform) = ent;
if (xform.MapID != map || !CanSee(uid, sprite))
return;

var position = _transform.GetWorldPosition(xform);
var rotation = _transform.GetWorldRotation(xform);


var originalColor = sprite.Color;
sprite.Color = color.WithAlpha(alpha);
sprite.Render(handle, eyeRot, rotation, position: position);
sprite.Color = originalColor;
}

private bool CanSee(EntityUid uid, SpriteComponent sprite)
{
return sprite.Visible && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) ||
_stealth.GetVisibility(uid, stealth) > 0.5f);
}

public void ResetLight(bool checkFirstTimePredicted = true)
{
if (_lightEntity == null || checkFirstTimePredicted && !_timing.IsFirstTimePredicted)
return;

_entity.DeleteEntity(_lightEntity);
_lightEntity = null;
}
}

public record struct IRHudRenderEntry(
Entity<SpriteComponent, TransformComponent> Ent,
MapId? Map,
Angle EyeRot);
112 changes: 112 additions & 0 deletions Content.Client/_White/Overlays/IRHudSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using Content.Client.Overlays;
using Content.Shared._White.Overlays;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Robust.Client.Graphics;

namespace Content.Client._White.Overlays;

public sealed class IRHudSystem : EquipmentHudSystem<IRHudComponent>
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;

private IRHudOverlay _IRHudOverlay = default!;
private BaseSwitchableOverlay<IRHudComponent> _overlay = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<IRHudComponent, SwitchableOverlayToggledEvent>(OnToggle);

_IRHudOverlay = new IRHudOverlay();
_overlay = new BaseSwitchableOverlay<IRHudComponent>();
}

protected override void OnRefreshComponentHud(Entity<IRHudComponent> ent,
ref RefreshEquipmentHudEvent<IRHudComponent> args)
{
if (!ent.Comp.IsEquipment)
base.OnRefreshComponentHud(ent, ref args);
}

protected override void OnRefreshEquipmentHud(Entity<IRHudComponent> ent,
ref InventoryRelayedEvent<RefreshEquipmentHudEvent<IRHudComponent>> args)
{
if (ent.Comp.IsEquipment)
base.OnRefreshEquipmentHud(ent, ref args);
}

private void OnToggle(Entity<IRHudComponent> ent, ref SwitchableOverlayToggledEvent args)
{
RefreshOverlay();
}

protected override void UpdateInternal(RefreshEquipmentHudEvent<IRHudComponent> args)
{
base.UpdateInternal(args);
IRHudComponent? tvComp = null;
var lightRadius = 0f;
foreach (var comp in args.Components)
{
if (!comp.IsActive && (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime))
continue;

if (tvComp == null)
tvComp = comp;
else if (!tvComp.DrawOverlay && comp.DrawOverlay)
tvComp = comp;
else if (tvComp.DrawOverlay == comp.DrawOverlay && tvComp.PulseTime > 0f && comp.PulseTime <= 0f)
tvComp = comp;

lightRadius = MathF.Max(lightRadius, comp.LightRadius);
}

UpdateIRHudOverlay(tvComp, lightRadius);
UpdateOverlay(tvComp);
}

protected override void DeactivateInternal()
{
base.DeactivateInternal();

_IRHudOverlay.ResetLight(false);
UpdateOverlay(null);
UpdateIRHudOverlay(null, 0f);
}

private void UpdateIRHudOverlay(IRHudComponent? comp, float lightRadius)
{
_IRHudOverlay.LightRadius = lightRadius;
_IRHudOverlay.Comp = comp;

switch (comp)
{
case not null when !_overlayMan.HasOverlay<IRHudOverlay>():
_overlayMan.AddOverlay(_IRHudOverlay);
break;
case null:
_overlayMan.RemoveOverlay(_IRHudOverlay);
_IRHudOverlay.ResetLight();
break;
}
}

private void UpdateOverlay(IRHudComponent? tvComp)
{
_overlay.Comp = tvComp;

switch (tvComp)
{
case { DrawOverlay: true } when !_overlayMan.HasOverlay<BaseSwitchableOverlay<IRHudComponent>>():
_overlayMan.AddOverlay(_overlay);
break;
case null or { DrawOverlay: false }:
_overlayMan.RemoveOverlay(_overlay);
break;
}

// Night vision overlay is prioritized
_overlay.IsActive = !_overlayMan.HasOverlay<BaseSwitchableOverlay<NVHudComponent>>();
}
}
Loading
Loading