using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.StatusEffectNew;
using Content.Shared.Traits.Assorted;
namespace Content.Shared.DamageOverlay;
///
/// A system that updates the damage overlay when a player's damage changes.
///
public abstract partial class SharedDamageOverlaySystem : EntitySystem
{
[Dependency] private MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private DamageableSystem _damageable = default!;
[Dependency] private StatusEffectsSystem _statusEffects = default!;
[Dependency] private EntityQuery _injurableQuery = default!;
[Dependency] private EntityQuery _damageableQuery = default!;
[Dependency] private EntityQuery _mobStateQuery = default!;
[Dependency] private EntityQuery _thresholdsQuery = default!;
[SubscribeLocalEvent]
protected virtual void OnStartup(Entity entity, ref ComponentStartup args)
{
EnsureOverlay(entity);
UpdateOverlays(entity);
}
[SubscribeLocalEvent]
private void OnMobStateChanged(Entity entity, ref MobStateChangedEvent args)
{
UpdateOverlays(entity, args.Component);
}
[SubscribeLocalEvent]
private void OnThresholdCheck(Entity entity, ref MobThresholdChecked args)
{
UpdateOverlays(entity, args.MobState, args.Damageable, args.Threshold);
}
protected void ClearOverlay(Entity entity)
{
entity.Comp.CurrentState = MobState.Alive;
entity.Comp.DeadLevel = 0f;
entity.Comp.CritLevel = 0f;
entity.Comp.PainLevel = 0f;
entity.Comp.OxygenLevel = 0f;
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.CurrentState));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.PainLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.OxygenLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.CritLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.DeadLevel));
}
//TODO: Jezi: adjust oxygen and hp overlays to use appropriate systems once bodysim is implemented
protected void UpdateOverlays(Entity entity,
MobStateComponent? mobState = null,
DamageableComponent? damageable = null,
MobThresholdsComponent? thresholds = null,
InjurableComponent? injurable = null)
{
if (mobState == null && !_mobStateQuery.TryComp(entity, out mobState) ||
thresholds == null && !_thresholdsQuery.TryComp(entity, out thresholds) ||
damageable == null && !_damageableQuery.TryComp(entity, out damageable) ||
injurable == null && !_injurableQuery.TryComp(entity, out injurable))
return;
if (!_mobThresholdSystem.TryGetIncapThreshold(entity, out var foundThreshold, thresholds))
return; //this entity cannot die or crit!!
if (!thresholds.ShowOverlays)
{
ClearOverlay(entity);
return; //this entity intentionally has no overlays
}
var damagePerGroup = _damageable.GetDamagePerGroup((entity, damageable));
var critThreshold = foundThreshold.Value;
entity.Comp.CurrentState = mobState.CurrentState;
switch (mobState.CurrentState)
{
case MobState.Alive:
{
FixedPoint2 painLevel = 0;
entity.Comp.PainLevel = 0;
if (!_statusEffects.TryEffectsWithComp(entity, out _))
{
foreach (var painDamageType in injurable.PainDamageGroups)
{
damagePerGroup.TryGetValue(painDamageType, out var painDamage);
painLevel += painDamage;
}
entity.Comp.PainLevel = FixedPoint2.Min(1f, painLevel / critThreshold).Float();
if (entity.Comp.PainLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
{
entity.Comp.PainLevel = 0;
}
}
if (damagePerGroup.TryGetValue("Airloss", out var oxyDamage))
{
entity.Comp.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
}
entity.Comp.CritLevel = 0;
entity.Comp.DeadLevel = 0;
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.PainLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.OxygenLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.CritLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.DeadLevel));
break;
}
case MobState.Critical:
{
if (!_mobThresholdSystem.TryGetDeadPercentage(entity,
FixedPoint2.Max(0.0, _damageable.GetTotalDamage((entity, damageable))),
out var critLevel))
return;
entity.Comp.CritLevel = critLevel.Value.Float();
entity.Comp.PainLevel = 0;
entity.Comp.DeadLevel = 0;
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.PainLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.CritLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.DeadLevel));
break;
}
case MobState.Dead:
{
entity.Comp.PainLevel = 0;
entity.Comp.CritLevel = 0;
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.PainLevel));
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.CritLevel));
break;
}
}
DirtyField(entity, entity.Comp, nameof(DamageOverlayComponent.CurrentState));
}
protected virtual void EnsureOverlay(Entity entity) { }
}