Drunk Shader (#8910)

Co-authored-by: Kara D <lunarautomaton6@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Leon Friedrich
2022-06-23 16:33:25 +12:00
committed by GitHub
parent bf489adb91
commit c25f2e6283
19 changed files with 395 additions and 4 deletions

View File

@@ -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<ShaderPrototype>("Drunk").InstanceUnique();
}
protected override void FrameUpdate(FrameEventArgs args)
{
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
if (playerEntity == null)
return;
if (!_entityManager.HasComponent<DrunkComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return;
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
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);
}
/// <summary>
/// Converts the # of seconds the drunk effect lasts for (booze power) to a percentage
/// used by the actual shader.
/// </summary>
/// <param name="boozePower"></param>
private float BoozePowerToVisual(float boozePower)
{
return Math.Clamp((boozePower - VisualThreshold) / PowerDivisor, 0.0f, 1.0f);
}
}

View File

@@ -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<DrunkComponent, ComponentInit>(OnDrunkInit);
SubscribeLocalEvent<DrunkComponent, ComponentShutdown>(OnDrunkShutdown);
SubscribeLocalEvent<DrunkComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DrunkComponent, PlayerDetachedEvent>(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);
}
}
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Speech.EntitySystems;
namespace Content.Client.Speech.EntitySystems;
public sealed class SlurredSystem : SharedSlurredSystem
{
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Drunk;
namespace Content.Server.Chemistry.ReagentEffects;
public sealed class Drunk : ReagentEffect
{
/// <summary>
/// BoozePower is how long each metabolism cycle will make the drunk effect last for.
/// </summary>
[DataField("boozePower")]
public float BoozePower = 2f;
public override void Effect(ReagentEffectArgs args)
{
var drunkSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedDrunkSystem>();
drunkSys.TryApplyDrunkenness(args.SolutionEntity, BoozePower);
}
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Drunk;
namespace Content.Server.Drunk;
public sealed class DrunkSystem : SharedDrunkSystem
{
}

View File

@@ -0,0 +1,4 @@
namespace Content.Server.Speech.Components;
[RegisterComponent]
public sealed class SlurredAccentComponent : Component { }

View File

@@ -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<SlurredAccentComponent, AccentGetEvent>(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<SlurredAccentComponent>(uid, SlurKey, time, true, status);
else
_statusEffectsSystem.TryAddTime(uid, SlurKey, time, status);
}
/// <summary>
/// Slur chance scales with "drunkeness", which is just measured using the time remaining on the status effect.
/// </summary>
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();
}
}

View File

@@ -0,0 +1,6 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Drunk;
[RegisterComponent, NetworkedComponent]
public sealed class DrunkComponent : Component { }

View File

@@ -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<DrunkComponent>(uid, DrunkKey, TimeSpan.FromSeconds(boozePower), true, status);
}
else
{
_statusEffectsSystem.TryAddTime(uid, DrunkKey, TimeSpan.FromSeconds(boozePower), status);
}
}
}

View File

@@ -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) { }
}

View File

@@ -78,3 +78,6 @@ reagent-desc-omnizine = A soothing milky liquid with an iridescent gleam. A well
reagent-name-ultravasculine = ultravasculine reagent-name-ultravasculine = ultravasculine
reagent-desc-ultravasculine = Rapidly flushes toxins from the body, but places some stress on the veins. Do not overdose. 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.

View File

@@ -0,0 +1,2 @@
slur-accent-confused = ...huuuhhh...
slur-accent-burp = *BURP*.

View File

@@ -82,6 +82,8 @@
- Stutter - Stutter
- SeeingRainbows - SeeingRainbows
- Electrocution - Electrocution
- Drunk
- SlurredSpeech
- PressureImmunity - PressureImmunity
- Muted - Muted
- type: DiseaseCarrier - type: DiseaseCarrier

View File

@@ -1,8 +1,6 @@
# Base Alcohol # Base Alcohol
# TODO MIRROR: drunkenness
- type: reagent - type: reagent
id: Absinthe id: Absinthe
name: reagent-name-absinthe name: reagent-name-absinthe
@@ -114,6 +112,10 @@
- !type:ReagentThreshold - !type:ReagentThreshold
reagent: Ethanol reagent: Ethanol
min: 3 min: 3
Alcohol:
effects:
- !type:Drunk
boozePower: 3
- type: reagent - type: reagent
id: Gin id: Gin
@@ -423,7 +425,7 @@
- type: reagent - type: reagent
id: Bilk id: Bilk
name: reagent-name-bilk name: reagent-name-bilk
parent: BaseAlcohol parent: BaseDrink
desc: reagent-desc-bilk desc: reagent-desc-bilk
physicalDesc: reagent-physical-desc-bilky physicalDesc: reagent-physical-desc-bilky
color: "#895C4C" color: "#895C4C"
@@ -519,7 +521,7 @@
- type: reagent - type: reagent
id: DoctorsDelight id: DoctorsDelight
name: reagent-name-doctors-delight name: reagent-name-doctors-delight
parent: BaseAlcohol parent: BaseDrink
desc: reagent-desc-doctors-delight desc: reagent-desc-doctors-delight
physicalDesc: reagent-physical-desc-strong-smelling physicalDesc: reagent-physical-desc-strong-smelling
color: "#FF8CFF" color: "#FF8CFF"

View File

@@ -45,6 +45,21 @@
time: 3.0 time: 3.0
type: Remove 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 - type: reagent
id: Arithrazine id: Arithrazine
name: reagent-name-arithrazine name: reagent-name-arithrazine
@@ -88,6 +103,9 @@
conditions: conditions:
- !type:ReagentThreshold - !type:ReagentThreshold
min: 30 min: 30
Alcohol:
effects:
- !type:Drunk
- type: reagent - type: reagent
id: Cryoxadone id: Cryoxadone

View File

@@ -10,6 +10,18 @@
products: products:
Dylovene: 3 Dylovene: 3
- type: reaction
id: Ethylredoxrazine
reactants:
Oxygen:
amount: 1
Dylovene:
amount: 1
Carbon:
amount: 1
products:
Ethylredoxrazine: 3
- type: reaction - type: reaction
id: Cryptobiolin id: Cryptobiolin
reactants: reactants:

View File

@@ -41,6 +41,11 @@
kind: source kind: source
path: "/Textures/Shaders/camera_static.swsl" path: "/Textures/Shaders/camera_static.swsl"
- type: shader
id: Drunk
kind: source
path: "/Textures/Shaders/drunk.swsl"
- type: shader - type: shader
id: Texture id: Texture
kind: source kind: source

View File

@@ -28,6 +28,12 @@
- type: statusEffect - type: statusEffect
id: Electrocution id: Electrocution
- type: statusEffect
id: Drunk
- type: statusEffect
id: SlurredSpeech
- type: statusEffect - type: statusEffect
id: PressureImmunity id: PressureImmunity

View File

@@ -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));
}