diff --git a/Content.Client/Overlays/EquipmentHudSystem.cs b/Content.Client/Overlays/EquipmentHudSystem.cs index c7578b6793..3672892ae3 100644 --- a/Content.Client/Overlays/EquipmentHudSystem.cs +++ b/Content.Client/Overlays/EquipmentHudSystem.cs @@ -102,7 +102,7 @@ public abstract class EquipmentHudSystem : EntitySystem where T : IComponent args.Components.Add(component); } - private void RefreshOverlay(EntityUid uid) + protected void RefreshOverlay(EntityUid uid) { if (uid != _player.LocalSession?.AttachedEntity) return; diff --git a/Content.Client/Overlays/Switchable/BaseSwitchableOverlay.cs b/Content.Client/Overlays/Switchable/BaseSwitchableOverlay.cs new file mode 100644 index 0000000000..5977355968 --- /dev/null +++ b/Content.Client/Overlays/Switchable/BaseSwitchableOverlay.cs @@ -0,0 +1,48 @@ +using System.Numerics; +using Content.Shared.Overlays.Switchable; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.Overlays.Switchable; + +public sealed class BaseSwitchableOverlay : Overlay where TComp : SwitchableOverlayComponent +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + private readonly ShaderInstance _shader; + + public TComp? Comp = null; + + public bool IsActive = true; + + public BaseSwitchableOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototype.Index("NightVision").InstanceUnique(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null || Comp is null || !IsActive) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("tint", Comp.Tint); + _shader.SetParameter("luminance_threshold", Comp.Strength); + _shader.SetParameter("noise_amount", Comp.Noise); + + var worldHandle = args.WorldHandle; + + var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime); + var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime); + + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_shader); + worldHandle.DrawRect(args.WorldBounds, Comp.Color.WithAlpha(alpha)); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/Switchable/NightVisionSystem.cs b/Content.Client/Overlays/Switchable/NightVisionSystem.cs new file mode 100644 index 0000000000..c85b3758d8 --- /dev/null +++ b/Content.Client/Overlays/Switchable/NightVisionSystem.cs @@ -0,0 +1,87 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Overlays.Switchable; +using Robust.Client.Graphics; + +namespace Content.Client.Overlays.Switchable; + +public sealed class NightVisionSystem : EquipmentHudSystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly ILightManager _lightManager = default!; + + private BaseSwitchableOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnToggle); + + _overlay = new BaseSwitchableOverlay(); + } + + private void OnToggle(Entity ent, ref SwitchableOverlayToggledEvent args) + { + RefreshOverlay(args.User); + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent args) + { + base.UpdateInternal(args); + + var active = false; + NightVisionComponent? nvComp = null; + foreach (var comp in args.Components) + { + if (comp.IsActive || comp.PulseTime > 0f && comp.PulseAccumulator < comp.PulseTime) + active = true; + else + continue; + + if (comp.DrawOverlay) + { + if (nvComp == null) + nvComp = comp; + else if (nvComp.PulseTime > 0f && comp.PulseTime <= 0f) + nvComp = comp; + } + + if (active && nvComp is { PulseTime: <= 0 }) + break; + } + + UpdateNightVision(active); + UpdateOverlay(nvComp); + } + + protected override void DeactivateInternal() + { + base.DeactivateInternal(); + + UpdateNightVision(false); + UpdateOverlay(null); + } + + private void UpdateNightVision(bool active) + { + _lightManager.DrawLighting = !active; + } + + private void UpdateOverlay(NightVisionComponent? nvComp) + { + _overlay.Comp = nvComp; + + switch (nvComp) + { + case not null when !_overlayMan.HasOverlay>(): + _overlayMan.AddOverlay(_overlay); + break; + case null: + _overlayMan.RemoveOverlay(_overlay); + break; + } + + if (_overlayMan.TryGetOverlay>(out var overlay)) + overlay.IsActive = nvComp == null; + } +} diff --git a/Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs b/Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs new file mode 100644 index 0000000000..eb12b33e3a --- /dev/null +++ b/Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs @@ -0,0 +1,159 @@ +using System.Linq; +using System.Numerics; +using Content.Client.Stealth; +using Content.Shared.Body.Components; +using Content.Shared.Overlays.Switchable; +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; +using Robust.Shared.Timing; + +namespace Content.Client.Overlays.Switchable; + +public sealed class ThermalVisionOverlay : Overlay +{ + [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 ContainerSystem _container; + private readonly SharedPointLightSystem _light; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + private readonly List _entries = []; + + private EntityUid? _lightEntity; + + public float LightRadius; + + public ThermalVisionComponent? Comp; + + public ThermalVisionOverlay() + { + IoCManager.InjectDependencies(this); + + _container = _entity.System(); + _transform = _entity.System(); + _stealth = _entity.System(); + _light = _entity.System(); + + ZIndex = -1; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null || Comp is null) + return; + + var worldHandle = args.WorldHandle; + var eye = args.Viewport.Eye; + + if (eye == null) + return; + + var player = _player.LocalEntity; + + if (!_entity.TryGetComponent(player, out TransformComponent? playerXform)) + return; + + var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime); + var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime); + + // Thermal vision grants some night vision (clientside light) + if (LightRadius > 0) + { + _lightEntity ??= _entity.SpawnAttachedTo(null, playerXform.Coordinates); + _transform.SetParent(_lightEntity.Value, player.Value); + var light = _entity.EnsureComponent(_lightEntity.Value); + _light.SetRadius(_lightEntity.Value, LightRadius, light); + _light.SetEnergy(_lightEntity.Value, alpha, light); + _light.SetColor(_lightEntity.Value, Comp.Color, light); + } + else + ResetLight(); + + var mapId = eye.Position.MapId; + var eyeRot = eye.Rotation; + + _entries.Clear(); + var entities = _entity.EntityQueryEnumerator(); + while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform)) + { + if (!CanSee(uid, sprite) || !body.ThermalVisibility) + continue; + + var entity = uid; + + if (_container.TryGetOuterContainer(uid, xform, out var container)) + { + var owner = container.Owner; + if (_entity.TryGetComponent(owner, out var ownerSprite) + && _entity.TryGetComponent(owner, out var ownerXform)) + { + entity = owner; + sprite = ownerSprite; + xform = ownerXform; + } + } + + if (_entries.Any(e => e.Ent.Owner == entity)) + continue; + + _entries.Add(new ThermalVisionRenderEntry((entity, sprite, xform), mapId, eyeRot)); + } + + foreach (var entry in _entries) + { + Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, alpha); + } + + worldHandle.SetTransform(Matrix3x2.Identity); + } + + private void Render(Entity ent, + MapId? map, + DrawingHandleWorld handle, + Angle eyeRot, + Color color, + float alpha) + { + var (uid, sprite, xform) = ent; + if (xform.MapID != map || !CanSee(uid, sprite)) + return; + + 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; + } + + private bool CanSee(EntityUid uid, SpriteComponent sprite) + { + return sprite.Visible && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) || + _stealth.GetVisibility(uid, stealth) > 0.5f); + } + + public void ResetLight() + { + if (_lightEntity == null || !_timing.IsFirstTimePredicted) + return; + + _entity.DeleteEntity(_lightEntity); + _lightEntity = null; + } +} + +public record struct ThermalVisionRenderEntry( + Entity Ent, + MapId? Map, + Angle EyeRot); diff --git a/Content.Client/Overlays/Switchable/ThermalVisionSystem.cs b/Content.Client/Overlays/Switchable/ThermalVisionSystem.cs new file mode 100644 index 0000000000..9b6e5eed0f --- /dev/null +++ b/Content.Client/Overlays/Switchable/ThermalVisionSystem.cs @@ -0,0 +1,95 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Overlays.Switchable; +using Robust.Client.Graphics; + +namespace Content.Client.Overlays.Switchable; + +public sealed class ThermalVisionSystem : EquipmentHudSystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private ThermalVisionOverlay _thermalOverlay = default!; + private BaseSwitchableOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnToggle); + + _thermalOverlay = new ThermalVisionOverlay(); + _overlay = new BaseSwitchableOverlay(); + } + + private void OnToggle(Entity ent, ref SwitchableOverlayToggledEvent args) + { + RefreshOverlay(args.User); + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent args) + { + base.UpdateInternal(args); + ThermalVisionComponent? tvComp = null; + var lightRadius = 0f; + foreach (var comp in args.Components) + { + if (!comp.IsActive && (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime)) + continue; + + if (tvComp == null) + tvComp = comp; + else if (!tvComp.DrawOverlay && comp.DrawOverlay) + tvComp = comp; + else if (tvComp.DrawOverlay == comp.DrawOverlay && tvComp.PulseTime > 0f && comp.PulseTime <= 0f) + tvComp = comp; + + lightRadius = MathF.Max(lightRadius, comp.LightRadius); + } + + UpdateThermalOverlay(tvComp, lightRadius); + UpdateOverlay(tvComp); + } + + protected override void DeactivateInternal() + { + base.DeactivateInternal(); + + UpdateOverlay(null); + UpdateThermalOverlay(null, 0f); + } + + private void UpdateThermalOverlay(ThermalVisionComponent? comp, float lightRadius) + { + _thermalOverlay.LightRadius = lightRadius; + _thermalOverlay.Comp = comp; + + switch (comp) + { + case not null when !_overlayMan.HasOverlay(): + _overlayMan.AddOverlay(_thermalOverlay); + break; + case null: + _overlayMan.RemoveOverlay(_thermalOverlay); + _thermalOverlay.ResetLight(); + break; + } + } + + private void UpdateOverlay(ThermalVisionComponent? tvComp) + { + _overlay.Comp = tvComp; + + switch (tvComp) + { + case { DrawOverlay: true } when !_overlayMan.HasOverlay>(): + _overlayMan.AddOverlay(_overlay); + break; + case null or { DrawOverlay: false }: + _overlayMan.RemoveOverlay(_overlay); + break; + } + + // Night vision overlay is prioritized + _overlay.IsActive = !_overlayMan.HasOverlay>(); + } +} diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 91acf47478..8cede391a8 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -14,6 +14,7 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -23,6 +24,8 @@ public abstract class SharedActionsSystem : EntitySystem { [Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; @@ -774,7 +777,7 @@ public abstract class SharedActionsSystem : EntitySystem if (!ResolveActionData(actionId, ref action)) return false; - DebugTools.Assert(action.Container == null || + DebugTools.Assert(_net.IsClient || action.Container == null || (TryComp(action.Container, out ActionsContainerComponent? containerComp) && containerComp.Container.Contains(actionId))); diff --git a/Content.Shared/Body/Components/BodyComponent.cs b/Content.Shared/Body/Components/BodyComponent.cs index 481e22150b..4ddfbdf979 100644 --- a/Content.Shared/Body/Components/BodyComponent.cs +++ b/Content.Shared/Body/Components/BodyComponent.cs @@ -41,4 +41,7 @@ public sealed partial class BodyComponent : Component [ViewVariables] [DataField, AutoNetworkedField] public HashSet LegEntities = new(); + + [DataField, AutoNetworkedField] + public bool ThermalVisibility = true; } diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index a65fe96296..585fa97623 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -21,6 +21,8 @@ using Content.Shared.Temperature; using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Chat; +using Content.Shared.Overlays.Switchable; + namespace Content.Shared.Inventory; @@ -65,6 +67,8 @@ public partial class InventorySystem SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(OnGetEquipmentVerbs); } diff --git a/Content.Shared/Overlays/BaseOverlayComponent.cs b/Content.Shared/Overlays/BaseOverlayComponent.cs new file mode 100644 index 0000000000..fe6a5c763e --- /dev/null +++ b/Content.Shared/Overlays/BaseOverlayComponent.cs @@ -0,0 +1,16 @@ +namespace Content.Shared.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; +} diff --git a/Content.Shared/Overlays/Switchable/NightVisionComponent.cs b/Content.Shared/Overlays/Switchable/NightVisionComponent.cs new file mode 100644 index 0000000000..cb8866dc85 --- /dev/null +++ b/Content.Shared/Overlays/Switchable/NightVisionComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Actions; +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays.Switchable; + +[RegisterComponent, NetworkedComponent] +public sealed partial class NightVisionComponent : SwitchableOverlayComponent +{ + public override string? ToggleAction { get; set; } = "ToggleNightVision"; + + public override Color Color { get; set; } = Color.FromHex("#98FB98"); +} + +public sealed partial class ToggleNightVisionEvent : InstantActionEvent; diff --git a/Content.Shared/Overlays/Switchable/NightVisionSystem.cs b/Content.Shared/Overlays/Switchable/NightVisionSystem.cs new file mode 100644 index 0000000000..f547b9dc76 --- /dev/null +++ b/Content.Shared/Overlays/Switchable/NightVisionSystem.cs @@ -0,0 +1,3 @@ +namespace Content.Shared.Overlays.Switchable; + +public sealed class NightVisionSystem : SwitchableOverlaySystem; diff --git a/Content.Shared/Overlays/Switchable/SwitchableOverlayComponent.cs b/Content.Shared/Overlays/Switchable/SwitchableOverlayComponent.cs new file mode 100644 index 0000000000..8565defe04 --- /dev/null +++ b/Content.Shared/Overlays/Switchable/SwitchableOverlayComponent.cs @@ -0,0 +1,48 @@ +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Overlays.Switchable; + +public abstract partial class SwitchableOverlayComponent : BaseOverlayComponent +{ + [DataField, AutoNetworkedField] + public bool IsActive; + + [DataField] + public bool DrawOverlay = true; + + /// + /// If it is greater than 0, overlay isn't toggled but pulsed instead + /// + [DataField] + public float PulseTime; + + [ViewVariables(VVAccess.ReadOnly)] + public float PulseAccumulator; + + [DataField] + public virtual SoundSpecifier? ActivateSound { get; set; } = + new SoundPathSpecifier("/Audio/Items/Goggles/activate.ogg"); + + [DataField] + public virtual SoundSpecifier? DeactivateSound { get; set; } = + new SoundPathSpecifier("/Audio/Items/Goggles/deactivate.ogg"); + + [DataField] + public virtual string? ToggleAction { get; set; } + + [ViewVariables] + public EntityUid? ToggleActionEntity; +} + +[Serializable, NetSerializable] +public sealed class SwitchableVisionOverlayComponentState : IComponentState +{ + public Color Color; + public bool IsActive; + public SoundSpecifier? ActivateSound; + public SoundSpecifier? DeactivateSound; + public EntProtoId? ToggleAction; + public float LightRadius; +} diff --git a/Content.Shared/Overlays/Switchable/SwitchableOverlaySystem.cs b/Content.Shared/Overlays/Switchable/SwitchableOverlaySystem.cs new file mode 100644 index 0000000000..eb7b230cbc --- /dev/null +++ b/Content.Shared/Overlays/Switchable/SwitchableOverlaySystem.cs @@ -0,0 +1,166 @@ +using Content.Shared.Actions; +using Content.Shared.Inventory; +using Robust.Shared.Audio.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Shared.Overlays.Switchable; + +public abstract class SwitchableOverlaySystem : EntitySystem + where TComp : SwitchableOverlayComponent + where TEvent : InstantActionEvent +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _net = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnToggle); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnGetItemActions); + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + if (_net.IsClient) + ActiveTick(frameTime); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (_net.IsServer) + ActiveTick(frameTime); + } + + private void ActiveTick(float frameTime) + { + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime) + continue; + + comp.PulseAccumulator += frameTime; + + if (comp.PulseAccumulator < comp.PulseTime) + continue; + + Toggle(uid, comp, false, false); + RaiseSwitchableOverlayToggledEvent(uid, uid, comp.IsActive); + RaiseSwitchableOverlayToggledEvent(uid, Transform(uid).ParentUid, comp.IsActive); + } + } + + private void OnGetState(EntityUid uid, TComp component, ref ComponentGetState args) + { + args.State = new SwitchableVisionOverlayComponentState + { + Color = component.Color, + IsActive = component.IsActive, + ActivateSound = component.ActivateSound, + DeactivateSound = component.DeactivateSound, + ToggleAction = component.ToggleAction, + LightRadius = component is ThermalVisionComponent thermal ? thermal.LightRadius : 0f, + }; + } + + private void OnHandleState(EntityUid uid, TComp component, ref ComponentHandleState args) + { + if (args.Current is not SwitchableVisionOverlayComponentState state) + return; + + component.Color = state.Color; + component.ActivateSound = state.ActivateSound; + component.DeactivateSound = state.DeactivateSound; + + if (component.ToggleAction != state.ToggleAction) + { + _actions.RemoveAction(uid, component.ToggleActionEntity); + component.ToggleAction = state.ToggleAction; + if (component.ToggleAction != null) + _actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction); + } + + if (component is ThermalVisionComponent thermal) + thermal.LightRadius = state.LightRadius; + + if (component.IsActive == state.IsActive) + return; + + component.IsActive = state.IsActive; + + RaiseSwitchableOverlayToggledEvent(uid, uid, component.IsActive); + RaiseSwitchableOverlayToggledEvent(uid, Transform(uid).ParentUid, component.IsActive); + } + + private void OnGetItemActions(Entity ent, ref GetItemActionsEvent args) + { + if (ent.Comp.ToggleAction != null && args.SlotFlags is not SlotFlags.POCKET and not null) + args.AddAction(ref ent.Comp.ToggleActionEntity, ent.Comp.ToggleAction); + } + + private void OnShutdown(EntityUid uid, TComp component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.ToggleActionEntity); + } + + private void OnInit(EntityUid uid, TComp component, ComponentInit args) + { + component.PulseAccumulator = component.PulseTime; + } + + private void OnMapInit(EntityUid uid, TComp component, MapInitEvent args) + { + if (component.ToggleActionEntity == null && component.ToggleAction != null) + _actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction); + } + + private void OnToggle(EntityUid uid, TComp component, TEvent args) + { + Toggle(uid, component, !component.IsActive); + RaiseSwitchableOverlayToggledEvent(uid, args.Performer, component.IsActive); + args.Handled = true; + } + + private void Toggle(EntityUid uid, TComp component, bool activate, bool playSound = true) + { + if (playSound && _net.IsClient && _timing.IsFirstTimePredicted) + { + _audio.PlayEntity(activate ? component.ActivateSound : component.DeactivateSound, + Filter.Local(), + uid, + false); + } + + if (component.PulseTime > 0f) + { + component.PulseAccumulator = activate ? 0f : component.PulseTime; + return; + } + + component.IsActive = activate; + Dirty(uid, component); + } + + private void RaiseSwitchableOverlayToggledEvent(EntityUid uid, EntityUid user, bool activated) + { + var ev = new SwitchableOverlayToggledEvent(user, activated); + RaiseLocalEvent(uid, ref ev); + } +} + +[ByRefEvent] +public record struct SwitchableOverlayToggledEvent(EntityUid User, bool Activated); diff --git a/Content.Shared/Overlays/Switchable/ThermalVisionComponent.cs b/Content.Shared/Overlays/Switchable/ThermalVisionComponent.cs new file mode 100644 index 0000000000..6e3d39f289 --- /dev/null +++ b/Content.Shared/Overlays/Switchable/ThermalVisionComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Actions; +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays.Switchable; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ThermalVisionComponent : SwitchableOverlayComponent +{ + public override string? ToggleAction { get; set; } = "ToggleThermalVision"; + + public override Color Color { get; set; } = Color.FromHex("#F84742"); + + [DataField] + public float LightRadius = 5f; +} + +public sealed partial class ToggleThermalVisionEvent : InstantActionEvent; diff --git a/Content.Shared/Overlays/Switchable/ThermalVisionSystem.cs b/Content.Shared/Overlays/Switchable/ThermalVisionSystem.cs new file mode 100644 index 0000000000..c19d0d1690 --- /dev/null +++ b/Content.Shared/Overlays/Switchable/ThermalVisionSystem.cs @@ -0,0 +1,3 @@ +namespace Content.Shared.Overlays.Switchable; + +public sealed class SharedThermalVisionSystem : SwitchableOverlaySystem; diff --git a/Resources/Audio/Items/Goggles/activate.ogg b/Resources/Audio/Items/Goggles/activate.ogg new file mode 100644 index 0000000000..96cdb288fe Binary files /dev/null and b/Resources/Audio/Items/Goggles/activate.ogg differ diff --git a/Resources/Audio/Items/Goggles/attributions.yml b/Resources/Audio/Items/Goggles/attributions.yml new file mode 100644 index 0000000000..7b1121f542 --- /dev/null +++ b/Resources/Audio/Items/Goggles/attributions.yml @@ -0,0 +1,9 @@ +- files: ["activate.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Taken from TGstation" + source: "https://github.com/tgstation/tgstation" + +- files: ["deactivate.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Taken from TGstation" + source: "https://github.com/tgstation/tgstation" \ No newline at end of file diff --git a/Resources/Audio/Items/Goggles/deactivate.ogg b/Resources/Audio/Items/Goggles/deactivate.ogg new file mode 100644 index 0000000000..e1e8f4fd82 Binary files /dev/null and b/Resources/Audio/Items/Goggles/deactivate.ogg differ diff --git a/Resources/Locale/en-US/research/technologies.ftl b/Resources/Locale/en-US/research/technologies.ftl index 26cd4f8315..88cd0be397 100644 --- a/Resources/Locale/en-US/research/technologies.ftl +++ b/Resources/Locale/en-US/research/technologies.ftl @@ -53,6 +53,8 @@ research-technology-advanced-anomaly-research = Advanced Anomaly Research research-technology-rped = Rapid Part Exchange research-technology-super-parts = Super Parts research-technology-deterrence = Deterrence Technologies +research-technology-night-vision = Night vision +research-technology-thermal-vision = Thermal vision research-technology-janitorial-equipment = Janitorial Equipment research-technology-laundry-tech = Laundry Tech diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 7eaacc5026..214a259296 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -329,6 +329,12 @@ uplink-hardsuit-syndieelite-desc = An elite version of the blood-red hardsuit, w uplink-clothing-outer-hardsuit-juggernaut-name = Cybersun Juggernaut Suit uplink-clothing-outer-hardsuit-juggernaut-desc = Hyper resilient armor made of materials tested in the Tau chromosphere facility. The only thing that's going to be slowing you down is this suit... and tasers. +uplink-night-vision-name = Night vision goggles +uplink-night-vision-desc = They allow you to see in the dark, all while looking like normal sunglasses! + +uplink-thermal-vision-name = Thermal vision goggles +uplink-thermal-vision-desc = They allow you to see living creatures regardless of obstacles, all while looking like normal sunglasses! + # Misc uplink-cyberpen-name = Cybersun Pen uplink-cyberpen-desc = Cybersun's legal department pen, invaluable for forging documents and escaping prisons. Smells vaguely of hard-light and war profiteering. diff --git a/Resources/Locale/ru-RU/prototypes/actions/types.ftl b/Resources/Locale/ru-RU/prototypes/actions/types.ftl index 4a9364816a..c14186ba7d 100644 --- a/Resources/Locale/ru-RU/prototypes/actions/types.ftl +++ b/Resources/Locale/ru-RU/prototypes/actions/types.ftl @@ -48,3 +48,12 @@ ent-ActionToggleEyes = Открыть/закрыть глаза .desc = Закройте глаза, чтобы защитить их, или откройте, чтобы насладиться яркими цветами. ent-ActionToggleWagging = action-name-toggle-wagging .desc = action-description-toggle-wagging + +ent-ToggleNightVision = Переключить ночное зрение + .desc = Переключает ночное зрение. + +ent-ToggleThermalVision = Переключить тепловизионное зрение + .desc = Переключает тепловизионное зрение. + +ent-PulseThermalVision = Подать термальный импульт + .desc = Временно активируйте тепловизионное зрение. diff --git a/Resources/Locale/ru-RU/prototypes/entities/clothing/eyes/goggles.ftl b/Resources/Locale/ru-RU/prototypes/entities/clothing/eyes/goggles.ftl new file mode 100644 index 0000000000..050e373c86 --- /dev/null +++ b/Resources/Locale/ru-RU/prototypes/entities/clothing/eyes/goggles.ftl @@ -0,0 +1,23 @@ +# Night Vision Goggles +ent-ClothingEyesNightVisionGoggles = ПНВ + .desc = Усовершенствованный дисплей, который обеспечивает видимость в полной темноте. +ent-ClothingEyesNightVisionSecurityGoggles = ПНВ службы безопасности + .desc = { ent-ClothingEyesNightVisionGoggles.desc } +ent-ClothingEyesNightVisionMedicalGoggles = ПНВ медицинский + .desc = { ent-ClothingEyesNightVisionGoggles.desc } +ent-ClothingEyesNightVisionDiagnosticGoggles = ПНВ диагностический + .desc = { ent-ClothingEyesNightVisionGoggles.desc } +ent-ClothingEyesNightVisionGogglesSyndie = { ent-ClothingEyesNightVisionGoggles } + .desc = { ent-ClothingEyesNightVisionGoggles.desc } +ent-ClothingEyesNightVisionGogglesNukie = { ent-ClothingEyesNightVisionGoggles } + .desc = { ent-ClothingEyesNightVisionGoggles.desc } + +# Thermal Vision Goggles +ent-ClothingEyesThermalVisionGoggles = ПТВ + .desc = Термальность - это как отношения, важно не только наличие тепла, но и его распределение. +ent-ClothingEyesThermalVisionMonocle = термонокль + .desc = Видение сквозь стены ещё никогда не было таким нежным и личностным. +ent-ClothingEyesThermalVisionGogglesSyndie = { ent-ClothingEyesThermalVisionGoggles } + .desc = { ent-ClothingEyesThermalVisionGoggles.desc } +ent-ClothingEyesThermalVisionGogglesNukie = { ent-ClothingEyesThermalVisionGoggles } + .desc = { ent-ClothingEyesThermalVisionGoggles.desc } diff --git a/Resources/Locale/ru-RU/entities/objects/tools/empflashlight.ftl b/Resources/Locale/ru-RU/prototypes/objects/tools/empflashlight.ftl similarity index 100% rename from Resources/Locale/ru-RU/entities/objects/tools/empflashlight.ftl rename to Resources/Locale/ru-RU/prototypes/objects/tools/empflashlight.ftl diff --git a/Resources/Locale/ru-RU/research/techologies.ftl b/Resources/Locale/ru-RU/research/techologies.ftl new file mode 100644 index 0000000000..54f3899255 --- /dev/null +++ b/Resources/Locale/ru-RU/research/techologies.ftl @@ -0,0 +1,2 @@ +research-technology-night-vision = Ночное видение +research-technology-thermal-vision = Термальное видение \ No newline at end of file diff --git a/Resources/Locale/ru-RU/store/uplink-catalog.ftl b/Resources/Locale/ru-RU/store/uplink-catalog.ftl index 47083e83a4..1f7ec3d6e9 100644 --- a/Resources/Locale/ru-RU/store/uplink-catalog.ftl +++ b/Resources/Locale/ru-RU/store/uplink-catalog.ftl @@ -341,6 +341,13 @@ uplink-hardsuit-syndieelite-desc = Элитная версия кроваво-к uplink-clothing-outer-hardsuit-juggernaut-name = Костюм джаггернаута Cybersun uplink-clothing-outer-hardsuit-juggernaut-desc = Сверхпрочная броня из материалов, испытанных в хромосферном комплексе Тау. Единственное, что будет замедлять вас - это сам костюм... и тазеры. + +uplink-night-vision-name = Прибор ночного видения +uplink-night-vision-desc = Позволяет вам видеть в темноте, при этом выглядя как обычные солнцезащитные очки! + +uplink-thermal-vision-name = Прибор термального видения +uplink-thermal-vision-desc = Позволяет вам видеть живых существ независимо от преград, при этом выглядя как обычные солнцезащитные очки! + # Misc uplink-cyberpen-name = Ручка Cybersun uplink-cyberpen-desc = Ручка юридического отдела "Cybersun". Бесценная для подделки документов и побегов из тюрем. Смутно пахнет хардлайтом и наживой на ВПК. @@ -443,4 +450,4 @@ uplink-hypodart-name = Гиподротик uplink-hypodart-desc = Неприметный на первый взгляд дротик с увеличенным резервуаром для химических веществ. Он вмещает в себя до 7 ед. реагентов и мгновенно впрыскивает их при попадании в цель. Изначально пуст. uplink-helmet-name = Шлем спецназа -uplink-helmet-desc = Чрезвычайно прочный шлем, обычно используемый военизированными формированиями. Он украшен гнусным рисунком в красную и черную полоску. \ No newline at end of file +uplink-helmet-desc = Чрезвычайно прочный шлем, обычно используемый военизированными формированиями. Он украшен гнусным рисунком в красную и черную полоску. diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index 21a2882897..0a57157f1b 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -405,3 +405,42 @@ event: !type:FabricateActionEvent fabrication: FoodGumball + +- type: entity + id: ToggleNightVision + name: Switch night vision + description: Switches night vision. + categories: [ HideSpawnMenu ] + components: + - type: InstantAction + itemIconStyle: BigAction + priority: -20 + icon: + sprite: Clothing/Eyes/Goggles/nightvision.rsi + state: icon + event: !type:ToggleNightVisionEvent + +- type: entity + id: ToggleThermalVision + name: Switch Thermal vision + description: Switches Thermal vision. + categories: [ HideSpawnMenu ] + components: + - type: InstantAction + itemIconStyle: BigAction + priority: -20 + icon: + sprite: Clothing/Eyes/Goggles/thermal.rsi + state: icon + event: !type:ToggleThermalVisionEvent + +- type: entity + id: PulseThermalVision + parent: ToggleThermalVision + name: Pulse Thermal Vision + description: Activate thermal vision temporarily. + categories: [ HideSpawnMenu ] + components: + - type: InstantAction + useDelay: 4 + diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml index bf3776e6a9..65916a84b9 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml @@ -44,7 +44,7 @@ prob: 0.2 - !type:EntSelector id: WeaponFlareGun - prob: 0.1 + prob: 0.05 - !type:EntSelector id: BoxMRE prob: 0.1 @@ -165,6 +165,10 @@ - !type:EntSelector id: WeaponSniperMosin weight: 2 + - !type:EntSelector + id: ClothingEyesNightVisionGogglesSyndie + - !type:EntSelector + id: ClothingEyesThermalVisionGogglesSyndie - type: entityTable id: MaintenanceLockerLoot diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 7f9088a3d0..cc0e0bb0f3 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -1532,6 +1532,78 @@ components: - SurplusBundle +- type: listing + id: UplinkNightGoggles + name: uplink-night-vision-name + description: uplink-night-vision-desc + productEntity: ClothingEyesNightVisionGogglesSyndie + discountCategory: rareDiscounts + discountDownTo: + Telecrystal: 1 + cost: + Telecrystal: 3 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + blacklist: + tags: + - NukeOpsUplink + +- type: listing + id: UplinkNightGogglesNukie + name: uplink-night-vision-name + description: uplink-night-vision-desc + productEntity: ClothingEyesNightVisionGogglesNukie + discountCategory: rareDiscounts + discountDownTo: + Telecrystal: 1 + cost: + Telecrystal: 3 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + whitelist: + tags: + - NukeOpsUplink + +- type: listing + id: UplinkThermalGoggles + name: uplink-thermal-vision-name + description: uplink-thermal-vision-desc + productEntity: ClothingEyesThermalVisionGogglesSyndie + discountCategory: rareDiscounts + discountDownTo: + Telecrystal: 1 + cost: + Telecrystal: 3 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + blacklist: + tags: + - NukeOpsUplink + +- type: listing + id: UplinkThermalGogglesNukie + name: uplink-thermal-vision-name + description: uplink-thermal-vision-desc + productEntity: ClothingEyesThermalVisionGogglesNukie + discountCategory: rareDiscounts + discountDownTo: + Telecrystal: 1 + cost: + Telecrystal: 3 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + whitelist: + tags: + - NukeOpsUplink + # Tools - type: listing diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index f0fdfa0a07..14474cf2ae 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -233,6 +233,7 @@ - type: Clothing sprite: Clothing/Eyes/Glasses/ninjavisor.rsi - type: FlashImmunity + - type: NightVision - type: entity #Fake goggles, the latest in anti-valid hunting technology parent: ClothingEyesBase diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/goggles.yml b/Resources/Prototypes/Entities/Clothing/Eyes/goggles.yml new file mode 100644 index 0000000000..4348c87227 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Eyes/goggles.yml @@ -0,0 +1,114 @@ +# Night Vision Goggles + +- type: entity + parent: ClothingEyesBase + id: ClothingEyesNightVisionGoggles + name: night vision goggles + description: An advanced heads-up display which provides id data and vision in complete darkness. + components: + - type: Sprite + sprite: Clothing/Eyes/Goggles/nightvision.rsi + - type: Clothing + sprite: Clothing/Eyes/Goggles/nightvision.rsi + - type: NightVision + - type: IdentityBlocker + coverage: EYES + +- type: entity + parent: [ClothingEyesNightVisionGoggles, ShowSecurityIcons] + id: ClothingEyesNightVisionSecurityGoggles + name: night vision security goggles + components: + - type: Sprite + sprite: Clothing/Eyes/Goggles/security_nightvision.rsi + - type: Clothing + sprite: Clothing/Eyes/Goggles/security_nightvision.rsi + +- type: entity + parent: [ClothingEyesNightVisionGoggles, ClothingEyesHudMedical] + id: ClothingEyesNightVisionMedicalGoggles + name: night vision medical goggles + components: + - type: Sprite + sprite: Clothing/Eyes/Goggles/medical_nightvision.rsi + - type: Clothing + sprite: Clothing/Eyes/Goggles/medical_nightvision.rsi + +- type: entity + parent: [ClothingEyesNightVisionGoggles, ClothingEyesHudDiagnostic] + id: ClothingEyesNightVisionDiagnosticGoggles + name: night vision diagnostic goggles + components: + - type: Sprite + sprite: Clothing/Eyes/Goggles/diagnostic_nightvision.rsi + - type: Clothing + sprite: Clothing/Eyes/Goggles/diagnostic_nightvision.rsi + +- type: entity + parent: ClothingEyesNightVisionGoggles + id: ClothingEyesNightVisionGogglesSyndie + suffix: "Chameleon" + components: + - type: ChameleonClothing + slot: [eyes] + default: ClothingEyesNightVisionGoggles + - type: UserInterface + interfaces: + enum.ChameleonUiKey.Key: + type: ChameleonBoundUserInterface + +- type: entity + parent: [ClothingEyesNightVisionGogglesSyndie, ShowSecurityIcons] + id: ClothingEyesNightVisionGogglesNukie + suffix: "Chameleon, NukeOps" + components: + - type: ShowSyndicateIcons + +# Thermal Vision Goggles + +- type: entity + parent: ClothingEyesBase + id: ClothingEyesThermalVisionGoggles + name: thermal vision goggles + description: Thermals in the shape of glasses. + components: + - type: Sprite + sprite: Clothing/Eyes/Goggles/thermal.rsi + - type: Clothing + sprite: Clothing/Eyes/Goggles/thermal.rsi + - type: ThermalVision + pulseTime: 2 + toggleAction: PulseThermalVision + - type: IdentityBlocker + coverage: EYES + +- type: entity + parent: ClothingEyesThermalVisionGoggles + id: ClothingEyesThermalVisionMonocle + name: thermonocle + description: Never before has seeing through walls felt so gentlepersonly. + components: + - type: Sprite + sprite: Clothing/Eyes/Goggles/monocle_thermal.rsi + - type: Clothing + sprite: Clothing/Eyes/Goggles/monocle_thermal.rsi + +- type: entity + parent: ClothingEyesThermalVisionGoggles + id: ClothingEyesThermalVisionGogglesSyndie + suffix: "Chameleon" + components: + - type: ChameleonClothing + slot: [eyes] + default: ClothingEyesThermalVisionGoggles + - type: UserInterface + interfaces: + enum.ChameleonUiKey.Key: + type: ChameleonBoundUserInterface + +- type: entity + parent: [ClothingEyesThermalVisionGogglesSyndie, ShowSecurityIcons] + id: ClothingEyesThermalVisionGogglesNukie + suffix: "Chameleon, NukeOps" + components: + - type: ShowSyndicateIcons diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index ed184845bf..6f28812549 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -799,6 +799,9 @@ - type: FlashSoundSuppression maxRange: 0 # WD EDIT END + - type: ThermalVision + color: "#98EEFB" + lightRadius: 15 #MISC. HARDSUITS #Clown Hardsuit diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index b8ff03abca..ada65ad33f 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -126,6 +126,7 @@ cell_slot: name: power-cell-slot-component-slot-name-default - type: Body + thermalVisibility: false - type: StatusEffects allowed: - Stun diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index d981061993..bf921c0885 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -70,6 +70,12 @@ barkType: mouse minTime: 10 # Mice like to squeak, I think. You can always put your pet mouse to sleep if it gets annoying maxTime: 160 + - type: NightVision + isActive: true + toggleAction: null + color: "#808080" + activateSound: null + deactivateSound: null - type: entity name: bee @@ -1793,6 +1799,12 @@ Taco: RatTaco Burger: RatBurger Skewer: RatSkewer + - type: NightVision + isActive: true + toggleAction: null + color: "#808080" + activateSound: null + deactivateSound: null # WD EDIT START - type: Destructible thresholds: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index d241173f2e..356615d2e9 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -88,6 +88,12 @@ interfaces: enum.SurgeryUIKey.Key: type: SurgeryBui + - type: NightVision + isActive: true + toggleAction: null + color: "#808080" + activateSound: null + deactivateSound: null - type: entity parent: BaseMobCarp diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index abe602037f..9e4f72bb0c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -132,6 +132,12 @@ interfaces: enum.SurgeryUIKey.Key: type: SurgeryBui + - type: NightVision + isActive: true + toggleAction: null + color: "#808080" + activateSound: null + deactivateSound: null - type: entity id: MobRatKingBuff @@ -317,6 +323,12 @@ - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning + - type: NightVision + isActive: true + toggleAction: null + color: "#808080" + activateSound: null + deactivateSound: null - type: weightedRandomEntity id: RatKingLoot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 7358d07236..db8287e13e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -140,6 +140,12 @@ - Xeno understands: - Xeno + - type: ThermalVision + isActive: true + lightRadius: 15 + color: "#808080" + activateSound: null + deactivateSound: null - type: entity name: Praetorian diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index fc87201b09..09791e6c7d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -38,6 +38,7 @@ bodyType: Kinematic - type: Body prototype: Aghost + thermalVisibility: false - type: Access groups: - AllAccess diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 0f24313712..4c03c6c688 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -140,6 +140,11 @@ tags: - CannotSuicide - DoorBumpOpener + - type: NightVision + isActive: true + color: "#808080" + activateSound: null + deactivateSound: null - type: entity parent: BaseMobDragon diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml index 7c65b89a76..b8de17a55f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml @@ -233,6 +233,7 @@ - type: Body prototype: IPC requiredLegs: 2 + thermalVisibility: false - type: Ensnareable sprite: Objects/Misc/ensnare.rsi - type: Speech diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 8ffa9e608c..d2924ad074 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -362,6 +362,9 @@ - AnimalTranslator - MofficTranslatorImplanter - MofficTranslator + - ClothingEyesNightVisionGoggles + - ClothingEyesNightVisionDiagnosticGoggles + - ClothingEyesThermalVisionGoggles - RCDAmmo #DeltaV - RCD #EE # Shitmed Change @@ -911,6 +914,7 @@ - WeaponDisablerSMG - WeaponLaserCannon - WeaponLaserCarbine + - ClothingEyesNightVisionSecurityGoggles - ClothingHeadHelmetInsulated # Nyanotrasen - Insulative headgear - ClothingHeadCage # Nyanotrasen - Insulative headgear - ShockCollar # Nyanotrasen - Shock Collar @@ -1059,6 +1063,7 @@ - ClothingEyesHudMedical # Nyano - ChemicalPayload # Nyano - SyringeCryostasis + - ClothingEyesNightVisionMedicalGoggles # Shitmed Change - EnergyScalpel - EnergyCautery diff --git a/Resources/Prototypes/Procedural/salvage_loot.yml b/Resources/Prototypes/Procedural/salvage_loot.yml index 7e7ddf6ff9..1b941cd1a2 100644 --- a/Resources/Prototypes/Procedural/salvage_loot.yml +++ b/Resources/Prototypes/Procedural/salvage_loot.yml @@ -106,6 +106,10 @@ - proto: WeaponTeslaGun prob: 0.1 cost: 2 + - proto: ClothingEyesNightVisionGoggles + cost: 8 + - proto: ClothingEyesGlassesThermal + cost: 8 # Mob loot table diff --git a/Resources/Prototypes/Recipes/Lathes/devices.yml b/Resources/Prototypes/Recipes/Lathes/devices.yml index 2b0d6fa44f..56e1739817 100644 --- a/Resources/Prototypes/Recipes/Lathes/devices.yml +++ b/Resources/Prototypes/Recipes/Lathes/devices.yml @@ -234,3 +234,33 @@ Steel: 500 Glass: 400 Gold: 100 + +- type: latheRecipe + id: ClothingEyesNightVisionGoggles + result: ClothingEyesNightVisionGoggles + completetime: 2 + materials: + Steel: 200 + Glass: 100 + Silver: 100 + Gold: 100 + +- type: latheRecipe + id: ClothingEyesNightVisionDiagnosticGoggles + result: ClothingEyesNightVisionDiagnosticGoggles + completetime: 2 + materials: + Steel: 200 + Glass: 100 + Silver: 100 + Gold: 100 + +- type: latheRecipe + id: ClothingEyesThermalVisionGoggles + result: ClothingEyesThermalVisionGoggles + completetime: 2 + materials: + Steel: 200 + Glass: 100 + Silver: 100 + Gold: 100 diff --git a/Resources/Prototypes/Recipes/Lathes/medical.yml b/Resources/Prototypes/Recipes/Lathes/medical.yml index ba8f596d21..29f3fed247 100644 --- a/Resources/Prototypes/Recipes/Lathes/medical.yml +++ b/Resources/Prototypes/Recipes/Lathes/medical.yml @@ -250,4 +250,15 @@ completetime: 2 materials: Steel: 100 - Plastic: 100 \ No newline at end of file + Plastic: 100 + +- type: latheRecipe + id: ClothingEyesNightVisionMedicalGoggles + result: ClothingEyesNightVisionMedicalGoggles + completetime: 7 + materials: + Steel: 300 + Glass: 300 + Silver: 100 + Gold: 100 + Plasma: 200 diff --git a/Resources/Prototypes/Recipes/Lathes/security.yml b/Resources/Prototypes/Recipes/Lathes/security.yml index 1d9bdcfeb0..ed37e3abeb 100644 --- a/Resources/Prototypes/Recipes/Lathes/security.yml +++ b/Resources/Prototypes/Recipes/Lathes/security.yml @@ -995,4 +995,14 @@ completetime: 2 materials: Plastic: 15 - Uranium: 10 \ No newline at end of file + Uranium: 10 + +- type: latheRecipe + id: ClothingEyesNightVisionSecurityGoggles + result: ClothingEyesNightVisionSecurityGoggles + completetime: 5 + materials: + Steel: 500 + Glass: 300 + Silver: 100 + Gold: 100 diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml index 423ec0f84d..cf6493847a 100644 --- a/Resources/Prototypes/Research/experimental.yml +++ b/Resources/Prototypes/Research/experimental.yml @@ -167,6 +167,33 @@ - MedicalScannerMachineCircuitboard - MetempsychoticMachineCircuitboard +- type: technology + id: NightVisionTech + name: research-technology-night-vision + icon: + sprite: Clothing/Eyes/Goggles/nightvision.rsi + state: icon + discipline: Experimental + tier: 2 + cost: 10000 + recipeUnlocks: + - ClothingEyesNightVisionGoggles + - ClothingEyesNightVisionSecurityGoggles + - ClothingEyesNightVisionMedicalGoggles + - ClothingEyesNightVisionDiagnosticGoggles + +- type: technology + id: ThermalVisionTech + name: research-technology-thermal-vision + icon: + sprite: Clothing/Eyes/Goggles/thermal.rsi + state: icon + discipline: Experimental + tier: 2 + cost: 10000 + recipeUnlocks: + - ClothingEyesThermalVisionGoggles + # Tier 3 - type: technology diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index 108b9f778b..96c51d1a98 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -127,4 +127,9 @@ - type: shader id: Ethereal kind: source - path: "/Textures/Shaders/ethereal.swsl" \ No newline at end of file + path: "/Textures/Shaders/ethereal.swsl" + +- type: shader + id: NightVision + kind: source + path: "/Textures/Shaders/nightvision.swsl" diff --git a/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml b/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml index 04279d51d2..bdbb4bac46 100644 --- a/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml +++ b/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml @@ -113,6 +113,12 @@ sprite: Clothing/Eyes/Misc/blindfold.rsi - type: FlashImmunity - type: EyeProtection + - type: NightVision + isActive: true + toggleAction: null + activateSound: null + deactivateSound: null + color: White - type: ShowHealthBars damageContainers: - Biological @@ -120,5 +126,3 @@ - type: ShowHealthIcons damageContainers: - Biological - # TODO: ADD NIGHT VISION - diff --git a/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/equipped-EYES-off.png b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/equipped-EYES-off.png new file mode 100644 index 0000000000..b63f30fc71 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/equipped-EYES-off.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/equipped-EYES.png new file mode 100644 index 0000000000..cd6c763b33 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/icon.png new file mode 100644 index 0000000000..8bcf7105c2 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/inhand-left.png new file mode 100644 index 0000000000..04fff80e4d Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/inhand-right.png new file mode 100644 index 0000000000..d80344136f Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/meta.json new file mode 100644 index 0000000000..2b65c68fe8 --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Goggles/diagnostic_nightvision.rsi/meta.json @@ -0,0 +1,48 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "equipped-EYES", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "equipped-EYES-off", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/equipped-EYES-off.png b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/equipped-EYES-off.png new file mode 100644 index 0000000000..b63f30fc71 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/equipped-EYES-off.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/equipped-EYES.png new file mode 100644 index 0000000000..a6513302b9 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/icon.png new file mode 100644 index 0000000000..52522c080e Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/inhand-left.png new file mode 100644 index 0000000000..bccfe98d09 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/inhand-right.png new file mode 100644 index 0000000000..f9c17318b4 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/meta.json new file mode 100644 index 0000000000..2b65c68fe8 --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Goggles/medical_nightvision.rsi/meta.json @@ -0,0 +1,48 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "equipped-EYES", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "equipped-EYES-off", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/equipped-EYES-off.png b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/equipped-EYES-off.png new file mode 100644 index 0000000000..5aaa7e580c Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/equipped-EYES-off.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/equipped-EYES.png new file mode 100644 index 0000000000..dc4d9e775b Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/icon.png new file mode 100644 index 0000000000..e0aaa8ae9a Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/meta.json new file mode 100644 index 0000000000..ad770fb0f8 --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Goggles/monocle_thermal.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "equipped-EYES", + "directions": 4 + }, + { + "name": "equipped-EYES-off", + "directions": 4 + }, + { + "name": "icon" + } + ] +} diff --git a/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png new file mode 100644 index 0000000000..b63f30fc71 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png new file mode 100644 index 0000000000..199c44122b Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/icon.png new file mode 100644 index 0000000000..bf770f70f8 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png new file mode 100644 index 0000000000..995b37471b Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png new file mode 100644 index 0000000000..c3efa67f83 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/meta.json new file mode 100644 index 0000000000..987b20b9af --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Goggles/nightvision.rsi/meta.json @@ -0,0 +1,48 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "equipped-EYES", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "equipped-EYES-off", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/equipped-EYES-off.png b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/equipped-EYES-off.png new file mode 100644 index 0000000000..b63f30fc71 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/equipped-EYES-off.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/equipped-EYES.png new file mode 100644 index 0000000000..0e156402bc Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/icon.png new file mode 100644 index 0000000000..80ae69d53b Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/inhand-left.png new file mode 100644 index 0000000000..02b00b1591 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/inhand-right.png new file mode 100644 index 0000000000..6ba4b3e64e Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/meta.json new file mode 100644 index 0000000000..2b65c68fe8 --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Goggles/security_nightvision.rsi/meta.json @@ -0,0 +1,48 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "equipped-EYES", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "equipped-EYES-off", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png new file mode 100644 index 0000000000..9bef0a8c05 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/icon.png new file mode 100644 index 0000000000..3d5f8ef9b6 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png new file mode 100644 index 0000000000..bf67e35d40 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png new file mode 100644 index 0000000000..4ede078291 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/meta.json new file mode 100644 index 0000000000..205508acfa --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-EYES", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Shaders/nightvision.swsl b/Resources/Textures/Shaders/nightvision.swsl new file mode 100644 index 0000000000..8a3e7706ad --- /dev/null +++ b/Resources/Textures/Shaders/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; +}