Blindness rework - damaged eyes are now a stylized simulation of legal blindness (#23212)
* blindness rework - damaged eyes now simulate legal blindness * hEY THATS FOR DEMONSTRATION PURPOSES ONLY AAA * attributions * makes eyeclosecomponent adminbus compatible * useshader(null)
This commit is contained in:
@@ -82,8 +82,10 @@ namespace Content.Client.Eye.Blinding
|
||||
|
||||
if (_entityManager.TryGetComponent<EyeComponent>(playerEntity, out var content))
|
||||
{
|
||||
_circleMaskShader?.SetParameter("ZOOM", content.Zoom.X);
|
||||
_circleMaskShader?.SetParameter("Zoom", content.Zoom.X);
|
||||
}
|
||||
|
||||
_circleMaskShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_greyscaleShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Eye.Blinding;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Eye.Blinding
|
||||
{
|
||||
@@ -11,24 +12,45 @@ namespace Content.Client.Eye.Blinding
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
private readonly ShaderInstance _cataractsShader;
|
||||
private readonly ShaderInstance _circleMaskShader;
|
||||
private float _magnitude;
|
||||
private float _correctionPower = 2.0f;
|
||||
|
||||
private const float Distortion_Pow = 2.0f; // Exponent for the distortion effect
|
||||
private const float Cloudiness_Pow = 1.0f; // Exponent for the cloudiness effect
|
||||
|
||||
private const float NoMotion_Radius = 30.0f; // Base radius for the nomotion variant at its full strength
|
||||
private const float NoMotion_Pow = 0.2f; // Exponent for the nomotion variant's gradient
|
||||
private const float NoMotion_Max = 8.0f; // Max value for the nomotion variant's gradient
|
||||
private const float NoMotion_Mult = 0.75f; // Multiplier for the nomotion variant
|
||||
|
||||
public BlurryVisionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_cataractsShader = _prototypeManager.Index<ShaderPrototype>("Cataracts").InstanceUnique();
|
||||
_circleMaskShader = _prototypeManager.Index<ShaderPrototype>("CircleMask").InstanceUnique();
|
||||
|
||||
_circleMaskShader.SetParameter("CircleMinDist", 0.0f);
|
||||
_circleMaskShader.SetParameter("CirclePow", NoMotion_Pow);
|
||||
_circleMaskShader.SetParameter("CircleMax", NoMotion_Max);
|
||||
_circleMaskShader.SetParameter("CircleMult", NoMotion_Mult);
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out EyeComponent? eyeComp))
|
||||
if (!_entityManager.TryGetComponent(_playerManager.LocalSession?.AttachedEntity, out EyeComponent? eyeComp))
|
||||
return false;
|
||||
|
||||
if (args.Viewport.Eye != eyeComp.Eye)
|
||||
return false;
|
||||
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var playerEntity = _playerManager.LocalSession?.AttachedEntity;
|
||||
|
||||
if (playerEntity == null)
|
||||
return false;
|
||||
@@ -44,21 +66,52 @@ namespace Content.Client.Eye.Blinding
|
||||
return false;
|
||||
|
||||
_magnitude = blurComp.Magnitude;
|
||||
_correctionPower = blurComp.CorrectionPower;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
// TODO make this better.
|
||||
// This is a really shitty effect.
|
||||
// Maybe gradually shrink the view-size?
|
||||
// Make the effect only apply to the edge of the viewport?
|
||||
// Actually make it blurry??
|
||||
var opacity = 1f * _magnitude / BlurryVisionComponent.MaxMagnitude;
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
|
||||
var playerEntity = _playerManager.LocalSession?.AttachedEntity;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var viewport = args.WorldBounds;
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.DrawRect(viewport, Color.Black.WithAlpha(opacity));
|
||||
var strength = (float) Math.Pow(Math.Min(_magnitude / BlurryVisionComponent.MaxMagnitude, 1.0f), _correctionPower);
|
||||
|
||||
var zoom = 1.0f;
|
||||
if (_entityManager.TryGetComponent<EyeComponent>(playerEntity, out var eyeComponent))
|
||||
{
|
||||
zoom = eyeComponent.Zoom.X;
|
||||
}
|
||||
|
||||
// While the cataracts shader is designed to be tame enough to keep motion sickness at bay, the general waviness means that those who are particularly sensitive to motion sickness will probably hurl.
|
||||
// So the reasonable alternative here is to replace it with a static effect! Specifically, one that replicates the blindness effect seen across most SS13 servers.
|
||||
if (_configManager.GetCVar(CCVars.ReducedMotion))
|
||||
{
|
||||
_circleMaskShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_circleMaskShader.SetParameter("Zoom", zoom);
|
||||
_circleMaskShader.SetParameter("CircleRadius", NoMotion_Radius / strength);
|
||||
|
||||
worldHandle.UseShader(_circleMaskShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.UseShader(null);
|
||||
return;
|
||||
}
|
||||
|
||||
_cataractsShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_cataractsShader.SetParameter("LIGHT_TEXTURE", args.Viewport.LightRenderTarget.Texture); // this is a little hacky but we spent way longer than we'd like to admit trying to do this a cleaner way to no avail
|
||||
|
||||
_cataractsShader.SetParameter("Zoom", zoom);
|
||||
|
||||
_cataractsShader.SetParameter("DistortionScalar", (float) Math.Pow(strength, Distortion_Pow));
|
||||
_cataractsShader.SetParameter("CloudinessScalar", (float) Math.Pow(strength, Cloudiness_Pow));
|
||||
|
||||
worldHandle.UseShader(_cataractsShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
<Control MinSize="4 0" />
|
||||
<OptionButton Name="HudLayoutOption" />
|
||||
</BoxContainer>
|
||||
<Label Text="{Loc 'ui-options-general-accessibility'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
||||
<Label Text="{Loc 'ui-options-general-discord'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
OpaqueStorageWindowCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
// ToggleWalk.OnToggled += OnCheckBoxToggled;
|
||||
StaticStorageUI.OnToggled += OnCheckBoxToggled;
|
||||
|
||||
@@ -109,6 +110,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
|
||||
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
|
||||
_cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
|
||||
|
||||
@@ -132,6 +134,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
||||
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
||||
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
||||
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
|
||||
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
|
||||
var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
|
||||
|
||||
@@ -144,6 +147,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
isLoocShowSame &&
|
||||
isFancyChatSame &&
|
||||
isFancyBackgroundSame &&
|
||||
isReducedMotionSame &&
|
||||
// isToggleWalkSame &&
|
||||
isStaticStorageUISame;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Content.Server.Eye.Blinding.EyeProtection
|
||||
|
||||
// Add permanent eye damage if they had zero protection, also somewhat scale their temporary blindness by
|
||||
// how much damage they already accumulated.
|
||||
_blindingSystem.AdjustEyeDamage(args.User, 1, blindable);
|
||||
_blindingSystem.AdjustEyeDamage((args.User, blindable), 1);
|
||||
var statusTimeSpan = TimeSpan.FromSeconds(time * MathF.Sqrt(blindable.EyeDamage));
|
||||
_statusEffectsSystem.TryAddStatusEffect(args.User, TemporaryBlindnessSystem.BlindingStatusEffect,
|
||||
statusTimeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect);
|
||||
|
||||
@@ -1505,6 +1505,18 @@ namespace Content.Shared.CCVar
|
||||
CVarDef.Create("ui.separated_chat_size", "0.6,0", CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
|
||||
/*
|
||||
* Accessibility
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Toggle for visual effects that may potentially cause motion sickness.
|
||||
/// Where reasonable, effects affected by this CVar should use an alternate effect.
|
||||
/// Please do not use this CVar as a bandaid for effects that could otherwise be made accessible without issue.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ReducedMotion =
|
||||
CVarDef.Create("accessibility.reduced_motion", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* CHAT
|
||||
*/
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace Content.Shared.Examine
|
||||
public const float ExamineRange = 16f;
|
||||
protected const float ExamineDetailsRange = 3f;
|
||||
|
||||
protected const float ExamineBlurrinessMult = 2.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new examine tooltip with arbitrary info.
|
||||
/// </summary>
|
||||
@@ -125,7 +127,7 @@ namespace Content.Shared.Examine
|
||||
return CritExamineRange;
|
||||
|
||||
if (TryComp<BlurryVisionComponent>(examiner, out var blurry))
|
||||
return Math.Clamp(ExamineRange - blurry.Magnitude, 2, ExamineRange);
|
||||
return Math.Clamp(ExamineRange - blurry.Magnitude * ExamineBlurrinessMult, 2, ExamineRange);
|
||||
}
|
||||
return ExamineRange;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed partial class BlindableComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("EyeDamage"), AutoNetworkedField]
|
||||
public int EyeDamage = 0;
|
||||
|
||||
public const int MaxDamage = 3;
|
||||
public const int MaxDamage = 9;
|
||||
|
||||
/// <description>
|
||||
/// Used to ensure that this doesn't break with sandbox or admin tools.
|
||||
|
||||
@@ -17,5 +17,12 @@ public sealed partial class BlurryVisionComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("magnitude"), AutoNetworkedField]
|
||||
public float Magnitude;
|
||||
|
||||
public const float MaxMagnitude = 3;
|
||||
/// <summary>
|
||||
/// Exponent that controls the magnitude of the effect.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("correctionPower"), AutoNetworkedField]
|
||||
public float CorrectionPower;
|
||||
|
||||
public const float MaxMagnitude = 6;
|
||||
public const float DefaultCorrectionPower = 2f;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Eye.Blinding.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Allows mobs to toggle their eyes between being closed and being not closed.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class EyeClosingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The prototype to grant to enable eye-toggling action.
|
||||
/// </summary>
|
||||
[DataField("eyeToggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string EyeToggleAction = "ActionToggleEyes";
|
||||
|
||||
/// <summary>
|
||||
/// The actual eye toggling action entity itself.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? EyeToggleActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Path to sound to play when opening eyes
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public string EyeOpenSound = "/Audio/Effects/eye_open.ogg";
|
||||
|
||||
/// <summary>
|
||||
/// Path to sound to play when closing eyes
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public string EyeCloseSound = "/Audio/Effects/eye_close.ogg";
|
||||
|
||||
/// <summary>
|
||||
/// Toggles whether the eyes are open or closed. This is really just exactly what it says on the tin. Honest.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public bool EyesClosed;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
public bool PreviousEyelidPosition;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
public bool NaturallyCreated;
|
||||
}
|
||||
@@ -9,6 +9,15 @@ namespace Content.Shared.Eye.Blinding.Components;
|
||||
[NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class VisionCorrectionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of effective eye damage to add when this item is worn
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("visionBonus"), AutoNetworkedField]
|
||||
public float VisionBonus = 3f;
|
||||
public float VisionBonus = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the exponent of the blur effect when worn
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("correctionPower"), AutoNetworkedField]
|
||||
public float CorrectionPower = 2f;
|
||||
}
|
||||
|
||||
@@ -7,57 +7,67 @@ namespace Content.Shared.Eye.Blinding.Systems;
|
||||
|
||||
public sealed class BlindableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly BlurryVisionSystem _blurriness = default!;
|
||||
[Dependency] private readonly EyeClosingSystem _eyelids = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BlindableComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
SubscribeLocalEvent<BlindableComponent, EyeDamageChangedEvent>(OnDamageChanged);
|
||||
}
|
||||
|
||||
private void OnRejuvenate(EntityUid uid, BlindableComponent component, RejuvenateEvent args)
|
||||
private void OnRejuvenate(Entity<BlindableComponent> ent, ref RejuvenateEvent args)
|
||||
{
|
||||
AdjustEyeDamage(uid, -component.EyeDamage, component);
|
||||
AdjustEyeDamage((ent.Owner, ent.Comp), -ent.Comp.EyeDamage);
|
||||
}
|
||||
|
||||
private void OnDamageChanged(Entity<BlindableComponent> ent, ref EyeDamageChangedEvent args)
|
||||
{
|
||||
_blurriness.UpdateBlurMagnitude((ent.Owner, ent.Comp));
|
||||
_eyelids.UpdateEyesClosable((ent.Owner, ent.Comp));
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void UpdateIsBlind(EntityUid uid, BlindableComponent? blindable = null)
|
||||
public void UpdateIsBlind(Entity<BlindableComponent?> blindable)
|
||||
{
|
||||
if (!Resolve(uid, ref blindable, false))
|
||||
if (!Resolve(blindable, ref blindable.Comp, false))
|
||||
return;
|
||||
|
||||
var old = blindable.IsBlind;
|
||||
var old = blindable.Comp.IsBlind;
|
||||
|
||||
// Don't bother raising an event if the eye is too damaged.
|
||||
if (blindable.EyeDamage >= BlindableComponent.MaxDamage)
|
||||
if (blindable.Comp.EyeDamage >= BlindableComponent.MaxDamage)
|
||||
{
|
||||
blindable.IsBlind = true;
|
||||
blindable.Comp.IsBlind = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var ev = new CanSeeAttemptEvent();
|
||||
RaiseLocalEvent(uid, ev);
|
||||
blindable.IsBlind = ev.Blind;
|
||||
RaiseLocalEvent(blindable.Owner, ev);
|
||||
blindable.Comp.IsBlind = ev.Blind;
|
||||
}
|
||||
|
||||
if (old == blindable.IsBlind)
|
||||
if (old == blindable.Comp.IsBlind)
|
||||
return;
|
||||
|
||||
var changeEv = new BlindnessChangedEvent(blindable.IsBlind);
|
||||
RaiseLocalEvent(uid, ref changeEv);
|
||||
var changeEv = new BlindnessChangedEvent(blindable.Comp.IsBlind);
|
||||
RaiseLocalEvent(blindable.Owner, ref changeEv);
|
||||
Dirty(blindable);
|
||||
}
|
||||
|
||||
public void AdjustEyeDamage(EntityUid uid, int amount, BlindableComponent? blindable = null)
|
||||
public void AdjustEyeDamage(Entity<BlindableComponent?> blindable, int amount)
|
||||
{
|
||||
if (!Resolve(uid, ref blindable, false) || amount == 0)
|
||||
if (!Resolve(blindable, ref blindable.Comp, false) || amount == 0)
|
||||
return;
|
||||
|
||||
blindable.EyeDamage += amount;
|
||||
blindable.EyeDamage = Math.Clamp(blindable.EyeDamage, 0, BlindableComponent.MaxDamage);
|
||||
blindable.Comp.EyeDamage += amount;
|
||||
blindable.Comp.EyeDamage = Math.Clamp(blindable.Comp.EyeDamage, 0, BlindableComponent.MaxDamage);
|
||||
Dirty(blindable);
|
||||
UpdateIsBlind(uid, blindable);
|
||||
UpdateIsBlind(blindable);
|
||||
|
||||
var ev = new EyeDamageChangedEvent(blindable.EyeDamage);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
var ev = new EyeDamageChangedEvent(blindable.Comp.EyeDamage);
|
||||
RaiseLocalEvent(blindable.Owner, ref ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,17 +17,17 @@ public sealed class BlindfoldSystem : EntitySystem
|
||||
SubscribeLocalEvent<BlindfoldComponent, InventoryRelayedEvent<CanSeeAttemptEvent>>(OnBlindfoldTrySee);
|
||||
}
|
||||
|
||||
private void OnBlindfoldTrySee(EntityUid uid, BlindfoldComponent component, InventoryRelayedEvent<CanSeeAttemptEvent> args)
|
||||
private void OnBlindfoldTrySee(Entity<BlindfoldComponent> blindfold, ref InventoryRelayedEvent<CanSeeAttemptEvent> args)
|
||||
{
|
||||
args.Args.Cancel();
|
||||
}
|
||||
|
||||
private void OnEquipped(EntityUid uid, BlindfoldComponent component, GotEquippedEvent args)
|
||||
private void OnEquipped(Entity<BlindfoldComponent> blindfold, ref GotEquippedEvent args)
|
||||
{
|
||||
_blindableSystem.UpdateIsBlind(args.Equipee);
|
||||
}
|
||||
|
||||
private void OnUnequipped(EntityUid uid, BlindfoldComponent component, GotUnequippedEvent args)
|
||||
private void OnUnequipped(Entity<BlindfoldComponent> blindfold, ref GotUnequippedEvent args)
|
||||
{
|
||||
_blindableSystem.UpdateIsBlind(args.Equipee);
|
||||
}
|
||||
|
||||
@@ -6,54 +6,52 @@ namespace Content.Shared.Eye.Blinding.Systems;
|
||||
|
||||
public sealed class BlurryVisionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BlindableComponent, EyeDamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<VisionCorrectionComponent, GotEquippedEvent>(OnGlassesEquipped);
|
||||
SubscribeLocalEvent<VisionCorrectionComponent, GotUnequippedEvent>(OnGlassesUnequipped);
|
||||
SubscribeLocalEvent<VisionCorrectionComponent, InventoryRelayedEvent<GetBlurEvent>>(OnGetBlur);
|
||||
}
|
||||
|
||||
private void OnGetBlur(EntityUid uid, VisionCorrectionComponent component, InventoryRelayedEvent<GetBlurEvent> args)
|
||||
private void OnGetBlur(Entity<VisionCorrectionComponent> glasses, ref InventoryRelayedEvent<GetBlurEvent> args)
|
||||
{
|
||||
args.Args.Blur += component.VisionBonus;
|
||||
args.Args.Blur += glasses.Comp.VisionBonus;
|
||||
args.Args.CorrectionPower *= glasses.Comp.CorrectionPower;
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, BlindableComponent component, ref EyeDamageChangedEvent args)
|
||||
public void UpdateBlurMagnitude(Entity<BlindableComponent?> ent)
|
||||
{
|
||||
UpdateBlurMagnitude(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateBlurMagnitude(EntityUid uid, BlindableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
if (!Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
var ev = new GetBlurEvent(component.EyeDamage);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
var ev = new GetBlurEvent(ent.Comp.EyeDamage);
|
||||
RaiseLocalEvent(ent, ev);
|
||||
|
||||
var blur = Math.Clamp(0, ev.Blur, BlurryVisionComponent.MaxMagnitude);
|
||||
var blur = Math.Clamp(ev.Blur, 0, BlurryVisionComponent.MaxMagnitude);
|
||||
if (blur <= 0)
|
||||
{
|
||||
RemCompDeferred<BlurryVisionComponent>(uid);
|
||||
RemCompDeferred<BlurryVisionComponent>(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var blurry = EnsureComp<BlurryVisionComponent>(uid);
|
||||
var blurry = EnsureComp<BlurryVisionComponent>(ent);
|
||||
blurry.Magnitude = blur;
|
||||
Dirty(blurry);
|
||||
blurry.CorrectionPower = ev.CorrectionPower;
|
||||
Dirty(ent, blurry);
|
||||
}
|
||||
|
||||
private void OnGlassesEquipped(EntityUid uid, VisionCorrectionComponent component, GotEquippedEvent args)
|
||||
private void OnGlassesEquipped(Entity<VisionCorrectionComponent> glasses, ref GotEquippedEvent args)
|
||||
{
|
||||
UpdateBlurMagnitude(uid);
|
||||
UpdateBlurMagnitude(args.Equipee);
|
||||
}
|
||||
|
||||
private void OnGlassesUnequipped(EntityUid uid, VisionCorrectionComponent component, GotUnequippedEvent args)
|
||||
private void OnGlassesUnequipped(Entity<VisionCorrectionComponent> glasses, ref GotUnequippedEvent args)
|
||||
{
|
||||
UpdateBlurMagnitude(uid);
|
||||
UpdateBlurMagnitude(args.Equipee);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +59,7 @@ public sealed class GetBlurEvent : EntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
public readonly float BaseBlur;
|
||||
public float Blur;
|
||||
public float CorrectionPower = BlurryVisionComponent.DefaultCorrectionPower;
|
||||
|
||||
public GetBlurEvent(float blur)
|
||||
{
|
||||
|
||||
141
Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs
Normal file
141
Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Eye.Blinding.Systems;
|
||||
|
||||
public sealed class EyeClosingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly BlindableSystem _blindableSystem = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EyeClosingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<EyeClosingComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<EyeClosingComponent, ToggleEyesActionEvent>(OnToggleAction);
|
||||
SubscribeLocalEvent<EyeClosingComponent, CanSeeAttemptEvent>(OnTrySee);
|
||||
SubscribeLocalEvent<EyeClosingComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<EyeClosingComponent> eyelids, ref MapInitEvent args)
|
||||
{
|
||||
_actionsSystem.AddAction(eyelids, ref eyelids.Comp.EyeToggleActionEntity, eyelids.Comp.EyeToggleAction);
|
||||
Dirty(eyelids);
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<EyeClosingComponent> eyelids, ref ComponentShutdown args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(eyelids, eyelids.Comp.EyeToggleActionEntity);
|
||||
|
||||
SetEyelids((eyelids.Owner, eyelids.Comp), false);
|
||||
}
|
||||
|
||||
private void OnToggleAction(Entity<EyeClosingComponent> eyelids, ref ToggleEyesActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
SetEyelids((eyelids.Owner, eyelids.Comp), !eyelids.Comp.EyesClosed);
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<EyeClosingComponent> eyelids, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
DoAudioFeedback((eyelids.Owner, eyelids.Comp), eyelids.Comp.EyesClosed);
|
||||
}
|
||||
|
||||
private void OnTrySee(Entity<EyeClosingComponent> eyelids, ref CanSeeAttemptEvent args)
|
||||
{
|
||||
if (eyelids.Comp.EyesClosed)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not the entity's eyelids are closed.
|
||||
/// </summary>
|
||||
/// <param name="eyelids">The entity that contains an EyeClosingComponent</param>
|
||||
/// <returns>Exactly what this function says on the tin. True if eyes are closed, false if they're open.</returns>
|
||||
public bool AreEyesClosed(Entity<EyeClosingComponent?> eyelids)
|
||||
{
|
||||
return Resolve(eyelids, ref eyelids.Comp, false) && eyelids.Comp.EyesClosed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether or not the entity's eyelids are closed.
|
||||
/// </summary>
|
||||
/// <param name="eyelids">The entity that contains an EyeClosingComponent</param>
|
||||
/// <param name="value">Set to true to close the entity's eyes. Set to false to open them</param>
|
||||
public void SetEyelids(Entity<EyeClosingComponent?> eyelids, bool value)
|
||||
{
|
||||
if (!Resolve(eyelids, ref eyelids.Comp))
|
||||
return;
|
||||
|
||||
if (eyelids.Comp.EyesClosed == value)
|
||||
return;
|
||||
|
||||
eyelids.Comp.EyesClosed = value;
|
||||
Dirty(eyelids);
|
||||
|
||||
if (eyelids.Comp.EyeToggleActionEntity != null)
|
||||
_actionsSystem.SetToggled(eyelids.Comp.EyeToggleActionEntity, eyelids.Comp.EyesClosed);
|
||||
|
||||
_blindableSystem.UpdateIsBlind(eyelids.Owner);
|
||||
|
||||
DoAudioFeedback(eyelids, eyelids.Comp.EyesClosed);
|
||||
}
|
||||
|
||||
public void DoAudioFeedback(Entity<EyeClosingComponent?> eyelids, bool eyelidTarget)
|
||||
{
|
||||
if (!Resolve(eyelids, ref eyelids.Comp))
|
||||
return;
|
||||
|
||||
if (!_net.IsClient || !_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
if (eyelids.Comp.PreviousEyelidPosition == eyelidTarget)
|
||||
return;
|
||||
|
||||
eyelids.Comp.PreviousEyelidPosition = eyelidTarget;
|
||||
|
||||
if (_playerManager.TryGetSessionByEntity(eyelids, out var session))
|
||||
_audio.PlayGlobal(eyelidTarget ? eyelids.Comp.EyeCloseSound : eyelids.Comp.EyeOpenSound, session);
|
||||
}
|
||||
|
||||
public void UpdateEyesClosable(Entity<BlindableComponent?> blindable)
|
||||
{
|
||||
if (!Resolve(blindable, ref blindable.Comp, false))
|
||||
return;
|
||||
|
||||
var ev = new GetBlurEvent(blindable.Comp.EyeDamage);
|
||||
RaiseLocalEvent(blindable.Owner, ev);
|
||||
|
||||
if (_entityManager.TryGetComponent<EyeClosingComponent>(blindable, out var eyelids) && !eyelids.NaturallyCreated)
|
||||
return;
|
||||
|
||||
if (ev.Blur < BlurryVisionComponent.MaxMagnitude || ev.Blur >= BlindableComponent.MaxDamage)
|
||||
{
|
||||
RemCompDeferred<EyeClosingComponent>(blindable);
|
||||
return;
|
||||
}
|
||||
|
||||
var naturalEyelids = EnsureComp<EyeClosingComponent>(blindable);
|
||||
naturalEyelids.NaturallyCreated = true;
|
||||
Dirty(blindable);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class ToggleEyesActionEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Eye.Blinding;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Shared.Network;
|
||||
@@ -12,6 +12,7 @@ namespace Content.Shared.Traits.Assorted;
|
||||
public sealed class PermanentBlindnessSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly BlindableSystem _blinding = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -19,31 +20,45 @@ public sealed class PermanentBlindnessSystem : EntitySystem
|
||||
{
|
||||
SubscribeLocalEvent<PermanentBlindnessComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<PermanentBlindnessComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<PermanentBlindnessComponent, CanSeeAttemptEvent>(OnTrySee);
|
||||
SubscribeLocalEvent<PermanentBlindnessComponent, EyeDamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<PermanentBlindnessComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, PermanentBlindnessComponent component, ExaminedEvent args)
|
||||
private void OnExamined(Entity<PermanentBlindnessComponent> blindness, ref ExaminedEvent args)
|
||||
{
|
||||
if (args.IsInDetailsRange && !_net.IsClient)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("permanent-blindness-trait-examined", ("target", Identity.Entity(uid, EntityManager))));
|
||||
args.PushMarkup(Loc.GetString("permanent-blindness-trait-examined", ("target", Identity.Entity(blindness, EntityManager))));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, PermanentBlindnessComponent component, ComponentShutdown args)
|
||||
private void OnShutdown(Entity<PermanentBlindnessComponent> blindness, ref ComponentShutdown args)
|
||||
{
|
||||
_blinding.UpdateIsBlind(uid);
|
||||
_blinding.UpdateIsBlind(blindness.Owner);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, PermanentBlindnessComponent component, ComponentStartup args)
|
||||
private void OnStartup(Entity<PermanentBlindnessComponent> blindness, ref ComponentStartup args)
|
||||
{
|
||||
_blinding.UpdateIsBlind(uid);
|
||||
if (!_entityManager.TryGetComponent<BlindableComponent>(blindness, out var blindable))
|
||||
return;
|
||||
|
||||
var damageToDeal = (int) BlurryVisionComponent.MaxMagnitude - blindable.EyeDamage;
|
||||
|
||||
if (damageToDeal <= 0)
|
||||
return;
|
||||
|
||||
_blinding.AdjustEyeDamage(blindness.Owner, damageToDeal);
|
||||
}
|
||||
|
||||
private void OnTrySee(EntityUid uid, PermanentBlindnessComponent component, CanSeeAttemptEvent args)
|
||||
private void OnDamageChanged(Entity<PermanentBlindnessComponent> blindness, ref EyeDamageChangedEvent args)
|
||||
{
|
||||
if (component.LifeStage <= ComponentLifeStage.Running)
|
||||
args.Cancel();
|
||||
if (args.Damage >= BlurryVisionComponent.MaxMagnitude)
|
||||
return;
|
||||
|
||||
if (!_entityManager.TryGetComponent<BlindableComponent>(blindness, out var blindable))
|
||||
return;
|
||||
|
||||
var damageRestoration = (int) BlurryVisionComponent.MaxMagnitude - args.Damage;
|
||||
_blinding.AdjustEyeDamage(blindness.Owner, damageRestoration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,3 +196,8 @@
|
||||
copyright: 'Sound effects from https://quicksounds.com"'
|
||||
license: CC0-1.0
|
||||
source: https://quicksounds.com/library/sounds/grenade
|
||||
|
||||
- files: ["eye_open.ogg", "eye_close.ogg"]
|
||||
copyright: Bhijn and Myr (github username deathride58)
|
||||
license: CC0-1.0
|
||||
source: https://github.com/space-wizards/space-station-14/pull/23212
|
||||
|
||||
BIN
Resources/Audio/Effects/eye_close.ogg
Normal file
BIN
Resources/Audio/Effects/eye_close.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/eye_open.ogg
Normal file
BIN
Resources/Audio/Effects/eye_open.ogg
Normal file
Binary file not shown.
@@ -19,6 +19,7 @@ ui-options-general-discord = Discord
|
||||
ui-options-general-cursor = Cursor
|
||||
ui-options-general-speech = Speech
|
||||
ui-options-general-storage = Storage
|
||||
ui-options-general-accessibility = Accessibility
|
||||
|
||||
## Audio menu
|
||||
|
||||
@@ -44,6 +45,7 @@ ui-options-opaque-storage-window = Opaque storage window
|
||||
ui-options-show-looc-on-head = Show LOOC chat above characters head
|
||||
ui-options-fancy-speech = Show names in speech bubbles
|
||||
ui-options-fancy-name-background = Add background to speech bubble names
|
||||
ui-options-reduced-motion = Reduce motion of visual effects
|
||||
ui-options-vsync = VSync
|
||||
ui-options-fullscreen = Fullscreen
|
||||
ui-options-lighting-label = Lighting Quality:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
trait-blindness-name = Blindness
|
||||
trait-blindness-desc = You lack vision
|
||||
trait-blindness-desc = You are legally blind, and can't see clearly past a few meters in front of you.
|
||||
|
||||
trait-narcolepsy-name = Narcolepsy
|
||||
trait-narcolepsy-decs = You fall asleep randomly
|
||||
|
||||
trait-pacifist-name = Pacifist
|
||||
|
||||
permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you.[/color]
|
||||
permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you well, if at all.[/color]
|
||||
|
||||
trait-lightweight-name = Lightweight Drunk
|
||||
trait-lightweight-desc = Alcohol has a stronger effect on you
|
||||
|
||||
@@ -274,3 +274,15 @@
|
||||
icon: { sprite: Objects/Fun/bikehorn.rsi, state: icon }
|
||||
event: !type:ActivateImplantEvent
|
||||
useDelay: 1
|
||||
|
||||
- type: entity
|
||||
id: ActionToggleEyes
|
||||
name: Open/Close eyes
|
||||
description: Close your eyes to protect your peepers, or open your eyes to enjoy the pretty lights.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
icon: Interface/Actions/eyeopen.png
|
||||
iconOn: Interface/Actions/eyeclose.png
|
||||
event: !type:ToggleEyesActionEvent
|
||||
useDelay: 1 # so u cant give yourself and observers eyestrain by rapidly spamming the action
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
id: CircleMask
|
||||
kind: source
|
||||
path: "/Textures/Shaders/circle_mask.swsl"
|
||||
params:
|
||||
CircleRadius: 15.0
|
||||
CircleMinDist: 0.25
|
||||
CirclePow: 0.5
|
||||
CircleMax: 4.0
|
||||
CircleMult: 0.5
|
||||
|
||||
- type: shader
|
||||
id: GradientCircleMask
|
||||
@@ -87,3 +93,9 @@
|
||||
id: HorizontalCut
|
||||
kind: source
|
||||
path: "/Textures/Shaders/hcut.swsl"
|
||||
|
||||
# Stylized simulation of blindness
|
||||
- type: shader
|
||||
id: Cataracts
|
||||
kind: source
|
||||
path: "/Textures/Shaders/cataracts.swsl"
|
||||
|
||||
BIN
Resources/Textures/Interface/Actions/eyeclose.png
Normal file
BIN
Resources/Textures/Interface/Actions/eyeclose.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
BIN
Resources/Textures/Interface/Actions/eyeopen.png
Normal file
BIN
Resources/Textures/Interface/Actions/eyeopen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
@@ -5,7 +5,7 @@
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation/commit/3d049e69fe71a0be2133005e65ea469135d648c8. Harm and disarm heavily modified. Web made by jackfrost (github) for SS14",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation/commit/3d049e69fe71a0be2133005e65ea469135d648c8. Harm and disarm heavily modified. Web made by jackfrost (github) for SS14. Eyeclose and Eyeopen by Bhijn and Myr (deathride58 on Github), under CC0.",
|
||||
"states": [
|
||||
{
|
||||
"name": "internal0"
|
||||
@@ -51,6 +51,12 @@
|
||||
},
|
||||
{
|
||||
"name": "ghostHearingToggled"
|
||||
},
|
||||
{
|
||||
"name": "eyeopen"
|
||||
},
|
||||
{
|
||||
"name": "eyeclose"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
76
Resources/Textures/Shaders/cataracts.swsl
Normal file
76
Resources/Textures/Shaders/cataracts.swsl
Normal file
@@ -0,0 +1,76 @@
|
||||
// This is a shader simulation of cataracts. Not exactly a realistic/faithful one (the faithful route would be bloom and blur strong enough to make anyone's eyes bleed), but rather, a stylized one!
|
||||
// This description and explainer here is mostly for the sake of future reference, and to make sure the artistic intent doesn't end up lost throughout the years to come.
|
||||
// The effects this goes for are based largely off reports from blind friends and colleagues alike. Mostly reported experiences of cataracts, but also other forms of blindness, such as neuro-opthalmic injuries.
|
||||
// The distortion here does the bulk of the work. This simulates the most common thing that those with the condition report: of everything being an amorphous blur, of seeing double even out of one eye, and more.
|
||||
// Of course, it'd be incredibly straight-forward to go for blur. However, this runs into a notable issue: making a blur strong enough to actually be debilitating would be pretty intense on GPU performance.
|
||||
// There's additional issues with blur as well, but the main sour point is that it *really* doesn't work well in 2D, where everything has the same effective focal point.
|
||||
// However, distortion is surprisingly suitable for the purpose of making everything amorphous, even if it's less a blur and more a haze.
|
||||
// This distortion also moves a bit. This definitely leans far more towards simulating neuro-opthalmic injuries than cataracts specifically (this is effectively oscillopsia), but it does make sure you can't negate the effect by memorizing what part of the screen correlates to exactly where.
|
||||
// As a bonus: this also simulates less-talked-about experiences, such as always seeing multiple, and attempts to rely on far-sighted vision being immensely disorienting.
|
||||
// But being unable to make out what's happening in your vision is far from the only pain that blind folks have to put up with.
|
||||
// Notably: another very common report is that it can be hard (but not impossible) to make out color, and that every light source is incredibly *bright* to the point of causing pain.
|
||||
// This, too, would be far more straight-forward to implement by slapping a heavy bloom filter on the effect.
|
||||
// ... However, this is a bad idea for a pretty obvious reason: heavy bloom almost always causes eyestrain. The idea here is to simulate blindness, not *cause* blindness!
|
||||
// So instead, this takes a slightly more creative route: aggressively tonemapping the distorted output, and pulling in a distorted lightmap to blend on top.
|
||||
// These two steps combined create a foggy and surprisingly blurry image. The result is deliberately muted a bit to reduce eyestrain, but it does lead to any amount of light overpowering everything else, which lines up with reported experiences.
|
||||
// All of this in combination manages to achieve an experience that hits the same notes of what's common for blind folks to report, but in a *very* stylized manner.
|
||||
// And of course, more importantly: the combination ensures that if your character is blind, then you simply can't see shit. You can certainly *try*, just as blind folks IRL are able to, but that'll be fruitless!
|
||||
// Oh. God. We're already at 18 lines of header comments. We rambled, huh? Anyway, thank you for coming to our TED talk. - Bhijn & Myr
|
||||
|
||||
// Boilerplate vars
|
||||
uniform sampler2D SCREEN_TEXTURE;
|
||||
uniform sampler2D LIGHT_TEXTURE;
|
||||
uniform highp float Zoom;
|
||||
|
||||
// Base distortion
|
||||
uniform highp float DistortionScalar; // Scales the total distortion applied to the final image.
|
||||
const highp float DistortionCenterRadius = 200.0; // radius of the gradient used to tone down the distortion effect
|
||||
const highp float DistortionCenterMinDist = 0.25; // minimum distance from the center of the screen for the distortion to appear at all
|
||||
const highp float DistortionCenterPow = 1.1; // the exponent used for the distortion center
|
||||
const highp float DistortionGradientMax = 8.0; // Maximum value for the gradient used for the distortion
|
||||
const highp float DistortionZoomPow = 0.5; // exponent for the zoom uniform when applied to distortion. The math is funky
|
||||
const highp float NoiseScalar = 10.0; // Multiplies the size of the FBM noise
|
||||
|
||||
// Base cloudiness
|
||||
uniform highp float CloudinessScalar;
|
||||
const highp float CloudinessCenterRadius = 125.0; // radius of the gradient used to tone down the cloudiness
|
||||
const highp float CloudinessCenterMinDist = 0.2; // minimum distance from the center of the screen for cloudiness to appear at all
|
||||
const highp float CloudinessCenterPow = 2.0; // the exponent used for the distortion center
|
||||
const highp float CloudinessGradientMax = 8.0; // Maximum value for the gradient used for cloudiness
|
||||
const highp float CloudinessGrayscaleMax = 0.8; // maximum desaturation for the cloudiness effect
|
||||
const highp float CloudinessGrayscaleMult = 0.15; // multiplier applied to the gradient for the desaturation scalar
|
||||
const highp float CloudinessZoomPow = 0.85; // exponent for the zoom uniform when applied to cloudiness
|
||||
|
||||
// Additional lightmap pass for the cloudiness
|
||||
const highp float CloudyLightDistortionMult = 0.5; // multiplier applied to the total amount of distortion added to the lightmap during the lightmap pass
|
||||
const highp float CloudyLightMult = 0.05; // multiplier for scaling the lightmap's brightness
|
||||
const highp float CloudyLightGrayscaleMax = 0.25; // maximum amount of grayscale effect for the lightmap
|
||||
const highp float CloudyLightGrayscaleMult = 0.15; // multiplier used to scale the grayscale effect
|
||||
|
||||
const highp float TimeScale = 0.25;
|
||||
|
||||
|
||||
void fragment() {
|
||||
highp vec2 aspect = vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y);
|
||||
|
||||
highp float timesin = (sin(TIME * TimeScale) + 0.5) * 0.2;
|
||||
highp float timecos = (cos(TIME * TimeScale) + 0.5) * 0.2;
|
||||
|
||||
highp float distortionZoom = pow(Zoom, DistortionZoomPow);
|
||||
|
||||
highp float centergradient = zCircleGradient(aspect * 0.5, FRAGCOORD.xy, DistortionGradientMax, DistortionCenterRadius / distortionZoom, DistortionCenterMinDist / distortionZoom, DistortionCenterPow);
|
||||
highp vec2 coord = (FRAGCOORD.xy * SCREEN_PIXEL_SIZE.xy) * aspect * centergradient * 0.0001; // That magic number on the end there is due to how tiny the FBM noise is. It's incredibly noisy.
|
||||
highp vec2 offset = vec2((zFBM(coord * NoiseScalar + timesin) - 0.5) * centergradient, (zFBM(coord * NoiseScalar + timecos) - 0.5) * centergradient);
|
||||
COLOR = zTextureSpec(SCREEN_TEXTURE, Pos + (offset * DistortionScalar));
|
||||
|
||||
highp float cloudyZoom = pow(Zoom, CloudinessZoomPow);
|
||||
|
||||
highp float cloudygradient = zCircleGradient(aspect * 0.5, FRAGCOORD.xy, CloudinessGradientMax, CloudinessCenterRadius / cloudyZoom, CloudinessCenterMinDist / cloudyZoom, CloudinessCenterPow) * CloudinessScalar;
|
||||
COLOR.rgb = mix(COLOR.rgb, vec3(zGrayscale(COLOR.rgb)), min(cloudygradient * CloudinessGrayscaleMult, CloudinessGrayscaleMax));
|
||||
COLOR.rgb = pow(COLOR.rgb, vec3((cloudygradient * CloudinessScalar + 1.0)));
|
||||
|
||||
//technically the highp makes this higher-quality than the actual default frag shader's lightmap sampling but shushhhhhh it looks prettier without as much banding
|
||||
highp vec3 lightsample = (texture2D(LIGHT_TEXTURE, Pos + (offset * DistortionScalar * CloudyLightDistortionMult)).rgb * pow(cloudygradient, 1.0 / CloudinessCenterPow) * CloudyLightMult);
|
||||
lightsample = mix(lightsample, vec3(zGrayscale(lightsample)), min(cloudygradient * CloudyLightGrayscaleMult, CloudyLightGrayscaleMax));
|
||||
COLOR.rgb = COLOR.rgb + lightsample;
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
uniform highp float ZOOM;
|
||||
uniform sampler2D SCREEN_TEXTURE;
|
||||
uniform highp float Zoom;
|
||||
|
||||
uniform highp float CircleRadius; //= 15.0; // radius of the circular gradient
|
||||
uniform highp float CircleMinDist; //= 0.25; // minimum distance from the center of the screen for the gradient
|
||||
uniform highp float CirclePow; //= 0.5; // the exponent used for the gradient
|
||||
uniform highp float CircleMax; //= 4.0; // Maximum value for the gradient used for the gradient. Don't worry, the end result gets clamped.
|
||||
uniform highp float CircleMult; //= 0.5; // Multiplier for the total value of the circle gradient.
|
||||
|
||||
|
||||
highp vec4 circle(in highp vec2 uv, in highp vec2 pos, highp float rad, in highp vec3 color) {
|
||||
highp float d = length(pos - uv) - rad;
|
||||
highp float t = clamp(d, 0.0, 1.0);
|
||||
return vec4(color, t);
|
||||
}
|
||||
void fragment(){
|
||||
highp vec2 uv = FRAGCOORD.xy;
|
||||
highp vec2 res_xy = vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y);
|
||||
highp vec2 center = res_xy*0.5;
|
||||
highp float radius = 0.05 * res_xy.y / ZOOM;
|
||||
highp vec2 aspect = vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y);
|
||||
highp float actualZoom = Zoom;
|
||||
|
||||
highp vec4 layer1 = vec4(vec3(0.0), 0.0);
|
||||
|
||||
highp vec4 layer2 = circle(uv, center, radius, vec3(0.0));
|
||||
|
||||
COLOR = mix(layer1, layer2, layer2.a);
|
||||
highp float circle = zCircleGradient(aspect * 0.5, FRAGCOORD.xy, CircleMax, CircleRadius / actualZoom, /*CircleMinDist / actualZoom*/ 0.0, CirclePow) * CircleMult;
|
||||
COLOR = mix(vec4(0.0), vec4(vec3(0.0), 1.0), clamp(circle, 0.0, 1.0));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user