using Content.Server.GameTicking; using Content.Server.Ghost.Components; using Content.Shared.Examine; using Content.Shared.Ghost; using Content.Shared.MobState.Components; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.Utility.Markup; using Robust.Shared.ViewVariables; namespace Content.Server.Mind.Components { /// /// Stores a on a mob. /// [RegisterComponent] #pragma warning disable 618 public class MindComponent : Component, IExamine #pragma warning restore 618 { [Dependency] private readonly IEntityManager _entMan = default!; /// public override string Name => "Mind"; /// /// The mind controlling this mob. Can be null. /// [ViewVariables] public Mind? Mind { get; private set; } /// /// True if we have a mind, false otherwise. /// [ViewVariables] public bool HasMind => Mind != null; /// /// Whether examining should show information about the mind or not. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("showExamineInfo")] public bool ShowExamineInfo { get; set; } /// /// Whether the mind will be put on a ghost after this component is shutdown. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("ghostOnShutdown")] public bool GhostOnShutdown { get; set; } = true; /// /// Don't call this unless you know what the hell you're doing. /// Use instead. /// If that doesn't cover it, make something to cover it. /// public void InternalEjectMind() { if (!Deleted) _entMan.EventBus.RaiseLocalEvent(Owner, new MindRemovedMessage()); Mind = null; } /// /// Don't call this unless you know what the hell you're doing. /// Use instead. /// If that doesn't cover it, make something to cover it. /// public void InternalAssignMind(Mind value) { Mind = value; _entMan.EventBus.RaiseLocalEvent(Owner, new MindAddedMessage()); } protected override void Shutdown() { base.Shutdown(); // Let's not create ghosts if not in the middle of the round. if (EntitySystem.Get().RunLevel != GameRunLevel.InRound) return; if (HasMind) { if (Mind?.VisitingEntity is {Valid: true} visiting) { if (_entMan.TryGetComponent(visiting, out GhostComponent? ghost)) { EntitySystem.Get().SetCanReturnToBody(ghost, false); } Mind!.TransferTo(visiting); } else if (GhostOnShutdown) { var spawnPosition = _entMan.GetComponent(Owner).Coordinates; // Use a regular timer here because the entity has probably been deleted. Timer.Spawn(0, () => { // Async this so that we don't throw if the grid we're on is being deleted. var mapMan = IoCManager.Resolve(); var gridId = spawnPosition.GetGridId(_entMan); if (gridId == GridId.Invalid || !mapMan.GridExists(gridId)) { spawnPosition = EntitySystem.Get().GetObserverSpawnPoint(); } var ghost = _entMan.SpawnEntity("MobObserver", spawnPosition); var ghostComponent = _entMan.GetComponent(ghost); EntitySystem.Get().SetCanReturnToBody(ghostComponent, false); if (Mind != null) { string? val = Mind.CharacterName ?? string.Empty; _entMan.GetComponent(ghost).EntityName = val; Mind.TransferTo(ghost); } }); } } } public void Examine(FormattedMessage.Builder message, bool inDetailsRange) { if (!ShowExamineInfo || !inDetailsRange) { return; } var dead = _entMan.TryGetComponent(Owner, out var state) && state.IsDead(); if (dead) { if (Mind?.Session == null) { // Player has no session attached and dead message.AddMarkup($"[color=yellow]{Loc.GetString("mind-component-no-mind-and-dead-text", ("ent", Owner))}[/color]"); } else { // Player is dead with session message.AddMarkup($"[color=red]{Loc.GetString("comp-mind-examined-dead", ("ent", Owner))}[/color]"); } } else if (!HasMind) { message.AddMarkup($"[color=purple]{Loc.GetString("comp-mind-examined-catatonic", ("ent", Owner))}[/color]"); } else if (Mind?.Session == null) { message.AddMarkup($"[color=yellow]{Loc.GetString("comp-mind-examined-ssd", ("ent", Owner))}[/color]"); } } } public class MindRemovedMessage : EntityEventArgs { } public class MindAddedMessage : EntityEventArgs { } }