fish-station/Content.Server/_Starlight/Actions/Stasis/StasisSystem.cs
2025-12-26 17:44:36 +03:00

245 lines
9.9 KiB
C#

using System.Linq;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.Actions;
using Content.Shared.Damage;
using Robust.Shared.Timing;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared._Starlight.Actions.Stasis;
using Content.Shared.Body.Components;
using Robust.Shared.Player;
using Content.Shared.Damage.Systems;
using Content.Shared.Damage.Components;
namespace Content.Server._Starlight.Actions.Stasis;
public sealed class StasisSystem : SharedStasisSystem
{
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StasisComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
}
private void OnMobStateChanged(MobStateChangedEvent args)
{
// If the entity has a stasis component, and the new mob state is dead, exit stasis.
if (TryComp<StasisComponent>(args.Target, out var comp))
{
if (args.NewMobState == MobState.Dead && comp.IsInStasis)
{
RaiseLocalEvent(args.Target, new ExitStasisActionEvent());
}
}
}
protected override void OnMapInit(EntityUid uid, StasisComponent comp, MapInitEvent args)
{
_actionsSystem.AddAction(uid, ref comp.EnterStasisActionEntity, comp.EnterStasisAction);
}
/// <summary>
/// Takeths away the action to preform stasis from the entity.
/// </summary>
protected override void OnCompRemove(EntityUid uid, StasisComponent comp, ComponentShutdown args)
{
_actionsSystem.RemoveAction(uid, comp.EnterStasisActionEntity);
_actionsSystem.RemoveAction(uid, comp.ExitStasisActionEntity);
}
protected override void OnPrepareStasisStart(EntityUid uid, StasisComponent comp,
PrepareStasisActionEvent args)
{
Dirty(uid, comp);
EnsureComp<StasisFrozenComponent>(uid);
_actionsSystem.RemoveAction(uid, comp.EnterStasisActionEntity);
_actionsSystem.AddAction(uid, ref comp.ExitStasisActionEntity, comp.ExitStasisAction);
_actionsSystem.SetCooldown(comp.ExitStasisActionEntity,
TimeSpan.FromSeconds(comp.StasisEnterEffectLifetime + 1));
// Send animation event to all clients
var ev = new StasisAnimationEvent(GetNetEntity(uid), GetNetCoordinates(Transform(uid).Coordinates),
StasisAnimationType.Prepare);
RaiseNetworkEvent(ev, Filter.Pvs(uid, entityManager: EntityManager));
// Schedule the enter stasis event after delay
Timer.Spawn(TimeSpan.FromSeconds(comp.StasisEnterEffectLifetime), () =>
{
if (!TryComp<StasisComponent>(uid, out var stasisComp))
return;
var enterEv = new EnterStasisActionEvent();
RaiseLocalEvent(uid, enterEv);
});
}
protected override void OnEnterStasisStart(EntityUid uid, StasisComponent comp,
EnterStasisActionEvent args)
{
comp.IsInStasis = true;
comp.IsVisible = false; // Entity becomes invisible when entering stasis to better show the effect
Dirty(uid, comp);
// Remove bleeding when entering stasis
if (TryComp<BloodstreamComponent>(uid, out var bloodstream))
{
_bloodstreamSystem.TryModifyBleedAmount((uid, bloodstream), -bloodstream.BleedAmount);
}
// Send animation event to all clients
var ev = new StasisAnimationEvent(GetNetEntity(uid), GetNetCoordinates(Transform(uid).Coordinates),
StasisAnimationType.Enter);
RaiseNetworkEvent(ev, Filter.Pvs(uid, entityManager: EntityManager));
}
protected override void OnExitStasisStart(EntityUid uid, StasisComponent comp, ExitStasisActionEvent args)
{
comp.IsInStasis = false;
comp.IsVisible = true; // Entity becomes visible when exiting stasis
Dirty(uid, comp);
_actionsSystem.RemoveAction(uid, comp.ExitStasisActionEntity);
_actionsSystem.AddAction(uid, ref comp.EnterStasisActionEntity, comp.EnterStasisAction);
_actionsSystem.SetCooldown(comp.EnterStasisActionEntity, TimeSpan.FromSeconds(comp.StasisCooldown));
RemComp<StasisFrozenComponent>(uid);
// Send animation event to all clients
var ev = new StasisAnimationEvent(GetNetEntity(uid), GetNetCoordinates(Transform(uid).Coordinates),
StasisAnimationType.Exit);
RaiseNetworkEvent(ev, Filter.Pvs(uid, entityManager: EntityManager));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<StasisComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (TryComp<MobStateComponent>(uid, out var mobState))
{
var currentState = mobState.CurrentState;
var healingValues = GetHealingValues(currentState, comp);
OnStasisUpdate(uid, comp, new FrameEventArgs(frameTime), healingValues);
}
}
}
private void OnDamageChanged(EntityUid uid, StasisComponent component, DamageChangedEvent args)
{
// If the entity has a mob state component, and the damage changed event is not healing, apply the resistance.
if (TryComp<MobStateComponent>(uid, out var mobState))
{
var currentState = mobState.CurrentState;
var healingValues = GetHealingValues(currentState, component);
ApplyResistance(uid, args, component, healingValues);
}
}
private void ApplyResistance(EntityUid uid, DamageChangedEvent args, StasisComponent comp,
StasisHealingValues healingValues)
{
// Skip if this is healing or if the damage change is from our own healing
if (!args.DamageIncreased || args.DamageDelta == null || args.Origin == uid)
return;
// Only apply resistance if in stasis
if (!comp.IsInStasis)
return;
// Skip if this damage was already modified by stasis
if (args.Origin == uid && args.DamageDelta.DamageDict.All(x => x.Value < 0))
return;
// Create new DamageSpecifier with reduced damage
var damageToApply = new DamageSpecifier();
foreach (var (type, amount) in args.DamageDelta.DamageDict)
{
damageToApply.DamageDict.Add(type, amount - amount * healingValues.AdditionalDamageResistance);
}
// Apply the reduced damage
_damageableSystem.TryChangeDamage(uid, damageToApply, true, origin: uid);
}
private void OnStasisUpdate(EntityUid uid, StasisComponent comp, FrameEventArgs args,
StasisHealingValues healingValues)
{
if (!comp.IsInStasis)
return;
// Apply healing effect
var healAmount = new DamageSpecifier();
healAmount.DamageDict.Add("Blunt", FixedPoint2.New(healingValues.BluntHeal * args.DeltaSeconds * -1));
healAmount.DamageDict.Add("Slash", FixedPoint2.New(healingValues.SlashHeal * args.DeltaSeconds * -1));
healAmount.DamageDict.Add("Piercing",
FixedPoint2.New(healingValues.PiercingHeal * args.DeltaSeconds * -1));
healAmount.DamageDict.Add("Heat", FixedPoint2.New(healingValues.HeatHeal * args.DeltaSeconds * -1));
healAmount.DamageDict.Add("Cold", FixedPoint2.New(healingValues.ColdHeal * args.DeltaSeconds * -1));
if (TryComp<DamageableComponent>(uid, out _))
{
_damageableSystem.TryChangeDamage(uid, healAmount, true, origin: uid);
}
// Heal bleeding
if (TryComp<BloodstreamComponent>(uid, out var bloodstream) && bloodstream.BleedAmount > 0)
{
_bloodstreamSystem.TryModifyBleedAmount((uid, bloodstream), -healingValues.BleedHeal * (float)args.DeltaSeconds);
}
}
/// <summary>
/// Gets the healing values for the stasis effect based on the mob state.
/// </summary>
private StasisHealingValues GetHealingValues(MobState state, StasisComponent comp)
{
return state switch
{
MobState.Alive => new StasisHealingValues(comp.StasisBluntHealPerSecond, comp.StasisSlashingHealPerSecond,
comp.StasisPiercingHealPerSecond, comp.StasisHeatHealPerSecond, comp.StasisColdHealPerSecond,
comp.StasisBleedHealPerSecond, comp.StasisAdditionalDamageResistance),
MobState.Critical => new StasisHealingValues(comp.StasisInCritBluntHealPerSecond,
comp.StasisInCritSlashingHealPerSecond, comp.StasisInCritPiercingHealPerSecond,
comp.StasisInCritHeatHealPerSecond, comp.StasisInCritColdHealPerSecond,
comp.StasisInCritBleedHealPerSecond, comp.StasisInCritAdditionalDamageResistance),
MobState.Invalid => new StasisHealingValues(0, 0, 0, 0, 0, 0, 0),
MobState.Dead => new StasisHealingValues(0, 0, 0, 0, 0, 0, 0),
_ => new StasisHealingValues(0, 0, 0, 0, 0, 0, 0),
};
}
}
/// <summary>
/// A struct that contains the healing values for the stasis effect.
/// </summary>
sealed class StasisHealingValues(
float bluntHeal,
float slashHeal,
float piercingHeal,
float heatHeal,
float coldHeal,
float bleedHeal,
float additionalDamageResistance)
{
public float BluntHeal { get; private set; } = bluntHeal;
public float SlashHeal { get; private set; } = slashHeal;
public float PiercingHeal { get; private set; } = piercingHeal;
public float HeatHeal { get; private set; } = heatHeal;
public float ColdHeal { get; private set; } = coldHeal;
public float AdditionalDamageResistance { get; private set; } = additionalDamageResistance;
public float BleedHeal { get; private set; } = bleedHeal;
}