[NEW STATUS SYSTEM] Drunkenness, Stuttering, Slurred Speech, and Bloodloss (#38678)

* The only commit that matters

* I had to stop playing with my cat to push this change

* Yaml removal

* Proper drunk status effect and remove shitcode

* Review changes

* whoops

* Whoops x2

* Update master fix merge conflicts

* Fix merge conflicts

* Dunk Component kill

* MORE RELAYS

* Holy fucking breaking changes

* Ough

* 46 file diff

* Fix bad commits

* Erm what the test fail?

* Fix those last two

* Merge conflicts

* Me when I fix the merge conflicts

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
Princess Cheeseballs
2025-08-18 13:26:29 -07:00
committed by GitHub
parent d737e39a98
commit 6b73d320b9
27 changed files with 235 additions and 176 deletions

View File

@@ -198,12 +198,6 @@ public sealed partial class BloodstreamComponent : Component
[ViewVariables]
public Entity<SolutionComponent>? TemporarySolution;
/// <summary>
/// Variable that stores the amount of status time added by having a low blood level.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan StatusTime;
/// <summary>
/// Alert to show when bleeding.
/// </summary>

View File

@@ -6,7 +6,6 @@ using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.Drunk;
using Content.Shared.EntityEffects.Effects;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
@@ -16,7 +15,7 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
using Content.Shared.Rejuvenate;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffectNew;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
@@ -27,17 +26,18 @@ namespace Content.Shared.Body.Systems;
public abstract class SharedBloodstreamSystem : EntitySystem
{
public static readonly EntProtoId Bloodloss = "StatusEffectBloodloss";
[Dependency] protected readonly SharedSolutionContainerSystem SolutionContainer = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedPuddleSystem _puddle = default!;
[Dependency] private readonly StatusEffectsSystem _status = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly SharedDrunkSystem _drunkSystem = default!;
[Dependency] private readonly SharedStutteringSystem _stutteringSystem = default!;
public override void Initialize()
{
@@ -100,15 +100,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
// Apply dizziness as a symptom of bloodloss.
// The effect is applied in a way that it will never be cleared without being healthy.
// Multiplying by 2 is arbitrary but works for this case, it just prevents the time from running out
_drunkSystem.TryApplyDrunkenness(
uid,
(float)bloodstream.AdjustedUpdateInterval.TotalSeconds * 2,
applySlur: false);
_stutteringSystem.DoStutter(uid, bloodstream.AdjustedUpdateInterval * 2, refresh: false);
// storing the drunk and stutter time so we can remove it independently from other effects additions
bloodstream.StatusTime += bloodstream.AdjustedUpdateInterval * 2;
DirtyField(uid, bloodstream, nameof(BloodstreamComponent.StatusTime));
_status.TrySetStatusEffectDuration(uid, Bloodloss);
}
else if (!_mobStateSystem.IsDead(uid))
{
@@ -118,12 +110,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
bloodstream.BloodlossHealDamage * bloodPercentage,
ignoreResistances: true, interruptsDoAfters: false);
// Remove the drunk effect when healthy. Should only remove the amount of drunk and stutter added by low blood level
_drunkSystem.TryRemoveDrunkenessTime(uid, bloodstream.StatusTime.TotalSeconds);
_stutteringSystem.DoRemoveStutterTime(uid, bloodstream.StatusTime.TotalSeconds);
// Reset the drunk and stutter time to zero
bloodstream.StatusTime = TimeSpan.Zero;
DirtyField(uid, bloodstream, nameof(BloodstreamComponent.StatusTime));
_status.TryRemoveStatusEffect(uid, Bloodloss);
}
}
}

View File

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

View File

@@ -0,0 +1,11 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Drunk;
/// <summary>
/// This is used by a status effect entity to apply the <see cref="DrunkComponent"/> to an entity.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class DrunkStatusEffectComponent : Component
{
}

View File

@@ -1,48 +0,0 @@
using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffect;
using Content.Shared.Traits.Assorted;
using Robust.Shared.Prototypes;
namespace Content.Shared.Drunk;
public abstract class SharedDrunkSystem : EntitySystem
{
public static readonly ProtoId<StatusEffectPrototype> DrunkKey = "Drunk";
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly SharedSlurredSystem _slurredSystem = default!;
public void TryApplyDrunkenness(EntityUid uid, float boozePower, bool applySlur = true,
StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status, false))
return;
if (TryComp<LightweightDrunkComponent>(uid, out var trait))
boozePower *= trait.BoozeStrengthMultiplier;
if (applySlur)
{
_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);
}
}
public void TryRemoveDrunkenness(EntityUid uid)
{
_statusEffectsSystem.TryRemoveStatusEffect(uid, DrunkKey);
}
public void TryRemoveDrunkenessTime(EntityUid uid, double timeRemoved)
{
_statusEffectsSystem.TryRemoveTime(uid, DrunkKey, TimeSpan.FromSeconds(timeRemoved));
}
}

View File

@@ -0,0 +1,50 @@
using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffectNew;
using Content.Shared.Traits.Assorted;
using Robust.Shared.Prototypes;
namespace Content.Shared.Drunk;
public abstract class SharedDrunkSystem : EntitySystem
{
public static EntProtoId Drunk = "StatusEffectDrunk";
public static EntProtoId Woozy = "StatusEffectWoozy";
/* I have no clue why this magic number was chosen, I copied it from slur system and needed it for the overlay
If you have a more intelligent magic number be my guest to completely explode this value.
There were no comments as to why this value was chosen three years ago. */
public static float MagicNumber = 1100f;
[Dependency] protected readonly StatusEffectsSystem Status = default!;
public override void Initialize()
{
SubscribeLocalEvent<LightweightDrunkComponent, DrunkEvent>(OnLightweightDrinking);
}
public void TryApplyDrunkenness(EntityUid uid, TimeSpan boozePower)
{
var ev = new DrunkEvent(boozePower);
RaiseLocalEvent(uid, ref ev);
Status.TryAddStatusEffectDuration(uid, Drunk, ev.Duration);
}
public void TryRemoveDrunkenness(EntityUid uid)
{
Status.TryRemoveStatusEffect(uid, Drunk);
}
public void TryRemoveDrunkennessTime(EntityUid uid, TimeSpan boozePower)
{
Status.TryAddTime(uid, Drunk, - boozePower);
}
private void OnLightweightDrinking(Entity<LightweightDrunkComponent> entity, ref DrunkEvent args)
{
args.Duration *= entity.Comp.BoozeStrengthMultiplier;
}
[ByRefEvent]
public record struct DrunkEvent(TimeSpan Duration);
}

View File

@@ -9,13 +9,7 @@ public sealed partial class Drunk : EntityEffect
/// BoozePower is how long each metabolism cycle will make the drunk effect last for.
/// </summary>
[DataField]
public float BoozePower = 3f;
/// <summary>
/// Whether speech should be slurred.
/// </summary>
[DataField]
public bool SlurSpeech = true;
public TimeSpan BoozePower = TimeSpan.FromSeconds(3f);
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-drunk", ("chance", Probability));
@@ -24,11 +18,10 @@ public sealed partial class Drunk : EntityEffect
{
var boozePower = BoozePower;
if (args is EntityEffectReagentArgs reagentArgs) {
if (args is EntityEffectReagentArgs reagentArgs)
boozePower *= reagentArgs.Scale.Float();
}
var drunkSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedDrunkSystem>();
drunkSys.TryApplyDrunkenness(args.TargetEntity, boozePower, SlurSpeech);
drunkSys.TryApplyDrunkenness(args.TargetEntity, boozePower);
}
}

View File

@@ -1,8 +1,11 @@
using Content.Shared.StatusEffect;
using Robust.Shared.Prototypes;
namespace Content.Shared.Speech.EntitySystems;
public abstract class SharedSlurredSystem : EntitySystem
{
public static readonly EntProtoId Stutter = "StatusEffectSlurred";
public virtual void DoSlur(EntityUid uid, TimeSpan time, StatusEffectsComponent? status = null) { }
}

View File

@@ -1,26 +1,24 @@
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew;
using Robust.Shared.Prototypes;
namespace Content.Shared.Speech.EntitySystems;
public abstract class SharedStutteringSystem : EntitySystem
{
public static readonly ProtoId<StatusEffectPrototype> StutterKey = "Stutter";
public static readonly EntProtoId Stuttering = "StatusEffectStutter";
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] protected readonly StatusEffectsSystem Status = default!;
// For code in shared... I imagine we ain't getting accent prediction anytime soon so let's not bother.
public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null)
public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh)
{
}
public virtual void DoRemoveStutterTime(EntityUid uid, double timeRemoved)
public virtual void DoRemoveStutterTime(EntityUid uid, TimeSpan timeRemoved)
{
_statusEffectsSystem.TryRemoveTime(uid, StutterKey, TimeSpan.FromSeconds(timeRemoved));
}
public void DoRemoveStutter(EntityUid uid, double timeRemoved)
public virtual void DoRemoveStutter(EntityUid uid)
{
_statusEffectsSystem.TryRemoveStatusEffect(uid, StutterKey);
}
}

View File

@@ -1,4 +1,3 @@
using Content.Shared.Alert;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

View File

@@ -1,6 +1,7 @@
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Rejuvenate;
using Content.Shared.Speech;
using Content.Shared.StatusEffectNew.Components;
using Content.Shared.Stunnable;
using Robust.Shared.Player;
@@ -23,6 +24,8 @@ public sealed partial class StatusEffectsSystem
SubscribeLocalEvent<StatusEffectContainerComponent, StandUpAttemptEvent>(RefRelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, StunEndAttemptEvent>(RefRelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, AccentGetEvent>(RelayStatusEffectEvent);
}
private void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct