Mobstate Refactor (#13389)

Refactors mobstate and moves mob health thresholds to their own component

Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
This commit is contained in:
Jezithyr
2023-01-13 16:57:10 -08:00
committed by GitHub
parent 97e4c477bd
commit eeb5b17b34
148 changed files with 1517 additions and 1290 deletions

View File

@@ -54,7 +54,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
{
if (damageVisComp.Thresholds.Count < 1)
{
Logger.ErrorS(SawmillName, $"Thresholds were invalid for entity {entity}. Thresholds: {damageVisComp.Thresholds}");
Logger.ErrorS(SawmillName, $"ThresholdsLookup were invalid for entity {entity}. ThresholdsLookup: {damageVisComp.Thresholds}");
damageVisComp.Valid = false;
return;
}
@@ -144,7 +144,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
if (damageVisComp.Thresholds[0] != 0)
{
Logger.ErrorS(SawmillName, $"Thresholds were invalid for entity {entity}. Thresholds: {damageVisComp.Thresholds}");
Logger.ErrorS(SawmillName, $"ThresholdsLookup were invalid for entity {entity}. ThresholdsLookup: {damageVisComp.Thresholds}");
damageVisComp.Valid = false;
return;
}

View File

@@ -1,8 +1,8 @@
using Content.Shared.MobState;
using Content.Shared.Mobs;
using Robust.Client.GameObjects;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
namespace Content.Client.MobState;
namespace Content.Client.DamageState;
public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVisualsComponent>
{
@@ -10,7 +10,7 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVi
{
var sprite = args.Sprite;
if (sprite == null || !args.Component.TryGetData(DamageStateVisuals.State, out DamageState data))
if (sprite == null || !args.Component.TryGetData(MobStateVisuals.State, out MobState data))
{
return;
}
@@ -24,8 +24,8 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVi
{
sprite.NoRotation = data switch
{
DamageState.Critical => false,
DamageState.Dead => false,
MobState.Critical => false,
MobState.Dead => false,
_ => true
};
}
@@ -48,7 +48,7 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVi
}
// So they don't draw over mobs anymore
if (data == DamageState.Dead)
if (data == MobState.Dead)
{
if (sprite.DrawDepth > (int) DrawDepth.FloorObjects)
{

View File

@@ -1,13 +1,13 @@
using Content.Shared.MobState;
using Content.Shared.Mobs;
namespace Content.Client.MobState;
namespace Content.Client.DamageState;
[RegisterComponent]
public sealed class DamageStateVisualsComponent : Component
{
public int? OriginalDrawDepth;
[DataField("states")] public Dictionary<DamageState, Dictionary<DamageStateVisualLayers, string>> States = new();
[DataField("states")] public Dictionary<MobState, Dictionary<DamageStateVisualLayers, string>> States = new();
/// <summary>
/// Should noRot be turned off when crit / dead.

View File

@@ -2,7 +2,7 @@ using System.Collections.Generic;
using Content.Client.HealthOverlay.UI;
using Content.Shared.Damage;
using Content.Shared.GameTicking;
using Content.Shared.MobState.Components;
using Content.Shared.Mobs.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;

View File

@@ -1,9 +1,10 @@
using Content.Client.IoC;
using Content.Client.MobState;
using Content.Client.Resources;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.MobState.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
@@ -84,11 +85,10 @@ namespace Content.Client.HealthOverlay.UI
}
var mobStateSystem = _entities.EntitySysManager.GetEntitySystem<MobStateSystem>();
FixedPoint2 threshold;
var mobThresholdSystem = _entities.EntitySysManager.GetEntitySystem<MobThresholdSystem>();
if (mobStateSystem.IsAlive(mobState.Owner, mobState))
{
if (!mobStateSystem.TryGetEarliestCriticalState(mobState, damageable.TotalDamage, out _, out threshold))
if (!mobThresholdSystem.TryGetThresholdForState(Entity,MobState.Critical, out var threshold))
{
CritBar.Visible = false;
HealthBar.Visible = false;
@@ -97,7 +97,7 @@ namespace Content.Client.HealthOverlay.UI
CritBar.Ratio = 1;
CritBar.Visible = true;
HealthBar.Ratio = 1 - (damageable.TotalDamage / threshold).Float();
HealthBar.Ratio = 1 - ((FixedPoint2)(damageable.TotalDamage / threshold)).Float();
HealthBar.Visible = true;
}
else if (mobStateSystem.IsCritical(mobState.Owner, mobState))
@@ -105,8 +105,8 @@ namespace Content.Client.HealthOverlay.UI
HealthBar.Ratio = 0;
HealthBar.Visible = false;
if (!mobStateSystem.TryGetPreviousCriticalState(mobState, damageable.TotalDamage, out _, out var critThreshold) ||
!mobStateSystem.TryGetEarliestDeadState(mobState, damageable.TotalDamage, out _, out var deadThreshold))
if (!mobThresholdSystem.TryGetThresholdForState(Entity, MobState.Critical, out var critThreshold) ||
!mobThresholdSystem.TryGetThresholdForState(Entity, MobState.Dead, out var deadThreshold))
{
CritBar.Visible = false;
return;
@@ -115,7 +115,7 @@ namespace Content.Client.HealthOverlay.UI
CritBar.Visible = true;
CritBar.Ratio = 1 -
((damageable.TotalDamage - critThreshold) /
(deadThreshold - critThreshold)).Float();
(deadThreshold - critThreshold)).Value.Float();
}
else if (mobStateSystem.IsDead(mobState.Owner, mobState))
{

View File

@@ -1,146 +0,0 @@
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction.Events;
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 IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private Overlays.DamageOverlay _overlay = default!;
public override void Initialize()
{
base.Initialize();
_overlay = new Overlays.DamageOverlay();
_overlayManager.AddOverlay(_overlay);
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttach);
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetach);
SubscribeLocalEvent<MobStateComponent, ComponentHandleState>(OnMobHandleState);
SubscribeLocalEvent<MobStateComponent, AttackAttemptEvent>(OnAttack);
}
private void OnAttack(EntityUid uid, MobStateComponent component, AttackAttemptEvent args)
{
if (IsIncapacitated(uid, component))
args.Cancel();
}
public override void Shutdown()
{
base.Shutdown();
_overlayManager.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<MobStateComponent>(ev.Entity, out var mobState) && TryComp<DamageableComponent>(ev.Entity, out var damageable))
{
_overlay.DeadLevel = 0f;
SetLevel(mobState, damageable.TotalDamage);
}
else
{
ClearOverlay();
}
}
private void OnPlayerDetach(PlayerDetachedEvent ev)
{
ClearOverlay();
}
private void ClearOverlay()
{
_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;
ClearOverlay();
if (!TryComp<DamageableComponent>(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;
}
}

View File

@@ -1,5 +1,5 @@
using Content.Client.Pointing.Components;
using Content.Shared.MobState.EntitySystems;
using Content.Shared.Mobs.Systems;
using Content.Shared.Pointing;
using Content.Shared.Verbs;
using Robust.Client.Animations;
@@ -12,7 +12,7 @@ namespace Content.Client.Pointing;
public sealed class PointingSystem : SharedPointingSystem
{
[Dependency] private readonly AnimationPlayerSystem _player = default!;
[Dependency] private readonly SharedMobStateSystem _mobState = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
private const string AnimationKey = "pointingarrow";

View File

@@ -0,0 +1,142 @@
using Content.Client.Alerts;
using Content.Client.Gameplay;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
namespace Content.Client.UserInterface.Systems.DamageOverlays;
[UsedImplicitly]
public sealed class DamageOverlayUiController : UIController, IOnStateChanged<GameplayState>
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[UISystemDependency] private readonly ClientAlertsSystem _alertsSystem = default!;
[UISystemDependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
private Overlays.DamageOverlay _overlay = default!;
public override void Initialize()
{
_overlay = new Overlays.DamageOverlay();
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttach);
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<MobThresholdChecked>(OnThresholdCheck);
}
public void OnStateEntered(GameplayState state)
{
_overlayManager.AddOverlay(_overlay);
}
public void OnStateExited(GameplayState state)
{
_overlayManager.RemoveOverlay(_overlay);
}
private void OnPlayerAttach(PlayerAttachedEvent args)
{
ClearOverlay();
if (!EntityManager.TryGetComponent<MobStateComponent>(args.Entity, out var mobState))
return;
if (mobState.CurrentState != MobState.Dead)
UpdateOverlays(args.Entity, mobState);
_overlayManager.AddOverlay(_overlay);
}
private void OnPlayerDetached(PlayerDetachedEvent args)
{
_overlayManager.RemoveOverlay(_overlay);
ClearOverlay();
}
private void OnMobStateChanged(MobStateChangedEvent args)
{
if (args.Target != _playerManager.LocalPlayer?.ControlledEntity)
return;
UpdateOverlays(args.Target, args.Component);
}
private void OnThresholdCheck(ref MobThresholdChecked args)
{
if (args.Target != _playerManager.LocalPlayer?.ControlledEntity)
return;
UpdateOverlays(args.Target, args.MobState, args.Damageable, args.Threshold);
}
private void ClearOverlay()
{
_overlay.DeadLevel = 0f;
_overlay.CritLevel = 0f;
_overlay.BruteLevel = 0f;
_overlay.OxygenLevel = 0f;
}
//TODO: Jezi: adjust oxygen and hp overlays to use appropriate systems once bodysim is implemented
private void UpdateOverlays(EntityUid entity, MobStateComponent? mobState, DamageableComponent? damageable = null, MobThresholdsComponent? thresholds = null)
{
if (mobState == null && !EntityManager.TryGetComponent(entity, out mobState) ||
thresholds == null && !EntityManager.TryGetComponent(entity, out thresholds) ||
damageable == null && !EntityManager.TryGetComponent(entity, out damageable))
return;
if (!_mobThresholdSystem.TryGetIncapThreshold(entity, out var foundThreshold, thresholds))
return; //this entity cannot die or crit!!
var critThreshold = foundThreshold.Value;
_overlay.State = mobState.CurrentState;
switch (mobState.CurrentState)
{
case MobState.Alive:
{
if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
{
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
}
if (damageable.DamagePerGroup.TryGetValue("Airloss", out var oxyDamage))
{
_overlay.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
}
if (_overlay.BruteLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
{
_overlay.BruteLevel = 0;
}
_overlay.CritLevel = 0;
_overlay.DeadLevel = 0;
break;
}
case MobState.Critical:
{
if (!_mobThresholdSystem.TryGetDeadPercentage(entity,
FixedPoint2.Max(0.0, damageable.TotalDamage), out var critLevel))
return;
_overlay.CritLevel = critLevel.Value.Float();
_overlay.BruteLevel = 0;
_overlay.DeadLevel = 0;
break;
}
case MobState.Dead:
{
_overlay.BruteLevel = 0;
_overlay.CritLevel = 0;
break;
}
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.MobState;
using Content.Shared.Mobs;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
@@ -6,7 +6,7 @@ using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.MobState.Overlays;
namespace Content.Client.UserInterface.Systems.DamageOverlays.Overlays;
public sealed class DamageOverlay : Overlay
{
@@ -21,7 +21,7 @@ public sealed class DamageOverlay : Overlay
private readonly ShaderInstance _oxygenShader;
private readonly ShaderInstance _bruteShader;
public DamageState State = DamageState.Alive;
public MobState State = MobState.Alive;
/// <summary>
/// Handles the red pulsing overlay
@@ -79,7 +79,7 @@ public sealed class DamageOverlay : Overlay
var lastFrameTime = (float) _timing.FrameTime.TotalSeconds;
// If they just died then lerp out the white overlay.
if (State != DamageState.Dead)
if (State != MobState.Dead)
{
DeadLevel = 1f;
}
@@ -169,7 +169,7 @@ public sealed class DamageOverlay : Overlay
_oldBruteLevel = BruteLevel;
}
level = State != DamageState.Critical ? _oldOxygenLevel : 1f;
level = State != MobState.Critical ? _oldOxygenLevel : 1f;
if (level > 0f)
{
@@ -215,7 +215,7 @@ public sealed class DamageOverlay : Overlay
handle.DrawRect(viewport, Color.White);
}
level = State != DamageState.Dead ? _oldCritLevel : DeadLevel;
level = State != MobState.Dead ? _oldCritLevel : DeadLevel;
if (level > 0f)
{

View File

@@ -1,7 +1,7 @@
using Content.Client.CombatMode;
using Content.Client.Gameplay;
using Content.Client.Hands;
using Content.Shared.MobState.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.StatusEffect;