Skip to content

Commit

Permalink
goob refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Spatison committed Jan 8, 2025
1 parent caf5c23 commit 618c3b7
Show file tree
Hide file tree
Showing 28 changed files with 433 additions and 191 deletions.
2 changes: 1 addition & 1 deletion Content.Client/Overlays/EquipmentHudSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ protected virtual void OnRefreshComponentHud(EntityUid uid, T component, Refresh
args.Components.Add(component);
}

private void RefreshOverlay(EntityUid uid)
protected void RefreshOverlay(EntityUid uid)
{
if (uid != _player.LocalSession?.AttachedEntity)
return;
Expand Down
29 changes: 15 additions & 14 deletions Content.Client/Overlays/Switchable/BaseSwitchableOverlay.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,48 @@
using System.Numerics;
using Content.Shared.Overlays.Switchable;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;

namespace Content.Client.Overlays.Switchable;

public class BaseSwitchableOverlay<TComp> : Overlay
where TComp : SwitchableOverlayComponent
public sealed class BaseSwitchableOverlay<TComp> : Overlay where TComp : SwitchableOverlayComponent
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IEntityManager _entity = 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>("NightVision").Instance().Duplicate();
_shader = _prototype.Index<ShaderPrototype>("NightVision").InstanceUnique();
}

protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture is null
|| _player.LocalEntity == null
|| !_entity.TryGetComponent<TComp>(_player.LocalEntity.Value, out var component)
|| !component.IsActive)
if (ScreenTexture is null || Comp is null || !IsActive)
return;

_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
_shader.SetParameter("tint", component.Tint);
_shader.SetParameter("luminance_threshold", component.Strength);
_shader.SetParameter("noise_amount", component.Noise);
_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, component.Color);
worldHandle.DrawRect(args.WorldBounds, Comp.Color.WithAlpha(alpha));
worldHandle.UseShader(null);
}
}
86 changes: 44 additions & 42 deletions Content.Client/Overlays/Switchable/NightVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using Content.Shared.GameTicking;
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays.Switchable;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;

namespace Content.Client.Overlays.Switchable;

public sealed class NightVisionSystem : SwitchableOverlaySystem<NightVisionComponent, ToggleNightVisionEvent>
public sealed class NightVisionSystem : EquipmentHudSystem<NightVisionComponent>
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly ILightManager _lightManager = default!;

Expand All @@ -18,68 +15,73 @@ public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<NightVisionComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<NightVisionComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRestart);
SubscribeLocalEvent<NightVisionComponent, SwitchableOverlayToggledEvent>(OnToggle);

_overlay = new BaseSwitchableOverlay<NightVisionComponent>();
}

private void OnPlayerAttached(EntityUid uid, NightVisionComponent component, PlayerAttachedEvent args)
private void OnToggle(Entity<NightVisionComponent> ent, ref SwitchableOverlayToggledEvent args)
{
if (!component.IsActive)
return;

UpdateVision(args.Player, component.IsActive);
}

private void OnPlayerDetached(EntityUid uid, NightVisionComponent component, PlayerDetachedEvent args)
{
UpdateVision(args.Player, false);
RefreshOverlay(args.User);
}

private void OnRestart(RoundRestartCleanupEvent ev)
protected override void UpdateInternal(RefreshEquipmentHudEvent<NightVisionComponent> args)
{
_overlayMan.RemoveOverlay(_overlay);
_lightManager.DrawLighting = true;
}
base.UpdateInternal(args);

protected override void UpdateVision(EntityUid uid, bool active)
{
if (_player.LocalSession?.AttachedEntity != uid)
return;
var active = false;
NightVisionComponent? nvComp = null;
foreach (var comp in args.Components)
{
if (comp.IsActive || comp.PulseTime > 0f && comp.PulseAccumulator < comp.PulseTime)
active = true;
else
continue;

if (comp.DrawOverlay)
{
if (nvComp == null)
nvComp = comp;
else if (nvComp.PulseTime > 0f && comp.PulseTime <= 0f)
nvComp = comp;
}

if (active && nvComp is { PulseTime: <= 0 })
break;
}

UpdateOverlay(active);
UpdateNightVision(active);
UpdateOverlay(nvComp);
}

private void UpdateVision(ICommonSession player, bool active)
protected override void DeactivateInternal()
{
if (_player.LocalSession != player)
return;
base.DeactivateInternal();

UpdateOverlay(active);
UpdateNightVision(active);
UpdateNightVision(false);
UpdateOverlay(null);
}

private void UpdateNightVision(bool active)
{
_lightManager.DrawLighting = !active;
}

private void UpdateOverlay(bool active)
private void UpdateOverlay(NightVisionComponent? nvComp)
{
if (_player.LocalEntity == null)
_overlay.Comp = nvComp;

switch (nvComp)
{
_overlayMan.RemoveOverlay(_overlay);
return;
case not null when !_overlayMan.HasOverlay<BaseSwitchableOverlay<NightVisionComponent>>():
_overlayMan.AddOverlay(_overlay);
break;
case null:
_overlayMan.RemoveOverlay(_overlay);
break;
}

active |= TryComp<NightVisionComponent>(_player.LocalEntity.Value, out var component) && component.IsActive;

if (active)
_overlayMan.AddOverlay(_overlay);
else
_overlayMan.RemoveOverlay(_overlay);
if (_overlayMan.TryGetOverlay<BaseSwitchableOverlay<ThermalVisionComponent>>(out var overlay))
overlay.IsActive = nvComp == null;
}
}
81 changes: 55 additions & 26 deletions Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,47 @@
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Timing;

namespace Content.Client.Overlays.Switchable;

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

private readonly TransformSystem _transform;
private readonly OccluderSystem _occluder;
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<ThermalVisionRenderEntry> _entries = [];

private EntityUid? _lightEntity;

public float LightRadius;

public ThermalVisionComponent? Comp;

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

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

ZIndex = -1;
}

protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture is null
|| _players.LocalEntity == null
|| !_entity.TryGetComponent<ThermalVisionComponent>(_players.LocalEntity.Value, out var component)
|| !component.IsActive)
if (ScreenTexture is null || Comp is null)
return;

var worldHandle = args.WorldHandle;
Expand All @@ -53,14 +58,35 @@ protected override void Draw(in OverlayDrawArgs args)
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))
if (!CanSee(uid, sprite) || !body.ThermalVisibility)
continue;

var entity = uid;
Expand All @@ -77,54 +103,57 @@ protected override void Draw(in OverlayDrawArgs args)
}
}

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

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

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

worldHandle.SetTransform(Matrix3x2.Identity);
}

private void Render(Entity<SpriteComponent, TransformComponent, BodyComponent> ent,
private void Render(Entity<SpriteComponent, TransformComponent> ent,
MapId? map,
DrawingHandleWorld handle,
Angle eyeRot)
Angle eyeRot,
Color color,
float alpha)
{
var (uid, sprite, xform, body) = ent;
if (xform.MapID != map || HasOccluders(uid) || !CanSee(uid, sprite, body))
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, BodyComponent body)
private bool CanSee(EntityUid uid, SpriteComponent sprite)
{
return sprite.Visible
&& body.ThermalVisibility
&& (!_entity.TryGetComponent(uid, out StealthComponent? stealth)
|| _stealth.GetVisibility(uid, stealth) > 0.5f);
return sprite.Visible && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) ||
_stealth.GetVisibility(uid, stealth) > 0.5f);
}

private bool HasOccluders(EntityUid uid)
public void ResetLight()
{
var mapCoordinates = _transform.GetMapCoordinates(uid);
var occluders = _occluder.QueryAabb(mapCoordinates.MapId,
Box2.CenteredAround(mapCoordinates.Position, new Vector2(0.3f, 0.3f)));
if (_lightEntity == null || !_timing.IsFirstTimePredicted)
return;

return occluders.Any(o => o.Component.Enabled);
_entity.DeleteEntity(_lightEntity);
_lightEntity = null;
}
}

public record struct ThermalVisionRenderEntry(
(EntityUid, SpriteComponent, TransformComponent, BodyComponent) Ent,
Entity<SpriteComponent, TransformComponent> Ent,
MapId? Map,
Angle EyeRot);
Loading

0 comments on commit 618c3b7

Please sign in to comment.