diff --git a/Content.Server/Chat/Commands/SuicideCommand.cs b/Content.Server/Chat/Commands/SuicideCommand.cs index 916dd63261..aaa4aa2a05 100644 --- a/Content.Server/Chat/Commands/SuicideCommand.cs +++ b/Content.Server/Chat/Commands/SuicideCommand.cs @@ -1,35 +1,15 @@ -using System.Linq; -using Content.Server.Act; -using Content.Server.Administration.Logs; -using Content.Server.Chat.Managers; using Content.Server.GameTicking; -using Content.Server.Hands.Components; using Content.Server.Players; -using Content.Server.Popups; using Content.Shared.Administration; -using Content.Shared.Damage; -using Content.Shared.Damage.Prototypes; -using Content.Shared.Database; -using Content.Shared.Item; -using Content.Shared.Popups; -using Content.Shared.Tag; using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.Enums; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using Content.Shared.MobState.Components; namespace Content.Server.Chat.Commands { [AnyCommand] internal sealed class SuicideCommand : IConsoleCommand { - [Dependency] private readonly IEntityManager _entities = default!; - public string Command => "suicide"; public string Description => Loc.GetString("suicide-command-description"); @@ -38,7 +18,37 @@ namespace Content.Server.Chat.Commands public void Execute(IConsoleShell shell, string argStr, string[] args) { - EntitySystem.Get().Suicide(shell); + if (shell.Player is not IPlayerSession player) + { + shell.WriteLine(Loc.GetString("shell-cannot-run-command-from-server")); + return; + } + + if (player.Status != SessionStatus.InGame || player.AttachedEntity == null) + return; + var mind = player.ContentData()?.Mind; + + // This check also proves mind not-null for at the end when the mob is ghosted. + if (mind?.OwnedComponent?.Owner is not { Valid: true } victim) + { + shell.WriteLine("You don't have a mind!"); + return; + } + var gameTicker = EntitySystem.Get(); + var suicideSystem = EntitySystem.Get(); + if (suicideSystem.Suicide(victim)) + { + // Prevent the player from returning to the body. + // Note that mind cannot be null because otherwise victim would be null. + gameTicker.OnGhostAttempt(mind!, false); + return; + } + + if (gameTicker.OnGhostAttempt(mind, true)) + return; + + shell?.WriteLine("You can't ghost right now."); + } } } diff --git a/Content.Server/Chat/SuicideSystem.cs b/Content.Server/Chat/SuicideSystem.cs index 2dc5cc33e5..d01653c9e5 100644 --- a/Content.Server/Chat/SuicideSystem.cs +++ b/Content.Server/Chat/SuicideSystem.cs @@ -1,9 +1,5 @@ -using Content.Server.Act; using Content.Server.Administration.Logs; -using Content.Server.Chat.Managers; -using Content.Server.GameTicking; using Content.Server.Hands.Components; -using Content.Server.Players; using Content.Server.Popups; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -13,138 +9,107 @@ using Content.Shared.Item; using Content.Shared.MobState.Components; using Content.Shared.Popups; using Content.Shared.Tag; -using Robust.Server.Player; -using Robust.Shared.Console; -using Robust.Shared.Enums; using Robust.Shared.Prototypes; -using System.Linq; namespace Content.Server.Chat { public sealed class SuicideSystem : EntitySystem { [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!; [Dependency] private readonly AdminLogSystem _adminLogSystem = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; - public void Suicide(IConsoleShell shell) + public bool Suicide(EntityUid victim) { - //TODO: Make this work without the console shell - - var player = shell.Player as IPlayerSession; - if (player == null) + // Checks to see if the CannotSuicide tag exits, ghosts instead. + if (_tagSystem.HasTag(victim, "CannotSuicide")) { - shell.WriteLine(Loc.GetString("shell-cannot-run-command-from-server")); - return; + return false; } - if (player.Status != SessionStatus.InGame || player.AttachedEntity == null) - return; - var mind = player.ContentData()?.Mind; - - // This check also proves mind not-null for at the end when the mob is ghosted. - if (mind?.OwnedComponent?.Owner is not { Valid: true } owner) + // Checks to see if the player is dead. + if (!EntityManager.TryGetComponent(victim, out var mobState) || mobState.IsDead()) { - shell.WriteLine("You don't have a mind!"); - return; + return false; } - //Checks to see if the player is dead. - if (EntityManager.TryGetComponent(owner, out var mobState) && mobState.IsDead()) - { - shell.WriteLine(Loc.GetString("suicide-command-already-dead")); - return; - } - - //Checks to see if the CannotSuicide tag exits, ghosts instead. - if (_tagSystem.HasTag(owner, "CannotSuicide")) - { - if (!_gameTicker.OnGhostAttempt(mind, true)) - { - shell?.WriteLine("You can't ghost right now."); - return; - } - return; - } - - //TODO: needs to check if the mob is actually alive - //TODO: maybe set a suicided flag to prevent resurrection? - _adminLogSystem.Add(LogType.Suicide, - $"{EntityManager.ToPrettyString(player.AttachedEntity.Value):player} is committing suicide"); + $"{EntityManager.ToPrettyString(victim):player} is committing suicide"); - var suicideEvent = new SuicideEvent(owner); - // Held item suicide - if (EntityManager.TryGetComponent(owner, out HandsComponent handsComponent) + var suicideEvent = new SuicideEvent(victim); + + // If you are critical, you wouldn't be able to use your surroundings to suicide, so you do the default suicide + if (!mobState.IsCritical()) + { + EnvironmentSuicideHandler(victim, suicideEvent); + } + DefaultSuicideHandler(victim, suicideEvent); + + ApplyDeath(victim, suicideEvent.Kind!.Value); + return true; + } + + /// + /// If not handled, does the default suicide, which is biting your own tongue + /// + /// The person attempting to die + private static void DefaultSuicideHandler(EntityUid victim, SuicideEvent suicideEvent) + { + if (suicideEvent.Handled) return; + var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", victim)); + victim.PopupMessageOtherClients(othersMessage); + + var selfMessage = Loc.GetString("suicide-command-default-text-self"); + victim.PopupMessage(selfMessage); + suicideEvent.SetHandled(SuicideKind.Bloodloss); + } + + /// + /// Raise event to attempt to use held item, or surrounding entities to commit suicide + /// + /// The person attempting to die + private void EnvironmentSuicideHandler(EntityUid victim, SuicideEvent suicideEvent) + { + // Suicide by held item + if (EntityManager.TryGetComponent(victim, out HandsComponent handsComponent) && handsComponent.ActiveHandEntity is EntityUid item) { RaiseLocalEvent(item, suicideEvent, false); if (suicideEvent.Handled) - { - ApplyDeath(owner, suicideEvent.Kind!.Value); return; - } } - // Get all entities in range of the suicider - var entities = _entityLookupSystem.GetEntitiesInRange(owner, 1, LookupFlags.Approximate | LookupFlags.Anchored).ToArray(); - - if (entities.Length > 0) + // Suicide by nearby entity (ex: Microwave) + foreach (var entity in _entityLookupSystem.GetEntitiesInRange(victim, 1, LookupFlags.Approximate | LookupFlags.Anchored)) { - foreach (var entity in entities) - { - if (EntityManager.HasComponent(entity)) - continue; - RaiseLocalEvent(entity, suicideEvent, false); + // Skip any nearby items that can be picked up, we already checked the active held item above + if (EntityManager.HasComponent(entity)) + continue; - if (suicideEvent.Handled) - { - ApplyDeath(owner, suicideEvent.Kind!.Value); - return; - } - } + RaiseLocalEvent(entity, suicideEvent, false); + + if (suicideEvent.Handled) + break; } - - // Default suicide, bite your tongue - var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", owner)); - owner.PopupMessageOtherClients(othersMessage); - - var selfMessage = Loc.GetString("suicide-command-default-text-self"); - owner.PopupMessage(selfMessage); - - ApplyDeath(owner, SuicideKind.Bloodloss); - - // Prevent the player from returning to the body. - // Note that mind cannot be null because otherwise owner would be null. - _gameTicker.OnGhostAttempt(mind!, false); } private void ApplyDeath(EntityUid target, SuicideKind kind) { - if (kind == SuicideKind.Special) return; - // TODO SUICIDE ..heh.. anyway, someone should fix this mess. - DamageSpecifier damage = new(kind switch - { - SuicideKind.Blunt => _prototypeManager.Index("Blunt"), - SuicideKind.Slash => _prototypeManager.Index("Slash"), - SuicideKind.Piercing => _prototypeManager.Index("Piercing"), - SuicideKind.Heat => _prototypeManager.Index("Heat"), - SuicideKind.Shock => _prototypeManager.Index("Shock"), - SuicideKind.Cold => _prototypeManager.Index("Cold"), - SuicideKind.Poison => _prototypeManager.Index("Poison"), - SuicideKind.Radiation => _prototypeManager.Index("Radiation"), - SuicideKind.Asphyxiation => _prototypeManager.Index("Asphyxiation"), - SuicideKind.Bloodloss => _prototypeManager.Index("Bloodloss"), - _ => _prototypeManager.Index("Blunt") - }, - 200); + if (kind == SuicideKind.Special) + return; - _damageableSystem.TryChangeDamage(target, damage, true); + if (!_prototypeManager.TryIndex(kind.ToString(), out var damagePrototype)) + { + const SuicideKind fallback = SuicideKind.Blunt; + Logger.Error( + $"{nameof(SuicideSystem)} could not find the damage type prototype associated with {kind}. Falling back to {fallback}"); + damagePrototype = _prototypeManager.Index(fallback.ToString()); + } + const int lethalAmountOfDamage = 200; // TODO: Would be nice to get this number from somewhere else + _damageableSystem.TryChangeDamage(target, new(damagePrototype, lethalAmountOfDamage), true); } } } diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs index 93f3bea723..22700ee82d 100644 --- a/Content.Server/GameTicking/GameTicker.GamePreset.cs +++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs @@ -154,7 +154,7 @@ namespace Content.Server.GameTicking if (handleEv.Handled) return handleEv.Result; - var playerEntity = mind.OwnedEntity; + var playerEntity = mind.CurrentEntity; var entities = IoCManager.Resolve(); if (entities.HasComponent(playerEntity)) diff --git a/Content.Server/Toilet/ToiletSystem.cs b/Content.Server/Toilet/ToiletSystem.cs index 0b5deced8b..5ece6dc126 100644 --- a/Content.Server/Toilet/ToiletSystem.cs +++ b/Content.Server/Toilet/ToiletSystem.cs @@ -43,7 +43,34 @@ namespace Content.Server.Toilet private void OnSuicide(EntityUid uid, ToiletComponent component, SuicideEvent args) { - Suicide(component.Owner, uid, component); + if (args.Handled) return; + + // Check that victim has a head + if (EntityManager.TryGetComponent(args.Victim, out var body) && + body.HasPartOfType(BodyPartType.Head)) + { + var othersMessage = Loc.GetString("toilet-component-suicide-head-message-others", + ("victim", args.Victim), ("owner", uid)); + _popupSystem.PopupEntity(othersMessage, uid, Filter.Pvs(args.Victim).RemoveWhereAttachedEntity(puid => puid == args.Victim)); + + var selfMessage = Loc.GetString("toilet-component-suicide-head-message", + ("owner", uid)); + _popupSystem.PopupEntity(selfMessage, uid, Filter.Entities(args.Victim)); + + args.SetHandled(SuicideKind.Asphyxiation); + } + else + { + var othersMessage = Loc.GetString("toilet-component-suicide-message-others", + ("victim", args.Victim), ("owner", uid)); + _popupSystem.PopupEntity(othersMessage, uid, Filter.Pvs(uid).RemoveWhereAttachedEntity(puid => puid == args.Victim)); + + var selfMessage = Loc.GetString("toilet-component-suicide-message", + ("owner", uid)); + _popupSystem.PopupEntity(selfMessage, uid, Filter.Entities(args.Victim)); + + args.SetHandled(SuicideKind.Blunt); + } } private void OnInit(EntityUid uid, ToiletComponent component, ComponentInit args) @@ -130,37 +157,6 @@ namespace Content.Server.Toilet } } - public SuicideKind Suicide(EntityUid uid, EntityUid victimUid, ToiletComponent? component = null, - MetaDataComponent? meta = null, MetaDataComponent? victimMeta = null) - { - // check that victim even have head - if (EntityManager.TryGetComponent(victimUid, out var body) && - body.HasPartOfType(BodyPartType.Head)) - { - var othersMessage = Loc.GetString("toilet-component-suicide-head-message-others", - ("victim", victimUid),("owner", uid)); - _popupSystem.PopupEntity(othersMessage, uid, Filter.Pvs(victimUid).RemoveWhereAttachedEntity(puid => puid == victimUid)); - - var selfMessage = Loc.GetString("toilet-component-suicide-head-message", - ("owner", uid)); - _popupSystem.PopupEntity(selfMessage, uid, Filter.Entities(victimUid)); - - return SuicideKind.Asphyxiation; - } - else - { - var othersMessage = Loc.GetString("toilet-component-suicide-message-others", - ("victim", victimUid),("owner", uid)); - _popupSystem.PopupEntity(othersMessage, uid, Filter.Pvs(uid).RemoveWhereAttachedEntity(puid => puid == victimUid)); - - var selfMessage = Loc.GetString("toilet-component-suicide-message", - ("owner", uid)); - _popupSystem.PopupEntity(selfMessage, uid, Filter.Entities(victimUid)); - - return SuicideKind.Blunt; - } - } - private void OnToiletInterrupt(ToiletPryInterrupted ev) { if (!EntityManager.TryGetComponent(ev.Uid, out ToiletComponent? toilet))