From c25f2e6283d23a01c1a0c496c7ea2e85a0f99af0 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:33:25 +1200 Subject: [PATCH] Drunk Shader (#8910) Co-authored-by: Kara D Co-authored-by: metalgearsloth --- Content.Client/Drunk/DrunkOverlay.cs | 81 ++++++++++++++ Content.Client/Drunk/DrunkSystem.cs | 53 +++++++++ .../Speech/EntitySystems/SlurredSystem.cs | 7 ++ .../Chemistry/ReagentEffects/Drunk.cs | 19 ++++ Content.Server/Drunk/DrunkSystem.cs | 7 ++ .../Components/SlurredAccentComponent.cs | 4 + .../Speech/EntitySystems/SlurredSystem.cs | 105 ++++++++++++++++++ Content.Shared/Drunk/DrunkComponent.cs | 6 + Content.Shared/Drunk/DrunkSystem.cs | 29 +++++ .../EntitySystems/SharedSlurredSystem.cs | 8 ++ .../Locale/en-US/reagents/meta/medicine.ftl | 3 + Resources/Locale/en-US/slur/slurring.ftl | 2 + .../Entities/Mobs/Species/human.yml | 2 + .../Reagents/Consumable/Drink/alcohol.yml | 10 +- Resources/Prototypes/Reagents/medicine.yml | 18 +++ .../Prototypes/Recipes/Reactions/medicine.yml | 12 ++ Resources/Prototypes/Shaders/shaders.yml | 5 + Resources/Prototypes/status_effects.yml | 6 + Resources/Textures/Shaders/drunk.swsl | 22 ++++ 19 files changed, 395 insertions(+), 4 deletions(-) create mode 100644 Content.Client/Drunk/DrunkOverlay.cs create mode 100644 Content.Client/Drunk/DrunkSystem.cs create mode 100644 Content.Client/Speech/EntitySystems/SlurredSystem.cs create mode 100644 Content.Server/Chemistry/ReagentEffects/Drunk.cs create mode 100644 Content.Server/Drunk/DrunkSystem.cs create mode 100644 Content.Server/Speech/Components/SlurredAccentComponent.cs create mode 100644 Content.Server/Speech/EntitySystems/SlurredSystem.cs create mode 100644 Content.Shared/Drunk/DrunkComponent.cs create mode 100644 Content.Shared/Drunk/DrunkSystem.cs create mode 100644 Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs create mode 100644 Resources/Locale/en-US/slur/slurring.ftl create mode 100644 Resources/Textures/Shaders/drunk.swsl diff --git a/Content.Client/Drunk/DrunkOverlay.cs b/Content.Client/Drunk/DrunkOverlay.cs new file mode 100644 index 0000000000..60378989c6 --- /dev/null +++ b/Content.Client/Drunk/DrunkOverlay.cs @@ -0,0 +1,81 @@ +using Content.Shared.Drunk; +using Content.Shared.StatusEffect; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.Drunk; + +public sealed class DrunkOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + private readonly ShaderInstance _drunkShader; + + public float CurrentBoozePower = 0.0f; + + private const float VisualThreshold = 10.0f; + private const float PowerDivisor = 250.0f; + + private float _visualScale = 0; + + public DrunkOverlay() + { + IoCManager.InjectDependencies(this); + _drunkShader = _prototypeManager.Index("Drunk").InstanceUnique(); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + var playerEntity = _playerManager.LocalPlayer?.ControlledEntity; + + if (playerEntity == null) + return; + + if (!_entityManager.HasComponent(playerEntity) + || !_entityManager.TryGetComponent(playerEntity, out var status)) + return; + + var statusSys = _sysMan.GetEntitySystem(); + if (!statusSys.TryGetTime(playerEntity.Value, SharedDrunkSystem.DrunkKey, out var time, status)) + return; + + var timeLeft = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds; + CurrentBoozePower += (timeLeft - CurrentBoozePower) * args.DeltaSeconds / 16f; + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + _visualScale = BoozePowerToVisual(CurrentBoozePower); + return _visualScale > 0; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + _drunkShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _drunkShader.SetParameter("boozePower", _visualScale); + handle.UseShader(_drunkShader); + handle.DrawRect(args.WorldBounds, Color.White); + } + + /// + /// Converts the # of seconds the drunk effect lasts for (booze power) to a percentage + /// used by the actual shader. + /// + /// + private float BoozePowerToVisual(float boozePower) + { + return Math.Clamp((boozePower - VisualThreshold) / PowerDivisor, 0.0f, 1.0f); + } +} diff --git a/Content.Client/Drunk/DrunkSystem.cs b/Content.Client/Drunk/DrunkSystem.cs new file mode 100644 index 0000000000..0573b2ff3e --- /dev/null +++ b/Content.Client/Drunk/DrunkSystem.cs @@ -0,0 +1,53 @@ +using Content.Shared.Drunk; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; + +namespace Content.Client.Drunk; + +public sealed class DrunkSystem : SharedDrunkSystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private DrunkOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDrunkInit); + SubscribeLocalEvent(OnDrunkShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + _overlay = new(); + } + + private void OnPlayerAttached(EntityUid uid, DrunkComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, DrunkComponent component, PlayerDetachedEvent args) + { + _overlay.CurrentBoozePower = 0; + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnDrunkInit(EntityUid uid, DrunkComponent component, ComponentInit args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + _overlayMan.AddOverlay(_overlay); + } + + private void OnDrunkShutdown(EntityUid uid, DrunkComponent component, ComponentShutdown args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + { + _overlay.CurrentBoozePower = 0; + _overlayMan.RemoveOverlay(_overlay); + } + } +} diff --git a/Content.Client/Speech/EntitySystems/SlurredSystem.cs b/Content.Client/Speech/EntitySystems/SlurredSystem.cs new file mode 100644 index 0000000000..28f05cb1c6 --- /dev/null +++ b/Content.Client/Speech/EntitySystems/SlurredSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Speech.EntitySystems; + +namespace Content.Client.Speech.EntitySystems; + +public sealed class SlurredSystem : SharedSlurredSystem +{ +} diff --git a/Content.Server/Chemistry/ReagentEffects/Drunk.cs b/Content.Server/Chemistry/ReagentEffects/Drunk.cs new file mode 100644 index 0000000000..be98590eca --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/Drunk.cs @@ -0,0 +1,19 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Drunk; + +namespace Content.Server.Chemistry.ReagentEffects; + +public sealed class Drunk : ReagentEffect +{ + /// + /// BoozePower is how long each metabolism cycle will make the drunk effect last for. + /// + [DataField("boozePower")] + public float BoozePower = 2f; + + public override void Effect(ReagentEffectArgs args) + { + var drunkSys = args.EntityManager.EntitySysManager.GetEntitySystem(); + drunkSys.TryApplyDrunkenness(args.SolutionEntity, BoozePower); + } +} diff --git a/Content.Server/Drunk/DrunkSystem.cs b/Content.Server/Drunk/DrunkSystem.cs new file mode 100644 index 0000000000..c736bca1af --- /dev/null +++ b/Content.Server/Drunk/DrunkSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Drunk; + +namespace Content.Server.Drunk; + +public sealed class DrunkSystem : SharedDrunkSystem +{ +} diff --git a/Content.Server/Speech/Components/SlurredAccentComponent.cs b/Content.Server/Speech/Components/SlurredAccentComponent.cs new file mode 100644 index 0000000000..18cfeea86c --- /dev/null +++ b/Content.Server/Speech/Components/SlurredAccentComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Server.Speech.Components; + +[RegisterComponent] +public sealed class SlurredAccentComponent : Component { } diff --git a/Content.Server/Speech/EntitySystems/SlurredSystem.cs b/Content.Server/Speech/EntitySystems/SlurredSystem.cs new file mode 100644 index 0000000000..f0db5cdbde --- /dev/null +++ b/Content.Server/Speech/EntitySystems/SlurredSystem.cs @@ -0,0 +1,105 @@ +using System.Text; +using Content.Server.Speech.Components; +using Content.Shared.Drunk; +using Content.Shared.Speech.EntitySystems; +using Content.Shared.StatusEffect; +using Robust.Shared.Random; + +namespace Content.Server.Speech.EntitySystems; + +public sealed class SlurredSystem : SharedSlurredSystem +{ + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + private const string SlurKey = "SlurredSpeech"; + + public override void Initialize() + { + SubscribeLocalEvent(OnAccent); + } + + public override void DoSlur(EntityUid uid, TimeSpan time, StatusEffectsComponent? status = null) + { + if (!Resolve(uid, ref status, false)) + return; + + if (!_statusEffectsSystem.HasStatusEffect(uid, SlurKey, status)) + _statusEffectsSystem.TryAddStatusEffect(uid, SlurKey, time, true, status); + else + _statusEffectsSystem.TryAddTime(uid, SlurKey, time, status); + } + + /// + /// Slur chance scales with "drunkeness", which is just measured using the time remaining on the status effect. + /// + private float GetProbabilityScale(EntityUid uid) + { + if (!_statusEffectsSystem.TryGetTime(uid, SharedDrunkSystem.DrunkKey, out var time)) + return 0; + + var timeLeft = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds; + return Math.Clamp(timeLeft / 200, 0f, 1f); + } + + private void OnAccent(EntityUid uid, SlurredAccentComponent component, AccentGetEvent args) + { + var scale = GetProbabilityScale(uid); + args.Message = Accentuate(args.Message, scale); + } + + private string Accentuate(string message, float scale) + { + var sb = new StringBuilder(); + + // This is pretty much ported from TG. + foreach (var character in message) + { + if (_random.Prob(scale / 3f)) + { + var lower = char.ToLowerInvariant(character); + var newString = lower switch + { + 'o' => "u", + 's' => "ch", + 'a' => "ah", + 'u' => "oo", + 'c' => "k", + _ => $"{character}", + }; + + sb.Append(newString); + } + + if (_random.Prob(scale / 20f)) + { + if (character == ' ') + { + sb.Append(Loc.GetString("slur-accent-confused")); + } + else if (character == '.') + { + sb.Append(' '); + sb.Append(Loc.GetString("slur-accent-burp")); + } + } + + if (!_random.Prob(scale * 3/20)) + { + sb.Append(character); + continue; + } + + var next = _random.Next(1, 3) switch + { + 1 => "'", + 2 => $"{character}{character}", + _ => $"{character}{character}{character}", + }; + + sb.Append(next); + } + + return sb.ToString(); + } +} diff --git a/Content.Shared/Drunk/DrunkComponent.cs b/Content.Shared/Drunk/DrunkComponent.cs new file mode 100644 index 0000000000..0d053bf753 --- /dev/null +++ b/Content.Shared/Drunk/DrunkComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Drunk; + +[RegisterComponent, NetworkedComponent] +public sealed class DrunkComponent : Component { } diff --git a/Content.Shared/Drunk/DrunkSystem.cs b/Content.Shared/Drunk/DrunkSystem.cs new file mode 100644 index 0000000000..57c3d8dcd5 --- /dev/null +++ b/Content.Shared/Drunk/DrunkSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.Speech.EntitySystems; +using Content.Shared.StatusEffect; + +namespace Content.Shared.Drunk; + +public abstract class SharedDrunkSystem : EntitySystem +{ + public const string DrunkKey = "Drunk"; + + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly SharedSlurredSystem _slurredSystem = default!; + + public void TryApplyDrunkenness(EntityUid uid, float boozePower, + StatusEffectsComponent? status = null) + { + if (!Resolve(uid, ref status, false)) + return; + + _slurredSystem.DoSlur(uid, TimeSpan.FromSeconds(boozePower), status); + if (!_statusEffectsSystem.HasStatusEffect(uid, DrunkKey, status)) + { + _statusEffectsSystem.TryAddStatusEffect(uid, DrunkKey, TimeSpan.FromSeconds(boozePower), true, status); + } + else + { + _statusEffectsSystem.TryAddTime(uid, DrunkKey, TimeSpan.FromSeconds(boozePower), status); + } + } +} diff --git a/Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs b/Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs new file mode 100644 index 0000000000..8718e054ba --- /dev/null +++ b/Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.StatusEffect; + +namespace Content.Shared.Speech.EntitySystems; + +public abstract class SharedSlurredSystem : EntitySystem +{ + public virtual void DoSlur(EntityUid uid, TimeSpan time, StatusEffectsComponent? status = null) { } +} diff --git a/Resources/Locale/en-US/reagents/meta/medicine.ftl b/Resources/Locale/en-US/reagents/meta/medicine.ftl index cd800f2cef..2f715b2aed 100644 --- a/Resources/Locale/en-US/reagents/meta/medicine.ftl +++ b/Resources/Locale/en-US/reagents/meta/medicine.ftl @@ -78,3 +78,6 @@ reagent-desc-omnizine = A soothing milky liquid with an iridescent gleam. A well reagent-name-ultravasculine = ultravasculine reagent-desc-ultravasculine = Rapidly flushes toxins from the body, but places some stress on the veins. Do not overdose. + +reagent-name-ethylredoxrazine = ethylredoxrazine +reagent-desc-ethylredoxrazine = Neutralises the effects of alcohol in the blood stream. Though it is commonly needed, it is rarely requested. diff --git a/Resources/Locale/en-US/slur/slurring.ftl b/Resources/Locale/en-US/slur/slurring.ftl new file mode 100644 index 0000000000..feb5753deb --- /dev/null +++ b/Resources/Locale/en-US/slur/slurring.ftl @@ -0,0 +1,2 @@ +slur-accent-confused = ...huuuhhh... +slur-accent-burp = *BURP*. diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 020bcf35cc..46d90c0643 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -82,6 +82,8 @@ - Stutter - SeeingRainbows - Electrocution + - Drunk + - SlurredSpeech - PressureImmunity - Muted - type: DiseaseCarrier diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml index 81f0a563b8..e1257df558 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml @@ -1,8 +1,6 @@ # Base Alcohol -# TODO MIRROR: drunkenness - - type: reagent id: Absinthe name: reagent-name-absinthe @@ -114,6 +112,10 @@ - !type:ReagentThreshold reagent: Ethanol min: 3 + Alcohol: + effects: + - !type:Drunk + boozePower: 3 - type: reagent id: Gin @@ -423,7 +425,7 @@ - type: reagent id: Bilk name: reagent-name-bilk - parent: BaseAlcohol + parent: BaseDrink desc: reagent-desc-bilk physicalDesc: reagent-physical-desc-bilky color: "#895C4C" @@ -519,7 +521,7 @@ - type: reagent id: DoctorsDelight name: reagent-name-doctors-delight - parent: BaseAlcohol + parent: BaseDrink desc: reagent-desc-doctors-delight physicalDesc: reagent-physical-desc-strong-smelling color: "#FF8CFF" diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 94c377712d..93e58ac700 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -45,6 +45,21 @@ time: 3.0 type: Remove +- type: reagent + id: Ethylredoxrazine + name: reagent-name-ethylredoxrazine + group: Medicine + desc: reagent-desc-ethylredoxrazine + physicalDesc: reagent-physical-desc-opaque + color: "#2d5708" + metabolisms: + Medicine: + effects: + - !type:GenericStatusEffect + key: Drunk + time: 2.0 + type: Remove + - type: reagent id: Arithrazine name: reagent-name-arithrazine @@ -88,6 +103,9 @@ conditions: - !type:ReagentThreshold min: 30 + Alcohol: + effects: + - !type:Drunk - type: reagent id: Cryoxadone diff --git a/Resources/Prototypes/Recipes/Reactions/medicine.yml b/Resources/Prototypes/Recipes/Reactions/medicine.yml index 60aed98ec4..b12c5b3f30 100644 --- a/Resources/Prototypes/Recipes/Reactions/medicine.yml +++ b/Resources/Prototypes/Recipes/Reactions/medicine.yml @@ -10,6 +10,18 @@ products: Dylovene: 3 +- type: reaction + id: Ethylredoxrazine + reactants: + Oxygen: + amount: 1 + Dylovene: + amount: 1 + Carbon: + amount: 1 + products: + Ethylredoxrazine: 3 + - type: reaction id: Cryptobiolin reactants: diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index 923a00ca2d..c362fa31a0 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -41,6 +41,11 @@ kind: source path: "/Textures/Shaders/camera_static.swsl" +- type: shader + id: Drunk + kind: source + path: "/Textures/Shaders/drunk.swsl" + - type: shader id: Texture kind: source diff --git a/Resources/Prototypes/status_effects.yml b/Resources/Prototypes/status_effects.yml index bee9c9dc0a..eeb9b8c500 100644 --- a/Resources/Prototypes/status_effects.yml +++ b/Resources/Prototypes/status_effects.yml @@ -28,6 +28,12 @@ - type: statusEffect id: Electrocution +- type: statusEffect + id: Drunk + +- type: statusEffect + id: SlurredSpeech + - type: statusEffect id: PressureImmunity diff --git a/Resources/Textures/Shaders/drunk.swsl b/Resources/Textures/Shaders/drunk.swsl new file mode 100644 index 0000000000..ad26180eb4 --- /dev/null +++ b/Resources/Textures/Shaders/drunk.swsl @@ -0,0 +1,22 @@ +uniform sampler2D SCREEN_TEXTURE; +uniform highp float boozePower; +const highp float TimeScale = 0.5; +const highp float DistortionScale = 0.01; + +void fragment() { + + highp float mod = mix(0.0, DistortionScale, boozePower); + vec2 coord = FRAGCOORD.xy * SCREEN_PIXEL_SIZE.xy; + float time = TIME * TimeScale; + + vec2 offset = vec2((mod * 1.5) * sin(time * 1.5), (mod * 2.0) * cos(time * 1.5 - 0.2)); + highp vec4 tex1 = zTextureSpec(SCREEN_TEXTURE, coord + offset); + + if (boozePower > 0.5) { + offset = vec2((mod * 2 - DistortionScale) * sin(time * 0.333 - 0.2), (mod * 2 - DistortionScale) * cos(time * 0.333)); + tex1 = mix(tex1, zTextureSpec(SCREEN_TEXTURE, coord + offset), mix(0.0, 0.3, boozePower*2-1)); + } + + offset = vec2((mod * 1.0) * sin(time * 1.0 + 0.1), (mod * 1.0) * cos(time * 1.0)); + COLOR = mix(tex1, zTextureSpec(SCREEN_TEXTURE, coord + offset), mix(0.0, 0.5, boozePower)); +}