diff --git a/Content.Client/Overlays/BlackAndWhiteOverlay.cs b/Content.Client/Overlays/BlackAndWhiteOverlay.cs index aae2b63acf..785d1dad7f 100644 --- a/Content.Client/Overlays/BlackAndWhiteOverlay.cs +++ b/Content.Client/Overlays/BlackAndWhiteOverlay.cs @@ -1,5 +1,4 @@ using Robust.Client.Graphics; -using Robust.Client.Player; using Robust.Shared.Enums; using Robust.Shared.Prototypes; @@ -7,9 +6,7 @@ namespace Content.Client.Overlays; public sealed partial class BlackAndWhiteOverlay : Overlay { - [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; public override OverlaySpace Space => OverlaySpace.WorldSpace; public override bool RequestScreenTexture => true; @@ -22,17 +19,6 @@ public sealed partial class BlackAndWhiteOverlay : Overlay ZIndex = 10; // draw this over the DamageOverlay, RainbowOverlay etc. } - protected override bool BeforeDraw(in OverlayDrawArgs args) - { - if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) - return false; - - if (args.Viewport.Eye != eyeComp.Eye) - return false; - - return true; - } - protected override void Draw(in OverlayDrawArgs args) { if (ScreenTexture == null) diff --git a/Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs b/Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs index 09c282d10a..7f5cd33a1f 100644 --- a/Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs +++ b/Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs @@ -1,7 +1,6 @@ using Content.Shared.Inventory.Events; using Content.Shared.Overlays; using Robust.Client.Graphics; -using Robust.Client.Player; namespace Content.Client.Overlays; diff --git a/Content.Client/Overlays/NoirOverlay.cs b/Content.Client/Overlays/NoirOverlay.cs new file mode 100644 index 0000000000..d2a6cbe8b3 --- /dev/null +++ b/Content.Client/Overlays/NoirOverlay.cs @@ -0,0 +1,33 @@ +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.Overlays; + +public sealed partial class NoirOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + private readonly ShaderInstance _noirShader; + + public NoirOverlay() + { + IoCManager.InjectDependencies(this); + _noirShader = _prototypeManager.Index("Noir").InstanceUnique(); + ZIndex = 9; // draw this over the DamageOverlay, RainbowOverlay etc, but before the black and white shader + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + _noirShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + handle.UseShader(_noirShader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/NoirOverlaySystem.cs b/Content.Client/Overlays/NoirOverlaySystem.cs new file mode 100644 index 0000000000..d51a323419 --- /dev/null +++ b/Content.Client/Overlays/NoirOverlaySystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Overlays; +using Robust.Client.Graphics; + +namespace Content.Client.Overlays; + +public sealed partial class NoirOverlaySystem : EquipmentHudSystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private NoirOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _overlay = new(); + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent component) + { + base.UpdateInternal(component); + + _overlayMan.AddOverlay(_overlay); + } + + protected override void DeactivateInternal() + { + base.DeactivateInternal(); + + _overlayMan.RemoveOverlay(_overlay); + } +} diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 55adadc4ed..7973af35ab 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -85,6 +85,7 @@ public partial class InventorySystem SubscribeLocalEvent>(RefRelayInventoryEvent); SubscribeLocalEvent>(RefRelayInventoryEvent); SubscribeLocalEvent>(RefRelayInventoryEvent); + SubscribeLocalEvent>(RefRelayInventoryEvent); SubscribeLocalEvent>(OnGetEquipmentVerbs); SubscribeLocalEvent>(OnGetInnateVerbs); diff --git a/Content.Shared/Overlays/NoirOverlayComponent.cs b/Content.Shared/Overlays/NoirOverlayComponent.cs new file mode 100644 index 0000000000..107def236c --- /dev/null +++ b/Content.Shared/Overlays/NoirOverlayComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays; + +/// +/// Makes the entity see everything with a sin city shader (everything in black and white, except red) by adding an overlay. +/// When added to a clothing item it will also grant the wearer the same overlay. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class NoirOverlayComponent : Component; diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index dd2183ca51..c8fd728bf6 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -281,7 +281,7 @@ - type: FlashImmunity - type: EyeProtection protectionTime: 5 - - type: BlackAndWhiteOverlay + - type: NoirOverlay - type: Tag tags: - HamsterWearable diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index 6e0bbd55b4..057abf0ac2 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -47,6 +47,12 @@ kind: source path: "/Textures/Shaders/rainbow.swsl" +# sin city effect: everything is greyscale, except red +- type: shader + id: Noir + kind: source + path: "/Textures/Shaders/noir.swsl" + - type: shader id: CameraStatic kind: source @@ -108,4 +114,4 @@ - type: shader id: Hologram kind: source - path: "/Textures/Shaders/hologram.swsl" \ No newline at end of file + path: "/Textures/Shaders/hologram.swsl" diff --git a/Resources/Textures/Shaders/noir.swsl b/Resources/Textures/Shaders/noir.swsl new file mode 100644 index 0000000000..ed1d0d70af --- /dev/null +++ b/Resources/Textures/Shaders/noir.swsl @@ -0,0 +1,46 @@ +// Sin City style shader. +// Makes everything black and white, but keeps red colors. + +uniform sampler2D SCREEN_TEXTURE; +// Sensitivity, in degrees. +const highp float RedHueRange = 5; +const highp float MinSaturation = 0.4; + +// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB +highp vec3 rgb2hsv(highp vec3 c) { + highp float xMax = max(c.r, max(c.g, c.b)); + highp float xMin = min(c.r, min(c.g, c.b)); + highp float delta = xMax - xMin; + + highp float hue = 0.0; + if (delta > 0.0) { + if (xMax == c.r) { + hue = mod((c.g - c.b) / delta, 6.0); + } else if (xMax == c.g) { + hue = (c.b - c.r) / delta + 2.0; + } else { + hue = (c.r - c.g) / delta + 4.0; + } + hue *= 60.0; + if (hue < 0.0) hue += 360.0; + } + + highp float sat = (xMax == 0.0) ? 0.0 : delta / xMax; + return vec3(hue, sat, xMax); +} + +void fragment() { + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, UV); + highp vec3 gray = vec3(zGrayscale(color.rgb)); + highp vec3 hsv = rgb2hsv(color.rgb); + + // Red is near 0 or 360 in hue + bool is_red = hsv.x < RedHueRange || hsv.x > (360.0 - RedHueRange); + bool saturated_enough = hsv.y > MinSaturation; // Avoid desaturated pinks/greys + + if (is_red && saturated_enough) { + COLOR = color; + } else { + COLOR = vec4(gray, color.a); + } +}