Thermal and Night Vision update (#429)

* base

* fix

* fix

* fix2

* xeno

* xenoborg + locale
This commit is contained in:
JunJun
2025-12-15 14:19:24 +03:00
committed by GitHub
parent e9616e5244
commit ae8d7ab59d
10 changed files with 134 additions and 8 deletions

View File

@@ -42,7 +42,7 @@ public sealed class BaseSwitchableOverlay<TComp> : Overlay where TComp : Switcha
worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_shader);
worldHandle.DrawRect(args.WorldBounds, Comp.Color.WithAlpha(alpha));
worldHandle.DrawRect(args.WorldBounds, Comp.Color.WithAlpha(alpha * Comp.OverlayOpacity));
worldHandle.UseShader(null);
}
}

View File

@@ -9,18 +9,21 @@ using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client._White.Overlays;
public sealed class ThermalVisionOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[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 SpriteSystem _sprite;
private readonly ContainerSystem _container;
private readonly SharedPointLightSystem _light;
@@ -42,6 +45,7 @@ public sealed class ThermalVisionOverlay : Overlay
_container = _entity.System<ContainerSystem>();
_transform = _entity.System<TransformSystem>();
_stealth = _entity.System<StealthSystem>();
_sprite = _entity.System<SpriteSystem>();
_light = _entity.System<SharedPointLightSystem>();
ZIndex = -1;
@@ -111,7 +115,7 @@ public sealed class ThermalVisionOverlay : Overlay
foreach (var entry in _entries)
{
Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, alpha);
Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, Comp.ThermalShader, alpha);
}
worldHandle.SetTransform(Matrix3x2.Identity);
@@ -122,6 +126,7 @@ public sealed class ThermalVisionOverlay : Overlay
DrawingHandleWorld handle,
Angle eyeRot,
Color color,
string? shader,
float alpha)
{
var (uid, sprite, xform) = ent;
@@ -131,11 +136,40 @@ public sealed class ThermalVisionOverlay : Overlay
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;
Dictionary<int, (ShaderInstance? shader, Color color)> layerData = new();
if (shader != null)
{
// Layer shaders break handle shader so we have to do this. It has a side effect of clothing not rendering
// on some species or on female characters but its fine cause shader itself makes things hard to see
var allLayers = sprite.AllLayers.ToList();
for (var i = 0; i < allLayers.Count; i++)
{
if (allLayers[i] is not SpriteComponent.Layer { Visible: true } layer)
continue;
if (layer.ShaderPrototype?.Id is "DisplacedDraw" or "DisplacedStencilDraw")
_sprite.LayerSetVisible((uid, sprite), i, false);
layerData[i] = (layer.Shader, layer.Color);
layer.Shader = null;
_sprite.LayerSetColor(layer, Color.White.WithAlpha(layer.Color.A));
}
_sprite.SetColor((uid, sprite), Color.White.WithAlpha(alpha));
handle.UseShader(_protoMan.Index<ShaderPrototype>(shader).Instance());
}
else
_sprite.SetColor((uid, sprite), color.WithAlpha(alpha));
_sprite.RenderSprite((uid, sprite), handle, eyeRot, rotation, position);
_sprite.SetColor((uid, sprite), originalColor);
handle.UseShader(null);
foreach (var (key, value) in layerData)
{
((SpriteComponent.Layer) sprite[key]).Shader = value.shader;
_sprite.LayerSetColor((uid, sprite), key, value.color);
_sprite.LayerSetVisible((uid, sprite), key, true);
}
}
private bool CanSee(EntityUid uid, SpriteComponent sprite)

View File

@@ -75,6 +75,8 @@ public abstract class SwitchableOverlaySystem<TComp, TEvent> : EntitySystem
DeactivateSound = component.DeactivateSound,
ToggleAction = component.ToggleAction,
LightRadius = component is ThermalVisionComponent thermal ? thermal.LightRadius : 0f,
DrawOverlay = component.DrawOverlay,
OverlayOpacity = component.OverlayOpacity,
};
}
@@ -87,6 +89,8 @@ public abstract class SwitchableOverlaySystem<TComp, TEvent> : EntitySystem
component.IsEquipment = state.IsEquipment;
component.ActivateSound = state.ActivateSound;
component.DeactivateSound = state.DeactivateSound;
component.DrawOverlay = state.DrawOverlay;
component.OverlayOpacity = state.OverlayOpacity;
if (component.ToggleAction != state.ToggleAction)
{

View File

@@ -12,6 +12,9 @@ public abstract partial class SwitchableVisionOverlayComponent : BaseVisionOverl
[DataField]
public bool DrawOverlay = true;
[DataField]
public float OverlayOpacity = 0.5f;
/// <summary>
/// Whether it should grant equipment enhanced vision or is it mob vision
/// </summary>
@@ -50,4 +53,6 @@ public sealed class SwitchableVisionOverlayComponentState : IComponentState
public SoundSpecifier? DeactivateSound;
public EntProtoId? ToggleAction;
public float LightRadius;
public bool DrawOverlay;
public float OverlayOpacity;
}

View File

@@ -9,10 +9,13 @@ public sealed partial class ThermalVisionComponent : SwitchableVisionOverlayComp
{
public override EntProtoId? ToggleAction { get; set; } = "ToggleThermalVision";
public override Color Color { get; set; } = Color.FromHex("#F84742");
public override Color Color { get; set; } = Color.FromHex("#d06764");
[DataField]
public float LightRadius = 5f;
public float LightRadius = 2f;
[DataField]
public string? ThermalShader = "ThermalVision";
}
public sealed partial class ToggleThermalVisionEvent : InstantActionEvent;

View File

@@ -0,0 +1,6 @@
ent-ToggleNightVision = Включить/выключить ночное зрение
.desc = Включает или выключает ночное зрение.
ent-ToggleThermalVision = Включить/выключить тепловое зрение
.desc = Включает или выключает тепловое зрение.
ent-PulseThermalVision = Импульс теплового зрения
.desc = Временно активирует тепловое зрение.

View File

@@ -246,6 +246,12 @@
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellHigh
# WL-Night-Vision-Start
- type: NightVision
color: "#224fff"
activateSound: null
deactivateSound: null
# WL-Night-Vision-End
# xenoborgs empty

View File

@@ -118,6 +118,12 @@
molsPerSecondPerUnitMass: 0.0005
- type: Speech
speechVerb: LargeMob
# WL-Night-Vision-Start
- type: NightVision
color: "#A3A3A3"
activateSound: null
deactivateSound: null
# WL-Night-Vision-End
- type: entity
name: praetorian

View File

@@ -0,0 +1,6 @@
- type: shader
id: ThermalVision
kind: source
path: "/Textures/_Goobstation/Shaders/thermal.swsl"
params:
pixelSize: 32.0

View File

@@ -0,0 +1,56 @@
light_mode unshaded;
uniform lowp float pixelSize;
//
// https://gamedev.stackexchange.com/a/59808
//
// Author: sam hocevar
// Answered: Jul 27, 2013 at 13:33
// License: CC BY-SA 3.0
//
highp vec3 rgb2hsv(highp vec3 c) {
highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
highp float d = q.x - min(q.w, q.y);
highp float e = 0.00000000010; //because this doesn't support doing 1.0e-10
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
highp vec3 hsv2rgb(highp vec3 c) {
highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
highp float getNeighbor(highp vec2 uv, lowp int x, lowp int y) {
lowp float xdif = float(x) / pixelSize;
lowp float ydif = float(y) / pixelSize;
highp vec2 res = vec2(uv.x + xdif, uv.y + ydif);
if (res.x < 0.0 || res.x > 1.0 || res.y < 0.0 || res.y > 1.0)
return 0.0;
return zTexture(res).a;
}
void fragment() {
highp vec4 sprite = zTexture(UV);
if (sprite.a == 0.0) {
discard;
}
highp float s = 0.0;
for(int y = -2; y <= 2; y++) {
for(int x = -2; x <= 2; x++) {
s = s + getNeighbor(UV, x, y) * 0.4;
}
}
highp vec3 hsv = rgb2hsv(sprite.xyz);
sprite.rgb = hsv2rgb(vec3(pow(hsv.z, 0.5), s, 1.0));
// sprite.rgb = sprite.rgb * vec3(1.0, 1.0, 0.5);
COLOR = sprite;
}