Skip to content

Commit

Permalink
Night and thermal vision device (#104)
Browse files Browse the repository at this point in the history
* first

* fix

* fix

* Apply suggestions from code review

Co-authored-by: FN <[email protected]>

* fix

* Apply suggestions from code review

* fix

---------

Co-authored-by: AwareFoxy <[email protected]>
Co-authored-by: FN <[email protected]>
  • Loading branch information
3 people authored Dec 9, 2024
1 parent 38269e4 commit e1a05f2
Show file tree
Hide file tree
Showing 42 changed files with 922 additions and 3 deletions.
47 changes: 47 additions & 0 deletions Content.Client/_CorvaxNext/Overlays/BaseSwitchableOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Content.Shared._CorvaxNext.Overlays;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using System.Numerics;

namespace Content.Client._CorvaxNext.Overlays;

public 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 BaseSwitchableOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototype.Index<ShaderPrototype>("NightVision").Instance().Duplicate();
}

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

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

var worldHandle = args.WorldHandle;

worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_shader);
worldHandle.DrawRect(args.WorldBounds, component.Color);
worldHandle.UseShader(null);
}
}
85 changes: 85 additions & 0 deletions Content.Client/_CorvaxNext/Overlays/NightVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Content.Shared._CorvaxNext.Overlays;
using Content.Shared.GameTicking;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;

namespace Content.Client._CorvaxNext.Overlays;

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

private BaseSwitchableOverlay<NightVisionComponent> _overlay = default!;

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

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

_overlay = new BaseSwitchableOverlay<NightVisionComponent>();
}

private void OnPlayerAttached(EntityUid uid, NightVisionComponent component, PlayerAttachedEvent args)
{
if (!component.IsActive)
return;

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

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

private void OnRestart(RoundRestartCleanupEvent ev)
{
_overlayMan.RemoveOverlay(_overlay);
_lightManager.DrawLighting = true;
}

protected override void UpdateVision(EntityUid uid, bool active)
{
if (_player.LocalSession?.AttachedEntity != uid)
return;

UpdateOverlay(active);
UpdateNightVision(active);
}

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

UpdateOverlay(active);
UpdateNightVision(active);
}

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

private void UpdateOverlay(bool active)
{
if (_player.LocalEntity == null)
{
_overlayMan.RemoveOverlay(_overlay);
return;
}

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

if (active)
_overlayMan.AddOverlay(_overlay);
else
_overlayMan.RemoveOverlay(_overlay);
}
}
130 changes: 130 additions & 0 deletions Content.Client/_CorvaxNext/Overlays/ThermalVisionOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Linq;
using System.Numerics;
using Content.Client.Stealth;
using Content.Shared._CorvaxNext.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;

namespace Content.Client._CorvaxNext.Overlays;

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

private readonly TransformSystem _transform;
private readonly OccluderSystem _occluder;
private readonly StealthSystem _stealth;
private readonly ContainerSystem _container;

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

private readonly List<ThermalVisionRenderEntry> _entries = [];

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

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

ZIndex = -1;
}

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

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

if (eye is null)
return;

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))
continue;

var entity = uid;

if (_container.TryGetOuterContainer(uid, xform, out var container))
{
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;
}
}

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

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

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

worldHandle.SetTransform(Matrix3x2.Identity);
}

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

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

sprite.Render(handle, eyeRot, rotation, position: position);
}

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

private bool HasOccluders(EntityUid uid)
{
var mapCoordinates = _transform.GetMapCoordinates(uid);
var occluders = _occluder.QueryAabb(mapCoordinates.MapId,
Box2.CenteredAround(mapCoordinates.Position, new Vector2(0.3f, 0.3f)));

return occluders.Any(o => o.Component.Enabled);
}
}

public record struct ThermalVisionRenderEntry(
(EntityUid, SpriteComponent, TransformComponent, BodyComponent) Ent,
MapId? Map,
Angle EyeRot);
81 changes: 81 additions & 0 deletions Content.Client/_CorvaxNext/Overlays/ThermalVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Content.Shared._CorvaxNext.Overlays;
using Content.Shared.GameTicking;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;

namespace Content.Client._CorvaxNext.Overlays;

public sealed class ThermalVisionSystem : SwitchableOverlaySystem<ThermalVisionComponent, ToggleThermalVisionEvent>
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;

private ThermalVisionOverlay _thermalOverlay = default!;
private BaseSwitchableOverlay<ThermalVisionComponent> _overlay = default!;

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

SubscribeLocalEvent<ThermalVisionComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ThermalVisionComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRestart);

_thermalOverlay = new ThermalVisionOverlay();
_overlay = new BaseSwitchableOverlay<ThermalVisionComponent>();
}

private void OnPlayerAttached(EntityUid uid, ThermalVisionComponent component, PlayerAttachedEvent args)
{
if (!component.IsActive)
return;

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

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

private void OnRestart(RoundRestartCleanupEvent ev)
{
_overlayMan.RemoveOverlay(_thermalOverlay);
_overlayMan.RemoveOverlay(_overlay);
}

protected override void UpdateVision(EntityUid uid, bool active)
{
if (_player.LocalSession?.AttachedEntity != uid)
return;

UpdateOverlay(active, _thermalOverlay);
UpdateOverlay(active, _overlay);
}

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

UpdateOverlay(active, _thermalOverlay);
UpdateOverlay(active, _overlay);
}

private void UpdateOverlay(bool active, Overlay overlay)
{
if (_player.LocalEntity == null)
{
_overlayMan.RemoveOverlay(overlay);
return;
}

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

if (active)
_overlayMan.AddOverlay(overlay);
else
_overlayMan.RemoveOverlay(overlay);
}
}
5 changes: 5 additions & 0 deletions Content.Server/_CorvaxNext/Overlays/NightVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared._CorvaxNext.Overlays;

namespace Content.Server._CorvaxNext.Overlays;

public sealed class NightVisionSystem : SwitchableOverlaySystem<NightVisionComponent, ToggleNightVisionEvent>;
5 changes: 5 additions & 0 deletions Content.Server/_CorvaxNext/Overlays/ThermalVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared._CorvaxNext.Overlays;

namespace Content.Server._CorvaxNext.Overlays;

public sealed class ThermalVisionSystem : SwitchableOverlaySystem<ThermalVisionComponent, ToggleThermalVisionEvent>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Robust.Shared.Prototypes;

namespace Content.Shared._CorvaxNext.Clothing
{
[RegisterComponent]
public sealed partial class ClothingGrantComponentComponent : Component
{
[DataField("component")]
[AlwaysPushInheritance]
public ComponentRegistry Components { get; private set; } = new();

public bool IsActive = false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Content.Shared._CorvaxNext.Clothing;

[RegisterComponent]
public sealed partial class ClothingGrantTagComponent : Component
{
[DataField("tag")]
public string Tag = "";

public bool IsActive = false;
}
Loading

0 comments on commit e1a05f2

Please sign in to comment.