From 040d28fb9d0d5e1efa7cd4735d15d16696373421 Mon Sep 17 00:00:00 2001 From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:19:14 +0000 Subject: [PATCH] Start slug stasis after taking a corpse (#46138) yay --- .../ServerChangelingLastResortSystem.cs | 20 +++++++++++++------ .../Actions/SharedActionsSystem.Relay.cs | 2 ++ .../Components/ChangelingSlugComponent.cs | 12 +++++++++++ .../Systems/ChangelingLastResortSystem.cs | 8 ++++++++ .../Systems/RegenerativeStasisSystem.cs | 13 ++++++++++-- .../Locale/en-US/changeling/changeling.ftl | 2 +- .../Entities/Mobs/Player/changeling.yml | 2 ++ 7 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Content.Server/Changeling/Systems/ServerChangelingLastResortSystem.cs b/Content.Server/Changeling/Systems/ServerChangelingLastResortSystem.cs index de1bdb9302..55643ef83e 100644 --- a/Content.Server/Changeling/Systems/ServerChangelingLastResortSystem.cs +++ b/Content.Server/Changeling/Systems/ServerChangelingLastResortSystem.cs @@ -35,10 +35,13 @@ public sealed partial class ServerChangelingLastResortSystem : ChangelingLastRes args.Handled = true; Audio.PlayPvs(ent.Comp.Sound, ent.Owner); - _popup.PopupEntity(Loc.GetString("changeling-takeover-start-others", ("user", Identity.Entity(ent.Owner, EntityManager))), + _popup.PopupEntity(Loc.GetString("changeling-takeover-start-others", + ("user", Identity.Entity(ent.Owner, EntityManager))), ent.Owner, PopupType.MediumCaution); + var examine = Loc.GetString("changeling-takeover-doafter", ("user", Identity.Entity(ent, EntityManager)), ("target", Identity.Entity(args.Target, EntityManager))); + var doAfter = new DoAfterArgs(EntityManager, ent.Owner, ent.Comp.TakeOverDuration, @@ -50,6 +53,7 @@ public sealed partial class ServerChangelingLastResortSystem : ChangelingLastRes BreakOnMove = true, DuplicateCondition = DuplicateConditions.None, RequireCanInteract = false, + ExamineText = examine }; _doAfter.TryStartDoAfter(doAfter); @@ -92,17 +96,21 @@ public sealed partial class ServerChangelingLastResortSystem : ChangelingLastRes if (!_mind.TryGetMind(args.User, out var mindId, out var mind)) return; - TakeOverCorpse(args.User, target, mindId, mind); - - _popup.PopupEntity(Loc.GetString("changeling-takeover-success-self"), target, target, PopupType.Large); + TakeOverCorpse(ent, target, mindId, mind); } - private void TakeOverCorpse(EntityUid user, EntityUid target, EntityUid mindId, MindComponent mind) + private void TakeOverCorpse(Entity ent, + EntityUid target, + EntityUid mindId, + MindComponent mind) { _mind.TransferTo(mindId, target, mind: mind); _antag.AssignAntagComponents(target, ChangelingAntag); - QueueDel(user); + var ev = new BodyTakenByHeadslugEvent(target, ent.Comp.AutoStasis, ent.Comp.AutoStasisDurationMultiplier); + RaiseLocalEvent(target, ref ev); + + QueueDel(ent); } } diff --git a/Content.Shared/Actions/SharedActionsSystem.Relay.cs b/Content.Shared/Actions/SharedActionsSystem.Relay.cs index 21d2913cfa..37c8868980 100644 --- a/Content.Shared/Actions/SharedActionsSystem.Relay.cs +++ b/Content.Shared/Actions/SharedActionsSystem.Relay.cs @@ -1,4 +1,5 @@ using Content.Shared.Actions.Components; +using Content.Shared.Changeling.Systems; using Content.Shared.Ghost; using Content.Shared.Ghost.Systems; using Content.Shared.Mobs; @@ -9,6 +10,7 @@ public abstract partial class SharedActionsSystem { private void InitializeRelay() { + SubscribeLocalEvent(RefRelayActionEvent); SubscribeLocalEvent(RefRelayActionEvent); SubscribeLocalEvent(RefRelayActionEvent); } diff --git a/Content.Shared/Changeling/Components/ChangelingSlugComponent.cs b/Content.Shared/Changeling/Components/ChangelingSlugComponent.cs index 96dfdb3f7b..8be740daf2 100644 --- a/Content.Shared/Changeling/Components/ChangelingSlugComponent.cs +++ b/Content.Shared/Changeling/Components/ChangelingSlugComponent.cs @@ -31,6 +31,18 @@ public sealed partial class ChangelingSlugComponent : Component [DataField] public TimeSpan TakeOverDuration = TimeSpan.FromSeconds(5); + /// + /// Whether the body should automatically enter regen stasis after being taken over. + /// + [DataField] + public bool AutoStasis = true; + + /// + /// The multiplier for the duration of the automatic stasis. + /// + [DataField] + public float AutoStasisDurationMultiplier = 1f; + /// /// The sound to play when starting the takeover. /// diff --git a/Content.Shared/Changeling/Systems/ChangelingLastResortSystem.cs b/Content.Shared/Changeling/Systems/ChangelingLastResortSystem.cs index 811df858f0..891d22a522 100644 --- a/Content.Shared/Changeling/Systems/ChangelingLastResortSystem.cs +++ b/Content.Shared/Changeling/Systems/ChangelingLastResortSystem.cs @@ -52,3 +52,11 @@ public abstract partial class ChangelingLastResortSystem : EntitySystem _gibbing.Gib(args.Performer); } } + +/// +/// Raised on a body after a headslug takes it over. +/// +/// Whether the body should instantly start regen stasis after being taken over. +/// Time multiplier for the regen stasis, if one is started. +[ByRefEvent] +public record struct BodyTakenByHeadslugEvent(EntityUid Target, bool StartStasis = true, float StasisDurationMultiplier = 1f); diff --git a/Content.Shared/Changeling/Systems/RegenerativeStasisSystem.cs b/Content.Shared/Changeling/Systems/RegenerativeStasisSystem.cs index fbe0ac7168..f994dad089 100644 --- a/Content.Shared/Changeling/Systems/RegenerativeStasisSystem.cs +++ b/Content.Shared/Changeling/Systems/RegenerativeStasisSystem.cs @@ -62,10 +62,17 @@ public sealed partial class RegenerativeStasisSystem : EntitySystem EnterStasis((ent, ent.Comp), args.Performer); } + [SubscribeLocalEvent] + private void OnHeadslugTookBody(Entity ent, ref ActionRelayedEvent args) + { + if (args.Args.StartStasis) + EnterStasis(ent.AsNullable(), args.Args.Target, args.Args.StasisDurationMultiplier); + } + /// /// Enter the stasis and set the action cooldown depending on the damage you have taken. /// - public void EnterStasis(Entity ent, EntityUid target) + public void EnterStasis(Entity ent, EntityUid target, float durationMultiplier = 1f) { if (!Resolve(ent.Owner, ref ent.Comp)) return; @@ -92,7 +99,8 @@ public sealed partial class RegenerativeStasisSystem : EntitySystem 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 + var calculatedDuration = (long)(Math.Clamp(stasisDuration.Ticks, ent.Comp.MinStasisCooldown.Ticks, ent.Comp.MaxStasisCooldown.Ticks) * durationMultiplier); // No clamp method for TimeSpans + stasisDuration = new TimeSpan(calculatedDuration); _metaData.SetEntityName(ent, Loc.GetString("changeling-stasis-active-name")); _metaData.SetEntityDescription(ent, Loc.GetString("changeling-stasis-active-desc")); @@ -160,6 +168,7 @@ public sealed partial class RegenerativeStasisSystem : EntitySystem _metaData.SetEntityDescription(ent, ent.Comp.InitialDescription); _actions.SetToggled(ent.Owner, ent.Comp.IsInStasis); + _actions.SetCooldown(ent.Owner, ent.Comp.MinStasisCooldown); // Don't allow entering stasis right after cancelling it. } } diff --git a/Resources/Locale/en-US/changeling/changeling.ftl b/Resources/Locale/en-US/changeling/changeling.ftl index 2d8e0943f5..cde8dca822 100644 --- a/Resources/Locale/en-US/changeling/changeling.ftl +++ b/Resources/Locale/en-US/changeling/changeling.ftl @@ -51,7 +51,7 @@ changeling-biodegrade-used-popup-self = We vomit acid over our {$restraint}! changeling-takeover-not-dead = This body yet lives! We cannot infest it alive. changeling-takeover-is-changeling = This body rejects our attempts to take over! changeling-takeover-start-others = {CAPITALIZE(THE($user))} begins to crawl inside the corpse! -changeling-takeover-success-self = We have successfully infested the body and assumed its form. +changeling-takeover-doafter = [color=red]{ CAPITALIZE(SUBJECT($user)) } { CONJUGATE-BE($user) } crawling inside of {$target}![/color] # stings changeling-sting-success = We silently sting {THE($target)}! diff --git a/Resources/Prototypes/Entities/Mobs/Player/changeling.yml b/Resources/Prototypes/Entities/Mobs/Player/changeling.yml index 09411890f0..e5e9f8dace 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/changeling.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/changeling.yml @@ -171,6 +171,8 @@ interactSuccessSpawn: EffectHearts interactSuccessSound: path: /Audio/Animals/cat_meow.ogg # meow + - type: ChangelingSlug + autoStasisDurationMultiplier: 0.5 # Mercy upon headslugs who already took forever to find a body. - type: entity # dummy prototype for the store listing id: ChangelingFleshClothingAbilityStoreDummy