diff --git a/Content.Client/White/Overlays/NightVisionOverlay.cs b/Content.Client/White/Overlays/NightVisionOverlay.cs new file mode 100644 index 0000000000..adc646a2e2 --- /dev/null +++ b/Content.Client/White/Overlays/NightVisionOverlay.cs @@ -0,0 +1,50 @@ +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 sealed class NightVisionOverlay : Overlay + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + public override bool RequestScreenTexture => true; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + private readonly ShaderInstance _shader; + + public NightVisionOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototypeManager.Index("NightVision").InstanceUnique(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + + if (!_entityManager.TryGetComponent(_playerManager.LocalSession?.AttachedEntity, + out var component)) + { + return; + } + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("tint", component.Tint); + _shader.SetParameter("luminance_threshold", component.Strength); + _shader.SetParameter("noise_amount", component.Noise); + + handle.UseShader(_shader); + handle.DrawRect(args.WorldBounds, component.Color); + handle.UseShader(null); + } + } +} diff --git a/Content.Client/White/Overlays/NightVisionSystem.cs b/Content.Client/White/Overlays/NightVisionSystem.cs new file mode 100644 index 0000000000..eea89161a5 --- /dev/null +++ b/Content.Client/White/Overlays/NightVisionSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.GameTicking; +using Content.Shared.White.Overlays; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client.White.Overlays; + +public sealed class NightVisionSystem : EntitySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly ILightManager _lightManager = default!; + + private NightVisionOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnRestart); + + _overlay = new NightVisionOverlay(); + } + + private void OnPlayerAttached(EntityUid uid, NightVisionComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + _lightManager.DrawLighting = false; + } + + private void OnPlayerDetached(EntityUid uid, NightVisionComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + _lightManager.DrawLighting = true; + } + + private void OnInit(EntityUid uid, NightVisionComponent component, ComponentInit args) + { + if (_player.LocalSession?.AttachedEntity != uid) + return; + + _overlayMan.AddOverlay(_overlay); + _lightManager.DrawLighting = false; + } + + private void OnRemove(EntityUid uid, NightVisionComponent component, ComponentRemove args) + { + if (_player.LocalSession?.AttachedEntity != uid) + return; + + _overlayMan.RemoveOverlay(_overlay); + _lightManager.DrawLighting = true; + } + + private void OnRestart(RoundRestartCleanupEvent ev) + { + _overlayMan.RemoveOverlay(_overlay); + _lightManager.DrawLighting = true; + } +} diff --git a/Content.Server/White/Items/Tricorder/TricorderSystem.cs b/Content.Server/White/Items/Tricorder/TricorderSystem.cs index 62d0f13689..f08de51a57 100644 --- a/Content.Server/White/Items/Tricorder/TricorderSystem.cs +++ b/Content.Server/White/Items/Tricorder/TricorderSystem.cs @@ -8,9 +8,8 @@ using Content.Shared.Examine; using Content.Shared.MedicalScanner; using Content.Shared.Verbs; using Content.Shared.White.Item.Tricorder; -using Robust.Server.GameObjects; +using Robust.Server.Audio; using Robust.Shared.Audio; -using Robust.Shared.GameStates; using Robust.Shared.Utility; namespace Content.Server.White.Items.Tricorder; @@ -38,8 +37,7 @@ public sealed class TricorderSystem : SharedTricorderSystem private void OnAddSwitchModeVerbs(EntityUid uid, TricorderComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || - !HasComp(args.Target)) + if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp(args.Target)) { return; } @@ -100,44 +98,41 @@ public sealed class TricorderSystem : SharedTricorderSystem if (!user.HasValue) return; - UpdateModeAppearance(user.Value, tricorder); + UpdateModeAppearance(user.Value, tricoderUid, tricorder); } - private void UpdateModeAppearance( - EntityUid userUid, - TricorderComponent tricorder) + private void UpdateModeAppearance(EntityUid userUid, EntityUid tricoderUid, TricorderComponent tricorder) { - Dirty(tricorder); _audioSystem.PlayPvs(tricorder.SoundSwitchMode, userUid, AudioParams.Default.WithVolume(1.5f)); + Dirty(tricoderUid, tricorder); } private void SetToMultitool(EntityUid uid) { - var comp = AddComp(uid); RemComp(uid); RemComp(uid); - Dirty(comp); - if (!TryComp(uid, out ActivatableUIComponent? ui)) + var comp = AddComp(uid); + if (TryComp(uid, out ActivatableUIComponent? ui)) { - return; + ui.Key = NetworkConfiguratorUiKey.Configure; } - ui.Key = NetworkConfiguratorUiKey.Configure; + Dirty(uid, comp); } private void SetToGasAnalyzer(EntityUid uid) { RemComp(uid); - AddComp(uid); RemComp(uid); - if (!TryComp(uid, out ActivatableUIComponent? ui)) + var comp = AddComp(uid); + if (TryComp(uid, out ActivatableUIComponent? ui)) { - return; + ui.Key = GasAnalyzerComponent.GasAnalyzerUiKey.Key; } - ui.Key = GasAnalyzerComponent.GasAnalyzerUiKey.Key; + Dirty(uid, comp); } private void SetToHealthAnalyzer(EntityUid uid) @@ -147,15 +142,12 @@ public sealed class TricorderSystem : SharedTricorderSystem var healthAnalyzerComponent = _componentFactory.GetComponent(); healthAnalyzerComponent.ScanningEndSound = new SoundPathSpecifier("/Audio/Items/Medical/healthscanner.ogg"); - healthAnalyzerComponent.Owner = uid; + _entityManager.AddComponent(uid, healthAnalyzerComponent); - - if (!TryComp(uid, out ActivatableUIComponent? ui)) + if (TryComp(uid, out ActivatableUIComponent? ui)) { - return; + ui.Key = HealthAnalyzerUiKey.Key; } - - ui.Key = HealthAnalyzerUiKey.Key; } -} \ No newline at end of file +} diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index b8fc551e75..e67223ae3c 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -23,9 +23,6 @@ false - - - diff --git a/Content.Shared/White/Item/Tricorder/TricorderComponent.cs b/Content.Shared/White/Item/Tricorder/TricorderComponent.cs index 7f391c8b2a..a8669ce97a 100644 --- a/Content.Shared/White/Item/Tricorder/TricorderComponent.cs +++ b/Content.Shared/White/Item/Tricorder/TricorderComponent.cs @@ -4,10 +4,8 @@ using Robust.Shared.Serialization; namespace Content.Shared.White.Item.Tricorder; -[RegisterComponent] -[NetworkedComponent] -[Access(typeof(SharedTricorderSystem))] -public sealed class TricorderComponent : Component +[RegisterComponent, NetworkedComponent, Access(typeof(SharedTricorderSystem))] +public sealed partial class TricorderComponent : Component { [DataField("currentState"), ViewVariables(VVAccess.ReadWrite)] public TricorderMode CurrentMode = TricorderMode.Multitool; @@ -35,4 +33,4 @@ public enum TricorderMode Multitool, GasAnalyzer, HealthAnalyzer -} \ No newline at end of file +} diff --git a/Content.Shared/White/Overlays/NightVisionComponent.cs b/Content.Shared/White/Overlays/NightVisionComponent.cs new file mode 100644 index 0000000000..a78ccbd8d5 --- /dev/null +++ b/Content.Shared/White/Overlays/NightVisionComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.White.Overlays; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class NightVisionComponent : Component +{ + [DataField("tint"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public Vector3 Tint = new(0.3f, 0.3f, 0.3f); + + [DataField("strength"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float Strength = 2f; + + [DataField("noise"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float Noise = 0.5f; + + [DataField("color"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public Color Color = Color.FromHex("#98FB98"); +} diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index f6f30fdd4e..b6b32d2be1 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -100,6 +100,11 @@ kind: source path: "/Textures/Shaders/cataracts.swsl" +- type: shader + id: NightVision + kind: source + path: "/Textures/Shaders/White/nightvision.swsl" + - type: shader id: SaturationScale kind: source diff --git a/Resources/Prototypes/White/Entities/Clothing/Head/night_vision_goggle.yml b/Resources/Prototypes/White/Entities/Clothing/Head/night_vision_goggle.yml index a484c97e96..e771deb421 100644 --- a/Resources/Prototypes/White/Entities/Clothing/Head/night_vision_goggle.yml +++ b/Resources/Prototypes/White/Entities/Clothing/Head/night_vision_goggle.yml @@ -17,6 +17,7 @@ - type: entity parent: ClothingEyesNightVisionGoggles id: ClothingEyesNightVisionGogglesSyndie + suffix: "Хамелеон" components: - type: ChameleonClothing slot: [ eyes ] diff --git a/Resources/Textures/Shaders/White/nightvision.swsl b/Resources/Textures/Shaders/White/nightvision.swsl new file mode 100644 index 0000000000..8a3e7706ad --- /dev/null +++ b/Resources/Textures/Shaders/White/nightvision.swsl @@ -0,0 +1,38 @@ +light_mode unshaded; + +uniform sampler2D SCREEN_TEXTURE; +uniform highp vec3 tint; // Colour of the tint +uniform highp float luminance_threshold; // number between 0 and 1 +uniform highp float noise_amount; // number between 0 and 1 + +lowp float rand (lowp vec2 n) { + return 0.5 + 0.5 * fract (sin (dot (n.xy, vec2 (12.9898, 78.233)))* 43758.5453); +} + +void fragment() { + + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy * SCREEN_PIXEL_SIZE); + + // convert color to grayscale using luminance + highp float grey = dot(color.rgb, vec3(0.298, 0.5882, 0.1137)); + + // calculate local threshold + highp float threshold = grey * luminance_threshold; + + // amplify low luminance parts + if (grey < threshold) { + grey += (threshold - grey) * 0.5; + if (grey > 1.0) { + grey = 1.0; + } + } + + // apply night vision color tint + color.rgb = mix(color.rgb, tint, grey); + + // add some noise for realism + lowp float noise = rand(FRAGCOORD.xy + TIME) * noise_amount / 10.0; + color.rgb += noise; + + COLOR = color; +}