using Content.Shared.Actions; using Content.Shared.Body.Components; using Content.Shared.Body.Systems; using Content.Shared.Changeling.Components; using Content.Shared.Damage.Systems; using Content.Shared.Ghost; using Content.Shared.Ghost.Systems; using Content.Shared.IdentityManagement; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Robust.Shared.Audio.Systems; namespace Content.Shared.Changeling.Systems; public sealed partial class RegenerativeStasisSystem : EntitySystem { [Dependency] private SharedActionsSystem _actions = default!; [Dependency] private SharedAudioSystem _audio = default!; [Dependency] private SharedPopupSystem _popup = default!; [Dependency] private MetaDataSystem _metaData = default!; [Dependency] private MobStateSystem _mobs = default!; [Dependency] private DamageableSystem _damage = default!; [Dependency] private BloodstreamSystem _bloodstream = default!; [Dependency] private SharedDeathgaspSystem _deathgasp = default!; [SubscribeLocalEvent] private void OnMapInit(Entity ent, ref MapInitEvent args) { ent.Comp.InitialName = MetaData(ent).EntityName; ent.Comp.InitialDescription = MetaData(ent).EntityDescription; Dirty(ent); } [SubscribeLocalEvent] private void OnStateChanged(Entity ent, ref ActionRelayedEvent args) { // If we are revived cancel the stasis. if (args.Args.NewMobState == MobState.Alive && ent.Comp.IsInStasis) CancelStasis(ent.AsNullable()); } [SubscribeLocalEvent] private void OnMoveGhost(Entity ent, ref ActionRelayedEvent args) { if (ent.Comp.AllowGhosting || !ent.Comp.IsInStasis) return; args.Args.Cancelled = true; } [SubscribeLocalEvent] private void OnStasisUse(Entity ent, ref ChangelingStasisActionEvent args) { if (ent.Comp.IsInStasis) { ExitStasis((ent, ent.Comp), args.Performer); args.Handled = true; //Only handle when exiting, as we don't need the useDelay otherwise. return; } EnterStasis((ent, ent.Comp), args.Performer); } /// /// Enter the stasis and set the action cooldown depending on the damage you have taken. /// public void EnterStasis(Entity ent, EntityUid target) { if (!Resolve(ent.Owner, ref ent.Comp)) return; if (ent.Comp.IsInStasis) return; // If going from Alive to Dead fake a death gasp. // If going from Critical to Dead then DeathGaspSystem is already doing this, // so we don't want to do it twice. if (_mobs.IsAlive(target)) _deathgasp.Deathgasp(target); // Die temporarily until we revive. // Ghosting will be blocked while in stasis. if (!_mobs.IsDead(target)) _mobs.ChangeMobState(target, MobState.Dead); _popup.PopupEntity(Loc.GetString("changeling-stasis-enter"), target, target, PopupType.MediumCaution); ent.Comp.IsInStasis = true; Dirty(ent); var stasisDuration = ent.Comp.MinStasisCooldown; stasisDuration += ent.Comp.BonusCooldownPerDamage * (double)_damage.GetTotalDamage(target); stasisDuration = new TimeSpan(Math.Clamp(stasisDuration.Ticks, ent.Comp.MinStasisCooldown.Ticks, ent.Comp.MaxStasisCooldown.Ticks)); // No clamp method for TimeSpans _metaData.SetEntityName(ent, Loc.GetString("changeling-stasis-active-name")); _metaData.SetEntityDescription(ent, Loc.GetString("changeling-stasis-active-desc")); _actions.SetToggled(ent.Owner, ent.Comp.IsInStasis); _actions.SetCooldown(ent.Owner, stasisDuration); } /// /// Exit the stasis and heal all damage and bloodloss. /// TODO: Maybe add a some sort of rejuvenate lite so that we can also heal some status effects? /// public void ExitStasis(Entity ent, EntityUid target) { if (!Resolve(ent, ref ent.Comp)) return; if (!ent.Comp.IsInStasis) return; // Heal all damage. _damage.ClearAllDamage(target); // Heal bloodloss and stop bleeding. if (TryComp(target, out var bloodstream)) { _bloodstream.TryRegulateBloodLevel((target, bloodstream), bloodstream.BloodReferenceSolution.MaxVolume); _bloodstream.TryModifyBleedAmount((target, bloodstream), -bloodstream.BleedAmount); } // Revive. _mobs.ChangeMobState(target, MobState.Alive); _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), Loc.GetString("changeling-stasis-exit-others", ("user", Identity.Entity(target, EntityManager))), target, target, PopupType.MediumCaution); _audio.PlayPredicted(ent.Comp.ExitSound, target, target); ent.Comp.IsInStasis = false; Dirty(ent); if (ent.Comp.InitialName != null) _metaData.SetEntityName(ent, ent.Comp.InitialName); if (ent.Comp.InitialDescription != null) _metaData.SetEntityDescription(ent, ent.Comp.InitialDescription); _actions.SetToggled(ent.Owner, ent.Comp.IsInStasis); } /// /// Cancel the stasis without healing. /// public void CancelStasis(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return; if (!ent.Comp.IsInStasis) return; ent.Comp.IsInStasis = false; Dirty(ent); if (ent.Comp.InitialName != null) _metaData.SetEntityName(ent, ent.Comp.InitialName); if (ent.Comp.InitialDescription != null) _metaData.SetEntityDescription(ent, ent.Comp.InitialDescription); _actions.SetToggled(ent.Owner, ent.Comp.IsInStasis); } } /// /// Action event for entering/leaving the stasis. /// public sealed partial class ChangelingStasisActionEvent : InstantActionEvent;