From a2a5df1990b50f8db390d7a80225fd4a8daec233 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:58:14 +1000 Subject: [PATCH] Damage masks (#9402) --- Content.Client/Entry/EntryPoint.cs | 2 - .../HealthOverlay/UI/HealthOverlayGui.cs | 8 +- Content.Client/MobState/MobStateSystem.cs | 132 +++++ .../MobState/Overlays/CircleMaskOverlay.cs | 36 -- .../MobState/Overlays/CritOverlay.cs | 58 --- .../MobState/Overlays/DamageOverlay.cs | 224 +++++++++ .../MobState/States/CriticalMobState.cs | 8 - .../MobState/States/DeadMobState.cs | 19 - .../MobState/States/NormalMobState.cs | 11 - .../Data/GeneratedParallaxTextureSource.cs | 10 - .../Tests/Body/LungTest.cs | 4 +- .../Tests/Commands/RejuvenateTest.cs | 22 +- Content.Server/AI/EntitySystems/NPCSystem.cs | 7 +- .../Commands/RejuvenateCommand.cs | 2 +- .../Systems/AdminVerbSystem.Smites.cs | 1 + .../Buckle/Components/BuckleComponent.cs | 4 +- Content.Server/Dragon/DragonSystem.cs | 8 +- Content.Server/Ghost/Roles/GhostRoleSystem.cs | 7 +- .../MobState/MobStateSystem.Crit.cs | 16 + .../MobState/MobStateSystem.Dead.cs | 19 + .../MobState/MobStateSystem.Norm.cs | 29 ++ Content.Server/MobState/MobStateSystem.cs | 19 + .../MobState/States/CriticalMobState.cs | 18 - .../MobState/States/DeadMobState.cs | 21 - .../MobState/States/NormalMobState.cs | 34 -- .../Damage/Systems/DamageableSystem.cs | 5 +- .../MobState/Components/MobStateComponent.cs | 290 +---------- Content.Shared/MobState/DamageStateVisuals.cs | 2 +- .../MobState/EntitySystems/MobStateSystem.cs | 148 ------ .../SharedMobStateSystem.Crit.cs | 26 + .../SharedMobStateSystem.Dead.cs | 36 ++ .../SharedMobStateSystem.Norm.cs | 20 + .../EntitySystems/SharedMobStateSystem.cs | 473 ++++++++++++++++++ .../MobState/MobStateChangedEvent.cs | 36 +- Content.Shared/MobState/State/BaseMobState.cs | 36 -- Content.Shared/MobState/State/IMobState.cs | 40 -- .../MobState/State/SharedCriticalMobState.cs | 34 -- .../MobState/State/SharedDeadMobState.cs | 44 -- .../MobState/State/SharedNormalMobState.cs | 24 - Resources/Prototypes/Body/Parts/skeleton.yml | 2 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 18 +- .../Prototypes/Entities/Mobs/NPCs/bear.yml | 4 +- .../Prototypes/Entities/Mobs/NPCs/carp.yml | 6 +- .../Entities/Mobs/NPCs/regalrat.yml | 12 +- .../Entities/Mobs/NPCs/simplemob.yml | 6 +- .../Entities/Mobs/NPCs/spacetick.yml | 4 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 28 +- .../Entities/Mobs/Player/dragon.yml | 6 +- .../Entities/Mobs/Player/familiars.yml | 6 +- .../Entities/Mobs/Player/guardian.yml | 2 +- .../Entities/Mobs/Player/silicon.yml | 8 +- .../Entities/Mobs/Species/human.yml | 12 +- .../Entities/Mobs/Species/skeleton.yml | 4 +- .../Prototypes/Entities/Objects/Fun/pai.yml | 2 +- .../Shaders/gradient_circle_mask.swsl | 37 +- 55 files changed, 1160 insertions(+), 930 deletions(-) create mode 100644 Content.Client/MobState/MobStateSystem.cs delete mode 100644 Content.Client/MobState/Overlays/CircleMaskOverlay.cs delete mode 100644 Content.Client/MobState/Overlays/CritOverlay.cs create mode 100644 Content.Client/MobState/Overlays/DamageOverlay.cs delete mode 100644 Content.Client/MobState/States/CriticalMobState.cs delete mode 100644 Content.Client/MobState/States/DeadMobState.cs delete mode 100644 Content.Client/MobState/States/NormalMobState.cs create mode 100644 Content.Server/MobState/MobStateSystem.Crit.cs create mode 100644 Content.Server/MobState/MobStateSystem.Dead.cs create mode 100644 Content.Server/MobState/MobStateSystem.Norm.cs create mode 100644 Content.Server/MobState/MobStateSystem.cs delete mode 100644 Content.Server/MobState/States/CriticalMobState.cs delete mode 100644 Content.Server/MobState/States/DeadMobState.cs delete mode 100644 Content.Server/MobState/States/NormalMobState.cs delete mode 100644 Content.Shared/MobState/EntitySystems/MobStateSystem.cs create mode 100644 Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Crit.cs create mode 100644 Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Dead.cs create mode 100644 Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Norm.cs create mode 100644 Content.Shared/MobState/EntitySystems/SharedMobStateSystem.cs delete mode 100644 Content.Shared/MobState/State/BaseMobState.cs delete mode 100644 Content.Shared/MobState/State/IMobState.cs delete mode 100644 Content.Shared/MobState/State/SharedCriticalMobState.cs delete mode 100644 Content.Shared/MobState/State/SharedDeadMobState.cs delete mode 100644 Content.Shared/MobState/State/SharedNormalMobState.cs diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index df52b7c6a2..a26fc40c02 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -187,8 +187,6 @@ namespace Content.Client.Entry var overlayMgr = IoCManager.Resolve(); overlayMgr.AddOverlay(new ParallaxOverlay()); overlayMgr.AddOverlay(new SingularityOverlay()); - overlayMgr.AddOverlay(new CritOverlay()); //Hopefully we can cut down on this list... don't see why a death overlay needs to be instantiated here. - overlayMgr.AddOverlay(new CircleMaskOverlay()); overlayMgr.AddOverlay(new FlashOverlay()); overlayMgr.AddOverlay(new RadiationPulseOverlay()); diff --git a/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs b/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs index 255520cb72..bd02f5558c 100644 --- a/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs +++ b/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs @@ -1,4 +1,5 @@ using Content.Client.IoC; +using Content.Client.MobState; using Content.Client.Resources; using Content.Shared.Damage; using Content.Shared.FixedPoint; @@ -86,11 +87,12 @@ namespace Content.Client.HealthOverlay.UI return; } + var mobStateSystem = _entities.EntitySysManager.GetEntitySystem(); FixedPoint2 threshold; if (mobState.IsAlive()) { - if (!mobState.TryGetEarliestCriticalState(damageable.TotalDamage, out _, out threshold)) + if (!mobStateSystem.TryGetEarliestCriticalState(mobState, damageable.TotalDamage, out _, out threshold)) { CritBar.Visible = false; HealthBar.Visible = false; @@ -107,8 +109,8 @@ namespace Content.Client.HealthOverlay.UI HealthBar.Ratio = 0; HealthBar.Visible = false; - if (!mobState.TryGetPreviousCriticalState(damageable.TotalDamage, out _, out var critThreshold) || - !mobState.TryGetEarliestDeadState(damageable.TotalDamage, out _, out var deadThreshold)) + if (!mobStateSystem.TryGetPreviousCriticalState(mobState, damageable.TotalDamage, out _, out var critThreshold) || + !mobStateSystem.TryGetEarliestDeadState(mobState, damageable.TotalDamage, out _, out var deadThreshold)) { CritBar.Visible = false; return; diff --git a/Content.Client/MobState/MobStateSystem.cs b/Content.Client/MobState/MobStateSystem.cs new file mode 100644 index 0000000000..c7036a3446 --- /dev/null +++ b/Content.Client/MobState/MobStateSystem.cs @@ -0,0 +1,132 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.MobState; +using Content.Shared.MobState.Components; +using Content.Shared.MobState.EntitySystems; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.GameStates; + +namespace Content.Client.MobState; + +public sealed partial class MobStateSystem : SharedMobStateSystem +{ + [Dependency] private readonly IPlayerManager _playerManager = default!; + private Overlays.DamageOverlay _overlay = default!; + + public const short Levels = 7; + + public override void Initialize() + { + base.Initialize(); + + _overlay = new Overlays.DamageOverlay(); + IoCManager.Resolve().AddOverlay(_overlay); + + SubscribeLocalEvent(OnPlayerAttach); + SubscribeLocalEvent(OnPlayerDetach); + SubscribeLocalEvent(OnMobHandleState); + } + + public override void Shutdown() + { + base.Shutdown(); + IoCManager.Resolve().RemoveOverlay(_overlay); + } + + private void OnMobHandleState(EntityUid uid, MobStateComponent component, ref ComponentHandleState args) + { + if (args.Current is not MobStateComponentState state) return; + + if (component.CurrentThreshold == state.CurrentThreshold) + return; + + if (state.CurrentThreshold == null) + { + RemoveState(component); + } + else + { + UpdateState(component, state.CurrentThreshold.Value); + } + } + + private void OnPlayerAttach(PlayerAttachedEvent ev) + { + if (TryComp(ev.Entity, out var mobState) && TryComp(ev.Entity, out var damageable)) + { + SetLevel(mobState, damageable.TotalDamage); + } + } + + private void OnPlayerDetach(PlayerDetachedEvent ev) + { + _overlay.State = DamageState.Alive; + _overlay.BruteLevel = 0f; + _overlay.OxygenLevel = 0f; + _overlay.CritLevel = 0f; + } + + protected override void UpdateState(MobStateComponent component, DamageState? state, FixedPoint2 threshold) + { + base.UpdateState(component, state, threshold); + SetLevel(component, threshold); + } + + private void SetLevel(MobStateComponent stateComponent, FixedPoint2 threshold) + { + var uid = stateComponent.Owner; + + if (_playerManager.LocalPlayer?.ControlledEntity != uid) return; + + _overlay.State = DamageState.Alive; + _overlay.BruteLevel = 0f; + _overlay.OxygenLevel = 0f; + _overlay.CritLevel = 0f; + + if (!TryComp(uid, out var damageable)) + return; + + switch (stateComponent.CurrentState) + { + case DamageState.Dead: + _overlay.State = DamageState.Dead; + return; + } + + var bruteLevel = 0f; + var oxyLevel = 0f; + var critLevel = 0f; + + if (TryGetEarliestIncapacitatedState(stateComponent, threshold, out _, out var earliestThreshold) && damageable.TotalDamage != 0) + { + if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage)) + { + bruteLevel = MathF.Min(1f, (bruteDamage / earliestThreshold).Float()); + } + + if (damageable.Damage.DamageDict.TryGetValue("Asphyxiation", out var oxyDamage)) + { + oxyLevel = MathF.Min(1f, (oxyDamage / earliestThreshold).Float()); + } + + if (threshold >= earliestThreshold && TryGetEarliestDeadState(stateComponent, threshold, out _, out var earliestDeadHold)) + { + critLevel = (float) Math.Clamp((damageable.TotalDamage - earliestThreshold).Double() / (earliestDeadHold - earliestThreshold).Double(), 0.1, 1); + } + } + + // Don't show damage overlay if they're near enough to max. + + if (bruteLevel < 0.05f) + { + bruteLevel = 0f; + } + + _overlay.State = critLevel > 0f ? DamageState.Critical : DamageState.Alive; + _overlay.BruteLevel = bruteLevel; + _overlay.OxygenLevel = oxyLevel; + _overlay.CritLevel = critLevel; + } +} diff --git a/Content.Client/MobState/Overlays/CircleMaskOverlay.cs b/Content.Client/MobState/Overlays/CircleMaskOverlay.cs deleted file mode 100644 index f2e9510010..0000000000 --- a/Content.Client/MobState/Overlays/CircleMaskOverlay.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Robust.Client.Graphics; -using Robust.Client.Player; -using Robust.Shared.Enums; -using Robust.Shared.IoC; -using Robust.Shared.Maths; -using Robust.Shared.Prototypes; - -namespace Content.Client.MobState.Overlays -{ - public sealed class CircleMaskOverlay : Overlay - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - - public override OverlaySpace Space => OverlaySpace.WorldSpace; - private readonly ShaderInstance _shader; - - public CircleMaskOverlay() - { - IoCManager.InjectDependencies(this); - _shader = _prototypeManager.Index("CircleMask").Instance(); - } - - protected override void Draw(in OverlayDrawArgs args) - { - if (!CritOverlay.LocalPlayerHasState(_playerManager, false, true)) - return; - - - var worldHandle = args.WorldHandle; - worldHandle.UseShader(_shader); - var viewport = args.WorldAABB; - worldHandle.DrawRect(viewport, Color.White); - } - } -} diff --git a/Content.Client/MobState/Overlays/CritOverlay.cs b/Content.Client/MobState/Overlays/CritOverlay.cs deleted file mode 100644 index defb0c9bcf..0000000000 --- a/Content.Client/MobState/Overlays/CritOverlay.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Content.Shared.MobState.Components; -using Robust.Client.Graphics; -using Robust.Client.Player; -using Robust.Shared.Enums; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; -using Robust.Shared.Prototypes; - -namespace Content.Client.MobState.Overlays -{ - public sealed class CritOverlay : Overlay - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IEntityManager _entities = default!; - - public override OverlaySpace Space => OverlaySpace.WorldSpace; - private readonly ShaderInstance _gradientCircleShader; - - public CritOverlay() - { - IoCManager.InjectDependencies(this); - _gradientCircleShader = _prototypeManager.Index("GradientCircleMask").Instance(); - } - - public static bool LocalPlayerHasState(IPlayerManager pm, bool critical, bool dead, IEntityManager? entities = null) { - if (pm.LocalPlayer?.ControlledEntity is not {Valid: true} player) - { - return false; - } - - IoCManager.Resolve(ref entities); - if (entities.TryGetComponent(player, out var mobState)) - { - if (critical) - if (mobState.IsCritical()) - return true; - if (dead) - if (mobState.IsDead()) - return true; - } - - return false; - } - - protected override void Draw(in OverlayDrawArgs args) - { - if (!LocalPlayerHasState(_playerManager, true, false, _entities)) - return; - - var worldHandle = args.WorldHandle; - var viewport = args.WorldAABB; - worldHandle.UseShader(_gradientCircleShader); - worldHandle.DrawRect(viewport, Color.White); - } - } -} diff --git a/Content.Client/MobState/Overlays/DamageOverlay.cs b/Content.Client/MobState/Overlays/DamageOverlay.cs new file mode 100644 index 0000000000..80ceaede73 --- /dev/null +++ b/Content.Client/MobState/Overlays/DamageOverlay.cs @@ -0,0 +1,224 @@ +using Content.Shared.MobState; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.MobState.Overlays; + +public sealed class DamageOverlay : Overlay +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public override OverlaySpace Space => OverlaySpace.ScreenSpace; + + private readonly ShaderInstance _critShader; + private readonly ShaderInstance _oxygenShader; + private readonly ShaderInstance _bruteShader; + + public DamageState State = DamageState.Alive; + + /// + /// Handles the red pulsing overlay + /// + public float BruteLevel = 0f; + + private float _oldBruteLevel = 0f; + + /// + /// Handles the darkening overlay. + /// + public float OxygenLevel = 0f; + + private float _oldOxygenLevel = 0f; + + /// + /// Handles the white overlay when crit. + /// + public float CritLevel = 0f; + + private float _oldCritLevel = 0f; + + private float _deadLevel = 1f; + + public DamageOverlay() + { + // TODO: Replace + IoCManager.InjectDependencies(this); + _oxygenShader = _prototypeManager.Index("GradientCircleMask").InstanceUnique(); + _critShader = _prototypeManager.Index("GradientCircleMask").InstanceUnique(); + _bruteShader = _prototypeManager.Index("GradientCircleMask").InstanceUnique(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + /* + * Here's the rundown: + * 1. There's lerping for each level so the transitions are smooth. + * 2. There's 3 overlays, 1 for brute damage, 1 for oxygen damage (that also doubles as a crit overlay), + * and a white one during crit that closes in as you progress towards death. When you die it slowly disappears. + * The crit overlay also occasionally reduces its alpha as a "blink" + */ + + var viewport = args.ViewportBounds; + var handle = args.ScreenHandle; + var distance = args.ViewportBounds.Width; + var lerpRate = 0.2f; + + var time = (float) _timing.RealTime.TotalSeconds; + var lastFrameTime = (float) _timing.FrameTime.TotalSeconds; + + // If they just died then lerp out the white overlay. + if (State != DamageState.Dead) + { + _deadLevel = 1f; + } + else if (!_deadLevel.Equals(0f)) + { + var diff = -_deadLevel; + _deadLevel += GetDiff(diff, lerpRate, lastFrameTime); + } + + if (!_oldBruteLevel.Equals(BruteLevel)) + { + var diff = BruteLevel - _oldBruteLevel; + _oldBruteLevel += GetDiff(diff, lerpRate, lastFrameTime); + } + + if (!_oldOxygenLevel.Equals(OxygenLevel)) + { + var diff = OxygenLevel - _oldOxygenLevel; + _oldOxygenLevel += GetDiff(diff, lerpRate, lastFrameTime); + } + + if (!_oldCritLevel.Equals(CritLevel)) + { + var diff = CritLevel - _oldCritLevel; + _oldCritLevel += GetDiff(diff, lerpRate, lastFrameTime); + } + + /* + * darknessAlphaOuter is the maximum alpha for anything outside of the larger circle + * darknessAlphaInner (on the shader) is the alpha for anything inside the smallest circle + * + * outerCircleRadius is what we end at for max level for the outer circle + * outerCircleMaxRadius is what we start at for 0 level for the outer circle + * + * innerCircleRadius is what we end at for max level for the inner circle + * innerCircleMaxRadius is what we start at for 0 level for the inner circle + */ + + // Makes debugging easier don't @ me + float level = 0f; + level = _oldBruteLevel; + + // TODO: Lerping + if (level > 0f && _oldCritLevel <= 0f) + { + var pulseRate = 3f; + var adjustedTime = time * pulseRate; + float outerMaxLevel = 2.0f * distance; + float outerMinLevel = 0.8f * distance; + float innerMaxLevel = 0.6f * distance; + float innerMinLevel = 0.2f * distance; + + var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel); + var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel); + + var pulse = MathF.Max(0f, MathF.Sin(adjustedTime)); + + _bruteShader.SetParameter("time", pulse); + _bruteShader.SetParameter("color", new Vector3(1f, 0f, 0f)); + _bruteShader.SetParameter("darknessAlphaOuter", 0.8f); + + _bruteShader.SetParameter("outerCircleRadius", outerRadius); + _bruteShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance); + _bruteShader.SetParameter("innerCircleRadius", innerRadius); + _bruteShader.SetParameter("innerCircleMaxRadius", innerRadius + 0.02f * distance); + handle.UseShader(_bruteShader); + handle.DrawRect(viewport, Color.White); + } + else + { + _oldBruteLevel = BruteLevel; + } + + level = State != DamageState.Critical ? _oldOxygenLevel : 1f; + + if (level > 0f) + { + float outerMaxLevel = 0.6f * distance; + float outerMinLevel = 0.06f * distance; + float innerMaxLevel = 0.02f * distance; + float innerMinLevel = 0.02f * distance; + + var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel); + var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel); + + float outerDarkness; + float critTime; + + // If in crit then just fix it; also pulse it very occasionally so they can see more. + if (_oldCritLevel > 0f) + { + var adjustedTime = time * 2f; + critTime = MathF.Max(0, MathF.Sin(adjustedTime) + 2 * MathF.Sin(2 * adjustedTime / 4f) + MathF.Sin(adjustedTime / 4f) - 3f); + + if (critTime > 0f) + { + outerDarkness = 1f - critTime / 1.5f; + } + else + { + outerDarkness = 1f; + } + } + else + { + outerDarkness = MathF.Min(0.98f, 0.3f * MathF.Log(level) + 1f); + } + + _oxygenShader.SetParameter("time", 0.0f); + _oxygenShader.SetParameter("color", new Vector3(0f, 0f, 0f)); + _oxygenShader.SetParameter("darknessAlphaOuter", outerDarkness); + _oxygenShader.SetParameter("innerCircleRadius", innerRadius); + _oxygenShader.SetParameter("innerCircleMaxRadius", innerRadius); + _oxygenShader.SetParameter("outerCircleRadius", outerRadius); + _oxygenShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance); + handle.UseShader(_oxygenShader); + handle.DrawRect(viewport, Color.White); + } + + level = State != DamageState.Dead ? _oldCritLevel : _deadLevel; + + if (level > 0f) + { + float outerMaxLevel = 2.0f * distance; + float outerMinLevel = 1.0f * distance; + float innerMaxLevel = 0.6f * distance; + float innerMinLevel = 0.02f * distance; + + var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel); + var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel); + + var pulse = MathF.Max(0f, MathF.Sin(time)); + + // If in crit then just fix it; also pulse it very occasionally so they can see more. + _critShader.SetParameter("time", pulse); + _critShader.SetParameter("color", new Vector3(1f, 1f, 1f)); + _critShader.SetParameter("darknessAlphaOuter", 1.0f); + _critShader.SetParameter("innerCircleRadius", innerRadius); + _critShader.SetParameter("innerCircleMaxRadius", innerRadius + 0.005f * distance); + _critShader.SetParameter("outerCircleRadius", outerRadius); + _critShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance); + handle.UseShader(_critShader); + handle.DrawRect(viewport, Color.White); + } + } + + private float GetDiff(float value, float lerpRate, float lastFrameTime) + { + return Math.Clamp(value, -1 * lerpRate * lastFrameTime, lerpRate * lastFrameTime); + } +} diff --git a/Content.Client/MobState/States/CriticalMobState.cs b/Content.Client/MobState/States/CriticalMobState.cs deleted file mode 100644 index 934d33921b..0000000000 --- a/Content.Client/MobState/States/CriticalMobState.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Shared.MobState.State; - -namespace Content.Client.MobState.States -{ - public sealed class CriticalMobState : SharedCriticalMobState - { - } -} diff --git a/Content.Client/MobState/States/DeadMobState.cs b/Content.Client/MobState/States/DeadMobState.cs deleted file mode 100644 index b6883a6c3e..0000000000 --- a/Content.Client/MobState/States/DeadMobState.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.MobState; -using Content.Shared.MobState.State; -using Robust.Shared.GameObjects; - -namespace Content.Client.MobState.States -{ - public sealed class DeadMobState : SharedDeadMobState - { - public override void EnterState(EntityUid uid, IEntityManager entityManager) - { - base.EnterState(uid, entityManager); - - if (entityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) - { - appearance.SetData(DamageStateVisuals.State, DamageState.Dead); - } - } - } -} diff --git a/Content.Client/MobState/States/NormalMobState.cs b/Content.Client/MobState/States/NormalMobState.cs deleted file mode 100644 index a04ac7fd3c..0000000000 --- a/Content.Client/MobState/States/NormalMobState.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.MobState; -using Content.Shared.MobState.State; -using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; - -namespace Content.Client.MobState.States -{ - public sealed class NormalMobState : SharedNormalMobState - { - } -} diff --git a/Content.Client/Parallax/Data/GeneratedParallaxTextureSource.cs b/Content.Client/Parallax/Data/GeneratedParallaxTextureSource.cs index 19a7d1142d..8c3ff55f6a 100644 --- a/Content.Client/Parallax/Data/GeneratedParallaxTextureSource.cs +++ b/Content.Client/Parallax/Data/GeneratedParallaxTextureSource.cs @@ -1,24 +1,14 @@ -using System; -using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; using JetBrains.Annotations; using Nett; -using Content.Shared; using Content.Shared.CCVar; -using Content.Client.Resources; using Content.Client.IoC; using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Utility; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index 24f1094b38..1f9c0b4c2f 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Body.Systems; @@ -27,7 +27,7 @@ namespace Content.IntegrationTests.Tests.Body centerSlot: torso - type: MobState thresholds: - 0: !type:NormalMobState {} + 0: Alive - type: ThermalRegulator metabolismHeat: 5000 radiatedHeat: 400 diff --git a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs index 0ba5827c59..4e806e6419 100644 --- a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Content.Client.MobState; using Content.Server.Administration.Commands; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -25,9 +26,9 @@ namespace Content.IntegrationTests.Tests.Commands damageContainer: Biological - type: MobState thresholds: - 0: !type:NormalMobState {} - 100: !type:CriticalMobState {} - 200: !type:DeadMobState {} + 0: Alive + 100: Critical + 200: Dead "; [Test] @@ -35,22 +36,19 @@ namespace Content.IntegrationTests.Tests.Commands { await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; + var entManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + var prototypeManager = server.ResolveDependency(); await server.WaitAssertion(() => { - var mapManager = IoCManager.Resolve(); - mapManager.CreateNewMapEntity(MapId.Nullspace); - var entityManager = IoCManager.Resolve(); - var prototypeManager = IoCManager.Resolve(); - - var human = entityManager.SpawnEntity("DamageableDummy", MapCoordinates.Nullspace); + var human = entManager.SpawnEntity("DamageableDummy", MapCoordinates.Nullspace); // Sanity check - Assert.True(IoCManager.Resolve().TryGetComponent(human, out DamageableComponent damageable)); - Assert.True(IoCManager.Resolve().TryGetComponent(human, out MobStateComponent mobState)); - mobState.UpdateState(0); + Assert.True(entManager.TryGetComponent(human, out DamageableComponent damageable)); + Assert.True(entManager.TryGetComponent(human, out MobStateComponent mobState)); Assert.That(mobState.IsAlive, Is.True); Assert.That(mobState.IsCritical, Is.False); Assert.That(mobState.IsDead, Is.False); diff --git a/Content.Server/AI/EntitySystems/NPCSystem.cs b/Content.Server/AI/EntitySystems/NPCSystem.cs index 77a919ff5b..da4a7191fa 100644 --- a/Content.Server/AI/EntitySystems/NPCSystem.cs +++ b/Content.Server/AI/EntitySystems/NPCSystem.cs @@ -1,6 +1,5 @@ using System.Linq; using Content.Server.AI.Components; -using Content.Server.MobState.States; using Content.Shared.CCVar; using Content.Shared.MobState; using JetBrains.Annotations; @@ -121,11 +120,11 @@ namespace Content.Server.AI.EntitySystems { switch (args.CurrentMobState) { - case NormalMobState: + case DamageState.Alive: component.Awake = true; break; - case CriticalMobState: - case DeadMobState: + case DamageState.Critical: + case DamageState.Dead: component.Awake = false; break; } diff --git a/Content.Server/Administration/Commands/RejuvenateCommand.cs b/Content.Server/Administration/Commands/RejuvenateCommand.cs index b6875279d6..ab6364c045 100644 --- a/Content.Server/Administration/Commands/RejuvenateCommand.cs +++ b/Content.Server/Administration/Commands/RejuvenateCommand.cs @@ -5,6 +5,7 @@ using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Disease.Components; using Content.Server.Disease; +using Content.Server.MobState; using Content.Server.Nutrition.Components; using Content.Server.Nutrition.EntitySystems; using Content.Shared.Administration; @@ -57,7 +58,6 @@ namespace Content.Server.Administration.Commands { var targetUid = target; var entMan = IoCManager.Resolve(); - entMan.GetComponentOrNull(targetUid)?.UpdateState(0); entMan.GetComponentOrNull(targetUid)?.ResetFood(); // TODO holy shit make this an event my man! diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 974deb0f9c..4292e2cdf7 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -30,6 +30,7 @@ using Content.Shared.Disease; using Content.Shared.Electrocution; using Content.Shared.Interaction.Components; using Content.Shared.Inventory; +using Content.Shared.MobState; using Content.Shared.MobState.Components; using Content.Shared.Movement.Components; using Content.Shared.Nutrition.Components; diff --git a/Content.Server/Buckle/Components/BuckleComponent.cs b/Content.Server/Buckle/Components/BuckleComponent.cs index 9af8472f85..8204b0826e 100644 --- a/Content.Server/Buckle/Components/BuckleComponent.cs +++ b/Content.Server/Buckle/Components/BuckleComponent.cs @@ -7,6 +7,7 @@ using Content.Shared.Alert; using Content.Shared.Buckle.Components; using Content.Shared.Interaction; using Content.Shared.MobState.Components; +using Content.Shared.MobState.EntitySystems; using Content.Shared.Popups; using Content.Shared.Pulling.Components; using Content.Shared.Standing; @@ -323,7 +324,8 @@ namespace Content.Server.Buckle.Components EntitySystem.Get().Stand(Owner); } - mobState?.CurrentState?.EnterState(Owner, _entMan); + IoCManager.Resolve().GetEntitySystem() + .EnterState(mobState, mobState?.CurrentState); UpdateBuckleStatus(); diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs index 8ef43a1bdd..58ef3770f7 100644 --- a/Content.Server/Dragon/DragonSystem.cs +++ b/Content.Server/Dragon/DragonSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Popups; using Content.Shared.Actions; using Content.Shared.CharacterAppearance.Components; using Content.Shared.Chemistry.Components; -using Content.Shared.Damage; using Content.Shared.MobState; using Content.Shared.MobState.Components; using Content.Shared.Tag; @@ -12,8 +11,6 @@ using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Player; using System.Threading; -using Content.Shared.MobState.State; -using Content.Shared.Doors.Components; namespace Content.Server.Dragon { @@ -24,7 +21,6 @@ namespace Content.Server.Dragon [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; public override void Initialize() { @@ -127,8 +123,8 @@ namespace Content.Server.Dragon { switch (targetState.CurrentState) { - case SharedCriticalMobState: - case SharedDeadMobState: + case DamageState.Critical: + case DamageState.Dead: component.CancelToken = new CancellationTokenSource(); _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, component.CancelToken.Token, target) diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index e8b5227d97..27c6a754f1 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Ghost.Components; using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.UI; using Content.Server.Mind.Components; -using Content.Server.MobState.States; using Content.Server.Players; using Content.Shared.Administration; using Content.Shared.Database; @@ -57,14 +56,14 @@ namespace Content.Server.Ghost.Roles { switch (args.CurrentMobState) { - case NormalMobState: + case DamageState.Alive: { if (!component.Taken) RegisterGhostRole(component); break; } - case CriticalMobState: - case DeadMobState: + case DamageState.Critical: + case DamageState.Dead: UnregisterGhostRole(component); break; } diff --git a/Content.Server/MobState/MobStateSystem.Crit.cs b/Content.Server/MobState/MobStateSystem.Crit.cs new file mode 100644 index 0000000000..e77015fd9e --- /dev/null +++ b/Content.Server/MobState/MobStateSystem.Crit.cs @@ -0,0 +1,16 @@ +using Content.Shared.StatusEffect; + +namespace Content.Server.MobState; + +public sealed partial class MobStateSystem +{ + public override void EnterCritState(EntityUid uid) + { + base.EnterCritState(uid); + + if (HasComp(uid)) + { + Status.TryRemoveStatusEffect(uid, "Stun"); + } + } +} diff --git a/Content.Server/MobState/MobStateSystem.Dead.cs b/Content.Server/MobState/MobStateSystem.Dead.cs new file mode 100644 index 0000000000..4a4cc09b69 --- /dev/null +++ b/Content.Server/MobState/MobStateSystem.Dead.cs @@ -0,0 +1,19 @@ +using Content.Shared.Alert; +using Content.Shared.StatusEffect; + +namespace Content.Server.MobState; + +public sealed partial class MobStateSystem +{ + public override void EnterDeadState(EntityUid uid) + { + base.EnterDeadState(uid); + + Alerts.ShowAlert(uid, AlertType.HumanDead); + + if (HasComp(uid)) + { + Status.TryRemoveStatusEffect(uid, "Stun"); + } + } +} diff --git a/Content.Server/MobState/MobStateSystem.Norm.cs b/Content.Server/MobState/MobStateSystem.Norm.cs new file mode 100644 index 0000000000..605d446ed2 --- /dev/null +++ b/Content.Server/MobState/MobStateSystem.Norm.cs @@ -0,0 +1,29 @@ +using Content.Shared.Alert; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.MobState.Components; + +namespace Content.Server.MobState; + +public sealed partial class MobStateSystem +{ + public override void UpdateNormState(EntityUid entity, FixedPoint2 threshold) + { + base.UpdateNormState(entity, threshold); + + if (!TryComp(entity, out var damageable)) + return; + + if (!TryComp(entity, out var stateComponent)) + return; + + short modifier = 0; + + if (TryGetEarliestIncapacitatedState(stateComponent, threshold, out _, out var earliestThreshold) && damageable.TotalDamage != 0) + { + modifier = (short)(damageable.TotalDamage / (earliestThreshold / 5) + 1); + } + + Alerts.ShowAlert(entity, AlertType.HumanHealth, modifier); + } +} diff --git a/Content.Server/MobState/MobStateSystem.cs b/Content.Server/MobState/MobStateSystem.cs new file mode 100644 index 0000000000..9036e04368 --- /dev/null +++ b/Content.Server/MobState/MobStateSystem.cs @@ -0,0 +1,19 @@ +using Content.Shared.MobState.Components; +using Content.Shared.MobState.EntitySystems; +using Robust.Shared.GameStates; + +namespace Content.Server.MobState; + +public sealed partial class MobStateSystem : SharedMobStateSystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMobGetState); + } + + private void OnMobGetState(EntityUid uid, MobStateComponent component, ref ComponentGetState args) + { + args.State = new MobStateComponentState(component.CurrentThreshold); + } +} diff --git a/Content.Server/MobState/States/CriticalMobState.cs b/Content.Server/MobState/States/CriticalMobState.cs deleted file mode 100644 index 93a2b6ba27..0000000000 --- a/Content.Server/MobState/States/CriticalMobState.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.MobState.State; -using Content.Shared.StatusEffect; - -namespace Content.Server.MobState.States -{ - public sealed class CriticalMobState : SharedCriticalMobState - { - public override void EnterState(EntityUid uid, IEntityManager entityManager) - { - base.EnterState(uid, entityManager); - - if (entityManager.TryGetComponent(uid, out StatusEffectsComponent? stun)) - { - EntitySystem.Get().TryRemoveStatusEffect(uid, "Stun"); - } - } - } -} diff --git a/Content.Server/MobState/States/DeadMobState.cs b/Content.Server/MobState/States/DeadMobState.cs deleted file mode 100644 index 60c8b90ca4..0000000000 --- a/Content.Server/MobState/States/DeadMobState.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Shared.Alert; -using Content.Shared.MobState.State; -using Content.Shared.StatusEffect; - -namespace Content.Server.MobState.States -{ - public sealed class DeadMobState : SharedDeadMobState - { - public override void EnterState(EntityUid uid, IEntityManager entityManager) - { - base.EnterState(uid, entityManager); - - EntitySystem.Get().ShowAlert(uid, AlertType.HumanDead); - - if (entityManager.TryGetComponent(uid, out StatusEffectsComponent? stun)) - { - EntitySystem.Get().TryRemoveStatusEffect(uid, "Stun"); - } - } - } -} diff --git a/Content.Server/MobState/States/NormalMobState.cs b/Content.Server/MobState/States/NormalMobState.cs deleted file mode 100644 index 726307f538..0000000000 --- a/Content.Server/MobState/States/NormalMobState.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Shared.Alert; -using Content.Shared.Damage; -using Content.Shared.FixedPoint; -using Content.Shared.MobState.Components; -using Content.Shared.MobState.State; - -namespace Content.Server.MobState.States -{ - public sealed class NormalMobState : SharedNormalMobState - { - public override void UpdateState(EntityUid entity, FixedPoint2 threshold, IEntityManager entityManager) - { - base.UpdateState(entity, threshold, entityManager); - - if (!entityManager.TryGetComponent(entity, out DamageableComponent? damageable)) - { - return; - } - - if (!entityManager.TryGetComponent(entity, out MobStateComponent? stateComponent)) - { - return; - } - - short modifier = 0; - - if (stateComponent.TryGetEarliestIncapacitatedState(threshold, out _, out var earliestThreshold) && damageable.TotalDamage != 0) - { - modifier = (short)(damageable.TotalDamage / (earliestThreshold / 5) + 1); - } - EntitySystem.Get().ShowAlert(entity, AlertType.HumanHealth, modifier); - } - } -} diff --git a/Content.Shared/Damage/Systems/DamageableSystem.cs b/Content.Shared/Damage/Systems/DamageableSystem.cs index 966e11fafd..5c7726ddc8 100644 --- a/Content.Shared/Damage/Systems/DamageableSystem.cs +++ b/Content.Shared/Damage/Systems/DamageableSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; using Content.Shared.Inventory; +using Content.Shared.MobState; using Content.Shared.MobState.Components; using Content.Shared.Radiation.Events; using Robust.Shared.GameStates; @@ -256,7 +257,7 @@ namespace Content.Shared.Damage int ent1DeadState = 0; foreach (var state in oldstate._highestToLowestStates) { - if (state.Value.IsDead()) + if (state.Value == DamageState.Dead) { ent1DeadState = state.Key; } @@ -265,7 +266,7 @@ namespace Content.Shared.Damage int ent2DeadState = 0; foreach (var state in newstate._highestToLowestStates) { - if (state.Value.IsDead()) + if (state.Value == DamageState.Dead) { ent2DeadState = state.Key; } diff --git a/Content.Shared/MobState/Components/MobStateComponent.cs b/Content.Shared/MobState/Components/MobStateComponent.cs index 8a6814ec02..4df2d5a1ac 100644 --- a/Content.Shared/MobState/Components/MobStateComponent.cs +++ b/Content.Shared/MobState/Components/MobStateComponent.cs @@ -1,11 +1,8 @@ -using System.Diagnostics.CodeAnalysis; using System.Linq; -using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.FixedPoint; -using Content.Shared.MobState.State; +using Content.Shared.MobState.EntitySystems; using Robust.Shared.GameStates; -using Robust.Shared.Serialization; namespace Content.Shared.MobState.Components { @@ -19,8 +16,6 @@ namespace Content.Shared.MobState.Components [NetworkedComponent] public sealed class MobStateComponent : Component { - [Dependency] private readonly IEntityManager _entMan = default!; - /// /// States that this mapped to /// the amount of damage at which they are triggered. @@ -30,294 +25,43 @@ namespace Content.Shared.MobState.Components /// [ViewVariables] [DataField("thresholds")] - private readonly SortedDictionary _lowestToHighestStates = new(); + public readonly SortedDictionary _lowestToHighestStates = new(); // TODO Remove Nullability? [ViewVariables] - public IMobState? CurrentState { get; private set; } + public DamageState? CurrentState { get; set; } [ViewVariables] - public FixedPoint2? CurrentThreshold { get; private set; } + public FixedPoint2? CurrentThreshold { get; set; } - public IEnumerable> _highestToLowestStates => _lowestToHighestStates.Reverse(); - - protected override void Startup() - { - base.Startup(); - - if (CurrentState != null && CurrentThreshold != null) - { - // Initialize with given states - SetMobState(null, (CurrentState, CurrentThreshold.Value)); - } - else - { - // Initialize with some amount of damage, defaulting to 0. - UpdateState(_entMan.GetComponentOrNull(Owner)?.TotalDamage ?? FixedPoint2.Zero); - } - } - - protected override void OnRemove() - { - EntitySystem.Get().ClearAlert(Owner, AlertType.HumanHealth); - - base.OnRemove(); - } - - public override ComponentState GetComponentState() - { - return new MobStateComponentState(CurrentThreshold); - } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not MobStateComponentState state) - { - return; - } - - if (CurrentThreshold == state.CurrentThreshold) - { - return; - } - - if (state.CurrentThreshold == null) - { - RemoveState(); - } - else - { - UpdateState(state.CurrentThreshold.Value); - } - } + public IEnumerable> _highestToLowestStates => _lowestToHighestStates.Reverse(); + [Obsolete("Use MobStateSystem")] public bool IsAlive() { - return CurrentState?.IsAlive() ?? false; + return IoCManager.Resolve().GetEntitySystem() + .IsAlive(Owner, this); } + [Obsolete("Use MobStateSystem")] public bool IsCritical() { - return CurrentState?.IsCritical() ?? false; + return IoCManager.Resolve().GetEntitySystem() + .IsCritical(Owner, this); } + [Obsolete("Use MobStateSystem")] public bool IsDead() { - return CurrentState?.IsDead() ?? false; + return IoCManager.Resolve().GetEntitySystem() + .IsDead(Owner, this); } + [Obsolete("Use MobStateSystem")] public bool IsIncapacitated() { - return CurrentState?.IsIncapacitated() ?? false; - } - - public (IMobState state, FixedPoint2 threshold)? GetState(FixedPoint2 damage) - { - foreach (var (threshold, state) in _highestToLowestStates) - { - if (damage >= threshold) - { - return (state, threshold); - } - } - - return null; - } - - public bool TryGetState( - FixedPoint2 damage, - [NotNullWhen(true)] out IMobState? state, - out FixedPoint2 threshold) - { - var highestState = GetState(damage); - - if (highestState == null) - { - state = default; - threshold = default; - return false; - } - - (state, threshold) = highestState.Value; - return true; - } - - private (IMobState state, FixedPoint2 threshold)? GetEarliestState(FixedPoint2 minimumDamage, Predicate predicate) - { - foreach (var (threshold, state) in _lowestToHighestStates) - { - if (threshold < minimumDamage || - !predicate(state)) - { - continue; - } - - return (state, threshold); - } - - return null; - } - - private (IMobState state, FixedPoint2 threshold)? GetPreviousState(FixedPoint2 maximumDamage, Predicate predicate) - { - foreach (var (threshold, state) in _highestToLowestStates) - { - if (threshold > maximumDamage || - !predicate(state)) - { - continue; - } - - return (state, threshold); - } - - return null; - } - - public (IMobState state, FixedPoint2 threshold)? GetEarliestCriticalState(FixedPoint2 minimumDamage) - { - return GetEarliestState(minimumDamage, s => s.IsCritical()); - } - - public (IMobState state, FixedPoint2 threshold)? GetEarliestIncapacitatedState(FixedPoint2 minimumDamage) - { - return GetEarliestState(minimumDamage, s => s.IsIncapacitated()); - } - - public (IMobState state, FixedPoint2 threshold)? GetEarliestDeadState(FixedPoint2 minimumDamage) - { - return GetEarliestState(minimumDamage, s => s.IsDead()); - } - - public (IMobState state, FixedPoint2 threshold)? GetPreviousCriticalState(FixedPoint2 minimumDamage) - { - return GetPreviousState(minimumDamage, s => s.IsCritical()); - } - - private bool TryGetState( - (IMobState state, FixedPoint2 threshold)? tuple, - [NotNullWhen(true)] out IMobState? state, - out FixedPoint2 threshold) - { - if (tuple == null) - { - state = default; - threshold = default; - return false; - } - - (state, threshold) = tuple.Value; - return true; - } - - public bool TryGetEarliestCriticalState( - FixedPoint2 minimumDamage, - [NotNullWhen(true)] out IMobState? state, - out FixedPoint2 threshold) - { - var earliestState = GetEarliestCriticalState(minimumDamage); - - return TryGetState(earliestState, out state, out threshold); - } - - public bool TryGetEarliestIncapacitatedState( - FixedPoint2 minimumDamage, - [NotNullWhen(true)] out IMobState? state, - out FixedPoint2 threshold) - { - var earliestState = GetEarliestIncapacitatedState(minimumDamage); - - return TryGetState(earliestState, out state, out threshold); - } - - public bool TryGetEarliestDeadState( - FixedPoint2 minimumDamage, - [NotNullWhen(true)] out IMobState? state, - out FixedPoint2 threshold) - { - var earliestState = GetEarliestDeadState(minimumDamage); - - return TryGetState(earliestState, out state, out threshold); - } - - public bool TryGetPreviousCriticalState( - FixedPoint2 maximumDamage, - [NotNullWhen(true)] out IMobState? state, - out FixedPoint2 threshold) - { - var earliestState = GetPreviousCriticalState(maximumDamage); - - return TryGetState(earliestState, out state, out threshold); - } - - private void RemoveState() - { - var old = CurrentState; - CurrentState = null; - CurrentThreshold = null; - - SetMobState(old, null); - } - - /// - /// Updates the mob state.. - /// - public void UpdateState(FixedPoint2 damage) - { - if (!TryGetState(damage, out var newState, out var threshold)) - { - return; - } - - SetMobState(CurrentState, (newState, threshold)); - } - - /// - /// Sets the mob state and marks the component as dirty. - /// - private void SetMobState(IMobState? old, (IMobState state, FixedPoint2 threshold)? current) - { - var entMan = _entMan; - - if (!current.HasValue) - { - old?.ExitState(Owner, entMan); - return; - } - - var (state, threshold) = current.Value; - - CurrentThreshold = threshold; - - if (state == old) - { - state.UpdateState(Owner, threshold, entMan); - return; - } - - old?.ExitState(Owner, entMan); - - CurrentState = state; - - state.EnterState(Owner, entMan); - state.UpdateState(Owner, threshold, entMan); - - var message = new MobStateChangedEvent(this, old, state); - entMan.EventBus.RaiseLocalEvent(Owner, message, true); - Dirty(); - } - } - - [Serializable, NetSerializable] - public sealed class MobStateComponentState : ComponentState - { - public readonly FixedPoint2? CurrentThreshold; - - public MobStateComponentState(FixedPoint2? currentThreshold) - { - CurrentThreshold = currentThreshold; + return IoCManager.Resolve().GetEntitySystem() + .IsIncapacitated(Owner, this); } } } diff --git a/Content.Shared/MobState/DamageStateVisuals.cs b/Content.Shared/MobState/DamageStateVisuals.cs index b04752b0d3..a5ef1d9835 100644 --- a/Content.Shared/MobState/DamageStateVisuals.cs +++ b/Content.Shared/MobState/DamageStateVisuals.cs @@ -3,7 +3,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.MobState { [Serializable, NetSerializable] - public enum DamageStateVisuals + public enum DamageStateVisuals : byte { State } diff --git a/Content.Shared/MobState/EntitySystems/MobStateSystem.cs b/Content.Shared/MobState/EntitySystems/MobStateSystem.cs deleted file mode 100644 index 0fdfbf905d..0000000000 --- a/Content.Shared/MobState/EntitySystems/MobStateSystem.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Damage; -using Content.Shared.DragDrop; -using Content.Shared.Emoting; -using Content.Shared.Interaction.Events; -using Content.Shared.Inventory.Events; -using Content.Shared.Item; -using Content.Shared.MobState.Components; -using Content.Shared.MobState.State; -using Content.Shared.Movement; -using Content.Shared.Movement.Events; -using Content.Shared.Pulling.Events; -using Content.Shared.Speech; -using Content.Shared.Standing; -using Content.Shared.Throwing; - -namespace Content.Shared.MobState.EntitySystems -{ - public sealed class MobStateSystem : EntitySystem - { - [Dependency] private readonly ActionBlockerSystem _blocker = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnChangeDirectionAttempt); - SubscribeLocalEvent(OnUseAttempt); - SubscribeLocalEvent(OnInteractAttempt); - SubscribeLocalEvent(OnThrowAttempt); - SubscribeLocalEvent(OnSpeakAttempt); - SubscribeLocalEvent(OnEquipAttempt); - SubscribeLocalEvent(OnEmoteAttempt); - SubscribeLocalEvent(OnUnequipAttempt); - SubscribeLocalEvent(OnDropAttempt); - SubscribeLocalEvent(OnPickupAttempt); - SubscribeLocalEvent(OnStartPullAttempt); - SubscribeLocalEvent(UpdateState); - SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(OnStandAttempt); - SubscribeLocalEvent(OnStateChanged); - // Note that there's no check for Down attempts because if a mob's in crit or dead, they can be downed... - } - - #region ActionBlocker - private void OnStateChanged(MobStateChangedEvent ev) - { - _blocker.UpdateCanMove(ev.Entity); - } - - private void CheckAct(EntityUid uid, MobStateComponent component, CancellableEntityEventArgs args) - { - switch (component.CurrentState) - { - case SharedDeadMobState: - case SharedCriticalMobState: - args.Cancel(); - break; - } - } - - private void OnChangeDirectionAttempt(EntityUid uid, MobStateComponent component, ChangeDirectionAttemptEvent args) - { - CheckAct(uid, component, args); - } - - private void OnUseAttempt(EntityUid uid, MobStateComponent component, UseAttemptEvent args) - { - CheckAct(uid, component, args); - } - - private void OnInteractAttempt(EntityUid uid, MobStateComponent component, InteractionAttemptEvent args) - { - CheckAct(uid, component, args); - } - - private void OnThrowAttempt(EntityUid uid, MobStateComponent component, ThrowAttemptEvent args) - { - CheckAct(uid, component, args); - } - - private void OnSpeakAttempt(EntityUid uid, MobStateComponent component, SpeakAttemptEvent args) - { - CheckAct(uid, component, args); - } - - private void OnEquipAttempt(EntityUid uid, MobStateComponent component, IsEquippingAttemptEvent args) - { - // is this a self-equip, or are they being stripped? - if (args.Equipee == uid) - CheckAct(uid, component, args); - } - - private void OnEmoteAttempt(EntityUid uid, MobStateComponent component, EmoteAttemptEvent args) - { - CheckAct(uid, component, args); - } - - private void OnUnequipAttempt(EntityUid uid, MobStateComponent component, IsUnequippingAttemptEvent args) - { - // is this a self-equip, or are they being stripped? - if (args.Unequipee == uid) - CheckAct(uid, component, args); - } - - private void OnDropAttempt(EntityUid uid, MobStateComponent component, DropAttemptEvent args) - { - CheckAct(uid, component, args); - } - - private void OnPickupAttempt(EntityUid uid, MobStateComponent component, PickupAttemptEvent args) - { - CheckAct(uid, component, args); - } - - #endregion - - private void OnStartPullAttempt(EntityUid uid, MobStateComponent component, StartPullAttemptEvent args) - { - if (component.IsIncapacitated()) - args.Cancel(); - } - - public void UpdateState(EntityUid _, MobStateComponent component, DamageChangedEvent args) - { - component.UpdateState(args.Damageable.TotalDamage); - } - - private void OnMoveAttempt(EntityUid uid, MobStateComponent component, UpdateCanMoveEvent args) - { - switch (component.CurrentState) - { - case SharedCriticalMobState: - case SharedDeadMobState: - args.Cancel(); - return; - default: - return; - } - } - - private void OnStandAttempt(EntityUid uid, MobStateComponent component, StandAttemptEvent args) - { - if (component.IsIncapacitated()) - args.Cancel(); - } - } -} diff --git a/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Crit.cs b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Crit.cs new file mode 100644 index 0000000000..4fdda95ed6 --- /dev/null +++ b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Crit.cs @@ -0,0 +1,26 @@ +using Content.Shared.Alert; +using Content.Shared.FixedPoint; + +namespace Content.Shared.MobState.EntitySystems; + +public abstract partial class SharedMobStateSystem +{ + public virtual void EnterCritState(EntityUid uid) + { + Alerts.ShowAlert(uid, AlertType.HumanCrit); + + Standing.Down(uid); + + if (TryComp(uid, out var appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Critical); + } + } + + public virtual void ExitCritState(EntityUid uid) + { + Standing.Stand(uid); + } + + public virtual void UpdateCritState(EntityUid entity, FixedPoint2 threshold) {} +} diff --git a/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Dead.cs b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Dead.cs new file mode 100644 index 0000000000..a4fde4ed1e --- /dev/null +++ b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Dead.cs @@ -0,0 +1,36 @@ +using Content.Shared.FixedPoint; + +namespace Content.Shared.MobState.EntitySystems; + +public abstract partial class SharedMobStateSystem +{ + public virtual void EnterDeadState(EntityUid uid) + { + EnsureComp(uid); + Standing.Down(uid); + + if (Standing.IsDown(uid) && TryComp(uid, out var physics)) + { + physics.CanCollide = false; + } + + if (TryComp(uid, out var appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Dead); + } + } + + public virtual void ExitDeadState(EntityUid uid) + { + RemComp(uid); + + Standing.Stand(uid); + + if (!Standing.IsDown(uid) && TryComp(uid, out var physics)) + { + physics.CanCollide = true; + } + } + + public virtual void UpdateDeadState(EntityUid entity, FixedPoint2 threshold) {} +} diff --git a/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Norm.cs b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Norm.cs new file mode 100644 index 0000000000..9c1d193a38 --- /dev/null +++ b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.Norm.cs @@ -0,0 +1,20 @@ +using Content.Shared.FixedPoint; + +namespace Content.Shared.MobState.EntitySystems; + +public abstract partial class SharedMobStateSystem +{ + public virtual void EnterNormState(EntityUid uid) + { + Standing.Stand(uid); + + if (TryComp(uid, out var appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Alive); + } + } + + public virtual void UpdateNormState(EntityUid entity, FixedPoint2 threshold) {} + + public virtual void ExitNormState(EntityUid uid) {} +} diff --git a/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.cs b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.cs new file mode 100644 index 0000000000..f0c66f6864 --- /dev/null +++ b/Content.Shared/MobState/EntitySystems/SharedMobStateSystem.cs @@ -0,0 +1,473 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.ActionBlocker; +using Content.Shared.Alert; +using Content.Shared.Damage; +using Content.Shared.DragDrop; +using Content.Shared.Emoting; +using Content.Shared.FixedPoint; +using Content.Shared.Interaction.Events; +using Content.Shared.Inventory.Events; +using Content.Shared.Item; +using Content.Shared.MobState.Components; +using Content.Shared.Movement.Events; +using Content.Shared.Pulling.Events; +using Content.Shared.Speech; +using Content.Shared.Standing; +using Content.Shared.StatusEffect; +using Content.Shared.Throwing; +using Robust.Shared.Serialization; + +namespace Content.Shared.MobState.EntitySystems +{ + public abstract partial class SharedMobStateSystem : EntitySystem + { + [Dependency] protected readonly AlertsSystem Alerts = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] protected readonly StandingStateSystem Standing = default!; + [Dependency] protected readonly StatusEffectsSystem Status = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMobShutdown); + SubscribeLocalEvent(OnMobStartup); + + SubscribeLocalEvent(OnChangeDirectionAttempt); + SubscribeLocalEvent(OnUseAttempt); + SubscribeLocalEvent(OnInteractAttempt); + SubscribeLocalEvent(OnThrowAttempt); + SubscribeLocalEvent(OnSpeakAttempt); + SubscribeLocalEvent(OnEquipAttempt); + SubscribeLocalEvent(OnEmoteAttempt); + SubscribeLocalEvent(OnUnequipAttempt); + SubscribeLocalEvent(OnDropAttempt); + SubscribeLocalEvent(OnPickupAttempt); + SubscribeLocalEvent(OnStartPullAttempt); + SubscribeLocalEvent(UpdateState); + SubscribeLocalEvent(OnMoveAttempt); + SubscribeLocalEvent(OnStandAttempt); + SubscribeLocalEvent(OnStateChanged); + // Note that there's no check for Down attempts because if a mob's in crit or dead, they can be downed... + } + + private void OnMobStartup(EntityUid uid, MobStateComponent component, ComponentStartup args) + { + if (component.CurrentState != null && component.CurrentThreshold != null) + { + // Initialize with given states + SetMobState(component, null, (component.CurrentState.Value, component.CurrentThreshold.Value)); + } + else + { + // Initialize with some amount of damage, defaulting to 0. + UpdateState(component, CompOrNull(uid)?.TotalDamage ?? FixedPoint2.Zero); + } + } + + private void OnMobShutdown(EntityUid uid, MobStateComponent component, ComponentShutdown args) + { + Alerts.ClearAlert(uid, AlertType.HumanHealth); + } + + public bool IsAlive(EntityUid uid, MobStateComponent? component = null) + { + if (!Resolve(uid, ref component, false)) return false; + return component.CurrentState == DamageState.Alive; + } + + public bool IsCritical(EntityUid uid, MobStateComponent? component = null) + { + if (!Resolve(uid, ref component, false)) return false; + return component.CurrentState == DamageState.Critical; + } + + public bool IsDead(EntityUid uid, MobStateComponent? component = null) + { + if (!Resolve(uid, ref component, false)) return false; + return component.CurrentState == DamageState.Dead; + } + + public bool IsIncapacitated(EntityUid uid, MobStateComponent? component = null) + { + if (!Resolve(uid, ref component, false)) return false; + return component.CurrentState is DamageState.Critical or DamageState.Dead; + } + + #region ActionBlocker + private void OnStateChanged(MobStateChangedEvent ev) + { + _blocker.UpdateCanMove(ev.Entity); + } + + private void CheckAct(EntityUid uid, MobStateComponent component, CancellableEntityEventArgs args) + { + switch (component.CurrentState) + { + case DamageState.Dead: + case DamageState.Critical: + args.Cancel(); + break; + } + } + + private void OnChangeDirectionAttempt(EntityUid uid, MobStateComponent component, ChangeDirectionAttemptEvent args) + { + CheckAct(uid, component, args); + } + + private void OnUseAttempt(EntityUid uid, MobStateComponent component, UseAttemptEvent args) + { + CheckAct(uid, component, args); + } + + private void OnInteractAttempt(EntityUid uid, MobStateComponent component, InteractionAttemptEvent args) + { + CheckAct(uid, component, args); + } + + private void OnThrowAttempt(EntityUid uid, MobStateComponent component, ThrowAttemptEvent args) + { + CheckAct(uid, component, args); + } + + private void OnSpeakAttempt(EntityUid uid, MobStateComponent component, SpeakAttemptEvent args) + { + CheckAct(uid, component, args); + } + + private void OnEquipAttempt(EntityUid uid, MobStateComponent component, IsEquippingAttemptEvent args) + { + // is this a self-equip, or are they being stripped? + if (args.Equipee == uid) + CheckAct(uid, component, args); + } + + private void OnEmoteAttempt(EntityUid uid, MobStateComponent component, EmoteAttemptEvent args) + { + CheckAct(uid, component, args); + } + + private void OnUnequipAttempt(EntityUid uid, MobStateComponent component, IsUnequippingAttemptEvent args) + { + // is this a self-equip, or are they being stripped? + if (args.Unequipee == uid) + CheckAct(uid, component, args); + } + + private void OnDropAttempt(EntityUid uid, MobStateComponent component, DropAttemptEvent args) + { + CheckAct(uid, component, args); + } + + private void OnPickupAttempt(EntityUid uid, MobStateComponent component, PickupAttemptEvent args) + { + CheckAct(uid, component, args); + } + + #endregion + + private void OnStartPullAttempt(EntityUid uid, MobStateComponent component, StartPullAttemptEvent args) + { + if (IsIncapacitated(uid, component)) + args.Cancel(); + } + + public void UpdateState(EntityUid _, MobStateComponent component, DamageChangedEvent args) + { + UpdateState(component, args.Damageable.TotalDamage); + } + + private void OnMoveAttempt(EntityUid uid, MobStateComponent component, UpdateCanMoveEvent args) + { + switch (component.CurrentState) + { + case DamageState.Critical: + case DamageState.Dead: + args.Cancel(); + return; + default: + return; + } + } + + private void OnStandAttempt(EntityUid uid, MobStateComponent component, StandAttemptEvent args) + { + if (IsIncapacitated(uid, component)) + args.Cancel(); + } + + public virtual void RemoveState(MobStateComponent component) + { + var old = component.CurrentState; + component.CurrentState = null; + component.CurrentThreshold = null; + + SetMobState(component, old, null); + } + + public virtual void EnterState(MobStateComponent? component, DamageState? state) + { + // TODO: Thanks buckle + if (component == null) return; + + switch (state) + { + case DamageState.Alive: + EnterNormState(component.Owner); + break; + case DamageState.Critical: + EnterCritState(component.Owner); + break; + case DamageState.Dead: + EnterDeadState(component.Owner); + break; + case null: + break; + default: + throw new NotImplementedException(); + } + } + + protected virtual void UpdateState(MobStateComponent component, DamageState? state, FixedPoint2 threshold) + { + switch (state) + { + case DamageState.Alive: + UpdateNormState(component.Owner, threshold); + break; + case DamageState.Critical: + UpdateCritState(component.Owner, threshold); + break; + case DamageState.Dead: + UpdateDeadState(component.Owner, threshold); + break; + case null: + break; + default: + throw new NotImplementedException(); + } + } + + protected virtual void ExitState(MobStateComponent component, DamageState? state) + { + switch (state) + { + case DamageState.Alive: + ExitNormState(component.Owner); + break; + case DamageState.Critical: + ExitCritState(component.Owner); + break; + case DamageState.Dead: + ExitDeadState(component.Owner); + break; + case null: + break; + default: + throw new NotImplementedException(); + } + } + + /// + /// Updates the mob state.. + /// + public void UpdateState(MobStateComponent component, FixedPoint2 damage) + { + if (!TryGetState(component, damage, out var newState, out var threshold)) + { + return; + } + + SetMobState(component, component.CurrentState, (newState.Value, threshold)); + } + + /// + /// Sets the mob state and marks the component as dirty. + /// + private void SetMobState(MobStateComponent component, DamageState? old, (DamageState state, FixedPoint2 threshold)? current) + { + if (!current.HasValue) + { + ExitState(component, old); + return; + } + + var (state, threshold) = current.Value; + + component.CurrentThreshold = threshold; + + if (state == old) + { + UpdateState(component, state, threshold); + return; + } + + ExitState(component, old); + + component.CurrentState = state; + + EnterState(component, state); + UpdateState(component, state, threshold); + + var message = new MobStateChangedEvent(component, old, state); + RaiseLocalEvent(component.Owner, message, true); + Dirty(component); + } + + public (DamageState state, FixedPoint2 threshold)? GetState(MobStateComponent component, FixedPoint2 damage) + { + foreach (var (threshold, state) in component._highestToLowestStates) + { + if (damage >= threshold) + { + return (state, threshold); + } + } + + return null; + } + + public bool TryGetState( + MobStateComponent component, + FixedPoint2 damage, + [NotNullWhen(true)] out DamageState? state, + out FixedPoint2 threshold) + { + var highestState = GetState(component, damage); + + if (highestState == null) + { + state = default; + threshold = default; + return false; + } + + (state, threshold) = highestState.Value; + return true; + } + + private (DamageState state, FixedPoint2 threshold)? GetEarliestState(MobStateComponent component, FixedPoint2 minimumDamage, Predicate predicate) + { + foreach (var (threshold, state) in component._lowestToHighestStates) + { + if (threshold < minimumDamage || + !predicate(state)) + { + continue; + } + + return (state, threshold); + } + + return null; + } + + private (DamageState state, FixedPoint2 threshold)? GetPreviousState(MobStateComponent component, FixedPoint2 maximumDamage, Predicate predicate) + { + foreach (var (threshold, state) in component._highestToLowestStates) + { + if (threshold > maximumDamage || + !predicate(state)) + { + continue; + } + + return (state, threshold); + } + + return null; + } + + public (DamageState state, FixedPoint2 threshold)? GetEarliestCriticalState(MobStateComponent component, FixedPoint2 minimumDamage) + { + return GetEarliestState(component, minimumDamage, s => s == DamageState.Critical); + } + + public (DamageState state, FixedPoint2 threshold)? GetEarliestIncapacitatedState(MobStateComponent component, FixedPoint2 minimumDamage) + { + return GetEarliestState(component, minimumDamage, s => s is DamageState.Critical or DamageState.Dead); + } + + public (DamageState state, FixedPoint2 threshold)? GetEarliestDeadState(MobStateComponent component, FixedPoint2 minimumDamage) + { + return GetEarliestState(component, minimumDamage, s => s == DamageState.Dead); + } + + public (DamageState state, FixedPoint2 threshold)? GetPreviousCriticalState(MobStateComponent component, FixedPoint2 minimumDamage) + { + return GetPreviousState(component, minimumDamage, s => s == DamageState.Critical); + } + + private bool TryGetState( + (DamageState state, FixedPoint2 threshold)? tuple, + [NotNullWhen(true)] out DamageState? state, + out FixedPoint2 threshold) + { + if (tuple == null) + { + state = default; + threshold = default; + return false; + } + + (state, threshold) = tuple.Value; + return true; + } + + public bool TryGetEarliestCriticalState( + MobStateComponent component, + FixedPoint2 minimumDamage, + [NotNullWhen(true)] out DamageState? state, + out FixedPoint2 threshold) + { + var earliestState = GetEarliestCriticalState(component, minimumDamage); + + return TryGetState(earliestState, out state, out threshold); + } + + public bool TryGetEarliestIncapacitatedState( + MobStateComponent component, + FixedPoint2 minimumDamage, + [NotNullWhen(true)] out DamageState? state, + out FixedPoint2 threshold) + { + var earliestState = GetEarliestIncapacitatedState(component, minimumDamage); + + return TryGetState(earliestState, out state, out threshold); + } + + public bool TryGetEarliestDeadState( + MobStateComponent component, + FixedPoint2 minimumDamage, + [NotNullWhen(true)] out DamageState? state, + out FixedPoint2 threshold) + { + var earliestState = GetEarliestDeadState(component, minimumDamage); + + return TryGetState(earliestState, out state, out threshold); + } + + public bool TryGetPreviousCriticalState( + MobStateComponent component, + FixedPoint2 maximumDamage, + [NotNullWhen(true)] out DamageState? state, + out FixedPoint2 threshold) + { + var earliestState = GetPreviousCriticalState(component, maximumDamage); + + return TryGetState(earliestState, out state, out threshold); + } + + [Serializable, NetSerializable] + protected sealed class MobStateComponentState : ComponentState + { + public readonly FixedPoint2? CurrentThreshold; + + public MobStateComponentState(FixedPoint2? currentThreshold) + { + CurrentThreshold = currentThreshold; + } + } + } +} diff --git a/Content.Shared/MobState/MobStateChangedEvent.cs b/Content.Shared/MobState/MobStateChangedEvent.cs index e761cc3b7f..aa482da8ba 100644 --- a/Content.Shared/MobState/MobStateChangedEvent.cs +++ b/Content.Shared/MobState/MobStateChangedEvent.cs @@ -1,5 +1,4 @@ using Content.Shared.MobState.Components; -using Content.Shared.MobState.State; namespace Content.Shared.MobState { @@ -7,8 +6,8 @@ namespace Content.Shared.MobState { public MobStateChangedEvent( MobStateComponent component, - IMobState? oldMobState, - IMobState currentMobState) + DamageState? oldMobState, + DamageState currentMobState) { Component = component; OldMobState = oldMobState; @@ -19,8 +18,35 @@ namespace Content.Shared.MobState public MobStateComponent Component { get; } - public IMobState? OldMobState { get; } + public DamageState? OldMobState { get; } - public IMobState CurrentMobState { get; } + public DamageState CurrentMobState { get; } + } + + public static class A + { + [Obsolete("Just check for the enum value instead")] + public static bool IsAlive(this DamageState state) + { + return state == DamageState.Alive; + } + + [Obsolete("Just check for the enum value instead")] + public static bool IsCritical(this DamageState state) + { + return state == DamageState.Critical; + } + + [Obsolete("Just check for the enum value instead")] + public static bool IsDead(this DamageState state) + { + return state == DamageState.Dead; + } + + [Obsolete("Just check for the enum value instead")] + public static bool IsIncapacitated(this DamageState state) + { + return state is DamageState.Dead or DamageState.Critical; + } } } diff --git a/Content.Shared/MobState/State/BaseMobState.cs b/Content.Shared/MobState/State/BaseMobState.cs deleted file mode 100644 index 06f72fb02b..0000000000 --- a/Content.Shared/MobState/State/BaseMobState.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Shared.FixedPoint; - -namespace Content.Shared.MobState.State -{ - [ImplicitDataDefinitionForInheritors] - public abstract class BaseMobState : IMobState - { - protected abstract DamageState DamageState { get; } - - public virtual bool IsAlive() - { - return DamageState == DamageState.Alive; - } - - public virtual bool IsCritical() - { - return DamageState == DamageState.Critical; - } - - public virtual bool IsDead() - { - return DamageState == DamageState.Dead; - } - - public virtual bool IsIncapacitated() - { - return IsCritical() || IsDead(); - } - - public virtual void EnterState(EntityUid uid, IEntityManager entityManager) { } - - public virtual void ExitState(EntityUid uid, IEntityManager entityManager) { } - - public virtual void UpdateState(EntityUid entity, FixedPoint2 threshold, IEntityManager entityManager) { } - } -} diff --git a/Content.Shared/MobState/State/IMobState.cs b/Content.Shared/MobState/State/IMobState.cs deleted file mode 100644 index dc6d5fdec4..0000000000 --- a/Content.Shared/MobState/State/IMobState.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Shared.FixedPoint; - -namespace Content.Shared.MobState.State -{ - /// - /// Defines the blocking effects of an associated - /// (i.e. Normal, Critical, Dead) and what effects to apply upon entering or - /// exiting the state. - /// - public interface IMobState - { - bool IsAlive(); - - bool IsCritical(); - - bool IsDead(); - - /// - /// Checks if the mob is in a critical or dead state. - /// See and . - /// - /// true if it is, false otherwise. - bool IsIncapacitated(); - - /// - /// Called when this state is entered. - /// - void EnterState(EntityUid uid, IEntityManager entityManager); - - /// - /// Called when this state is left for a different state. - /// - void ExitState(EntityUid uid, IEntityManager entityManager); - - /// - /// Called when this state is updated. - /// - void UpdateState(EntityUid entity, FixedPoint2 threshold, IEntityManager entityManager); - } -} diff --git a/Content.Shared/MobState/State/SharedCriticalMobState.cs b/Content.Shared/MobState/State/SharedCriticalMobState.cs deleted file mode 100644 index 92db3be97a..0000000000 --- a/Content.Shared/MobState/State/SharedCriticalMobState.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Shared.Alert; -using Content.Shared.Standing; - -namespace Content.Shared.MobState.State -{ - /// - /// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious). - /// - public abstract class SharedCriticalMobState : BaseMobState - { - protected override DamageState DamageState => DamageState.Critical; - - public override void EnterState(EntityUid uid, IEntityManager entityManager) - { - base.EnterState(uid, entityManager); - - EntitySystem.Get().ShowAlert(uid, AlertType.HumanCrit); // TODO: combine humancrit-0 and humancrit-1 into a gif and display it - - EntitySystem.Get().Down(uid); - - if (entityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) - { - appearance.SetData(DamageStateVisuals.State, DamageState.Critical); - } - } - - public override void ExitState(EntityUid uid, IEntityManager entityManager) - { - base.ExitState(uid, entityManager); - - EntitySystem.Get().Stand(uid); - } - } -} diff --git a/Content.Shared/MobState/State/SharedDeadMobState.cs b/Content.Shared/MobState/State/SharedDeadMobState.cs deleted file mode 100644 index 8621691006..0000000000 --- a/Content.Shared/MobState/State/SharedDeadMobState.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Shared.Standing; - -namespace Content.Shared.MobState.State -{ - public abstract class SharedDeadMobState : BaseMobState - { - protected override DamageState DamageState => DamageState.Dead; - - public override void EnterState(EntityUid uid, IEntityManager entityManager) - { - base.EnterState(uid, entityManager); - entityManager.EnsureComponent(uid); - var standingState = EntitySystem.Get(); - standingState.Down(uid); - - if (standingState.IsDown(uid) && entityManager.TryGetComponent(uid, out PhysicsComponent? physics)) - { - physics.CanCollide = false; - } - - if (entityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) - { - appearance.SetData(DamageStateVisuals.State, DamageState.Dead); - } - } - - public override void ExitState(EntityUid uid, IEntityManager entityManager) - { - base.ExitState(uid, entityManager); - if (entityManager.HasComponent(uid)) - { - entityManager.RemoveComponent(uid); - } - - var standingState = EntitySystem.Get(); - standingState.Stand(uid); - - if (!standingState.IsDown(uid) && entityManager.TryGetComponent(uid, out PhysicsComponent? physics)) - { - physics.CanCollide = true; - } - } - } -} diff --git a/Content.Shared/MobState/State/SharedNormalMobState.cs b/Content.Shared/MobState/State/SharedNormalMobState.cs deleted file mode 100644 index abb6d4a363..0000000000 --- a/Content.Shared/MobState/State/SharedNormalMobState.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Shared.Standing; - - -namespace Content.Shared.MobState.State -{ - /// - /// The standard state an entity is in; no negative effects. - /// - public abstract class SharedNormalMobState : BaseMobState - { - protected override DamageState DamageState => DamageState.Alive; - - public override void EnterState(EntityUid uid, IEntityManager entityManager) - { - base.EnterState(uid, entityManager); - EntitySystem.Get().Stand(uid); - - if (entityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) - { - appearance.SetData(DamageStateVisuals.State, DamageState.Alive); - } - } - } -} diff --git a/Resources/Prototypes/Body/Parts/skeleton.yml b/Resources/Prototypes/Body/Parts/skeleton.yml index f25740935e..b619f954cd 100644 --- a/Resources/Prototypes/Body/Parts/skeleton.yml +++ b/Resources/Prototypes/Body/Parts/skeleton.yml @@ -68,7 +68,7 @@ - type: Actions - type: MobState thresholds: - 0: !type:NormalMobState {} + 0: Alive # criticalThreshold: 50 # deadThreshold: 120 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 9ace097cbc..0b7cb19938 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -95,9 +95,9 @@ - FlyingMobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 5: !type:CriticalMobState {} - 10: !type:DeadMobState {} + 0: Alive + 5: Critical + 10: Dead - type: Appearance - type: DamageStateVisuals rotate: true @@ -310,9 +310,9 @@ - FlyingMobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 5: !type:CriticalMobState {} - 10: !type:DeadMobState {} + 0: Alive + 5: Critical + 10: Dead - type: RandomSpriteColor state: butterfly colors: @@ -763,9 +763,9 @@ - SmallMobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 10: !type:CriticalMobState {} - 20: !type:DeadMobState {} + 0: Alive + 10: Critical + 20: Dead - type: MovementSpeedModifier baseWalkSpeed : 5 baseSprintSpeed : 5 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml b/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml index f38df1856a..a9e45fea6a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml @@ -32,8 +32,8 @@ - type: MovementIgnoreGravity - type: MobState thresholds: - 0: !type:NormalMobState {} - 150: !type:DeadMobState {} + 0: Alive + 150: Dead - type: Appearance - type: DamageStateVisuals states: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 2e31edc274..cf7dd778e7 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -37,9 +37,9 @@ - MobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 50: !type:CriticalMobState {} - 100: !type:DeadMobState {} + 0: Alive + 50: Critical + 100: Dead - type: MovementIgnoreGravity - type: Appearance - type: DamageStateVisuals diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index 26f2d6827e..9e679563d6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -37,9 +37,9 @@ - MobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 150: !type:CriticalMobState {} - 200: !type:DeadMobState {} + 0: Alive + 150: Critical + 200: Dead - type: MeleeWeapon range: 1 arcwidth: 0 @@ -156,9 +156,9 @@ - SmallMobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 30: !type:CriticalMobState {} - 60: !type:DeadMobState {} + 0: Alive + 30: Critical + 60: Dead - type: MeleeWeapon range: 1 arcwidth: 0 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 5295cb6a3d..a4d6c14270 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -92,9 +92,9 @@ Heat : 0.1 #per second, scales with temperature & other constants - type: MobState thresholds: - 0: !type:NormalMobState {} - 50: !type:CriticalMobState {} - 100: !type:DeadMobState {} + 0: Alive + 50: Critical + 100: Dead - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml index 7ca3f7ed9f..a09ec00d70 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml @@ -36,8 +36,8 @@ - SmallMobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 15: !type:DeadMobState {} + 0: Alive + 15: Dead - type: MovementIgnoreGravity - type: Appearance - type: DamageStateVisuals diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 3cddf63e05..7d7a68deca 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -50,8 +50,8 @@ - MobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 200: !type:DeadMobState {} + 0: Alive + 200: Dead - type: Bloodstream bloodReagent: FluorosulfuricAcid bloodlossDamage: @@ -121,8 +121,8 @@ state: running - type: MobState thresholds: - 0: !type:NormalMobState {} - 300: !type:DeadMobState {} + 0: Alive + 300: Dead - type: SlowOnDamage speedModifierThresholds: 250: 0.7 @@ -151,8 +151,8 @@ state: running - type: MobState thresholds: - 0: !type:NormalMobState {} - 200: !type:DeadMobState {} + 0: Alive + 200: Dead - type: MovementSpeedModifier baseWalkSpeed : 3.0 baseSprintSpeed : 5.5 @@ -184,8 +184,8 @@ state: running - type: MobState thresholds: - 0: !type:NormalMobState {} - 1500: !type:DeadMobState {} + 0: Alive + 1500: Dead - type: MovementSpeedModifier baseWalkSpeed : 2.8 baseSprintSpeed : 3.8 @@ -222,8 +222,8 @@ state: running - type: MobState thresholds: - 0: !type:NormalMobState {} - 550: !type:DeadMobState {} + 0: Alive + 550: Dead - type: MovementSpeedModifier baseWalkSpeed : 2.3 baseSprintSpeed : 4.2 @@ -260,8 +260,8 @@ state: running - type: MobState thresholds: - 0: !type:NormalMobState {} - 250: !type:DeadMobState {} + 0: Alive + 250: Dead - type: MovementSpeedModifier baseWalkSpeed : 2.7 baseSprintSpeed : 6.0 @@ -308,8 +308,8 @@ state: running - type: MobState thresholds: - 0: !type:NormalMobState {} - 300: !type:DeadMobState {} + 0: Alive + 300: Dead - type: SlowOnDamage speedModifierThresholds: 250: 0.4 diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index b87f12ce7a..7bd25d6136 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -55,9 +55,9 @@ - FlyingMobLayer - type: MobState thresholds: - 0: !type:NormalMobState {} - 450: !type:CriticalMobState {} - 500: !type:DeadMobState {} + 0: Alive + 450: Critical + 500: Dead - type: Metabolizer solutionOnBody: false updateFrequency: 0.25 diff --git a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml index 824ea06bcc..845c7e3260 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml @@ -56,9 +56,9 @@ interactFailureString: petting-failure-corrupted-corgi - type: MobState thresholds: - 0: !type:NormalMobState {} - 80: !type:CriticalMobState {} - 160: !type:DeadMobState {} + 0: Alive + 80: Critical + 160: Dead - type: Grammar attributes: gender: male diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index a15ccfca7b..d8525476ae 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -50,7 +50,7 @@ damageContainer: Biological - type: MobState thresholds: - 0: !type:NormalMobState {} + 0: Alive - type: HeatResistance - type: CombatMode - type: Internals diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 9d4bd0eb6f..f1c5e63f85 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -96,8 +96,8 @@ baseSprintSpeed : 5 - type: MobState thresholds: - 0: !type:NormalMobState {} - 60: !type:DeadMobState {} + 0: Alive + 60: Dead - type: Flashable - type: NoSlip - type: StatusEffects @@ -190,8 +190,8 @@ baseSprintSpeed : 2 - type: MobState thresholds: - 0: !type:NormalMobState {} - 1000: !type:DeadMobState {} + 0: Alive + 1000: Dead - type: Sprite drawdepth: Mobs netsync: false diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 2b54430a68..e6195740c2 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -235,9 +235,9 @@ - type: Internals - type: MobState thresholds: - 0: !type:NormalMobState {} - 100: !type:CriticalMobState {} - 200: !type:DeadMobState {} + 0: Alive + 100: Critical + 200: Dead - type: Destructible thresholds: - trigger: @@ -442,9 +442,9 @@ damageContainer: Biological - type: MobState thresholds: - 0: !type:NormalMobState {} - 100: !type:CriticalMobState {} - 200: !type:DeadMobState {} + 0: Alive + 100: Critical + 200: Dead - type: Appearance visuals: - type: RotationVisualizer diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml index 300b1a1a74..bedf5f8c5d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml @@ -185,8 +185,8 @@ - type: Internals - type: MobState thresholds: - 0: !type:NormalMobState {} - 100: !type:DeadMobState {} + 0: Alive + 100: Dead - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index f5f8f4a774..cb1fbef0fd 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -47,7 +47,7 @@ # Note that the personal AI never "dies". - type: MobState thresholds: - 0: !type:NormalMobState {} + 0: Alive - type: Appearance visuals: - type: GenericEnumVisualizer diff --git a/Resources/Textures/Shaders/gradient_circle_mask.swsl b/Resources/Textures/Shaders/gradient_circle_mask.swsl index 23563f1cc4..35f4f88893 100644 --- a/Resources/Textures/Shaders/gradient_circle_mask.swsl +++ b/Resources/Textures/Shaders/gradient_circle_mask.swsl @@ -1,25 +1,36 @@ -//This shader defines two circles - everything inside the inner circle will be darkened, while everything outside the outer circle -//will be full black. Between the inner and outer circle it LERPs from the inner darkness to full black. +// Has 2 circles, an inner one that is unaffected and an outer one. Anything past the outer one is full red +// and in-between is a gradient. light_mode unshaded; -const highp float darknessAlphaInner = 0.6; -const highp float innerCircleRadius = 40.0; //Note: this is in pixels -const highp float outerCircleRadius = 80.0; +uniform highp vec3 color; +uniform highp float time; +// Uniforms because it makes testing way easier. If needed make it const. +uniform highp float outerCircleRadius; +uniform highp float outerCircleMaxRadius; +uniform highp float innerCircleRadius; +uniform highp float innerCircleMaxRadius; +uniform highp float darknessAlphaOuter; + +const highp float darknessAlphaInner = 0.0; void fragment() { highp vec2 pixelSize = vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y); - highp vec2 pixelCenter = pixelSize*0.5; + highp vec2 pixelCenter = pixelSize*0.5; highp float distance = length(FRAGCOORD.xy - pixelCenter); - if(distance > outerCircleRadius){ - COLOR = vec4(0.0, 0.0, 0.0, 1.0); + + highp float innerRadius = innerCircleRadius + time * (innerCircleMaxRadius - innerCircleRadius); + highp float outerRadius = outerCircleRadius + time * (outerCircleMaxRadius - outerCircleRadius); + + if(distance > outerRadius) { + COLOR = vec4(color.x, color.y, color.z, darknessAlphaOuter); } - else if(distance < innerCircleRadius){ + else if(distance < innerRadius) { COLOR = vec4(0.0, 0.0, 0.0, darknessAlphaInner); } - else{ - highp float intensity = (distance-innerCircleRadius)/(outerCircleRadius-innerCircleRadius); - COLOR = vec4(0.0, 0.0, 0.0, (1.0-intensity)*darknessAlphaInner + intensity); + else { + highp float ratio = (distance - innerRadius) / (outerRadius - innerRadius); + highp float alpha = darknessAlphaInner + (darknessAlphaOuter - darknessAlphaInner) * ratio; + COLOR = vec4(color.x, color.y, color.z, alpha); } } -