|
| 1 | +using Content.Goobstation.Shared.MisandryBox.Mind; |
| 2 | +using Content.Server.Ghost; |
| 3 | +using Content.Server.Mind; |
| 4 | +using Content.Shared.Ghost; |
| 5 | +using Content.Shared.Mind; |
| 6 | +using Content.Shared.Mind.Components; |
| 7 | +using Robust.Server.Player; |
| 8 | +using Robust.Shared.Enums; |
| 9 | +using Robust.Shared.Player; |
| 10 | +using Robust.Shared.Utility; |
| 11 | + |
| 12 | +namespace Content.Goobstation.Server.MisandryBox.Mind; |
| 13 | + |
| 14 | +/// <summary> |
| 15 | +/// Manages temporary mind swaps: shelves a player's real mind and gives them a fresh |
| 16 | +/// disposable one for a temporary body. On cleanup the disposable mind is deleted |
| 17 | +/// and the player reconnects to their original mind, preserving all roles and objectives. |
| 18 | +/// </summary> |
| 19 | +public sealed class TemporaryMindSystem : EntitySystem |
| 20 | +{ |
| 21 | + [Dependency] private readonly MindSystem _mind = default!; |
| 22 | + [Dependency] private readonly GhostSystem _ghost = default!; |
| 23 | + [Dependency] private readonly MetaDataSystem _meta = default!; |
| 24 | + [Dependency] private readonly SharedTransformSystem _transform = default!; |
| 25 | + [Dependency] private readonly IPlayerManager _playerManager = default!; |
| 26 | + |
| 27 | + public override void Initialize() |
| 28 | + { |
| 29 | + base.Initialize(); |
| 30 | + |
| 31 | + SubscribeLocalEvent<TemporaryMindComponent, PlayerDetachedEvent>(OnPlayerDetached); |
| 32 | + } |
| 33 | + |
| 34 | + private void OnPlayerDetached(Entity<TemporaryMindComponent> ent, ref PlayerDetachedEvent args) |
| 35 | + { |
| 36 | + if (args.Player.Status != SessionStatus.Disconnected) |
| 37 | + return; |
| 38 | + |
| 39 | + CleanupDisposableMind(ent.Comp); |
| 40 | + RemComp<TemporaryMindComponent>(ent); |
| 41 | + } |
| 42 | + |
| 43 | + /// <summary> |
| 44 | + /// Shelves the player's current mind and creates a fresh disposable one for the new body. |
| 45 | + /// </summary> |
| 46 | + public bool TrySwapTempMind(ICommonSession session, EntityUid newBody) |
| 47 | + { |
| 48 | + if (session.AttachedEntity is not { Valid: true } currentEntity) |
| 49 | + return false; |
| 50 | + |
| 51 | + if (!_mind.TryGetMind(currentEntity, out var origMindId, out var origMind)) |
| 52 | + return false; |
| 53 | + |
| 54 | + var userId = origMind.UserId; |
| 55 | + _mind.UnVisit(origMindId, origMind); |
| 56 | + |
| 57 | + // Clear userId from original mind so the disposable mind can claim it in UserMinds |
| 58 | + _mind.SetUserId(origMindId, null, origMind); |
| 59 | + |
| 60 | + // Suppress catatonic examine on the original body while mind is swapped |
| 61 | + if (origMind.OwnedEntity is { } originalBody |
| 62 | + && TryComp<MindContainerComponent>(originalBody, out var mindContainer)) |
| 63 | + _mind.SetShowExamineInfo((originalBody, mindContainer), false); |
| 64 | + |
| 65 | + var newMind = _mind.CreateMind(userId, origMind.CharacterName); |
| 66 | + _mind.TransferTo(newMind, newBody); |
| 67 | + _playerManager.SetAttachedEntity(session, newBody); |
| 68 | + |
| 69 | + var comp = EnsureComp<TemporaryMindComponent>(newBody); |
| 70 | + comp.OriginalMind = origMindId; |
| 71 | + comp.DisposableMind = newMind; |
| 72 | + |
| 73 | + return true; |
| 74 | + } |
| 75 | + |
| 76 | + /// <summary> |
| 77 | + /// Discards the disposable mind and restores the original mind as a ghost at the body's location. |
| 78 | + /// </summary> |
| 79 | + public EntityUid? TryRestoreAsGhost(EntityUid temporaryBody) |
| 80 | + { |
| 81 | + if (!TryComp<TemporaryMindComponent>(temporaryBody, out var temp)) |
| 82 | + return null; |
| 83 | + |
| 84 | + if (!TryComp<MindComponent>(temp.OriginalMind, out var origMind)) |
| 85 | + return null; |
| 86 | + |
| 87 | + var coords = _transform.GetMapCoordinates(temporaryBody); |
| 88 | + |
| 89 | + CleanupDisposableMind(temp); |
| 90 | + |
| 91 | + var ghost = Spawn("MobObserver", coords); |
| 92 | + _mind.Visit(temp.OriginalMind, ghost, origMind); |
| 93 | + |
| 94 | + var canReturn = origMind.OwnedEntity is { } ownedEntity && Exists(ownedEntity); |
| 95 | + |
| 96 | + if (TryComp<GhostComponent>(ghost, out var ghostComp)) |
| 97 | + _ghost.SetCanReturnToBody((ghost, ghostComp), canReturn); |
| 98 | + |
| 99 | + if (!string.IsNullOrWhiteSpace(origMind.CharacterName)) |
| 100 | + _meta.SetEntityName(ghost, FormattedMessage.EscapeText(origMind.CharacterName)); |
| 101 | + else if (origMind.UserId is { } userId && _playerManager.TryGetSessionById(userId, out var session)) |
| 102 | + _meta.SetEntityName(ghost, FormattedMessage.EscapeText(session.Name)); |
| 103 | + |
| 104 | + RemComp<TemporaryMindComponent>(temporaryBody); |
| 105 | + return ghost; |
| 106 | + } |
| 107 | + |
| 108 | + /// <summary> |
| 109 | + /// Discards the disposable mind and restores the original mind to its owned body. |
| 110 | + /// </summary> |
| 111 | + public bool TryRestoreToOriginalBody(EntityUid temporaryBody) |
| 112 | + { |
| 113 | + if (!TryComp<TemporaryMindComponent>(temporaryBody, out var temp)) |
| 114 | + return false; |
| 115 | + |
| 116 | + if (!TryComp<MindComponent>(temp.OriginalMind, out var origMind)) |
| 117 | + return false; |
| 118 | + |
| 119 | + var originalBody = origMind.OwnedEntity; |
| 120 | + if (originalBody == null || !Exists(originalBody)) |
| 121 | + return false; |
| 122 | + |
| 123 | + CleanupDisposableMind(temp); |
| 124 | + |
| 125 | + if (origMind.UserId is { } userId && _playerManager.TryGetSessionById(userId, out var session)) |
| 126 | + _playerManager.SetAttachedEntity(session, originalBody.Value); |
| 127 | + |
| 128 | + RemComp<TemporaryMindComponent>(temporaryBody); |
| 129 | + return true; |
| 130 | + } |
| 131 | + |
| 132 | + private void CleanupDisposableMind(TemporaryMindComponent temp) |
| 133 | + { |
| 134 | + if (Exists(temp.DisposableMind)) |
| 135 | + { |
| 136 | + _mind.WipeMind(temp.DisposableMind); |
| 137 | + QueueDel(temp.DisposableMind); |
| 138 | + } |
| 139 | + |
| 140 | + if (TryComp<MindComponent>(temp.OriginalMind, out var origMind) |
| 141 | + && origMind.OriginalOwnerUserId is { } userId) |
| 142 | + { |
| 143 | + _mind.SetUserId(temp.OriginalMind, userId); |
| 144 | + |
| 145 | + if (origMind.OwnedEntity is { } originalBody |
| 146 | + && TryComp<MindContainerComponent>(originalBody, out var mindContainer)) |
| 147 | + _mind.SetShowExamineInfo((originalBody, mindContainer), true); |
| 148 | + } |
| 149 | + } |
| 150 | +} |
0 commit comments