From 02de328d9c0b16338f95b8377ba7bdae18c8cb3b Mon Sep 17 00:00:00 2001 From: MLGTASTICa <61350382+MLGTASTICa@users.noreply.github.com> Date: Mon, 9 May 2022 08:50:36 +0300 Subject: [PATCH] Fixes thirst not applying speed debuffs properly , Makes Thirst a server-side component only (#7767) Co-authored-by: MLGTASTICa Co-authored-by: metalgearsloth --- Content.Client/Entry/IgnoredComponents.cs | 1 + .../Nutrition/Components/ThirstComponent.cs | 28 --- .../Commands/RejuvenateCommand.cs | 6 +- .../Chemistry/ReagentEffects/SatiateThirst.cs | 5 +- .../Nutrition/Components/ThirstComponent.cs | 203 ++---------------- .../Nutrition/EntitySystems/ThirstSystem.cs | 140 +++++++++++- .../Components/SharedThirstComponent.cs | 39 ---- .../EntitySystems/SharedThirstSystem.cs | 22 -- 8 files changed, 166 insertions(+), 278 deletions(-) delete mode 100644 Content.Client/Nutrition/Components/ThirstComponent.cs delete mode 100644 Content.Shared/Nutrition/Components/SharedThirstComponent.cs delete mode 100644 Content.Shared/Nutrition/EntitySystems/SharedThirstSystem.cs diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 71bd6a731d..2e2209f2ab 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -347,6 +347,7 @@ namespace Content.Client.Entry "DoorRemote", "InteractionPopup", "HealthAnalyzer", + "Thirst", "Wires" }; } diff --git a/Content.Client/Nutrition/Components/ThirstComponent.cs b/Content.Client/Nutrition/Components/ThirstComponent.cs deleted file mode 100644 index 9ca977afd3..0000000000 --- a/Content.Client/Nutrition/Components/ThirstComponent.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Movement.Components; -using Content.Shared.Movement.EntitySystems; -using Content.Shared.Nutrition.Components; -using Robust.Shared.GameObjects; - -namespace Content.Client.Nutrition.Components -{ - [RegisterComponent] - public sealed class ThirstComponent : SharedThirstComponent - { - private ThirstThreshold _currentThirstThreshold; - public override ThirstThreshold CurrentThirstThreshold => _currentThirstThreshold; - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not ThirstComponentState thirst) - { - return; - } - - _currentThirstThreshold = thirst.CurrentThreshold; - - EntitySystem.Get().RefreshMovementSpeedModifiers(Owner); - } - } -} diff --git a/Content.Server/Administration/Commands/RejuvenateCommand.cs b/Content.Server/Administration/Commands/RejuvenateCommand.cs index ea7dccc96d..7554e25986 100644 --- a/Content.Server/Administration/Commands/RejuvenateCommand.cs +++ b/Content.Server/Administration/Commands/RejuvenateCommand.cs @@ -58,11 +58,15 @@ namespace Content.Server.Administration.Commands var entMan = IoCManager.Resolve(); entMan.GetComponentOrNull(targetUid)?.UpdateState(0); entMan.GetComponentOrNull(targetUid)?.ResetFood(); - entMan.GetComponentOrNull(targetUid)?.ResetThirst(); // TODO holy shit make this an event my man! EntitySystem.Get().TryRemoveAllStatusEffects(target); + if(entMan.TryGetComponent(target , out ThirstComponent? thirst)) + { + EntitySystem.Get().ResetThirst(thirst); + } + if (entMan.TryGetComponent(target, out FlammableComponent? flammable)) { EntitySystem.Get().Extinguish(target, flammable); diff --git a/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs b/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs index 52b43d9fba..fafb9cfc1d 100644 --- a/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs +++ b/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs @@ -1,8 +1,9 @@ -using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.Components; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; +using Content.Server.Nutrition.EntitySystems; namespace Content.Server.Chemistry.ReagentEffects { @@ -21,7 +22,7 @@ namespace Content.Server.Chemistry.ReagentEffects public override void Effect(ReagentEffectArgs args) { if (args.EntityManager.TryGetComponent(args.SolutionEntity, out ThirstComponent? thirst)) - thirst.UpdateThirst(HydrationFactor); + EntitySystem.Get().UpdateThirst(thirst, HydrationFactor); } } } diff --git a/Content.Server/Nutrition/Components/ThirstComponent.cs b/Content.Server/Nutrition/Components/ThirstComponent.cs index fd5bb4ab8a..f046077866 100644 --- a/Content.Server/Nutrition/Components/ThirstComponent.cs +++ b/Content.Server/Nutrition/Components/ThirstComponent.cs @@ -1,66 +1,40 @@ -using System; -using System.Collections.Generic; -using Content.Server.Administration.Logs; -using Content.Shared.Administration.Logs; using Content.Shared.Alert; using Content.Shared.Damage; -using Content.Shared.Database; -using Content.Shared.MobState.Components; -using Content.Shared.Movement.Components; -using Content.Shared.Movement.EntitySystems; -using Content.Shared.Nutrition.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Players; -using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; namespace Content.Server.Nutrition.Components { - [RegisterComponent] - public sealed class ThirstComponent : SharedThirstComponent + [Flags] + public enum ThirstThreshold : byte { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IRobustRandom _random = default!; + // Hydrohomies + Dead = 0, + Parched = 1 << 0, + Thirsty = 1 << 1, + Okay = 1 << 2, + OverHydrated = 1 << 3, + } - private float _accumulatedFrameTime; + [RegisterComponent] + public sealed class ThirstComponent : Component + { // Base stuff [ViewVariables(VVAccess.ReadWrite)] - public float BaseDecayRate - { - get => _baseDecayRate; - set => _baseDecayRate = value; - } [DataField("baseDecayRate")] - private float _baseDecayRate = 0.1f; + public float BaseDecayRate = 0.1f; [ViewVariables(VVAccess.ReadWrite)] - public float ActualDecayRate - { - get => _actualDecayRate; - set => _actualDecayRate = value; - } - private float _actualDecayRate; + public float ActualDecayRate; // Thirst [ViewVariables(VVAccess.ReadOnly)] - public override ThirstThreshold CurrentThirstThreshold => _currentThirstThreshold; - private ThirstThreshold _currentThirstThreshold; + public ThirstThreshold CurrentThirstThreshold; - private ThirstThreshold _lastThirstThreshold; + public ThirstThreshold LastThirstThreshold; [ViewVariables(VVAccess.ReadWrite)] - public float CurrentThirst - { - get => _currentThirst; - set => _currentThirst = value; - } - private float _currentThirst; + public float CurrentThirst; - [ViewVariables(VVAccess.ReadOnly)] public Dictionary ThirstThresholds { get; } = new() { {ThirstThreshold.OverHydrated, 600.0f}, @@ -77,150 +51,9 @@ namespace Content.Server.Nutrition.Components {ThirstThreshold.Parched, AlertType.Parched}, }; + [DataField("damage", required: true)] [ViewVariables(VVAccess.ReadWrite)] public DamageSpecifier Damage = default!; - - public void ThirstThresholdEffect(bool force = false) - { - if (_currentThirstThreshold != _lastThirstThreshold || force) - { - // Revert slow speed if required - if (_lastThirstThreshold == ThirstThreshold.Parched && _currentThirstThreshold != ThirstThreshold.Dead && - _entMan.TryGetComponent(Owner, out MovementSpeedModifierComponent? movementSlowdownComponent)) - { - EntitySystem.Get().RefreshMovementSpeedModifiers(Owner); - } - - // Update UI - if (ThirstThresholdAlertTypes.TryGetValue(_currentThirstThreshold, out var alertId)) - { - EntitySystem.Get().ShowAlert(Owner, alertId); - } - else - { - EntitySystem.Get().ClearAlertCategory(Owner, AlertCategory.Thirst); - } - - switch (_currentThirstThreshold) - { - case ThirstThreshold.OverHydrated: - _lastThirstThreshold = _currentThirstThreshold; - _actualDecayRate = _baseDecayRate * 1.2f; - return; - - case ThirstThreshold.Okay: - _lastThirstThreshold = _currentThirstThreshold; - _actualDecayRate = _baseDecayRate; - return; - - case ThirstThreshold.Thirsty: - // Same as okay except with UI icon saying drink soon. - _lastThirstThreshold = _currentThirstThreshold; - _actualDecayRate = _baseDecayRate * 0.8f; - return; - - case ThirstThreshold.Parched: - EntitySystem.Get().RefreshMovementSpeedModifiers(Owner); - _lastThirstThreshold = _currentThirstThreshold; - _actualDecayRate = _baseDecayRate * 0.6f; - return; - - case ThirstThreshold.Dead: - return; - default: - Logger.ErrorS("thirst", $"No thirst threshold found for {_currentThirstThreshold}"); - throw new ArgumentOutOfRangeException($"No thirst threshold found for {_currentThirstThreshold}"); - } - } - } - - protected override void Startup() - { - base.Startup(); - _currentThirst = _random.Next( - (int)ThirstThresholds[ThirstThreshold.Thirsty] + 10, - (int)ThirstThresholds[ThirstThreshold.Okay] - 1); - _currentThirstThreshold = GetThirstThreshold(_currentThirst); - _lastThirstThreshold = ThirstThreshold.Okay; // TODO: Potentially change this -> Used Okay because no effects. - // TODO: Check all thresholds make sense and throw if they don't. - ThirstThresholdEffect(true); - Dirty(); - } - - public ThirstThreshold GetThirstThreshold(float drink) - { - ThirstThreshold result = ThirstThreshold.Dead; - var value = ThirstThresholds[ThirstThreshold.OverHydrated]; - foreach (var threshold in ThirstThresholds) - { - if (threshold.Value <= value && threshold.Value >= drink) - { - result = threshold.Key; - value = threshold.Value; - } - } - - return result; - } - - public void UpdateThirst(float amount) - { - _currentThirst = Math.Min(_currentThirst + amount, ThirstThresholds[ThirstThreshold.OverHydrated]); - } - - // TODO: If mob is moving increase rate of consumption. - // Should use a multiplier as something like a disease would overwrite decay rate. - public void OnUpdate(float frametime) - { - _currentThirst -= frametime * ActualDecayRate; - UpdateCurrentThreshold(); - - if (_currentThirstThreshold != ThirstThreshold.Dead) - return; - // --> Current Hunger is below dead threshold - - if (!_entMan.TryGetComponent(Owner, out MobStateComponent? mobState)) - return; - - if (!mobState.IsDead()) - { - // --> But they are not dead yet. - _accumulatedFrameTime += frametime; - if (_accumulatedFrameTime >= 1) - { - EntitySystem.Get().TryChangeDamage(Owner, Damage * (int) _accumulatedFrameTime, true); - _accumulatedFrameTime -= (int) _accumulatedFrameTime; - } - } - } - - private void UpdateCurrentThreshold() - { - var calculatedThirstThreshold = GetThirstThreshold(_currentThirst); - // _trySound(calculatedThreshold); - if (calculatedThirstThreshold != _currentThirstThreshold) - { - if (_currentThirstThreshold == ThirstThreshold.Dead) - EntitySystem.Get().Add(LogType.Thirst, $"{_entMan.ToPrettyString(Owner):entity} has stopped taking dehydration damage"); - else if (calculatedThirstThreshold == ThirstThreshold.Dead) - EntitySystem.Get().Add(LogType.Thirst, $"{_entMan.ToPrettyString(Owner):entity} has started taking dehydration damage"); - - _currentThirstThreshold = calculatedThirstThreshold; - ThirstThresholdEffect(); - Dirty(); - } - } - - public void ResetThirst() - { - _currentThirst = ThirstThresholds[ThirstThreshold.Okay]; - UpdateCurrentThreshold(); - } - - public override ComponentState GetComponentState() - { - return new ThirstComponentState(_currentThirstThreshold); - } } } diff --git a/Content.Server/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Server/Nutrition/EntitySystems/ThirstSystem.cs index 68bb7885d0..3116c6eb1b 100644 --- a/Content.Server/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/ThirstSystem.cs @@ -1,14 +1,131 @@ using Content.Server.Nutrition.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Content.Shared.Movement.EntitySystems; +using Content.Shared.Nutrition.Components; +using Robust.Shared.Random; +using Content.Shared.MobState.Components; +using Content.Shared.Movement.Components; +using Content.Shared.Alert; +using Content.Server.Administration.Logs; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Damage; namespace Content.Server.Nutrition.EntitySystems { [UsedImplicitly] public sealed class ThirstSystem : EntitySystem { + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly AdminLogSystem _adminlog = default!; + [Dependency] private readonly DamageableSystem _damage = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; + + private ISawmill _sawmill = default!; private float _accumulatedFrameTime; + public override void Initialize() + { + base.Initialize(); + + _sawmill = Logger.GetSawmill("thirst"); + SubscribeLocalEvent(OnRefreshMovespeed); + SubscribeLocalEvent(OnComponentStartup); + } + private void OnComponentStartup(EntityUid uid, ThirstComponent component, ComponentStartup args) + { + component.CurrentThirst = _random.Next( + (int) component.ThirstThresholds[ThirstThreshold.Thirsty] + 10, + (int) component.ThirstThresholds[ThirstThreshold.Okay] - 1); + component.CurrentThirstThreshold = GetThirstThreshold(component, component.CurrentThirst); + component.LastThirstThreshold = ThirstThreshold.Okay; // TODO: Potentially change this -> Used Okay because no effects. + // TODO: Check all thresholds make sense and throw if they don't. + UpdateEffects(component); + } + + private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args) + { + var mod = (component.CurrentThirstThreshold & (ThirstThreshold.Parched | ThirstThreshold.Dead)) != 0x0 ? 0.75f : 1.0f; + args.ModifySpeed(mod, mod); + } + + private ThirstThreshold GetThirstThreshold(ThirstComponent component, float amount) + { + ThirstThreshold result = ThirstThreshold.Dead; + var value = component.ThirstThresholds[ThirstThreshold.OverHydrated]; + foreach (var threshold in component.ThirstThresholds) + { + if (threshold.Value <= value && threshold.Value >= amount) + { + result = threshold.Key; + value = threshold.Value; + } + } + + return result; + } + + public void UpdateThirst(ThirstComponent component, float amount) + { + component.CurrentThirst = Math.Min(component.CurrentThirst + amount, component.ThirstThresholds[ThirstThreshold.OverHydrated]); + } + + public void ResetThirst(ThirstComponent component) + { + component.CurrentThirst = component.ThirstThresholds[ThirstThreshold.Okay]; + } + + private void UpdateEffects(ThirstComponent component) + { + if (component.LastThirstThreshold == ThirstThreshold.Parched && component.CurrentThirstThreshold != ThirstThreshold.Dead && + EntityManager.TryGetComponent(component.Owner, out MovementSpeedModifierComponent? movementSlowdownComponent)) + { + _movement.RefreshMovementSpeedModifiers(component.Owner); + } + + // Update UI + if (ThirstComponent.ThirstThresholdAlertTypes.TryGetValue(component.CurrentThirstThreshold, out var alertId)) + { + _alerts.ShowAlert(component.Owner, alertId); + } + else + { + _alerts.ClearAlertCategory(component.Owner, AlertCategory.Thirst); + } + + switch (component.CurrentThirstThreshold) + { + case ThirstThreshold.OverHydrated: + component.LastThirstThreshold = component.CurrentThirstThreshold; + component.ActualDecayRate = component.BaseDecayRate * 1.2f; + return; + + case ThirstThreshold.Okay: + component.LastThirstThreshold = component.CurrentThirstThreshold; + component.ActualDecayRate = component.BaseDecayRate; + return; + + case ThirstThreshold.Thirsty: + // Same as okay except with UI icon saying drink soon. + component.LastThirstThreshold = component.CurrentThirstThreshold; + component.ActualDecayRate = component.BaseDecayRate * 0.8f; + return; + case ThirstThreshold.Parched: + _movement.RefreshMovementSpeedModifiers(component.Owner); + component.LastThirstThreshold = component.CurrentThirstThreshold; + component.ActualDecayRate = component.BaseDecayRate * 0.6f; + return; + + case ThirstThreshold.Dead: + return; + + default: + _sawmill.Error($"No thirst threshold found for {component.CurrentThirstThreshold}"); + throw new ArgumentOutOfRangeException($"No thirst threshold found for {component.CurrentThirstThreshold}"); + } + } public override void Update(float frameTime) { _accumulatedFrameTime += frameTime; @@ -17,7 +134,28 @@ namespace Content.Server.Nutrition.EntitySystems { foreach (var component in EntityManager.EntityQuery()) { - component.OnUpdate(_accumulatedFrameTime); + component.CurrentThirst -= component.ActualDecayRate; + var calculatedThirstThreshold = GetThirstThreshold(component, component.CurrentThirst); + if (calculatedThirstThreshold != component.CurrentThirstThreshold) + { + if (component.CurrentThirstThreshold == ThirstThreshold.Dead) + _adminlog.Add(LogType.Thirst, $"{EntityManager.ToPrettyString(component.Owner):entity} has stopped taking dehydration damage"); + else if (calculatedThirstThreshold == ThirstThreshold.Dead) + _adminlog.Add(LogType.Thirst, $"{EntityManager.ToPrettyString(component.Owner):entity} has started taking dehydration damage"); + + component.CurrentThirstThreshold = calculatedThirstThreshold; + UpdateEffects(component); + } + if (component.CurrentThirstThreshold == ThirstThreshold.Dead) + { + if (!EntityManager.TryGetComponent(component.Owner, out MobStateComponent? mobState)) + return; + + if (!mobState.IsDead()) + { + _damage.TryChangeDamage(component.Owner, component.Damage, true); + } + } } _accumulatedFrameTime -= 1; } diff --git a/Content.Shared/Nutrition/Components/SharedThirstComponent.cs b/Content.Shared/Nutrition/Components/SharedThirstComponent.cs deleted file mode 100644 index d596f5dab6..0000000000 --- a/Content.Shared/Nutrition/Components/SharedThirstComponent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Content.Shared.Movement.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Shared.Nutrition.Components -{ - [NetworkedComponent()] - public abstract class SharedThirstComponent : Component - { - [ViewVariables] - public abstract ThirstThreshold CurrentThirstThreshold { get; } - - [Serializable, NetSerializable] - protected sealed class ThirstComponentState : ComponentState - { - public ThirstThreshold CurrentThreshold { get; } - - public ThirstComponentState(ThirstThreshold currentThreshold) - { - CurrentThreshold = currentThreshold; - } - } - - } - - [NetSerializable, Serializable] - public enum ThirstThreshold : byte - { - // Hydrohomies - OverHydrated, - Okay, - Thirsty, - Parched, - Dead, - } -} diff --git a/Content.Shared/Nutrition/EntitySystems/SharedThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedThirstSystem.cs deleted file mode 100644 index 36a0106e90..0000000000 --- a/Content.Shared/Nutrition/EntitySystems/SharedThirstSystem.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Movement.EntitySystems; -using Content.Shared.Nutrition.Components; -using Robust.Shared.GameObjects; - -namespace Content.Shared.Nutrition.EntitySystems -{ - public sealed class SharedThirstSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnRefreshMovespeed); - } - - private void OnRefreshMovespeed(EntityUid uid, SharedThirstComponent component, RefreshMovementSpeedModifiersEvent args) - { - float mod = component.CurrentThirstThreshold == ThirstThreshold.Parched ? 0.75f : 1.0f; - args.ModifySpeed(mod, mod); - } - } -}