Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Night and thermal vision device #104

Merged
merged 9 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
AwareFoxy marked this conversation as resolved.
Show resolved Hide resolved
{
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
Loading