diff --git a/Content.Server/Commands/Chat/SuicideCommand.cs b/Content.Server/Commands/Chat/SuicideCommand.cs index d42b73de53..61e36b8505 100644 --- a/Content.Server/Commands/Chat/SuicideCommand.cs +++ b/Content.Server/Commands/Chat/SuicideCommand.cs @@ -7,6 +7,7 @@ using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameTicking; using Content.Server.Players; using Content.Server.Utility; using Content.Shared.Damage; @@ -69,8 +70,10 @@ namespace Content.Server.Commands.Chat return; var chat = IoCManager.Resolve(); - var owner = player.ContentData()?.Mind?.OwnedComponent?.Owner; + var mind = player.ContentData()?.Mind; + var owner = mind?.OwnedComponent?.Owner; + // This check also proves mind not-null for at the end when the mob is ghosted. if (owner == null) { shell.WriteLine("You don't have a mind!"); @@ -121,9 +124,9 @@ namespace Content.Server.Commands.Chat dmgComponent.SetDamage(DamageType.Piercing, 200, owner); - // Prevent the player from returning to the body. Yes, this is an ugly hack. - var ghost = new Ghost(){CanReturn = false}; - ghost.Execute(shell, argStr, Array.Empty()); + // Prevent the player from returning to the body. + // Note that mind cannot be null because otherwise owner would be null. + IoCManager.Resolve().OnGhostAttempt(mind!, false); } } } diff --git a/Content.Server/Commands/Observer/Ghost.cs b/Content.Server/Commands/Observer/Ghost.cs index 32d0a96689..27ea1df0e6 100644 --- a/Content.Server/Commands/Observer/Ghost.cs +++ b/Content.Server/Commands/Observer/Ghost.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; using Content.Server.Players; @@ -14,7 +15,6 @@ namespace Content.Server.Commands.Observer public string Command => "ghost"; public string Description => "Give up on life and become a ghost."; public string Help => "ghost"; - public bool CanReturn { get; set; } = true; public void Execute(IConsoleShell shell, string argStr, string[] args) { @@ -32,7 +32,7 @@ namespace Content.Server.Commands.Observer return; } - if (!IoCManager.Resolve().OnGhostAttempt(mind, CanReturn)) + if (!IoCManager.Resolve().OnGhostAttempt(mind, true)) { shell?.WriteLine("You can't ghost right now."); return; diff --git a/Content.Server/GameObjects/Components/Body/BodyComponent.cs b/Content.Server/GameObjects/Components/Body/BodyComponent.cs index 974148a731..fdde3f6f9b 100644 --- a/Content.Server/GameObjects/Components/Body/BodyComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyComponent.cs @@ -2,6 +2,8 @@ using System; using Content.Server.Commands.Observer; using Content.Server.GameObjects.Components.Observer; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.Interfaces.GameTicking; using Content.Shared.Audio; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; @@ -28,6 +30,7 @@ namespace Content.Server.GameObjects.Components.Body public class BodyComponent : SharedBodyComponent, IRelayMoveInput, IGhostOnMove { private Container _partContainer = default!; + [Dependency] private readonly IGameTicker _gameTicker = default!; protected override bool CanAddPart(string slotId, IBodyPart part) { @@ -92,11 +95,11 @@ namespace Content.Server.GameObjects.Components.Body void IRelayMoveInput.MoveInputPressed(ICommonSession session) { if (Owner.TryGetComponent(out IMobStateComponent? mobState) && - mobState.IsDead()) + mobState.IsDead() && + Owner.TryGetComponent(out MindComponent? mind) && + mind.HasMind) { - var host = IoCManager.Resolve(); - - new Ghost().Execute(new ConsoleShell(host, session), string.Empty, Array.Empty()); + _gameTicker.OnGhostAttempt(mind.Mind!, true); } } diff --git a/Content.Server/GameObjects/Components/Observer/GhostOnMoveComponent.cs b/Content.Server/GameObjects/Components/Observer/GhostOnMoveComponent.cs index 5876280f0d..cd166be7e3 100644 --- a/Content.Server/GameObjects/Components/Observer/GhostOnMoveComponent.cs +++ b/Content.Server/GameObjects/Components/Observer/GhostOnMoveComponent.cs @@ -1,7 +1,7 @@ #nullable enable using System; -using Content.Server.Commands.Observer; using Content.Server.GameObjects.Components.Mobs; +using Content.Server.Interfaces.GameTicking; using Content.Shared.GameObjects.Components.Movement; using Robust.Server.Console; using Robust.Shared.Console; @@ -17,6 +17,7 @@ namespace Content.Server.GameObjects.Components.Observer public class GhostOnMoveComponent : Component, IRelayMoveInput, IGhostOnMove { public override string Name => "GhostOnMove"; + [Dependency] private readonly IGameTicker _gameTicker = default!; [DataField("canReturn")] public bool CanReturn { get; set; } = true; @@ -26,8 +27,7 @@ namespace Content.Server.GameObjects.Components.Observer if (Owner.HasComponent()) return; if (!Owner.TryGetComponent(out MindComponent? mind) || !mind.HasMind || mind.Mind!.IsVisitingEntity) return; - var host = IoCManager.Resolve(); - new Ghost().Execute(new ConsoleShell(host, session), string.Empty, Array.Empty()); + _gameTicker.OnGhostAttempt(mind.Mind!, CanReturn); } } } diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index ed61a016c7..d0fb398175 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -48,15 +48,19 @@ namespace Content.Server.GameTicking } var position = playerEntity?.Transform.Coordinates ?? IoCManager.Resolve().GetObserverSpawnPoint(); - var canReturn = false; + // Ok, so, this is the master place for the logic for if ghosting is "too cheaty" to allow returning. + // There's no reason at this time to move it to any other place, especially given that the 'side effects required' situations would also have to be moved. + // + If CharacterDeadPhysically applies, we're physically dead. Therefore, ghosting OK, and we can return (this is critical for gibbing) + // Note that we could theoretically be ICly dead and still physically alive and vice versa. + // (For example, a zombie could be dead ICly, but may retain memories and is definitely physically active) + // + If we're in a mob that is critical, and we're supposed to be able to return if possible, + /// we're succumbing - the mob is killed. Therefore, character is dead. Ghosting OK. + // (If the mob survives, that's a bug. Ghosting is kept regardless.) + var canReturn = canReturnGlobal && mind.CharacterDeadPhysically; if (playerEntity != null && canReturnGlobal && playerEntity.TryGetComponent(out IMobStateComponent? mobState)) { - if (mobState.IsDead()) - { - canReturn = true; - } - else if (mobState.IsCritical()) + if (mobState.IsCritical()) { canReturn = true; @@ -66,10 +70,6 @@ namespace Content.Server.GameTicking damageable.SetDamage(DamageType.Asphyxiation, 200, playerEntity); } } - else - { - canReturn = false; - } } var entityManager = IoCManager.Resolve(); diff --git a/Content.Server/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs index 3ffc1afe56..079dcd9e0e 100644 --- a/Content.Server/Mobs/Mind.cs +++ b/Content.Server/Mobs/Mind.cs @@ -114,7 +114,13 @@ namespace Content.Server.Mobs /// (Maybe you were looking for the action blocker system?) /// [ViewVariables] - public bool CharacterDeadIC + public bool CharacterDeadIC => CharacterDeadPhysically; + /// + /// True if the OwnedEntity of this mind is physically dead. + /// This specific definition, as opposed to CharacterDeadIC, is used to determine if ghosting should allow return. + /// + [ViewVariables] + public bool CharacterDeadPhysically { get {