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

[Port] Night And Thermal Vision / Ночное И Термальное Зрение #89

Merged
merged 13 commits into from
Oct 26, 2024
46 changes: 46 additions & 0 deletions Content.Client/_White/Overlays/BaseSwitchableOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Content.Shared._White.Overlays;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;

namespace Content.Client._White.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();
Spatison marked this conversation as resolved.
Show resolved Hide resolved
}

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)
return;
Spatison marked this conversation as resolved.
Show resolved Hide resolved

_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(Matrix3.Identity);
worldHandle.UseShader(_shader);
worldHandle.DrawRect(args.WorldBounds, component.Color);
worldHandle.UseShader(null);
}
}
85 changes: 85 additions & 0 deletions Content.Client/_White/Overlays/NightVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Content.Shared._White.Overlays;
using Content.Shared.GameTicking;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;

namespace Content.Client._White.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);
}
Spatison marked this conversation as resolved.
Show resolved Hide resolved

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;
Spatison marked this conversation as resolved.
Show resolved Hide resolved

UpdateOverlay(active);
UpdateNightVision(active);
}

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

UpdateOverlay(active);
UpdateNightVision(active);
}
Spatison marked this conversation as resolved.
Show resolved Hide resolved

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;
Spatison marked this conversation as resolved.
Show resolved Hide resolved

if (active)
_overlayMan.AddOverlay(_overlay);
else
_overlayMan.RemoveOverlay(_overlay);
}
}
130 changes: 130 additions & 0 deletions Content.Client/_White/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._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;

namespace Content.Client._White.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 == null
|| !_entity.TryGetComponent<ThermalVisionComponent>(_players.LocalEntity.Value, out var component)
|| !component.IsActive)
return;

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

if (eye == 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;
Spatison marked this conversation as resolved.
Show resolved Hide resolved

_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(Matrix3.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
&& body.ThermalVisibility
&& (!_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);
}
Spatison marked this conversation as resolved.
Show resolved Hide resolved
}

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

namespace Content.Client._White.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);
}
Spatison marked this conversation as resolved.
Show resolved Hide resolved

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);
}
Spatison marked this conversation as resolved.
Show resolved Hide resolved

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);
}
Spatison marked this conversation as resolved.
Show resolved Hide resolved
}
5 changes: 5 additions & 0 deletions Content.Server/_White/Overlays/NightVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared._White.Overlays;

namespace Content.Server._White.Overlays;

public sealed class NightVisionSystem : SwitchableOverlaySystem<NightVisionComponent, ToggleNightVisionEvent>;
Spatison marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions Content.Server/_White/Overlays/ThermalVisionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared._White.Overlays;

namespace Content.Server._White.Overlays;

public sealed class ThermalVisionSystem : SwitchableOverlaySystem<ThermalVisionComponent, ToggleThermalVisionEvent>;
5 changes: 5 additions & 0 deletions Content.Shared/Body/Components/BodyComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ public sealed partial class BodyComponent : Component
[ViewVariables]
[DataField, AutoNetworkedField]
public HashSet<EntityUid> LegEntities = new();

// WD EDIT START
[DataField, AutoNetworkedField]
public bool ThermalVisibility = true;
// WD EDIT END
}
16 changes: 16 additions & 0 deletions Content.Shared/_White/Overlays/BaseOverlayComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Content.Shared._White.Overlays;

public abstract partial class BaseOverlayComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public virtual Vector3 Tint { get; set; } = new(0.3f, 0.3f, 0.3f);

[DataField, ViewVariables(VVAccess.ReadOnly)]
public virtual float Strength { get; set; } = 2f;

[DataField, ViewVariables(VVAccess.ReadOnly)]
public virtual float Noise { get; set; } = 0.5f;

[DataField, ViewVariables(VVAccess.ReadOnly)]
public virtual Color Color { get; set; } = Color.White;
}
Loading
Loading