diff --git a/Content.Client/SimpleStation14/Overlays/Shaders/NearsightedOverlays.cs b/Content.Client/SimpleStation14/Overlays/Shaders/NearsightedOverlays.cs new file mode 100644 index 0000000000..2597975bd4 --- /dev/null +++ b/Content.Client/SimpleStation14/Overlays/Shaders/NearsightedOverlays.cs @@ -0,0 +1,130 @@ +using Content.Shared.SimpleStation14.Traits.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.SimpleStation14.Overlays.Shaders; + +public sealed class NearsightedOverlay : Overlay +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _nearsightShader; + + public float Radius; + private float _oldRadius; + public float Darkness; + private float _oldDarkness; + + private float _lerpTime; + public float LerpDuration; + + + public NearsightedOverlay() + { + IoCManager.InjectDependencies(this); + _nearsightShader = _prototypeManager.Index("GradientCircleMask").InstanceUnique(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + // Check if the player has a NearsightedComponent and is controlling it + if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out NearsightedComponent? nearComp) || + _playerManager.LocalPlayer?.ControlledEntity != nearComp.Owner) + return false; + + // Check if the player has an EyeComponent and if the overlay should be drawn for this eye + if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out EyeComponent? eyeComp) || + args.Viewport.Eye != eyeComp.Eye) + return false; + + return true; + } + + protected override void Draw(in OverlayDrawArgs args) + { + // We already checked if they have a NearsightedComponent and are controlling it in BeforeDraw, so we assume this hasn't changed + var nearComp = _entityManager.GetComponent(_playerManager.LocalPlayer!.ControlledEntity!.Value); + + // Set LerpDuration based on nearComp.LerpDuration + LerpDuration = nearComp.LerpDuration; + + // Set the radius and darkness values based on whether the player is wearing glasses or not + if (nearComp.Active) + { + Radius = nearComp.EquippedRadius; + Darkness = nearComp.EquippedAlpha; + } + else + { + Radius = nearComp.Radius; + Darkness = nearComp.Alpha; + } + + + var viewport = args.WorldAABB; + var handle = args.WorldHandle; + var distance = args.ViewportBounds.Width; + + var lastFrameTime = (float) _timing.FrameTime.TotalSeconds; + + + // If the current radius value is different from the previous one, lerp between them + if (!MathHelper.CloseTo(_oldRadius, Radius, 0.001f)) + { + _lerpTime += lastFrameTime; + var t = MathHelper.Clamp(_lerpTime / LerpDuration, 0f, 1f); // Calculate lerp time + _oldRadius = MathHelper.Lerp(_oldRadius, Radius, t); // Lerp between old and new radius values + } + // If the current radius value is the same as the previous one, reset the lerp time and old radius value + else + { + _lerpTime = 0f; + _oldRadius = Radius; + } + + // If the current darkness value is different from the previous one, lerp between them + if (!MathHelper.CloseTo(_oldDarkness, Darkness, 0.001f)) + { + _lerpTime += lastFrameTime; + var t = MathHelper.Clamp(_lerpTime / LerpDuration, 0f, 1f); // Calculate lerp time + _oldDarkness = MathHelper.Lerp(_oldDarkness, Darkness, t); // Lerp between old and new darkness values + } + // If the current darkness value is the same as the previous one, reset the lerp time and old darkness value + else + { + _lerpTime = 0f; + _oldDarkness = Darkness; + } + + + // Calculate the outer and inner radii based on the current radius value + var outerMaxLevel = 0.6f * distance; + var outerMinLevel = 0.06f * distance; + var innerMaxLevel = 0.02f * distance; + var innerMinLevel = 0.02f * distance; + + var outerRadius = outerMaxLevel - _oldRadius * (outerMaxLevel - outerMinLevel); + var innerRadius = innerMaxLevel - _oldRadius * (innerMaxLevel - innerMinLevel); + + // Set the shader parameters and draw the overlay + _nearsightShader.SetParameter("time", 0.0f); + _nearsightShader.SetParameter("color", new Vector3(1f, 1f, 1f)); + _nearsightShader.SetParameter("darknessAlphaOuter", _oldDarkness); + _nearsightShader.SetParameter("innerCircleRadius", innerRadius); + _nearsightShader.SetParameter("innerCircleMaxRadius", innerRadius); + _nearsightShader.SetParameter("outerCircleRadius", outerRadius); + _nearsightShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance); + handle.UseShader(_nearsightShader); + handle.DrawRect(viewport, Color.Black); + + handle.UseShader(null); + } +} diff --git a/Content.Client/SimpleStation14/Overlays/Systems/NearsightedSystem.cs b/Content.Client/SimpleStation14/Overlays/Systems/NearsightedSystem.cs new file mode 100644 index 0000000000..ef413ec65d --- /dev/null +++ b/Content.Client/SimpleStation14/Overlays/Systems/NearsightedSystem.cs @@ -0,0 +1,58 @@ +using Content.Client.SimpleStation14.Overlays.Shaders; +using Content.Shared.Inventory.Events; +using Content.Shared.SimpleStation14.Traits; +using Content.Shared.SimpleStation14.Traits.Components; +using Content.Shared.Tag; +using Robust.Client.Graphics; + +namespace Content.Client.SimpleStation14.Overlays.Systems; + +public sealed class NearsightedSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private NearsightedOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _overlay = new NearsightedOverlay(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnEquip); + SubscribeLocalEvent(OnUnEquip); + } + + + private void OnStartup(EntityUid uid, NearsightedComponent component, ComponentStartup args) + { + UpdateShader(component, false); + } + + private void OnEquip(GotEquippedEvent args) + { + if (TryComp(args.Equipee, out var nearsighted) && + EnsureComp(args.Equipment).Tags.Contains("GlassesNearsight")) + UpdateShader(nearsighted, true); + } + + private void OnUnEquip(GotUnequippedEvent args) + { + if (TryComp(args.Equipee, out var nearsighted) && + EnsureComp(args.Equipment).Tags.Contains("GlassesNearsight")) + UpdateShader(nearsighted, false); + } + + + private void UpdateShader(NearsightedComponent component, bool booLean) + { + while (_overlayMan.HasOverlay()) + { + _overlayMan.RemoveOverlay(_overlay); + } + + component.Active = booLean; + _overlayMan.AddOverlay(_overlay); + } +} diff --git a/Content.Shared/SimpleStation14/Clothing/Components/ClothingGrantTagComponent.cs b/Content.Shared/SimpleStation14/Clothing/Components/ClothingGrantTagComponent.cs new file mode 100644 index 0000000000..01b70f90f4 --- /dev/null +++ b/Content.Shared/SimpleStation14/Clothing/Components/ClothingGrantTagComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.SimpleStation14.Clothing; + +[RegisterComponent] +public sealed class ClothingGrantTagComponent : Component +{ + [DataField("tag", required: true), ViewVariables(VVAccess.ReadWrite)] + public string Tag = ""; + + [ViewVariables(VVAccess.ReadWrite)] + public bool IsActive = false; +} diff --git a/Content.Shared/SimpleStation14/Clothing/Systems/ClothingGrantingSystem.cs b/Content.Shared/SimpleStation14/Clothing/Systems/ClothingGrantingSystem.cs index b65f11c6bc..5fbc83a4b0 100644 --- a/Content.Shared/SimpleStation14/Clothing/Systems/ClothingGrantingSystem.cs +++ b/Content.Shared/SimpleStation14/Clothing/Systems/ClothingGrantingSystem.cs @@ -14,8 +14,12 @@ public sealed class ClothingGrantingSystem : EntitySystem public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnCompEquip); SubscribeLocalEvent(OnCompUnequip); + + SubscribeLocalEvent(OnTagEquip); + SubscribeLocalEvent(OnTagUnequip); } private void OnCompEquip(EntityUid uid, ClothingGrantComponentComponent component, GotEquippedEvent args) @@ -60,4 +64,29 @@ private void OnCompUnequip(EntityUid uid, ClothingGrantComponentComponent compon component.IsActive = false; } + + + private void OnTagEquip(EntityUid uid, ClothingGrantTagComponent component, GotEquippedEvent args) + { + if (!TryComp(uid, out var clothing)) + return; + + if (!clothing.Slots.HasFlag(args.SlotFlags)) + return; + + EnsureComp(args.Equipee); + _tagSystem.AddTag(args.Equipee, component.Tag); + + component.IsActive = true; + } + + private void OnTagUnequip(EntityUid uid, ClothingGrantTagComponent component, GotUnequippedEvent args) + { + if (!component.IsActive) + return; + + _tagSystem.RemoveTag(args.Equipee, component.Tag); + + component.IsActive = false; + } } diff --git a/Content.Shared/SimpleStation14/Traits/Components/NearsightedComponent.cs b/Content.Shared/SimpleStation14/Traits/Components/NearsightedComponent.cs new file mode 100644 index 0000000000..fafd8f8710 --- /dev/null +++ b/Content.Shared/SimpleStation14/Traits/Components/NearsightedComponent.cs @@ -0,0 +1,49 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.SimpleStation14.Traits.Components; + +/// +/// Owner entity cannot see well, without prescription glasses. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class NearsightedComponent : Component +{ + /// + /// Distance from the edge of the screen to the center + /// + /// + /// I don't know how the distance is measured, 1 is very close to the center, 0 is maybe visible around the edge + /// + [DataField("radius"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float Radius = 0.8f; + + /// + /// How dark the circle mask is from + /// + /// + /// I also don't know how this works, it only starts getting noticeably dark at 0.7, and is definitely noticeable at 0.9, 1 is black + /// + [DataField("alpha"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float Alpha = 0.995f; + + /// + [DataField("equippedRadius"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float EquippedRadius = 0.45f; + + /// + [DataField("equippedAlpha"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float EquippedAlpha = 0.93f; + + /// + /// How long the lerp animation should go on for in seconds. + /// + [DataField("lerpDuration"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float LerpDuration = 0.25f; + + /// + /// If true, uses the variables prefixed "Equipped" + /// If false, uses the variables without a prefix + /// + [ViewVariables(VVAccess.ReadWrite)] // Make the system shared if you want this networked, I don't wanna do that + public bool Active = false; +} diff --git a/Resources/Locale/en-US/deltav/accessories/new_hair.ftl b/Resources/Locale/en-US/delta-v/accessories/new_hair.ftl similarity index 100% rename from Resources/Locale/en-US/deltav/accessories/new_hair.ftl rename to Resources/Locale/en-US/delta-v/accessories/new_hair.ftl diff --git a/Resources/Locale/en-US/simplestation14/Traits/disabilities.ftl b/Resources/Locale/en-US/simplestation14/Traits/disabilities.ftl new file mode 100644 index 0000000000..3d8af06139 --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/Traits/disabilities.ftl @@ -0,0 +1,3 @@ +trait-nearsighted-name = Nearsighted +trait-nearsighted-desc = You require glasses to see properly. +trait-nearsighted-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are pretty unfocused. It doesn't seem like {SUBJECT($target)} can see things that well.[/color] diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml index 8887ab5b97..f677985db7 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml @@ -71,3 +71,4 @@ ClothingHeadTinfoil: 2 ClothingHeadRastaHat: 2 ClothingBeltStorageWaistbag: 3 + ClothingEyesGlasses: 5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml index 43e7cf7c94..75b0d5803d 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml @@ -7,3 +7,4 @@ Bloodpack: 5 EpinephrineChemistryBottle: 3 Syringe: 5 + ClothingEyesGlasses: 5 diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index ce1bf45973..c3870188b8 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -76,6 +76,9 @@ - type: Clothing sprite: Clothing/Eyes/Glasses/glasses.rsi - type: VisionCorrection + - type: Tag + tags: + - GlassesNearsight - type: entity parent: ClothingEyesBase diff --git a/Resources/Prototypes/SimpleStation14/Traits/disabilities.yml b/Resources/Prototypes/SimpleStation14/Traits/disabilities.yml new file mode 100644 index 0000000000..fa49747729 --- /dev/null +++ b/Resources/Prototypes/SimpleStation14/Traits/disabilities.yml @@ -0,0 +1,7 @@ +- type: trait + id: Nearsighted + name: trait-nearsighted-name + description: You require glasses to see properly. + traitGear: ClothingEyesGlasses + components: + - type: Nearsighted diff --git a/Resources/Prototypes/SimpleStation14/tags.yml b/Resources/Prototypes/SimpleStation14/tags.yml new file mode 100644 index 0000000000..3b885a5801 --- /dev/null +++ b/Resources/Prototypes/SimpleStation14/tags.yml @@ -0,0 +1,2 @@ +- type: Tag + id: GlassesNearsight