diff --git a/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs b/Content.IntegrationTests/Tests/Minds/MindEntityDeletionTest.cs similarity index 88% rename from Content.IntegrationTests/Tests/MindEntityDeletionTest.cs rename to Content.IntegrationTests/Tests/Minds/MindEntityDeletionTest.cs index 93c5fe41f1..3f50ffcb0d 100644 --- a/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs +++ b/Content.IntegrationTests/Tests/Minds/MindEntityDeletionTest.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Threading.Tasks; using Content.Server.Mind; -using Content.Shared.Coordinates; using NUnit.Framework; using Robust.Server.GameObjects; using Robust.Server.Player; @@ -10,7 +9,7 @@ using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; -namespace Content.IntegrationTests.Tests +namespace Content.IntegrationTests.Tests.Minds { // Tests various scenarios of deleting the entity that a player's mind is connected to. [TestFixture] @@ -26,6 +25,8 @@ namespace Content.IntegrationTests.Tests var playerMan = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + EntityUid playerEnt = default; EntityUid visitEnt = default; Mind mind = null; @@ -39,11 +40,9 @@ namespace Content.IntegrationTests.Tests playerEnt = entMan.SpawnEntity(null, pos); visitEnt = entMan.SpawnEntity(null, pos); - mind = new Mind(player.UserId); - mind.ChangeOwningPlayer(player.UserId); - - mind.TransferTo(playerEnt); - mind.Visit(visitEnt); + mind = mindSystem.CreateMind(player.UserId); + mindSystem.TransferTo(mind, playerEnt); + mindSystem.Visit(mind, visitEnt); Assert.That(player.AttachedEntity, Is.EqualTo(visitEnt)); Assert.That(mind.VisitingEntity, Is.EqualTo(visitEnt)); @@ -91,6 +90,9 @@ namespace Content.IntegrationTests.Tests var entMan = server.ResolveDependency(); var playerMan = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + var map = await PoolManager.CreateTestMap(pairTracker); EntityUid playerEnt = default; @@ -103,10 +105,8 @@ namespace Content.IntegrationTests.Tests playerEnt = entMan.SpawnEntity(null, pos); - mind = new Mind(player.UserId); - mind.ChangeOwningPlayer(player.UserId); - - mind.TransferTo(playerEnt); + mind = mindSystem.CreateMind(player.UserId); + mindSystem.TransferTo(mind, playerEnt); Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt)); }); @@ -136,27 +136,26 @@ namespace Content.IntegrationTests.Tests [Test] public async Task TestGhostOnDeleteMap() { - await using var pairTracker = await PoolManager.GetServerClient(); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true }); var server = pairTracker.Pair.Server; var testMap = await PoolManager.CreateTestMap(pairTracker); var coordinates = testMap.GridCoords; var entMan = server.ResolveDependency(); - var playerMan = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + var map = await PoolManager.CreateTestMap(pairTracker); EntityUid playerEnt = default; Mind mind = null; await server.WaitAssertion(() => { - var player = playerMan.ServerSessions.Single(); - playerEnt = entMan.SpawnEntity(null, coordinates); - mind = new Mind(player.UserId); - mind.ChangeOwningPlayer(player.UserId); - mind.TransferTo(playerEnt); + mind = mindSystem.CreateMind(null); + mindSystem.TransferTo(mind, playerEnt); Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt)); }); diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs new file mode 100644 index 0000000000..402ce95398 --- /dev/null +++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs @@ -0,0 +1,295 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using Content.Server.Mind; +using Content.Server.Mind.Components; +using Content.Server.Roles; +using Content.Server.Traitor; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.Roles; +using NUnit.Framework; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Minds; + +[TestFixture] +public sealed class MindTests +{ + private const string Prototypes = @" +- type: entity + id: MindTestEntity + components: + - type: MindContainer + +- type: entity + parent: MindTestEntity + id: MindTestEntityDamageable + components: + - type: Damageable + damageContainer: Biological + - type: Body + prototype: Human + requiredLegs: 2 + - type: MobState + - type: MobThresholds + thresholds: + 0: Alive + 100: Critical + 200: Dead + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 400 + behaviors: + - !type:GibBehavior { } +"; + + /// + /// Exception handling for PlayerData and NetUserId invalid due to testing. + /// Can be removed when Players can be mocked. + /// + /// + private void CatchPlayerDataException(Action func) + { + try + { + func(); + } + catch (ArgumentException e) + { + // Prevent exiting due to PlayerData not being initialized. + if (e.Message == "New owner must have previously logged into the server. (Parameter 'newOwner')") + return; + throw; + } + } + + [Test] + public async Task TestCreateAndTransferMindToNewEntity() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ NoClient = true }); + var server = pairTracker.Pair.Server; + + var entMan = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + + var entity = entMan.SpawnEntity(null, new MapCoordinates()); + var mindComp = entMan.EnsureComponent(entity); + + var mind = mindSystem.CreateMind(null); + + Assert.That(mind.UserId, Is.EqualTo(null)); + + mindSystem.TransferTo(mind, entity); + Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind)); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestReplaceMind() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ NoClient = true }); + var server = pairTracker.Pair.Server; + + var entMan = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + + var entity = entMan.SpawnEntity(null, new MapCoordinates()); + var mindComp = entMan.EnsureComponent(entity); + + var mind = mindSystem.CreateMind(null); + mindSystem.TransferTo(mind, entity); + Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind)); + + var mind2 = mindSystem.CreateMind(null); + mindSystem.TransferTo(mind2, entity); + Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind2)); + Assert.That(mind.OwnedEntity != entity); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestEntityDeadWhenGibbed() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ NoClient = true, ExtraPrototypes = Prototypes }); + var server = pairTracker.Pair.Server; + + var entMan = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); + + EntityUid entity = default!; + MindContainerComponent mindContainerComp = default!; + Mind mind = default!; + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + var damageableSystem = entMan.EntitySysManager.GetEntitySystem(); + + await server.WaitAssertion(() => + { + entity = entMan.SpawnEntity("MindTestEntityDamageable", new MapCoordinates()); + mindContainerComp = entMan.EnsureComponent(entity); + + mind = mindSystem.CreateMind(null); + + mindSystem.TransferTo(mind, entity); + Assert.That(mindSystem.GetMind(entity, mindContainerComp), Is.EqualTo(mind)); + Assert.That(!mindSystem.IsCharacterDeadPhysically(mind)); + }); + + await PoolManager.RunTicksSync(pairTracker.Pair, 5); + + await server.WaitAssertion(() => + { + var damageable = entMan.GetComponent(entity); + if (!protoMan.TryIndex("Blunt", out var prototype)) + { + return; + } + + damageableSystem.SetDamage(entity, damageable, new DamageSpecifier(prototype, FixedPoint2.New(401))); + Assert.That(mindSystem.GetMind(entity, mindContainerComp), Is.EqualTo(mind)); + }); + + await PoolManager.RunTicksSync(pairTracker.Pair, 5); + + await server.WaitAssertion(() => + { + Assert.That(mindSystem.IsCharacterDeadPhysically(mind)); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestMindTransfersToOtherEntity() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ NoClient = true }); + var server = pairTracker.Pair.Server; + + var entMan = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + + var entity = entMan.SpawnEntity(null, new MapCoordinates()); + var targetEntity = entMan.SpawnEntity(null, new MapCoordinates()); + var mindComp = entMan.EnsureComponent(entity); + entMan.EnsureComponent(targetEntity); + + var mind = mindSystem.CreateMind(null); + + mindSystem.TransferTo(mind, entity); + + Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind)); + + mindSystem.TransferTo(mind, targetEntity); + Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(null)); + Assert.That(mindSystem.GetMind(targetEntity), Is.EqualTo(mind)); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestOwningPlayerCanBeChanged() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ NoClient = true }); + var server = pairTracker.Pair.Server; + + var entMan = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + + var entity = entMan.SpawnEntity(null, new MapCoordinates()); + var mindComp = entMan.EnsureComponent(entity); + + var mind = mindSystem.CreateMind(null); + + mindSystem.TransferTo(mind, entity); + + Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind)); + + var newUserId = new NetUserId(Guid.NewGuid()); + Assert.That(mindComp.HasMind); + CatchPlayerDataException(() => + mindSystem.ChangeOwningPlayer(mindComp.Mind!, newUserId)); + + Assert.That(mind.UserId, Is.EqualTo(newUserId)); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestAddRemoveHasRoles() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ NoClient = true }); + var server = pairTracker.Pair.Server; + + var entMan = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var mindSystem = entMan.EntitySysManager.GetEntitySystem(); + + var entity = entMan.SpawnEntity(null, new MapCoordinates()); + var mindComp = entMan.EnsureComponent(entity); + + var mind = mindSystem.CreateMind(null); + + Assert.That(mind.UserId, Is.EqualTo(null)); + + mindSystem.TransferTo(mind, entity); + Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind)); + + Assert.That(!mindSystem.HasRole(mind)); + Assert.That(!mindSystem.HasRole(mind)); + + var traitorRole = new TraitorRole(mind, new AntagPrototype()); + + mindSystem.AddRole(mind, traitorRole); + + Assert.That(mindSystem.HasRole(mind)); + Assert.That(!mindSystem.HasRole(mind)); + + var jobRole = new Job(mind, new JobPrototype()); + + mindSystem.AddRole(mind, jobRole); + + Assert.That(mindSystem.HasRole(mind)); + Assert.That(mindSystem.HasRole(mind)); + + mindSystem.RemoveRole(mind, traitorRole); + + Assert.That(!mindSystem.HasRole(mind)); + Assert.That(mindSystem.HasRole(mind)); + + mindSystem.RemoveRole(mind, jobRole); + + Assert.That(!mindSystem.HasRole(mind)); + Assert.That(!mindSystem.HasRole(mind)); + }); + + await pairTracker.CleanReturnAsync(); + } +} diff --git a/Content.Server/AME/Components/AMEControllerComponent.cs b/Content.Server/AME/Components/AMEControllerComponent.cs index 1a398a5eb5..4285ff5fbd 100644 --- a/Content.Server/AME/Components/AMEControllerComponent.cs +++ b/Content.Server/AME/Components/AMEControllerComponent.cs @@ -176,20 +176,20 @@ namespace Content.Server.AME.Components } // Logging - _entities.TryGetComponent(player, out MindComponent? mindComponent); - if (mindComponent != null) + _entities.TryGetComponent(player, out MindContainerComponent? mindContainerComponent); + if (mindContainerComponent != null) { var humanReadableState = _injecting ? "Inject" : "Not inject"; if (msg.Button == UiButton.IncreaseFuel || msg.Button == UiButton.DecreaseFuel) - _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{_entities.ToPrettyString(mindComponent.Owner):player} has set the AME to inject {InjectionAmount} while set to {humanReadableState}"); + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{_entities.ToPrettyString(mindContainerComponent.Owner):player} has set the AME to inject {InjectionAmount} while set to {humanReadableState}"); if (msg.Button == UiButton.ToggleInjection) - _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{_entities.ToPrettyString(mindComponent.Owner):player} has set the AME to {humanReadableState}"); + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{_entities.ToPrettyString(mindContainerComponent.Owner):player} has set the AME to {humanReadableState}"); // Admin alert if (GetCoreCount() * 2 == InjectionAmount - 2 && msg.Button == UiButton.IncreaseFuel) - _chat.SendAdminAlert(player, $"increased AME over safe limit to {InjectionAmount}", mindComponent); + _chat.SendAdminAlert(player, $"increased AME over safe limit to {InjectionAmount}", mindContainerComponent); } GetAMENodeGroup()?.UpdateCoreVisuals(); diff --git a/Content.Server/Administration/Commands/AGhost.cs b/Content.Server/Administration/Commands/AGhost.cs index 037c289cbd..82c786af86 100644 --- a/Content.Server/Administration/Commands/AGhost.cs +++ b/Content.Server/Administration/Commands/AGhost.cs @@ -1,5 +1,6 @@ using Content.Server.GameTicking; using Content.Server.Ghost.Components; +using Content.Server.Mind; using Content.Server.Players; using Content.Shared.Administration; using Content.Shared.Ghost; @@ -33,10 +34,12 @@ namespace Content.Server.Administration.Commands shell.WriteLine("You can't ghost here!"); return; } + + var mindSystem = _entities.System(); if (mind.VisitingEntity != default && _entities.HasComponent(mind.VisitingEntity)) { - player.ContentData()!.Mind?.UnVisit(); + mindSystem.UnVisit(mind); return; } @@ -56,12 +59,12 @@ namespace Content.Server.Administration.Commands else if (!string.IsNullOrWhiteSpace(mind.Session?.Name)) _entities.GetComponent(ghost).EntityName = mind.Session.Name; - mind.Visit(ghost); + mindSystem.Visit(mind, ghost); } else { _entities.GetComponent(ghost).EntityName = player.Name; - mind.TransferTo(ghost); + mindSystem.TransferTo(mind, ghost); } var comp = _entities.GetComponent(ghost); diff --git a/Content.Server/Administration/Commands/ControlMob.cs b/Content.Server/Administration/Commands/ControlMob.cs index 4346ca7f17..18fb3bbb65 100644 --- a/Content.Server/Administration/Commands/ControlMob.cs +++ b/Content.Server/Administration/Commands/ControlMob.cs @@ -1,3 +1,4 @@ +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Server.Players; using Content.Shared.Administration; @@ -44,7 +45,7 @@ namespace Content.Server.Administration.Commands return; } - if (!_entities.HasComponent(target)) + if (!_entities.HasComponent(target)) { shell.WriteLine(Loc.GetString("shell-entity-is-not-mob")); return; @@ -54,7 +55,8 @@ namespace Content.Server.Administration.Commands DebugTools.AssertNotNull(mind); - mind!.TransferTo(target); + var mindSystem = _entities.System(); + mindSystem.TransferTo(mind!, target); } } } diff --git a/Content.Server/Administration/Commands/SetMindCommand.cs b/Content.Server/Administration/Commands/SetMindCommand.cs index 5c728f6599..6ef9c7eede 100644 --- a/Content.Server/Administration/Commands/SetMindCommand.cs +++ b/Content.Server/Administration/Commands/SetMindCommand.cs @@ -1,3 +1,4 @@ +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Server.Players; using Content.Shared.Administration; @@ -9,9 +10,10 @@ namespace Content.Server.Administration.Commands [AdminCommand(AdminFlags.Admin)] sealed class SetMindCommand : IConsoleCommand { + public string Command => "setmind"; - public string Description => Loc.GetString("set-mind-command-description", ("requiredComponent", nameof(MindComponent))); + public string Description => Loc.GetString("set-mind-command-description", ("requiredComponent", nameof(MindContainerComponent))); public string Help => Loc.GetString("set-mind-command-help-text", ("command", Command)); @@ -39,7 +41,7 @@ namespace Content.Server.Administration.Commands return; } - if (!entityManager.HasComponent(eUid)) + if (!entityManager.HasComponent(eUid)) { shell.WriteLine(Loc.GetString("set-mind-command-target-has-no-mind-message")); return; @@ -59,16 +61,16 @@ namespace Content.Server.Administration.Commands return; } + var mindSystem = entityManager.System(); + var mind = playerCData.Mind; if (mind == null) { - mind = new Mind.Mind(session.UserId) - { - CharacterName = entityManager.GetComponent(eUid).EntityName - }; - mind.ChangeOwningPlayer(session.UserId); + mind = mindSystem.CreateMind(session.UserId); + mind.CharacterName = entityManager.GetComponent(eUid).EntityName; } - mind.TransferTo(eUid); + + mindSystem.TransferTo(mind, eUid); } } } diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index b134d15fb3..d340c39c7a 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -28,7 +28,7 @@ public sealed partial class AdminVerbSystem if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun)) return; - var targetHasMind = TryComp(args.Target, out MindComponent? targetMindComp); + var targetHasMind = TryComp(args.Target, out MindContainerComponent? targetMindComp); if (!targetHasMind || targetMindComp == null) return; @@ -56,7 +56,7 @@ public sealed partial class AdminVerbSystem Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Structures/Wallmounts/signs.rsi"), "bio"), Act = () => { - TryComp(args.Target, out MindComponent? mindComp); + TryComp(args.Target, out MindContainerComponent? mindComp); if (mindComp == null || mindComp.Mind == null) return; diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 7b1def620b..3707014bda 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -8,6 +8,7 @@ using Content.Server.Configurable; using Content.Server.Disposal.Tube.Components; using Content.Server.EUI; using Content.Server.Ghost.Roles; +using Content.Server.Mind; using Content.Server.Mind.Commands; using Content.Server.Mind.Components; using Content.Server.Players; @@ -51,6 +52,7 @@ namespace Content.Server.Administration.Systems [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly PrayerSystem _prayerSystem = default!; [Dependency] private readonly EuiManager _eui = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; private readonly Dictionary _openSolutionUis = new(); @@ -229,7 +231,12 @@ namespace Content.Server.Administration.Systems Act = () => { MakeSentientCommand.MakeSentient(args.Target, EntityManager); - player.ContentData()?.Mind?.TransferTo(args.Target, ghostCheckOverride: true); + + var mind = player.ContentData()?.Mind; + if (mind == null) + return; + + _mindSystem.TransferTo(mind, args.Target, ghostCheckOverride: true); }, Impact = LogImpact.High, ConfirmationPopup = true @@ -263,7 +270,7 @@ namespace Content.Server.Administration.Systems // Make Sentient verb if (_groupController.CanCommand(player, "makesentient") && args.User != args.Target && - !EntityManager.HasComponent(args.Target)) + !EntityManager.HasComponent(args.Target)) { Verb verb = new() { @@ -326,7 +333,7 @@ namespace Content.Server.Administration.Systems // Make ghost role verb if (_groupController.CanCommand(player, "makeghostrole") && - !(EntityManager.GetComponentOrNull(args.Target)?.HasMind ?? false)) + !(EntityManager.GetComponentOrNull(args.Target)?.HasMind ?? false)) { Verb verb = new(); verb.Text = Loc.GetString("make-ghost-role-verb-get-data-text"); diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index 472b6b8acd..3a70b51893 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -4,6 +4,7 @@ using Content.Server.Body.Components; using Content.Server.GameTicking; using Content.Server.Humanoid; using Content.Server.Kitchen.Components; +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Shared.Body.Components; using Content.Shared.Body.Part; @@ -28,6 +29,7 @@ public sealed class BodySystem : SharedBodySystem [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { @@ -40,16 +42,14 @@ public sealed class BodySystem : SharedBodySystem private void OnRelayMoveInput(EntityUid uid, BodyComponent component, ref MoveInputEvent args) { - if (_mobState.IsDead(uid) && - EntityManager.TryGetComponent(uid, out var mind) && - mind.HasMind) + if (_mobState.IsDead(uid) && _mindSystem.TryGetMind(uid, out var mind)) { - if (!mind.Mind!.TimeOfDeath.HasValue) + if (!mind.TimeOfDeath.HasValue) { - mind.Mind.TimeOfDeath = _gameTiming.RealTime; + mind.TimeOfDeath = _gameTiming.RealTime; } - _ticker.OnGhostAttempt(mind.Mind!, true); + _ticker.OnGhostAttempt(mind, true); } } diff --git a/Content.Server/Body/Systems/BrainSystem.cs b/Content.Server/Body/Systems/BrainSystem.cs index 52b743eebc..2098293870 100644 --- a/Content.Server/Body/Systems/BrainSystem.cs +++ b/Content.Server/Body/Systems/BrainSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Body.Components; using Content.Server.Ghost.Components; +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Shared.Body.Components; using Content.Shared.Body.Events; @@ -10,6 +11,8 @@ namespace Content.Server.Body.Systems { public sealed class BrainSystem : EntitySystem { + [Dependency] private readonly MindSystem _mindSystem = default!; + public override void Initialize() { base.Initialize(); @@ -34,8 +37,8 @@ namespace Content.Server.Body.Systems private void HandleMind(EntityUid newEntity, EntityUid oldEntity) { - EntityManager.EnsureComponent(newEntity); - var oldMind = EntityManager.EnsureComponent(oldEntity); + EntityManager.EnsureComponent(newEntity); + var oldMind = EntityManager.EnsureComponent(oldEntity); EnsureComp(newEntity); if (HasComp(newEntity)) @@ -44,7 +47,10 @@ namespace Content.Server.Body.Systems // TODO: This is an awful solution. EnsureComp(newEntity); - oldMind.Mind?.TransferTo(newEntity); + if (!_mindSystem.TryGetMind(oldEntity, out var mind, oldMind)) + return; + + _mindSystem.TransferTo(mind, newEntity); } } } diff --git a/Content.Server/Bql/QuerySelectors.cs b/Content.Server/Bql/QuerySelectors.cs index a3b943bc9d..829dee7e38 100644 --- a/Content.Server/Bql/QuerySelectors.cs +++ b/Content.Server/Bql/QuerySelectors.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Chemistry.Components.SolutionManager; +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Server.Power.Components; using Content.Shared.Tag; @@ -21,7 +22,7 @@ namespace Content.Server.Bql { return input.Where(e => { - if (entityManager.TryGetComponent(e, out var mind)) + if (entityManager.TryGetComponent(e, out var mind)) return (mind.Mind?.VisitingEntity == e) ^ isInverted; return isInverted; @@ -32,7 +33,7 @@ namespace Content.Server.Bql { return DoSelection( - entityManager.EntityQuery().Select(x => x.Owner), + entityManager.EntityQuery().Select(x => x.Owner), arguments, isInverted, entityManager); } } @@ -68,14 +69,16 @@ namespace Content.Server.Bql public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) { + var mindSystem = entityManager.System(); return input.Where(e => - (entityManager.TryGetComponent(e, out var mind) && - !(mind.Mind?.CharacterDeadPhysically ?? false)) ^ isInverted); + entityManager.TryGetComponent(e, out var mind) + && mind.Mind != null + && !mindSystem.IsCharacterDeadPhysically(mind.Mind)); } public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) { - return DoSelection(entityManager.EntityQuery().Select(x => x.Owner), arguments, + return DoSelection(entityManager.EntityQuery().Select(x => x.Owner), arguments, isInverted, entityManager); } } diff --git a/Content.Server/CharacterInfo/CharacterInfoSystem.cs b/Content.Server/CharacterInfo/CharacterInfoSystem.cs index 282722ceae..bd26a4836b 100644 --- a/Content.Server/CharacterInfo/CharacterInfoSystem.cs +++ b/Content.Server/CharacterInfo/CharacterInfoSystem.cs @@ -26,9 +26,9 @@ public sealed class CharacterInfoSystem : EntitySystem var conditions = new Dictionary>(); var jobTitle = "No Profession"; var briefing = "!!ERROR: No Briefing!!"; //should never show on the UI unless there's a bug - if (EntityManager.TryGetComponent(entity, out MindComponent? mindComponent) && mindComponent.Mind != null) + if (EntityManager.TryGetComponent(entity, out MindContainerComponent? mindContainerComponent) && mindContainerComponent.Mind != null) { - var mind = mindComponent.Mind; + var mind = mindContainerComponent.Mind; // Get objectives foreach (var objective in mind.AllObjectives) diff --git a/Content.Server/Chat/Commands/SuicideCommand.cs b/Content.Server/Chat/Commands/SuicideCommand.cs index 2f08881cb0..16c699b0a9 100644 --- a/Content.Server/Chat/Commands/SuicideCommand.cs +++ b/Content.Server/Chat/Commands/SuicideCommand.cs @@ -29,7 +29,7 @@ namespace Content.Server.Chat.Commands 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) + if (mind?.OwnedEntity is not { Valid: true } victim) { shell.WriteLine("You don't have a mind!"); return; diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index 90924188b2..b81c5f96b0 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -116,19 +116,19 @@ namespace Content.Server.Chat.Managers ChatMessageToMany(ChatChannel.AdminAlert, message, wrappedMessage, default, false, true, clients); } - public void SendAdminAlert(EntityUid player, string message, MindComponent? mindComponent = null) + public void SendAdminAlert(EntityUid player, string message, MindContainerComponent? mindContainerComponent = null) { - if(mindComponent == null && !_entityManager.TryGetComponent(player, out mindComponent)) + if(mindContainerComponent == null && !_entityManager.TryGetComponent(player, out mindContainerComponent)) { SendAdminAlert(message); return; } var adminSystem = _entityManager.System(); - var antag = mindComponent.Mind!.UserId != null - && (adminSystem.GetCachedPlayerInfo(mindComponent.Mind!.UserId.Value)?.Antag ?? false); + var antag = mindContainerComponent.Mind!.UserId != null + && (adminSystem.GetCachedPlayerInfo(mindContainerComponent.Mind!.UserId.Value)?.Antag ?? false); - SendAdminAlert($"{mindComponent.Mind!.Session?.Name}{(antag ? " (ANTAG)" : "")} {message}"); + SendAdminAlert($"{mindContainerComponent.Mind!.Session?.Name}{(antag ? " (ANTAG)" : "")} {message}"); } public void SendHookOOC(string sender, string message) diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs index 6d74f76422..f3e0e8e2cf 100644 --- a/Content.Server/Chat/Managers/IChatManager.cs +++ b/Content.Server/Chat/Managers/IChatManager.cs @@ -24,7 +24,7 @@ namespace Content.Server.Chat.Managers void SendHookOOC(string sender, string message); void SendAdminAnnouncement(string message); void SendAdminAlert(string message); - void SendAdminAlert(EntityUid player, string message, MindComponent? mindComponent = null); + void SendAdminAlert(EntityUid player, string message, MindContainerComponent? mindContainerComponent = null); void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0); diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index fd3182e65c..a4e23558fb 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -497,9 +497,9 @@ public sealed partial class ChatSystem : SharedChatSystem if (player == null) return true; - var mindComponent = player.ContentData()?.Mind; + var mindContainerComponent = player.ContentData()?.Mind; - if (mindComponent == null) + if (mindContainerComponent == null) { shell?.WriteError("You don't have a mind!"); return false; diff --git a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs index c762ba8a83..81f92cb8da 100644 --- a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs +++ b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs @@ -13,6 +13,12 @@ public sealed class MakeSentient : ReagentEffect var entityManager = args.EntityManager; var uid = args.SolutionEntity; + // This makes it so it doesn't affect things that are already sentient + if (entityManager.HasComponent(uid)) + { + return; + } + // This piece of code makes things able to speak "normally". One thing of note is that monkeys have a unique accent and won't be affected by this. entityManager.RemoveComponent(uid); @@ -20,7 +26,7 @@ public sealed class MakeSentient : ReagentEffect entityManager.RemoveComponent(uid); // This makes it so it doesn't add a ghost role to things that are already sentient - if (entityManager.HasComponent(uid)) + if (entityManager.HasComponent(uid)) { return; } diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index eb8e2b80cb..4da87a6339 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -164,7 +164,7 @@ namespace Content.Server.Cloning if (body is null) return; - if (!TryComp(body, out var mindComp)) + if (!TryComp(body, out var mindComp)) return; var mind = mindComp.Mind; @@ -214,7 +214,7 @@ namespace Content.Server.Cloning { scanBodyInfo = MetaData(scanBody.Value).EntityName; - TryComp(scanBody, out var mindComp); + TryComp(scanBody, out var mindComp); if (!_mobStateSystem.IsDead(scanBody.Value)) { diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 45ffc4a967..c17dff31ef 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -19,6 +19,7 @@ using Content.Server.Construction; using Content.Server.Materials; using Content.Server.Stack; using Content.Server.Jobs; +using Content.Server.Mind; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Zombies; @@ -54,6 +55,7 @@ namespace Content.Server.Cloning [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly MaterialStorageSystem _material = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public readonly Dictionary ClonesWaitingForMind = new(); public const float EasyModeCloningCost = 0.7f; @@ -97,12 +99,12 @@ namespace Content.Server.Cloning { if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) || !EntityManager.EntityExists(entity) || - !TryComp(entity, out var mindComp) || + !TryComp(entity, out var mindComp) || mindComp.Mind != null) return; - mind.TransferTo(entity, ghostCheckOverride: true); - mind.UnVisit(); + _mindSystem.TransferTo(mind, entity, ghostCheckOverride: true); + _mindSystem.UnVisit(mind); ClonesWaitingForMind.Remove(mind); } @@ -157,7 +159,7 @@ namespace Content.Server.Cloning { if (EntityManager.EntityExists(clone) && !_mobStateSystem.IsDead(clone) && - TryComp(clone, out var cloneMindComp) && + TryComp(clone, out var cloneMindComp) && (cloneMindComp.Mind == null || cloneMindComp.Mind == mind)) return false; // Mind already has clone diff --git a/Content.Server/Drone/DroneSystem.cs b/Content.Server/Drone/DroneSystem.cs index 00bde35a58..1cffb79ae7 100644 --- a/Content.Server/Drone/DroneSystem.cs +++ b/Content.Server/Drone/DroneSystem.cs @@ -72,7 +72,7 @@ namespace Content.Server.Drone private void OnExamined(EntityUid uid, DroneComponent component, ExaminedEvent args) { - if (TryComp(uid, out var mind) && mind.HasMind) + if (TryComp(uid, out var mind) && mind.HasMind) { args.PushMarkup(Loc.GetString("drone-active")); } @@ -132,7 +132,7 @@ namespace Content.Server.Drone foreach (var entity in _lookup.GetEntitiesInRange(xform.MapPosition, component.InteractionBlockRange)) { // Return true if the entity is/was controlled by a player and is not a drone or ghost. - if (HasComp(entity) && !HasComp(entity) && !HasComp(entity)) + if (HasComp(entity) && !HasComp(entity) && !HasComp(entity)) { // Filter out dead ghost roles. Dead normal players are intended to block. if ((TryComp(entity, out var entityMobState) && HasComp(entity) && _mobStateSystem.IsDead(entity, entityMobState))) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 7f03e68aa4..441696f0e2 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -392,7 +392,7 @@ public sealed partial class ExplosionSystem : EntitySystem { // no damage-dict multiplication required. _damageableSystem.TryChangeDamage(uid, damage, ignoreResistances: true, damageable: damageable); - if (HasComp(uid) || HasComp(uid)) + if (HasComp(uid) || HasComp(uid)) { var damageStr = string.Join(", ", damage.DamageDict.Select(entry => $"{entry.Key}: {entry.Value}")); _adminLogger.Add(LogType.Explosion, LogImpact.Medium, @@ -403,7 +403,7 @@ public sealed partial class ExplosionSystem : EntitySystem { var appliedDamage = damage * ev.DamageCoefficient; _damageableSystem.TryChangeDamage(uid, appliedDamage, ignoreResistances: true, damageable: damageable); - if (HasComp(uid) || HasComp(uid)) + if (HasComp(uid) || HasComp(uid)) { var damageStr = string.Join(", ", appliedDamage.DamageDict.Select(entry => $"{entry.Key}: {entry.Value}")); _adminLogger.Add(LogType.Explosion, LogImpact.Medium, diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs index eb44943e76..46483eb099 100644 --- a/Content.Server/GameTicking/GameTicker.GamePreset.cs +++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs @@ -5,6 +5,7 @@ using Content.Server.GameTicking.Events; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules; using Content.Server.Ghost.Components; +using Content.Server.Mind; using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -20,6 +21,7 @@ namespace Content.Server.GameTicking public const float PresetFailedCooldownIncrease = 30f; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public GamePresetPrototype? Preset { get; private set; } @@ -178,7 +180,7 @@ namespace Content.Server.GameTicking if (mind.VisitingEntity != default) { - mind.UnVisit(); + _mindSystem.UnVisit(mind); } var position = playerEntity is {Valid: true} @@ -196,7 +198,7 @@ namespace Content.Server.GameTicking // + 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; + var canReturn = canReturnGlobal && _mindSystem.IsCharacterDeadPhysically(mind); if (canReturnGlobal && TryComp(playerEntity, out MobStateComponent? mobState)) { @@ -238,9 +240,9 @@ namespace Content.Server.GameTicking _ghosts.SetCanReturnToBody(ghostComponent, canReturn); if (canReturn) - mind.Visit(ghost); + _mindSystem.Visit(mind, ghost); else - mind.TransferTo(ghost); + _mindSystem.TransferTo(mind, ghost); return true; } diff --git a/Content.Server/GameTicking/GameTicker.Player.cs b/Content.Server/GameTicking/GameTicker.Player.cs index 10edb7d096..fd4a2e6a9c 100644 --- a/Content.Server/GameTicking/GameTicker.Player.cs +++ b/Content.Server/GameTicking/GameTicker.Player.cs @@ -8,6 +8,7 @@ using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.Timing; using Robust.Shared.Utility; +using PlayerData = Content.Server.Players.PlayerData; namespace Content.Server.GameTicking { diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index e64837ca9f..6ab473c6d2 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -21,6 +21,7 @@ using System.Linq; using System.Threading.Tasks; using Content.Shared.Database; using Robust.Shared.Asynchronous; +using PlayerData = Content.Server.Players.PlayerData; namespace Content.Server.GameTicking { @@ -320,12 +321,12 @@ namespace Content.Server.GameTicking var connected = false; var observer = mind.AllRoles.Any(role => role is ObserverRole); // Continuing - if (_playerManager.TryGetSessionById(userId, out var ply)) + if (userId != null && _playerManager.ValidSessionId(userId.Value)) { connected = true; } PlayerData? contentPlayerData = null; - if (_playerManager.TryGetPlayerData(userId, out var playerData)) + if (userId != null && _playerManager.TryGetPlayerData(userId.Value, out var playerData)) { contentPlayerData = playerData.ContentData(); } diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 0826638a6f..6a5373981c 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -172,15 +172,12 @@ namespace Content.Server.GameTicking DebugTools.AssertNotNull(data); data!.WipeMind(); - var newMind = new Mind.Mind(data.UserId) - { - CharacterName = character.Name - }; - newMind.ChangeOwningPlayer(data.UserId); + var newMind = _mindSystem.CreateMind(data.UserId, character.Name); + _mindSystem.ChangeOwningPlayer(newMind, data.UserId); var jobPrototype = _prototypeManager.Index(jobId); var job = new Job(newMind, jobPrototype); - newMind.AddRole(job); + _mindSystem.AddRole(newMind, job); _playTimeTrackings.PlayerRolesChanged(player); @@ -189,7 +186,7 @@ namespace Content.Server.GameTicking DebugTools.AssertNotNull(mobMaybe); var mob = mobMaybe!.Value; - newMind.TransferTo(mob); + _mindSystem.TransferTo(newMind, mob); if (lateJoin) { @@ -278,15 +275,15 @@ namespace Content.Server.GameTicking DebugTools.AssertNotNull(data); data!.WipeMind(); - var newMind = new Mind.Mind(data.UserId); - newMind.ChangeOwningPlayer(data.UserId); - newMind.AddRole(new ObserverRole(newMind)); + var newMind = _mindSystem.CreateMind(data.UserId); + _mindSystem.ChangeOwningPlayer(newMind, data.UserId); + _mindSystem.AddRole(newMind, new ObserverRole(newMind)); var mob = SpawnObserverMob(); EntityManager.GetComponent(mob).EntityName = name; var ghost = EntityManager.GetComponent(mob); EntitySystem.Get().SetCanReturnToBody(ghost, false); - newMind.TransferTo(mob); + _mindSystem.TransferTo(newMind, mob); _playerGameStatuses[player.UserId] = PlayerGameStatus.JoinedGame; RaiseNetworkEvent(GetStatusSingle(player, PlayerGameStatus.JoinedGame)); diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index bf2c7946db..4e36b8d5f1 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -6,6 +6,8 @@ using Content.Server.GameTicking.Rules.Configurations; using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Events; using Content.Server.Humanoid; +using Content.Server.Humanoid.Systems; +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Server.NPC.Systems; using Content.Server.Nuke; @@ -53,7 +55,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly MapLoaderSystem _map = default!; [Dependency] private readonly ShuttleSystem _shuttle = default!; - + [Dependency] private readonly RandomHumanoidSystem _randomHumanoid = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; private enum WinType { @@ -168,10 +171,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem private void OnComponentInit(EntityUid uid, NukeOperativeComponent component, ComponentInit args) { // If entity has a prior mind attached, add them to the players list. - if (!TryComp(uid, out var mindComponent) || !RuleAdded) + if (!TryComp(uid, out var mindContainerComponent) || !RuleAdded) return; - var session = mindComponent.Mind?.Session; + var session = mindContainerComponent.Mind?.Session; var name = MetaData(uid).EntityName; if (session != null) _operativePlayers.Add(name, session); @@ -573,18 +576,18 @@ public sealed class NukeopsRuleSystem : GameRuleSystem private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args) { - if (!TryComp(uid, out var mindComponent) || mindComponent.Mind == null) + if (!TryComp(uid, out var mindContainerComponent) || mindContainerComponent.Mind == null) return; - var mind = mindComponent.Mind; + var mind = mindContainerComponent.Mind; if (_operativeMindPendingData.TryGetValue(uid, out var role)) { - mind.AddRole(new TraitorRole(mind, _prototypeManager.Index(role))); + _mindSystem.AddRole(mind, new TraitorRole(mind, _prototypeManager.Index(role))); _operativeMindPendingData.Remove(uid); } - if (!mind.TryGetSession(out var playerSession)) + if (!_mindSystem.TryGetSession(mind, out var playerSession)) return; if (_operativePlayers.ContainsValue(playerSession)) return; @@ -758,14 +761,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem var mob = EntityManager.SpawnEntity(species.Prototype, _random.Pick(spawns)); SetupOperativeEntity(mob, spawnDetails.Name, spawnDetails.Gear, profile); - var newMind = new Mind.Mind(session.UserId) - { - CharacterName = spawnDetails.Name - }; - newMind.ChangeOwningPlayer(session.UserId); - newMind.AddRole(new TraitorRole(newMind, nukeOpsAntag)); + var newMind = _mindSystem.CreateMind(session.UserId, spawnDetails.Name); + _mindSystem.ChangeOwningPlayer(newMind, session.UserId); + _mindSystem.AddRole(newMind, new TraitorRole(newMind, nukeOpsAntag)); - newMind.TransferTo(mob); + _mindSystem.TransferTo(newMind, mob); } else if (addSpawnPoints) { @@ -801,7 +801,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem if (!mind.OwnedEntity.HasValue) return; - mind.AddRole(new TraitorRole(mind, _prototypeManager.Index(_nukeopsRuleConfig.OperativeRoleProto))); + _mindSystem.AddRole(mind, new TraitorRole(mind, _prototypeManager.Index(_nukeopsRuleConfig.OperativeRoleProto))); SetOutfitCommand.SetOutfit(mind.OwnedEntity.Value, "SyndicateOperativeGearFull", EntityManager); } @@ -862,10 +862,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem } // Add pre-existing nuke operatives to the credit list. - var query = EntityQuery(true); + var query = EntityQuery(true); foreach (var (_, mindComp) in query) { - if (mindComp.Mind == null || !mindComp.Mind.TryGetSession(out var session)) + if (!mindComp.HasMind || !_mindSystem.TryGetSession(mindComp.Mind, out var session)) continue; var name = MetaData(mindComp.Owner).EntityName; _operativePlayers.Add(name, session); diff --git a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs index c5877ab513..d917342e1b 100644 --- a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Server.Administration.Commands; using Content.Server.Cargo.Systems; using Content.Server.Chat.Managers; +using Content.Server.Mind; using Content.Server.Preferences.Managers; using Content.Server.Spawners.Components; using Content.Server.Station.Components; @@ -38,6 +39,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem [Dependency] private readonly PricingSystem _pricingSystem = default!; [Dependency] private readonly MapLoaderSystem _map = default!; [Dependency] private readonly NamingSystem _namingSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; [ViewVariables] private List _pirates = new(); @@ -206,16 +208,13 @@ public sealed class PiratesRuleSystem : GameRuleSystem var name = _namingSystem.GetName("Human", gender); var session = ops[i]; - var newMind = new Mind.Mind(session.UserId) - { - CharacterName = name - }; - newMind.ChangeOwningPlayer(session.UserId); + var newMind = _mindSystem.CreateMind(session.UserId, name); + _mindSystem.ChangeOwningPlayer(newMind, session.UserId); var mob = Spawn("MobHuman", _random.Pick(spawns)); MetaData(mob).EntityName = name; - newMind.TransferTo(mob); + _mindSystem.TransferTo(newMind, mob); var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile; _stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile); diff --git a/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs b/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs index 3c82367182..d9e3abba08 100644 --- a/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading; using Content.Server.Chat.Managers; using Content.Server.GameTicking.Rules.Configurations; +using Content.Server.Mind; using Content.Server.Players; using Content.Server.Roles; using Content.Server.Station.Components; @@ -50,6 +51,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem [Dependency] private readonly SharedDoorSystem _doorSystem = default!; [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; [Dependency] private readonly UplinkSystem _uplink = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public override string Prototype => "Suspicion"; @@ -171,11 +173,11 @@ public sealed class SuspicionRuleSystem : GameRuleSystem DebugTools.AssertNotNull(mind?.OwnedEntity); var traitorRole = new SuspicionTraitorRole(mind!, antagPrototype); - mind!.AddRole(traitorRole); + _mindSystem.AddRole(mind!, traitorRole); traitors.Add(traitorRole); // try to place uplink - _uplink.AddUplink(mind.OwnedEntity!.Value, traitorStartingBalance); + _uplink.AddUplink(mind!.OwnedEntity!.Value, traitorStartingBalance); } foreach (var player in list) @@ -185,7 +187,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem DebugTools.AssertNotNull(mind); - mind!.AddRole(new SuspicionInnocentRole(mind, antagPrototype)); + _mindSystem.AddRole(mind!, new SuspicionInnocentRole(mind!, antagPrototype)); } foreach (var traitor in traitors) @@ -204,8 +206,11 @@ public sealed class SuspicionRuleSystem : GameRuleSystem _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-added-announcement")); - var filter = Filter.Empty() - .AddWhere(session => ((IPlayerSession) session).ContentData()?.Mind?.HasRole() ?? false); + var filter = Filter.Empty().AddWhere(session => + { + var mind = ((IPlayerSession) session).ContentData()?.Mind; + return mind != null && _mindSystem.HasRole(mind); + }); SoundSystem.Play(_addedSound.GetSound(), filter, AudioParams.Default); @@ -301,7 +306,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem var mind = playerSession.ContentData()?.Mind; - if (mind != null && mind.HasRole()) + if (mind != null && _mindSystem.HasRole(mind)) traitorsAlive++; else innocentsAlive++; diff --git a/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs index f1bb0c1ee7..4c3f6214a4 100644 --- a/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; using Content.Server.GameTicking.Rules.Configurations; using Content.Server.Hands.Components; +using Content.Server.Mind; using Content.Server.PDA; using Content.Server.Players; using Content.Server.Spawners.Components; @@ -41,6 +42,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly UplinkSystem _uplink = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public override string Prototype => "TraitorDeathMatch"; @@ -81,7 +83,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem var antagPrototype = _prototypeManager.Index(TraitorPrototypeID); var traitorRole = new TraitorRole(mind, antagPrototype); - mind.AddRole(traitorRole); + _mindSystem.AddRole(mind, traitorRole); // Delete anything that may contain "dangerous" role-specific items. // (This includes the PDA, as everybody gets the captain PDA in this mode for true-all-access reasons.) diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index e005357f1f..9fee214de4 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Chat.Managers; +using Content.Server.Mind; using Content.Server.Objectives.Interfaces; using Content.Server.Players; using Content.Server.Roles; @@ -36,6 +37,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly UplinkSystem _uplink = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; private ISawmill _sawmill = default!; @@ -254,7 +256,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem var antagPrototype = _prototypeManager.Index(TraitorPrototypeID); var traitorRole = new TraitorRole(mind, antagPrototype); - mind.AddRole(traitorRole); + _mindSystem.AddRole(mind, traitorRole); Traitors.Add(traitorRole); traitorRole.GreetTraitor(Codewords); @@ -270,7 +272,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem { var objective = _objectivesManager.GetRandomObjective(traitorRole.Mind, "TraitorObjectiveGroups"); if (objective == null) continue; - if (traitorRole.Mind.TryAddObjective(objective)) + if (_mindSystem.TryAddObjective(traitorRole.Mind, objective)) difficulty += objective.Difficulty; } @@ -342,7 +344,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem foreach (var traitor in Traitors) { var name = traitor.Mind.CharacterName; - traitor.Mind.TryGetSession(out var session); + _mindSystem.TryGetSession(traitor.Mind, out var session); var username = session?.Name; var objectives = traitor.Mind.AllObjectives.ToArray(); diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index f51a22b42d..126032a780 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Chat.Managers; using Content.Server.Disease; using Content.Server.Disease.Components; using Content.Server.Humanoid; +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Server.Players; using Content.Server.Popups; @@ -43,6 +44,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem [Dependency] private readonly ActionsSystem _action = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly ZombifyOnDeathSystem _zombify = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; private Dictionary _initialInfectedNames = new(); @@ -102,7 +104,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem { var meta = MetaData(survivor); var username = string.Empty; - if (TryComp(survivor, out var mindcomp)) + if (TryComp(survivor, out var mindcomp)) if (mindcomp.Mind != null && mindcomp.Mind.Session != null) username = mindcomp.Mind.Session.Name; @@ -284,7 +286,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem DebugTools.AssertNotNull(mind.OwnedEntity); - mind.AddRole(new TraitorRole(mind, _prototypeManager.Index(PatientZeroPrototypeID))); + _mindSystem.AddRole(mind, new TraitorRole(mind, _prototypeManager.Index(PatientZeroPrototypeID))); var inCharacterName = string.Empty; if (mind.OwnedEntity != null) diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 5213ec83c0..2b6dd4f716 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -92,13 +92,13 @@ namespace Content.Server.Ghost if (EntityManager.HasComponent(uid)) return; - if (!EntityManager.TryGetComponent(uid, out var mind) || !mind.HasMind || mind.Mind!.IsVisitingEntity) + if (!EntityManager.TryGetComponent(uid, out var mind) || !mind.HasMind || mind.Mind.IsVisitingEntity) return; if (component.MustBeDead && (_mobState.IsAlive(uid) || _mobState.IsCritical(uid))) return; - _ticker.OnGhostAttempt(mind.Mind!, component.CanReturn); + _ticker.OnGhostAttempt(mind.Mind, component.CanReturn); } private void OnGhostStartup(EntityUid uid, GhostComponent component, ComponentStartup args) @@ -199,7 +199,7 @@ namespace Content.Server.Ghost return; } - actor.PlayerSession.ContentData()!.Mind?.UnVisit(); + _mindSystem.UnVisit(actor.PlayerSession.ContentData()!.Mind); } private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args) @@ -236,7 +236,7 @@ namespace Content.Server.Ghost if (Deleted(uid) || Terminating(uid)) return; - if (EntityManager.TryGetComponent(uid, out var mind)) + if (EntityManager.TryGetComponent(uid, out var mind)) _mindSystem.SetGhostOnShutdown(uid, false, mind); EntityManager.DeleteEntity(uid); } @@ -260,7 +260,7 @@ namespace Content.Server.Ghost { if (attached == except) continue; - TryComp(attached, out var mind); + TryComp(attached, out var mind); string playerInfo = $"{EntityManager.GetComponent(attached).EntityName} ({mind?.Mind?.CurrentJob?.Name ?? "Unknown"})"; diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs index 720bd5387e..0833f738bc 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs @@ -48,7 +48,7 @@ namespace Content.Server.Ghost.Roles.Components if (MakeSentient) MakeSentientCommand.MakeSentient(mob, _entMan, AllowMovement, AllowSpeech); - mob.EnsureComponent(); + mob.EnsureComponent(); var ghostRoleSystem = EntitySystem.Get(); ghostRoleSystem.GhostRoleInternalCreateMindAndTransfer(session, Owner, mob, this); diff --git a/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs b/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs index 0a9a9435f9..e6eec1d37b 100644 --- a/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs @@ -17,7 +17,7 @@ namespace Content.Server.Ghost.Roles.Components Taken = true; - var mind = Owner.EnsureComponent(); + var mind = Owner.EnsureComponent(); if (mind.HasMind) return false; diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 00f53c27fa..4e17477b34 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -3,6 +3,7 @@ using Content.Server.EUI; using Content.Server.Ghost.Components; using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.UI; +using Content.Server.Mind; using Content.Server.Mind.Components; using Content.Server.Players; using Content.Shared.Administration; @@ -30,6 +31,7 @@ namespace Content.Server.Ghost.Roles [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly FollowerSystem _followerSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; private uint _nextRoleIdentifier; private bool _needsUpdateGhostRoleCount = true; @@ -204,14 +206,12 @@ namespace Content.Server.Ghost.Roles DebugTools.AssertNotNull(contentData); - var newMind = new Mind.Mind(player.UserId) - { - CharacterName = EntityManager.GetComponent(mob).EntityName - }; - newMind.AddRole(new GhostRoleMarkerRole(newMind, role.RoleName)); + var newMind = _mindSystem.CreateMind(player.UserId, + EntityManager.GetComponent(mob).EntityName); + _mindSystem.AddRole(newMind, new GhostRoleMarkerRole(newMind, role.RoleName)); - newMind.ChangeOwningPlayer(player.UserId); - newMind.TransferTo(mob); + _mindSystem.ChangeOwningPlayer(newMind, player.UserId); + _mindSystem.TransferTo(newMind, mob); } public GhostRoleInfo[] GetGhostRolesInfo() diff --git a/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs b/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs index 128afc20bc..ceeeb52077 100644 --- a/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs +++ b/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs @@ -35,7 +35,7 @@ namespace Content.Server.Ghost.Roles return; } - if (entityManager.TryGetComponent(uid, out MindComponent? mind) && + if (entityManager.TryGetComponent(uid, out MindContainerComponent? mind) && mind.HasMind) { shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a mind."); diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index b418e6a6e3..295ceb05d1 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -246,7 +246,7 @@ namespace Content.Server.Medical.BiomassReclaimer // Reject souled bodies in easy mode. if (_configManager.GetCVar(CCVars.BiomassEasyMode) && HasComp(dragged) && - TryComp(dragged, out var mindComp)) + TryComp(dragged, out var mindComp)) { if (mindComp.Mind?.UserId != null && _playerManager.TryGetSessionById(mindComp.Mind.UserId.Value, out _)) return false; diff --git a/Content.Server/Mind/Commands/MakeSentientCommand.cs b/Content.Server/Mind/Commands/MakeSentientCommand.cs index d7f7f7087b..22ca8ebde0 100644 --- a/Content.Server/Mind/Commands/MakeSentientCommand.cs +++ b/Content.Server/Mind/Commands/MakeSentientCommand.cs @@ -44,7 +44,7 @@ namespace Content.Server.Mind.Commands public static void MakeSentient(EntityUid uid, IEntityManager entityManager, bool allowMovement = true, bool allowSpeech = true) { - entityManager.EnsureComponent(uid); + entityManager.EnsureComponent(uid); if (allowMovement) { entityManager.EnsureComponent(uid); diff --git a/Content.Server/Mind/Commands/MindInfoCommand.cs b/Content.Server/Mind/Commands/MindInfoCommand.cs index fc2b0ff001..749135d260 100644 --- a/Content.Server/Mind/Commands/MindInfoCommand.cs +++ b/Content.Server/Mind/Commands/MindInfoCommand.cs @@ -40,7 +40,7 @@ namespace Content.Server.Mind.Commands } var builder = new StringBuilder(); - builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedComponent?.Owner); + builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity); foreach (var role in mind.AllRoles) { builder.AppendFormat("{0} ", role.Name); diff --git a/Content.Server/Mind/Commands/RenameCommand.cs b/Content.Server/Mind/Commands/RenameCommand.cs index 7b3cb92c16..5fdb311f2e 100644 --- a/Content.Server/Mind/Commands/RenameCommand.cs +++ b/Content.Server/Mind/Commands/RenameCommand.cs @@ -48,7 +48,7 @@ public sealed class RenameCommand : IConsoleCommand var entSysMan = IoCManager.Resolve(); - if (entMan.TryGetComponent(entityUid, out MindComponent? mind) && mind.Mind != null) + if (entMan.TryGetComponent(entityUid, out MindContainerComponent? mind) && mind.Mind != null) { // Mind mind.Mind.CharacterName = name; diff --git a/Content.Server/Mind/Components/MindComponent.cs b/Content.Server/Mind/Components/MindContainerComponent.cs similarity index 88% rename from Content.Server/Mind/Components/MindComponent.cs rename to Content.Server/Mind/Components/MindContainerComponent.cs index 325e92123c..5dad7a46c5 100644 --- a/Content.Server/Mind/Components/MindComponent.cs +++ b/Content.Server/Mind/Components/MindContainerComponent.cs @@ -1,10 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using YamlDotNet.Core.Tokens; + namespace Content.Server.Mind.Components { /// /// Stores a on a mob. /// [RegisterComponent, Access(typeof(MindSystem))] - public sealed class MindComponent : Component + public sealed class MindContainerComponent : Component { /// /// The mind controlling this mob. Can be null. @@ -17,6 +20,7 @@ namespace Content.Server.Mind.Components /// True if we have a mind, false otherwise. /// [ViewVariables] + [MemberNotNullWhen(true, nameof(Mind))] public bool HasMind => Mind != null; /// diff --git a/Content.Server/Mind/Components/VisitingMindComponent.cs b/Content.Server/Mind/Components/VisitingMindComponent.cs index e7b81c0cc7..38e3c18c21 100644 --- a/Content.Server/Mind/Components/VisitingMindComponent.cs +++ b/Content.Server/Mind/Components/VisitingMindComponent.cs @@ -3,14 +3,8 @@ namespace Content.Server.Mind.Components [RegisterComponent] public sealed class VisitingMindComponent : Component { - [ViewVariables] public Mind Mind { get; set; } = default!; - - protected override void OnRemove() - { - base.OnRemove(); - - Mind?.UnVisit(); - } + [ViewVariables] + public Mind Mind { get; set; } = default!; } public sealed class MindUnvisitedMessage : EntityEventArgs diff --git a/Content.Server/Mind/Mind.cs b/Content.Server/Mind/Mind.cs index c022300b89..81f26d55a9 100644 --- a/Content.Server/Mind/Mind.cs +++ b/Content.Server/Mind/Mind.cs @@ -1,19 +1,9 @@ -using System.Diagnostics.CodeAnalysis; using System.Linq; -using Content.Server.Administration.Logs; -using Content.Server.GameTicking; -using Content.Server.Ghost.Components; using Content.Server.Mind.Components; using Content.Server.Objectives; -using Content.Server.Players; using Content.Server.Roles; -using Content.Shared.Database; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Network; -using Robust.Shared.Utility; namespace Content.Server.Mind { @@ -29,16 +19,9 @@ namespace Content.Server.Mind /// public sealed class Mind { - private readonly MobStateSystem _mobStateSystem = default!; - private readonly GameTicker _gameTickerSystem = default!; - private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; + internal readonly ISet Roles = new HashSet(); - private readonly ISet _roles = new HashSet(); - - private readonly List _objectives = new(); + internal readonly List Objectives = new(); public string Briefing = String.Empty; @@ -48,36 +31,32 @@ namespace Content.Server.Mind /// The provided UserId is solely for tracking of intended owner. /// /// The session ID of the original owner (may get credited). - public Mind(NetUserId userId) + public Mind(NetUserId? userId) { OriginalOwnerUserId = userId; - IoCManager.InjectDependencies(this); - _entityManager.EntitySysManager.Resolve(ref _mobStateSystem); - _entityManager.EntitySysManager.Resolve(ref _gameTickerSystem); - _entityManager.EntitySysManager.Resolve(ref _mindSystem); } - // TODO: This session should be able to be changed, probably. /// /// The session ID of the player owning this mind. /// [ViewVariables] - public NetUserId? UserId { get; private set; } + public NetUserId? UserId { get; internal set; } /// /// The session ID of the original owner, if any. /// May end up used for round-end information (as the owner may have abandoned Mind since) /// [ViewVariables] - public NetUserId OriginalOwnerUserId { get; } + public NetUserId? OriginalOwnerUserId { get; } [ViewVariables] public bool IsVisitingEntity => VisitingEntity != null; [ViewVariables] - public EntityUid? VisitingEntity { get; private set; } + public EntityUid? VisitingEntity { get; set; } - [ViewVariables] public EntityUid? CurrentEntity => VisitingEntity ?? OwnedEntity; + [ViewVariables] + public EntityUid? CurrentEntity => VisitingEntity ?? OwnedEntity; [ViewVariables(VVAccess.ReadWrite)] public string? CharacterName { get; set; } @@ -87,33 +66,33 @@ namespace Content.Server.Mind /// Can be null - will be null if the Mind is not considered "dead". /// [ViewVariables] - public TimeSpan? TimeOfDeath { get; set; } = null; + public TimeSpan? TimeOfDeath { get; set; } /// /// The component currently owned by this mind. /// Can be null. /// [ViewVariables] - public MindComponent? OwnedComponent { get; private set; } + public MindContainerComponent? OwnedComponent { get; internal set; } /// /// The entity currently owned by this mind. /// Can be null. /// [ViewVariables] - public EntityUid? OwnedEntity => OwnedComponent?.Owner; + public EntityUid? OwnedEntity { get; internal set; } /// /// An enumerable over all the roles this mind has. /// [ViewVariables] - public IEnumerable AllRoles => _roles; + public IEnumerable AllRoles => Roles; /// /// An enumerable over all the objectives this mind has. /// [ViewVariables] - public IEnumerable AllObjectives => _objectives; + public IEnumerable AllObjectives => Objectives; /// /// Prevents user from ghosting out @@ -134,343 +113,11 @@ namespace Content.Server.Mind /// Can be null, in which case the player is currently not logged in. /// [ViewVariables] - public IPlayerSession? Session - { - get - { - if (!UserId.HasValue) - { - return null; - } - _playerManager.TryGetSessionById(UserId.Value, out var ret); - return ret; - } - } - - /// - /// True if this Mind is 'sufficiently dead' IC (objectives, endtext). - /// Note that this is *IC logic*, it's not necessarily tied to any specific truth. - /// "If administrators decide that zombies are dead, this returns true for zombies." - /// (Maybe you were looking for the action blocker system?) - /// - [ViewVariables] - 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 - { - // This is written explicitly so that the logic can be understood. - // But it's also weird and potentially situational. - // Specific considerations when updating this: - // + Does being turned into a borg (if/when implemented) count as dead? - // *If not, add specific conditions to users of this property where applicable.* - // + Is being transformed into a donut 'dead'? - // TODO: Consider changing the way ghost roles work. - // Mind is an *IC* mind, therefore ghost takeover is IC revival right now. - // + Is it necessary to have a reference to a specific 'mind iteration' to cycle when certain events happen? - // (If being a borg or AI counts as dead, then this is highly likely, as it's still the same Mind for practical purposes.) - - // This can be null if they're deleted (spike / brain nom) - var targetMobState = _entityManager.GetComponentOrNull(OwnedEntity); - // This can be null if it's a brain (this happens very often) - // Brains are the result of gibbing so should definitely count as dead - if (targetMobState == null) - return true; - // They might actually be alive. - return _mobStateSystem.IsDead(OwnedEntity!.Value, targetMobState); - } - } - - /// - /// A string to represent the mind for logging - /// - private string MindOwnerLoggingString - { - get - { - if (OwnedEntity != null) - return _entityManager.ToPrettyString(OwnedEntity.Value); - if (UserId != null) - return UserId.Value.ToString(); - return "(originally " + OriginalOwnerUserId + ")"; - } - } - - /// - /// Gives this mind a new role. - /// - /// The type of the role to give. - /// The instance of the role. - /// - /// Thrown if we already have a role with this type. - /// - public Role AddRole(Role role) - { - if (_roles.Contains(role)) - { - throw new ArgumentException($"We already have this role: {role}"); - } - - _roles.Add(role); - role.Greet(); - - var message = new RoleAddedEvent(this, role); - if (OwnedEntity != null) - { - _entityManager.EventBus.RaiseLocalEvent(OwnedEntity.Value, message, true); - } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'{role.Name}' added to mind of {MindOwnerLoggingString}"); - - return role; - } - - /// - /// Removes a role from this mind. - /// - /// The type of the role to remove. - /// - /// Thrown if we do not have this role. - /// - public void RemoveRole(Role role) - { - if (!_roles.Contains(role)) - { - throw new ArgumentException($"We do not have this role: {role}"); - } - - _roles.Remove(role); - - var message = new RoleRemovedEvent(this, role); - - if (OwnedEntity != null) - { - _entityManager.EventBus.RaiseLocalEvent(OwnedEntity.Value, message, true); - } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'{role.Name}' removed from mind of {MindOwnerLoggingString}"); - } - - public bool HasRole() where T : Role - { - var t = typeof(T); - - return _roles.Any(role => role.GetType() == t); - } + public IPlayerSession? Session { get; internal set; } /// /// Gets the current job /// - public Job? CurrentJob => _roles.OfType().SingleOrDefault(); - - /// - /// Adds an objective to this mind. - /// - public bool TryAddObjective(ObjectivePrototype objectivePrototype) - { - if (!objectivePrototype.CanBeAssigned(this)) - return false; - var objective = objectivePrototype.GetObjective(this); - if (_objectives.Contains(objective)) - return false; - - foreach (var condition in objective.Conditions) - _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' added to mind of {MindOwnerLoggingString}"); - - - _objectives.Add(objective); - return true; - } - - /// - /// Removes an objective to this mind. - /// - /// Returns true if the removal succeeded. - public bool TryRemoveObjective(int index) - { - if (_objectives.Count >= index) return false; - - var objective = _objectives[index]; - - foreach (var condition in objective.Conditions) - _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' removed from the mind of {MindOwnerLoggingString}"); - - _objectives.Remove(objective); - return true; - } - - /// - /// Transfer this mind's control over to a new entity. - /// - /// - /// The entity to control. - /// Can be null, in which case it will simply detach the mind from any entity. - /// - /// - /// If true, skips ghost check for Visiting Entity - /// - /// - /// Thrown if is already owned by another mind. - /// - public void TransferTo(EntityUid? entity, bool ghostCheckOverride = false) - { - // Looks like caller just wants us to go back to normal. - if (entity == OwnedEntity) - { - UnVisit(); - return; - } - - MindComponent? component = null; - var alreadyAttached = false; - - if (entity != null) - { - if (!_entityManager.TryGetComponent(entity.Value, out component)) - { - component = _entityManager.AddComponent(entity.Value); - } - else if (component.HasMind) - { - _gameTickerSystem.OnGhostAttempt(component.Mind!, false); - } - - if (_entityManager.TryGetComponent(entity.Value, out var actor)) - { - // Happens when transferring to your currently visited entity. - if (actor.PlayerSession != Session) - { - throw new ArgumentException("Visit target already has a session.", nameof(entity)); - } - - alreadyAttached = true; - } - } - - if(OwnedComponent != null) - _mindSystem.InternalEjectMind(OwnedComponent.Owner, OwnedComponent); - - OwnedComponent = component; - if(OwnedComponent != null) - _mindSystem.InternalAssignMind(OwnedComponent.Owner, this, OwnedComponent); - - // Don't do the full deletion cleanup if we're transferring to our visitingentity - if (alreadyAttached) - { - // Set VisitingEntity null first so the removal of VisitingMind doesn't get through Unvisit() and delete what we're visiting. - // Yes this control flow sucks. - VisitingEntity = null; - _entityManager.RemoveComponent(entity!.Value); - } - else if (VisitingEntity != null - && (ghostCheckOverride // to force mind transfer, for example from ControlMobVerb - || !_entityManager.TryGetComponent(VisitingEntity!, out GhostComponent? ghostComponent) // visiting entity is not a Ghost - || !ghostComponent.CanReturnToBody)) // it is a ghost, but cannot return to body anyway, so it's okay - { - RemoveVisitingEntity(); - } - - // Player is CURRENTLY connected. - if (Session != null && !alreadyAttached && VisitingEntity == null) - { - Session.AttachToEntity(entity); - Logger.Info($"Session {Session.Name} transferred to entity {entity}."); - } - } - - public void ChangeOwningPlayer(NetUserId? newOwner) - { - var playerMgr = IoCManager.Resolve(); - PlayerData? newOwnerData = null; - - if (newOwner.HasValue) - { - if (!playerMgr.TryGetPlayerData(newOwner.Value, out var uncast)) - { - // This restriction is because I'm too lazy to initialize the player data - // for a client that hasn't logged in yet. - // Go ahead and remove it if you need. - throw new ArgumentException("new owner must have previously logged into the server."); - } - - newOwnerData = uncast.ContentData(); - } - - // Make sure to remove control from our old owner if they're logged in. - var oldSession = Session; - oldSession?.AttachToEntity(null); - - if (UserId.HasValue) - { - var data = playerMgr.GetPlayerData(UserId.Value).ContentData(); - DebugTools.AssertNotNull(data); - data!.UpdateMindFromMindChangeOwningPlayer(null); - } - - UserId = newOwner; - if (!newOwner.HasValue) - { - return; - } - - // Yank new owner out of their old mind too. - // Can I mention how much I love the word yank? - DebugTools.AssertNotNull(newOwnerData); - newOwnerData!.Mind?.ChangeOwningPlayer(null); - newOwnerData.UpdateMindFromMindChangeOwningPlayer(this); - } - - public void Visit(EntityUid entity) - { - Session?.AttachToEntity(entity); - VisitingEntity = entity; - - var comp = _entityManager.AddComponent(entity); - comp.Mind = this; - - Logger.Info($"Session {Session?.Name} visiting entity {entity}."); - } - - /// - /// Returns the mind to its original entity. - /// - public void UnVisit() - { - var currentEntity = Session?.AttachedEntity; - Session?.AttachToEntity(OwnedEntity); - RemoveVisitingEntity(); - - if (Session != null && OwnedEntity != null && currentEntity != OwnedEntity) - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"{Session.Name} returned to {_entityManager.ToPrettyString(OwnedEntity.Value)}"); - } - - /// - /// Cleans up the VisitingEntity. - /// - private void RemoveVisitingEntity() - { - if (VisitingEntity == null) - return; - - var oldVisitingEnt = VisitingEntity.Value; - // Null this before removing the component to avoid any infinite loops. - VisitingEntity = null; - - DebugTools.AssertNotNull(oldVisitingEnt); - _entityManager.RemoveComponent(oldVisitingEnt); - _entityManager.EventBus.RaiseLocalEvent(oldVisitingEnt, new MindUnvisitedMessage(), true); - } - - public bool TryGetSession([NotNullWhen(true)] out IPlayerSession? session) - { - return (session = Session) != null; - } + public Job? CurrentJob => Roles.OfType().SingleOrDefault(); } } diff --git a/Content.Server/Mind/MindSystem.cs b/Content.Server/Mind/MindSystem.cs index f9101234ce..1bc99ab78e 100644 --- a/Content.Server/Mind/MindSystem.cs +++ b/Content.Server/Mind/MindSystem.cs @@ -1,13 +1,24 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Administration.Logs; using Content.Server.GameTicking; using Content.Server.Ghost; using Content.Server.Ghost.Components; using Content.Server.Mind.Components; +using Content.Server.Objectives; +using Content.Server.Players; +using Content.Server.Roles; +using Content.Shared.Database; using Content.Shared.Examine; -using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Interaction.Events; +using Content.Shared.Mobs.Components; +using Robust.Server.GameObjects; +using Robust.Server.Player; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Mind; @@ -17,17 +28,25 @@ public sealed class MindSystem : EntitySystem [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly GhostSystem _ghostSystem = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnSuicide); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnSuicide); + SubscribeLocalEvent(OnVisitingMindRemoved); } - public void SetGhostOnShutdown(EntityUid uid, bool value, MindComponent? mind = null) + private void OnVisitingMindRemoved(EntityUid uid, VisitingMindComponent component, ComponentRemove args) + { + UnVisit(component.Mind); + } + + public void SetGhostOnShutdown(EntityUid uid, bool value, MindContainerComponent? mind = null) { if (!Resolve(uid, ref mind)) return; @@ -37,10 +56,10 @@ public sealed class MindSystem : EntitySystem /// /// Don't call this unless you know what the hell you're doing. - /// Use instead. + /// Use instead. /// If that doesn't cover it, make something to cover it. /// - public void InternalAssignMind(EntityUid uid, Mind value, MindComponent? mind = null) + private void InternalAssignMind(EntityUid uid, Mind value, MindContainerComponent? mind = null) { if (!Resolve(uid, ref mind)) return; @@ -51,10 +70,10 @@ public sealed class MindSystem : EntitySystem /// /// Don't call this unless you know what the hell you're doing. - /// Use instead. + /// Use instead. /// If that doesn't cover it, make something to cover it. /// - public void InternalEjectMind(EntityUid uid, MindComponent? mind = null) + private void InternalEjectMind(EntityUid uid, MindContainerComponent? mind = null) { if (!Resolve(uid, ref mind)) return; @@ -65,109 +84,501 @@ public sealed class MindSystem : EntitySystem mind.Mind = null; } - private void OnShutdown(EntityUid uid, MindComponent mind, ComponentShutdown args) + private void OnShutdown(EntityUid uid, MindContainerComponent mindContainerComp, ComponentShutdown args) { // Let's not create ghosts if not in the middle of the round. if (_gameTicker.RunLevel != GameRunLevel.InRound) return; - if (mind.HasMind) + if (!TryGetMind(uid, out var mind, mindContainerComp)) + return; + + if (mind.VisitingEntity is {Valid: true} visiting) { - if (mind.Mind?.VisitingEntity is {Valid: true} visiting) + if (TryComp(visiting, out GhostComponent? ghost)) { - if (TryComp(visiting, out GhostComponent? ghost)) + _ghostSystem.SetCanReturnToBody(ghost, false); + } + + TransferTo(mind, visiting); + } + else if (mindContainerComp.GhostOnShutdown) + { + // Changing an entities parents while deleting is VERY sus. This WILL throw exceptions. + // TODO: just find the applicable spawn position directly without actually updating the transform's parent. + Transform(uid).AttachToGridOrMap(); + var spawnPosition = Transform(uid).Coordinates; + + // Use a regular timer here because the entity has probably been deleted. + Timer.Spawn(0, () => + { + // Make extra sure the round didn't end between spawning the timer and it being executed. + if (_gameTicker.RunLevel != GameRunLevel.InRound) + return; + + // Async this so that we don't throw if the grid we're on is being deleted. + var gridId = spawnPosition.GetGridUid(EntityManager); + if (!spawnPosition.IsValid(EntityManager) || gridId == EntityUid.Invalid || !_mapManager.GridExists(gridId)) { - _ghostSystem.SetCanReturnToBody(ghost, false); + spawnPosition = _gameTicker.GetObserverSpawnPoint(); } - mind.Mind!.TransferTo(visiting); - } - else if (mind.GhostOnShutdown) - { - // Changing an entities parents while deleting is VERY sus. This WILL throw exceptions. - // TODO: just find the applicable spawn position dirctly without actually updating the transform's parent. - Transform(uid).AttachToGridOrMap(); - var spawnPosition = Transform(uid).Coordinates; - - // Use a regular timer here because the entity has probably been deleted. - Timer.Spawn(0, () => + // TODO refactor observer spawning. + // please. + if (!spawnPosition.IsValid(EntityManager)) { - // Make extra sure the round didn't end between spawning the timer and it being executed. - if (_gameTicker.RunLevel != GameRunLevel.InRound) - return; + // This should be an error, if it didn't cause tests to start erroring when they delete a player. + Logger.WarningS("mind", $"Entity \"{ToPrettyString(uid)}\" for {mind.CharacterName} was deleted, and no applicable spawn location is available."); + TransferTo(mind, null); + return; + } - // Async this so that we don't throw if the grid we're on is being deleted. - var gridId = spawnPosition.GetGridUid(EntityManager); - if (!spawnPosition.IsValid(EntityManager) || gridId == EntityUid.Invalid || !_mapManager.GridExists(gridId)) - { - spawnPosition = _gameTicker.GetObserverSpawnPoint(); - } + var ghost = Spawn("MobObserver", spawnPosition); + var ghostComponent = Comp(ghost); + _ghostSystem.SetCanReturnToBody(ghostComponent, false); - // TODO refactor observer spawning. - // please. - if (!spawnPosition.IsValid(EntityManager)) - { - // This should be an error, if it didn't cause tests to start erroring when they delete a player. - Logger.WarningS("mind", $"Entity \"{ToPrettyString(uid)}\" for {mind.Mind?.CharacterName} was deleted, and no applicable spawn location is available."); - mind.Mind?.TransferTo(null); - return; - } + // Log these to make sure they're not causing the GameTicker round restart bugs... + Logger.DebugS("mind", $"Entity \"{ToPrettyString(uid)}\" for {mind.CharacterName} was deleted, spawned \"{ToPrettyString(ghost)}\"."); - var ghost = Spawn("MobObserver", spawnPosition); - var ghostComponent = Comp(ghost); - _ghostSystem.SetCanReturnToBody(ghostComponent, false); - - // Log these to make sure they're not causing the GameTicker round restart bugs... - Logger.DebugS("mind", $"Entity \"{ToPrettyString(uid)}\" for {mind.Mind?.CharacterName} was deleted, spawned \"{ToPrettyString(ghost)}\"."); - - if (mind.Mind == null) - return; - - var val = mind.Mind.CharacterName ?? string.Empty; - MetaData(ghost).EntityName = val; - mind.Mind.TransferTo(ghost); - }); - } + var val = mind.CharacterName ?? string.Empty; + MetaData(ghost).EntityName = val; + TransferTo(mind, ghost); + }); } } - private void OnExamined(EntityUid uid, MindComponent mind, ExaminedEvent args) + private void OnExamined(EntityUid uid, MindContainerComponent mindContainer, ExaminedEvent args) { - if (!mind.ShowExamineInfo || !args.IsInDetailsRange) - { + if (!mindContainer.ShowExamineInfo || !args.IsInDetailsRange) return; - } - var dead = TryComp(uid, out var state) && _mobStateSystem.IsDead(uid, state); + var dead = _mobStateSystem.IsDead(uid); + var hasSession = mindContainer.Mind?.Session; - if (dead) - { - if (mind.Mind?.Session == null) { - // Player has no session attached and dead - args.PushMarkup($"[color=yellow]{Loc.GetString("mind-component-no-mind-and-dead-text", ("ent", uid))}[/color]"); - } else { - // Player is dead with session - args.PushMarkup($"[color=red]{Loc.GetString("comp-mind-examined-dead", ("ent", uid))}[/color]"); - } - } - else if (!mind.HasMind) - { + if (dead && hasSession == null) + args.PushMarkup($"[color=yellow]{Loc.GetString("comp-mind-examined-dead-and-ssd", ("ent", uid))}[/color]"); + else if (dead) + args.PushMarkup($"[color=red]{Loc.GetString("comp-mind-examined-dead", ("ent", uid))}[/color]"); + else if (!mindContainer.HasMind) args.PushMarkup($"[color=mediumpurple]{Loc.GetString("comp-mind-examined-catatonic", ("ent", uid))}[/color]"); - } - else if (mind.Mind?.Session == null) - { + else if (hasSession == null) args.PushMarkup($"[color=yellow]{Loc.GetString("comp-mind-examined-ssd", ("ent", uid))}[/color]"); - } } - private void OnSuicide(EntityUid uid, MindComponent component, SuicideEvent args) + private void OnSuicide(EntityUid uid, MindContainerComponent component, SuicideEvent args) { if (args.Handled) return; - if (component.HasMind && component.Mind!.PreventSuicide) + if (component.HasMind && component.Mind.PreventSuicide) { args.BlockSuicideAttempt(true); } } + + public Mind? GetMind(EntityUid uid, MindContainerComponent? mind = null) + { + if (!Resolve(uid, ref mind)) + return null; + + if (mind.HasMind) + return mind.Mind; + return null; + } + + public Mind CreateMind(NetUserId? userId, string? name = null) + { + var mind = new Mind(userId); + mind.CharacterName = name; + ChangeOwningPlayer(mind, userId); + return mind; + } + + /// + /// 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. + /// + public bool IsCharacterDeadPhysically(Mind mind) + { + // This is written explicitly so that the logic can be understood. + // But it's also weird and potentially situational. + // Specific considerations when updating this: + // + Does being turned into a borg (if/when implemented) count as dead? + // *If not, add specific conditions to users of this property where applicable.* + // + Is being transformed into a donut 'dead'? + // TODO: Consider changing the way ghost roles work. + // Mind is an *IC* mind, therefore ghost takeover is IC revival right now. + // + Is it necessary to have a reference to a specific 'mind iteration' to cycle when certain events happen? + // (If being a borg or AI counts as dead, then this is highly likely, as it's still the same Mind for practical purposes.) + + if (mind.OwnedEntity == null) + return true; + + // This can be null if they're deleted (spike / brain nom) + var targetMobState = EntityManager.GetComponentOrNull(mind.OwnedEntity); + // This can be null if it's a brain (this happens very often) + // Brains are the result of gibbing so should definitely count as dead + if (targetMobState == null) + return true; + // They might actually be alive. + return _mobStateSystem.IsDead(mind.OwnedEntity.Value, targetMobState); + } + + public void Visit(Mind mind, EntityUid entity) + { + mind.Session?.AttachToEntity(entity); + mind.VisitingEntity = entity; + + var comp = AddComp(entity); + comp.Mind = mind; + + Logger.Info($"Session {mind.Session?.Name} visiting entity {entity}."); + } + + /// + /// Returns the mind to its original entity. + /// + public void UnVisit(Mind? mind) + { + if (mind == null) + return; + + var currentEntity = mind.Session?.AttachedEntity; + mind.Session?.AttachToEntity(mind.OwnedEntity); + RemoveVisitingEntity(mind); + + var owned = mind.OwnedEntity; + if (mind.Session != null && owned != null && owned != currentEntity) + { + _adminLogger.Add(LogType.Mind, LogImpact.Low, + $"{mind.Session.Name} returned to {ToPrettyString(owned.Value)}"); + } + } + + /// + /// Cleans up the VisitingEntity. + /// + /// + private void RemoveVisitingEntity(Mind mind) + { + if (mind.VisitingEntity == null) + return; + + var oldVisitingEnt = mind.VisitingEntity.Value; + // Null this before removing the component to avoid any infinite loops. + mind.VisitingEntity = null; + + DebugTools.AssertNotNull(oldVisitingEnt); + RemComp(oldVisitingEnt); + RaiseLocalEvent(oldVisitingEnt, new MindUnvisitedMessage(), true); + } + + /// + /// Transfer this mind's control over to a new entity. + /// + /// The mind to transfer + /// + /// The entity to control. + /// Can be null, in which case it will simply detach the mind from any entity. + /// + /// + /// If true, skips ghost check for Visiting Entity + /// + /// + /// Thrown if is already owned by another mind. + /// + public void TransferTo(Mind mind, EntityUid? entity, bool ghostCheckOverride = false) + { + // Looks like caller just wants us to go back to normal. + if (entity == mind.OwnedEntity) + { + UnVisit(mind); + return; + } + + MindContainerComponent? component = null; + var alreadyAttached = false; + + if (entity != null) + { + if (!TryComp(entity.Value, out component)) + { + component = AddComp(entity.Value); + } + else if (component.HasMind) + { + _gameTicker.OnGhostAttempt(component.Mind, false); + } + + if (TryComp(entity.Value, out var actor)) + { + // Happens when transferring to your currently visited entity. + if (actor.PlayerSession != mind.Session) + { + throw new ArgumentException("Visit target already has a session.", nameof(entity)); + } + + alreadyAttached = true; + } + } + + var oldComp = mind.OwnedComponent; + var oldEntity = mind.OwnedEntity; + if(oldComp != null && oldEntity != null) + InternalEjectMind(oldEntity.Value, oldComp); + + SetOwnedEntity(mind, entity, component); + if (mind.OwnedComponent != null) + InternalAssignMind(mind.OwnedEntity!.Value, mind, mind.OwnedComponent); + + // Don't do the full deletion cleanup if we're transferring to our VisitingEntity + if (alreadyAttached) + { + // Set VisitingEntity null first so the removal of VisitingMind doesn't get through Unvisit() and delete what we're visiting. + // Yes this control flow sucks. + mind.VisitingEntity = null; + RemComp(entity!.Value); + } + else if (mind.VisitingEntity != null + && (ghostCheckOverride // to force mind transfer, for example from ControlMobVerb + || !TryComp(mind.VisitingEntity!, out GhostComponent? ghostComponent) // visiting entity is not a Ghost + || !ghostComponent.CanReturnToBody)) // it is a ghost, but cannot return to body anyway, so it's okay + { + RemoveVisitingEntity(mind); + } + + // Player is CURRENTLY connected. + if (mind.Session != null && !alreadyAttached && mind.VisitingEntity == null) + { + mind.Session.AttachToEntity(entity); + Logger.Info($"Session {mind.Session.Name} transferred to entity {entity}."); + } + } + + public void ChangeOwningPlayer(Mind mind, NetUserId? newOwner) + { + // Make sure to remove control from our old owner if they're logged in. + var oldSession = mind.Session; + oldSession?.AttachToEntity(null); + + if (mind.UserId.HasValue) + { + if (_playerManager.TryGetPlayerData(mind.UserId.Value, out var oldUncast)) + { + var data = oldUncast.ContentData(); + DebugTools.AssertNotNull(data); + data!.UpdateMindFromMindChangeOwningPlayer(null); + } + else + { + Logger.Warning($"Mind UserId {newOwner} is does not exist in PlayerManager"); + } + } + + SetUserId(mind, newOwner); + if (!newOwner.HasValue) + { + return; + } + + if (!_playerManager.TryGetPlayerData(newOwner.Value, out var uncast)) + { + // This restriction is because I'm too lazy to initialize the player data + // for a client that hasn't logged in yet. + // Go ahead and remove it if you need. + throw new ArgumentException("New owner must have previously logged into the server.", nameof(newOwner)); + } + + // PlayerData? newOwnerData = null; + var newOwnerData = uncast.ContentData(); + + // Yank new owner out of their old mind too. + // Can I mention how much I love the word yank? + DebugTools.AssertNotNull(newOwnerData); + if (newOwnerData!.Mind != null) + ChangeOwningPlayer(newOwnerData.Mind, null); + newOwnerData.UpdateMindFromMindChangeOwningPlayer(mind); + } + + /// + /// Adds an objective to this mind. + /// + public bool TryAddObjective(Mind mind, ObjectivePrototype objectivePrototype) + { + if (!objectivePrototype.CanBeAssigned(mind)) + return false; + var objective = objectivePrototype.GetObjective(mind); + if (mind.Objectives.Contains(objective)) + return false; + + foreach (var condition in objective.Conditions) + { + _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' added to mind of {MindOwnerLoggingString(mind)}"); + } + + + mind.Objectives.Add(objective); + return true; + } + + /// + /// Removes an objective to this mind. + /// + /// Returns true if the removal succeeded. + public bool TryRemoveObjective(Mind mind, int index) + { + if (mind.Objectives.Count >= index) return false; + + var objective = mind.Objectives[index]; + + foreach (var condition in objective.Conditions) + { + _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' removed from the mind of {MindOwnerLoggingString(mind)}"); + } + + mind.Objectives.Remove(objective); + return true; + } + + /// + /// Gives this mind a new role. + /// + /// The mind to add the role to. + /// The type of the role to give. + /// The instance of the role. + /// + /// Thrown if we already have a role with this type. + /// + public void AddRole(Mind mind, Role role) + { + if (mind.Roles.Contains(role)) + { + throw new ArgumentException($"We already have this role: {role}"); + } + + mind.Roles.Add(role); + role.Greet(); + + var message = new RoleAddedEvent(mind, role); + if (mind.OwnedEntity != null) + { + RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + } + + _adminLogger.Add(LogType.Mind, LogImpact.Low, + $"'{role.Name}' added to mind of {MindOwnerLoggingString(mind)}"); + } + + /// + /// Removes a role from this mind. + /// + /// The mind to remove the role from. + /// The type of the role to remove. + /// + /// Thrown if we do not have this role. + /// + public void RemoveRole(Mind mind, Role role) + { + if (!mind.Roles.Contains(role)) + { + throw new ArgumentException($"We do not have this role: {role}"); + } + + mind.Roles.Remove(role); + + var message = new RoleRemovedEvent(mind, role); + + if (mind.OwnedEntity != null) + { + RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + } + _adminLogger.Add(LogType.Mind, LogImpact.Low, + $"'{role.Name}' removed from mind of {MindOwnerLoggingString(mind)}"); + } + + public bool HasRole(Mind mind) where T : Role + { + var t = typeof(T); + + return mind.Roles.Any(role => role.GetType() == t); + } + + public bool TryGetSession(Mind mind, [NotNullWhen(true)] out IPlayerSession? session) + { + return (session = mind.Session) != null; + } + + /// + /// Gets a mind from uid and/or MindContainerComponent. Used for null checks. + /// + /// Entity UID that owns the mind. + /// The returned mind. + /// Mind component on to get the mind from. + /// True if mind found. False if not. + public bool TryGetMind(EntityUid uid, [NotNullWhen(true)] out Mind? mind, MindContainerComponent? mindContainerComponent = null) + { + mind = null; + if (!Resolve(uid, ref mindContainerComponent)) + return false; + + if (!mindContainerComponent.HasMind) + return false; + + mind = mindContainerComponent.Mind; + return true; + } + + /// + /// Sets the Mind's OwnedComponent and OwnedEntity + /// + /// Mind to set OwnedComponent and OwnedEntity on + /// Entity owned by + /// MindContainerComponent owned by + private void SetOwnedEntity(Mind mind, EntityUid? uid, MindContainerComponent? mindContainerComponent) + { + if (uid != null) + Resolve(uid.Value, ref mindContainerComponent); + + mind.OwnedEntity = uid; + mind.OwnedComponent = mindContainerComponent; + } + + /// + /// Sets the Mind's UserId and Session + /// + /// + /// + private void SetUserId(Mind mind, NetUserId? userId) + { + mind.UserId = userId; + + if (!userId.HasValue) + return; + + _playerManager.TryGetSessionById(userId.Value, out var ret); + mind.Session = ret; + } + + /// + /// True if this Mind is 'sufficiently dead' IC (Objectives, EndText). + /// Note that this is *IC logic*, it's not necessarily tied to any specific truth. + /// "If administrators decide that zombies are dead, this returns true for zombies." + /// (Maybe you were looking for the action blocker system?) + /// + public bool IsCharacterDeadIc(Mind mind) + { + return IsCharacterDeadPhysically(mind); + } + + /// + /// A string to represent the mind for logging + /// + private string MindOwnerLoggingString(Mind mind) + { + if (mind.OwnedEntity != null) + return ToPrettyString(mind.OwnedEntity.Value); + if (mind.UserId != null) + return mind.UserId.Value.ToString(); + return "(originally " + mind.OriginalOwnerUserId + ")"; + } } diff --git a/Content.Server/Mind/MindTrackerSystem.cs b/Content.Server/Mind/MindTrackerSystem.cs index 49f2400b08..2ab76ce9af 100644 --- a/Content.Server/Mind/MindTrackerSystem.cs +++ b/Content.Server/Mind/MindTrackerSystem.cs @@ -20,7 +20,7 @@ namespace Content.Server.Mind base.Initialize(); SubscribeLocalEvent(Reset); - SubscribeLocalEvent(OnMindAdded); + SubscribeLocalEvent(OnMindAdded); } void Reset(RoundRestartCleanupEvent ev) @@ -28,7 +28,7 @@ namespace Content.Server.Mind AllMinds.Clear(); } - void OnMindAdded(EntityUid uid, MindComponent mc, MindAddedMessage args) + void OnMindAdded(EntityUid uid, MindContainerComponent mc, MindAddedMessage args) { var mind = mc.Mind; if (mind != null) diff --git a/Content.Server/Mind/TransferMindOnGibSystem.cs b/Content.Server/Mind/TransferMindOnGibSystem.cs index 1aed10c324..d499d6a4dd 100644 --- a/Content.Server/Mind/TransferMindOnGibSystem.cs +++ b/Content.Server/Mind/TransferMindOnGibSystem.cs @@ -15,6 +15,7 @@ public sealed class TransferMindOnGibSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; /// public override void Initialize() @@ -24,7 +25,7 @@ public sealed class TransferMindOnGibSystem : EntitySystem private void OnGib(EntityUid uid, TransferMindOnGibComponent component, BeingGibbedEvent args) { - if (!TryComp(uid, out var mindcomp) || mindcomp.Mind == null) + if (!TryComp(uid, out var mindcomp) || mindcomp.Mind == null) return; var validParts = args.GibbedParts.Where(p => _tag.HasTag(p, component.TargetTag)).ToHashSet(); @@ -32,6 +33,6 @@ public sealed class TransferMindOnGibSystem : EntitySystem return; var ent = _random.Pick(validParts); - mindcomp.Mind.TransferTo(ent); + _mindSystem.TransferTo(mindcomp.Mind, ent); } } diff --git a/Content.Server/Objectives/Commands/AddObjectiveCommand.cs b/Content.Server/Objectives/Commands/AddObjectiveCommand.cs index ea4372b1aa..7353763b63 100644 --- a/Content.Server/Objectives/Commands/AddObjectiveCommand.cs +++ b/Content.Server/Objectives/Commands/AddObjectiveCommand.cs @@ -1,4 +1,5 @@ using Content.Server.Administration; +using Content.Server.Mind; using Content.Server.Players; using Content.Shared.Administration; using Robust.Server.Player; @@ -10,6 +11,8 @@ namespace Content.Server.Objectives.Commands [AdminCommand(AdminFlags.Admin)] public sealed class AddObjectiveCommand : IConsoleCommand { + [Dependency] private readonly IEntityManager _entityManager = default!; + public string Command => "addobjective"; public string Description => "Adds an objective to the player's mind."; public string Help => "addobjective "; @@ -42,8 +45,10 @@ namespace Content.Server.Objectives.Commands shell.WriteLine($"Can't find matching ObjectivePrototype {objectivePrototype}"); return; } + + var mindSystem = _entityManager.System(); - if (!mind.TryAddObjective(objectivePrototype)) + if (!mindSystem.TryAddObjective(mind, objectivePrototype)) { shell.WriteLine("Objective requirements dont allow that objective to be added."); } diff --git a/Content.Server/Objectives/Commands/RemoveObjectiveCommand.cs b/Content.Server/Objectives/Commands/RemoveObjectiveCommand.cs index 96979ab6fa..c0846a1e43 100644 --- a/Content.Server/Objectives/Commands/RemoveObjectiveCommand.cs +++ b/Content.Server/Objectives/Commands/RemoveObjectiveCommand.cs @@ -1,4 +1,5 @@ using Content.Server.Administration; +using Content.Server.Mind; using Content.Server.Players; using Content.Shared.Administration; using Robust.Server.Player; @@ -9,6 +10,8 @@ namespace Content.Server.Objectives.Commands [AdminCommand(AdminFlags.Admin)] public sealed class RemoveObjectiveCommand : IConsoleCommand { + [Dependency] private readonly IEntityManager _entityManager = default!; + public string Command => "rmobjective"; public string Description => "Removes an objective from the player's mind."; public string Help => "rmobjective "; @@ -32,7 +35,8 @@ namespace Content.Server.Objectives.Commands if (int.TryParse(args[1], out var i)) { - shell.WriteLine(mind.TryRemoveObjective(i) + var mindSystem = _entityManager.System(); + shell.WriteLine(mindSystem.TryRemoveObjective(mind, i) ? "Objective successfully removed!" : "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!"); } diff --git a/Content.Server/Objectives/Conditions/DieCondition.cs b/Content.Server/Objectives/Conditions/DieCondition.cs index 340b5ba60c..b6ec92804a 100644 --- a/Content.Server/Objectives/Conditions/DieCondition.cs +++ b/Content.Server/Objectives/Conditions/DieCondition.cs @@ -1,3 +1,4 @@ +using Content.Server.Mind; using Content.Server.Objectives.Interfaces; using JetBrains.Annotations; using Robust.Shared.Utility; @@ -21,7 +22,15 @@ namespace Content.Server.Objectives.Conditions public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResourcePath("Mobs/Ghosts/ghost_human.rsi"), "icon"); - public float Progress => (_mind?.CharacterDeadIC ?? true) ? 1f : 0f; + public float Progress + { + get + { + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + return _mind == null || mindSystem.IsCharacterDeadIc(_mind) ? 1f : 0f; + } + } public float Difficulty => 0.5f; diff --git a/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs b/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs index ea86c2c7d6..1a34cdd34d 100644 --- a/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs +++ b/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs @@ -1,3 +1,4 @@ +using Content.Server.Mind; using Content.Server.Objectives.Interfaces; using Content.Server.Station.Components; using Content.Shared.Cuffs.Components; @@ -46,13 +47,14 @@ namespace Content.Server.Objectives.Conditions { get { var entMan = IoCManager.Resolve(); + var mindSystem = entMan.System(); if (_mind?.OwnedEntity == null || !entMan.TryGetComponent(_mind.OwnedEntity, out var xform)) return 0f; var shuttleContainsAgent = false; - var agentIsAlive = !_mind.CharacterDeadIC; + var agentIsAlive = !mindSystem.IsCharacterDeadIc(_mind); var agentIsEscaping = true; if (entMan.TryGetComponent(_mind.OwnedEntity, out var cuffed) diff --git a/Content.Server/Objectives/Conditions/KillPersonCondition.cs b/Content.Server/Objectives/Conditions/KillPersonCondition.cs index 2ae101f40f..34b06cd094 100644 --- a/Content.Server/Objectives/Conditions/KillPersonCondition.cs +++ b/Content.Server/Objectives/Conditions/KillPersonCondition.cs @@ -1,3 +1,4 @@ +using Content.Server.Mind; using Content.Server.Objectives.Interfaces; using Content.Shared.Mobs.Systems; using Robust.Shared.Utility; @@ -32,7 +33,15 @@ namespace Content.Server.Objectives.Conditions public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResourcePath("Objects/Weapons/Guns/Pistols/viper.rsi"), "icon"); - public float Progress => (Target?.CharacterDeadIC ?? true) ? 1f : 0f; + public float Progress + { + get + { + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + return Target == null || mindSystem.IsCharacterDeadIc(Target) ? 1f : 0f; + } + } public float Difficulty => 2f; diff --git a/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs b/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs index 5cdef407dd..d84b75fff3 100644 --- a/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs +++ b/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs @@ -13,7 +13,7 @@ namespace Content.Server.Objectives.Conditions { public override IObjectiveCondition GetAssigned(Mind.Mind mind) { - var allHumans = EntityManager.EntityQuery(true).Where(mc => + var allHumans = EntityManager.EntityQuery(true).Where(mc => { var entity = mc.Mind?.OwnedEntity; diff --git a/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs b/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs index 8906a2b737..87a43bfdee 100644 --- a/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs +++ b/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs @@ -3,6 +3,7 @@ using Content.Server.Objectives.Interfaces; using Robust.Shared.Random; using Robust.Shared.Utility; using Content.Server.GameTicking.Rules; +using Content.Server.Mind; namespace Content.Server.Objectives.Conditions { @@ -41,7 +42,15 @@ namespace Content.Server.Objectives.Conditions public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResourcePath("Objects/Misc/bureaucracy.rsi"), "folder-white"); - public float Progress => (!_target?.CharacterDeadIC ?? true) ? 1f : 0f; + public float Progress + { + get + { + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + return _target == null || mindSystem.IsCharacterDeadIc(_target) ? 1f : 0f; + } + } public float Difficulty => 1.75f; diff --git a/Content.Server/Objectives/Requirements/TraitorRequirement.cs b/Content.Server/Objectives/Requirements/TraitorRequirement.cs index 3f25ce593a..ae08b77973 100644 --- a/Content.Server/Objectives/Requirements/TraitorRequirement.cs +++ b/Content.Server/Objectives/Requirements/TraitorRequirement.cs @@ -1,4 +1,5 @@ -using Content.Server.Objectives.Interfaces; +using Content.Server.Mind; +using Content.Server.Objectives.Interfaces; using Content.Server.Traitor; using JetBrains.Annotations; @@ -10,7 +11,9 @@ namespace Content.Server.Objectives.Requirements { public bool CanBeAssigned(Mind.Mind mind) { - return mind.HasRole(); + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + return mindSystem.HasRole(mind); } } } diff --git a/Content.Server/PAI/PAISystem.cs b/Content.Server/PAI/PAISystem.cs index 2939ae58c0..9454377b16 100644 --- a/Content.Server/PAI/PAISystem.cs +++ b/Content.Server/PAI/PAISystem.cs @@ -33,7 +33,7 @@ namespace Content.Server.PAI { if (args.IsInDetailsRange) { - if (EntityManager.TryGetComponent(uid, out var mind) && mind.HasMind) + if (EntityManager.TryGetComponent(uid, out var mind) && mind.HasMind) { args.PushMarkup(Loc.GetString("pai-system-pai-installed")); } @@ -58,7 +58,7 @@ namespace Content.Server.PAI args.Handled = true; // Check for pAI activation - if (EntityManager.TryGetComponent(uid, out var mind) && mind.HasMind) + if (EntityManager.TryGetComponent(uid, out var mind) && mind.HasMind) { _popupSystem.PopupEntity(Loc.GetString("pai-system-pai-installed"), uid, args.User, PopupType.Large); return; @@ -136,7 +136,7 @@ namespace Content.Server.PAI if (!args.CanAccess || !args.CanInteract) return; - if (EntityManager.TryGetComponent(uid, out var mind) && mind.HasMind) + if (EntityManager.TryGetComponent(uid, out var mind) && mind.HasMind) { ActivationVerb verb = new(); verb.Text = Loc.GetString("pai-system-wipe-device-verb-text"); @@ -146,9 +146,9 @@ namespace Content.Server.PAI // Wiping device :( // The shutdown of the Mind should cause automatic reset of the pAI during OnMindRemoved // EDIT: But it doesn't!!!! Wtf? Do stuff manually - if (EntityManager.HasComponent(uid)) + if (EntityManager.HasComponent(uid)) { - EntityManager.RemoveComponent(uid); + EntityManager.RemoveComponent(uid); _popupSystem.PopupEntity(Loc.GetString("pai-system-wiped-device"), uid, args.User, PopupType.Large); PAITurningOff(uid); } diff --git a/Content.Server/PDA/PDASystem.cs b/Content.Server/PDA/PDASystem.cs index fd8626cf74..4ca293b25a 100644 --- a/Content.Server/PDA/PDASystem.cs +++ b/Content.Server/PDA/PDASystem.cs @@ -4,6 +4,7 @@ using Content.Server.Instruments; using Content.Server.Light.Components; using Content.Server.Light.EntitySystems; using Content.Server.Light.Events; +using Content.Server.Mind; using Content.Server.PDA.Ringer; using Content.Server.Store.Components; using Content.Server.Store.Systems; @@ -27,6 +28,7 @@ namespace Content.Server.PDA [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!; [Dependency] private readonly StoreSystem _storeSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { @@ -124,8 +126,10 @@ namespace Content.Server.PDA if (session.AttachedEntity is not { Valid: true } user) continue; - if (storeComponent.AccountOwner == user || (TryComp(session.AttachedEntity, out var mindcomp) && mindcomp.Mind != null && - mindcomp.Mind.HasRole())) + if (storeComponent.AccountOwner == user + || (TryComp(session.AttachedEntity, out var mindComp) + && mindComp.Mind != null + && _mindSystem.HasRole(mindComp.Mind))) _cartridgeLoaderSystem?.UpdateUiState(pda.Owner, uplinkState, session); } } @@ -181,9 +185,10 @@ namespace Content.Server.PDA if (!TryComp(pda.Owner, out var storeComp)) return; - if (storeComp.AccountOwner != args.User && - !(TryComp(args.User, out var mindcomp) && mindcomp.Mind != null && mindcomp.Mind.HasRole())) - return; + if (storeComp.AccountOwner != args.User + && !(TryComp(args.User, out var mindComp) + && mindComp.Mind != null + && _mindSystem.HasRole(mindComp.Mind))) return; if (!_uiSystem.TryGetUi(pda.Owner, PDAUiKey.Key, out var ui)) return; diff --git a/Content.Server/ParticleAccelerator/Components/ParticleAcceleratorControlBoxComponent.cs b/Content.Server/ParticleAccelerator/Components/ParticleAcceleratorControlBoxComponent.cs index 435b285421..58ea128a3c 100644 --- a/Content.Server/ParticleAccelerator/Components/ParticleAcceleratorControlBoxComponent.cs +++ b/Content.Server/ParticleAccelerator/Components/ParticleAcceleratorControlBoxComponent.cs @@ -456,9 +456,9 @@ namespace Content.Server.ParticleAccelerator.Components } // Logging - _entMan.TryGetComponent(playerSession?.AttachedEntity, out MindComponent? mindComponent); - if(mindComponent != null) - _adminLogger.Add(LogType.Action, LogImpact.Low, $"{_entMan.ToPrettyString(mindComponent.Owner):player} has set {_entMan.ToPrettyString(Owner)} to on"); + _entMan.TryGetComponent(playerSession?.AttachedEntity, out MindContainerComponent? mindContainerComponent); + if(mindContainerComponent != null) + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{_entMan.ToPrettyString(mindContainerComponent.Owner):player} has set {_entMan.ToPrettyString(Owner)} to on"); _isEnabled = true; UpdatePowerDraw(); @@ -480,9 +480,9 @@ namespace Content.Server.ParticleAccelerator.Components public void SwitchOff(IPlayerSession? playerSession = null, bool rescan = false) { // Logging - _entMan.TryGetComponent(playerSession?.AttachedEntity, out MindComponent? mindComponent); - if(mindComponent != null) - _adminLogger.Add(LogType.Action, LogImpact.Low, $"{_entMan.ToPrettyString(mindComponent.Owner):player} has set {_entMan.ToPrettyString(Owner)} to off{(rescan ? " via rescan" : "")}"); + _entMan.TryGetComponent(playerSession?.AttachedEntity, out MindContainerComponent? mindContainerComponent); + if(mindContainerComponent != null) + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{_entMan.ToPrettyString(mindContainerComponent.Owner):player} has set {_entMan.ToPrettyString(Owner)} to off{(rescan ? " via rescan" : "")}"); _isEnabled = false; PowerOff(); @@ -535,7 +535,7 @@ namespace Content.Server.ParticleAccelerator.Components UpdatePartVisualStates(); // Logging - _entMan.TryGetComponent(playerSession?.AttachedEntity, out MindComponent? mindComponent); + _entMan.TryGetComponent(playerSession?.AttachedEntity, out MindContainerComponent? mindContainerComponent); LogImpact impact; switch (state) { @@ -552,8 +552,8 @@ namespace Content.Server.ParticleAccelerator.Components impact = LogImpact.Extreme; break; } - if(mindComponent != null) - _adminLogger.Add(LogType.Action, impact, $"{_entMan.ToPrettyString(mindComponent.Owner):player} has set the strength of {_entMan.ToPrettyString(Owner)} to {state}"); + if(mindContainerComponent != null) + _adminLogger.Add(LogType.Action, impact, $"{_entMan.ToPrettyString(mindContainerComponent.Owner):player} has set the strength of {_entMan.ToPrettyString(Owner)} to {state}"); if (_isEnabled) { diff --git a/Content.Server/Players/PlayerData.cs b/Content.Server/Players/PlayerData.cs index f811dfee76..c8e368cfed 100644 --- a/Content.Server/Players/PlayerData.cs +++ b/Content.Server/Players/PlayerData.cs @@ -1,3 +1,4 @@ +using Content.Server.Mind; using Robust.Server.Player; using Robust.Shared.Network; @@ -37,9 +38,15 @@ namespace Content.Server.Players public void WipeMind() { - Mind?.TransferTo(null); + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + // This will ensure Mind == null - Mind?.ChangeOwningPlayer(null); + if (Mind == null) + return; + + mindSystem.TransferTo(Mind, null); + mindSystem.ChangeOwningPlayer(Mind, null); } /// diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index c08a92be17..24ffa658fa 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Actions; using Content.Server.Buckle.Systems; using Content.Server.Humanoid; using Content.Server.Inventory; +using Content.Server.Mind; using Content.Server.Mind.Commands; using Content.Server.Mind.Components; using Content.Server.Polymorph.Components; @@ -40,6 +41,7 @@ namespace Content.Server.Polymorph.Systems [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; private readonly ISawmill _saw = default!; @@ -195,8 +197,8 @@ namespace Content.Server.Polymorph.Systems _humanoid.CloneAppearance(uid, child); } - if (TryComp(uid, out var mind) && mind.Mind != null) - mind.Mind.TransferTo(child); + if (_mindSystem.TryGetMind(uid, out var mind)) + _mindSystem.TransferTo(mind, child); //Ensures a map to banish the entity to EnsurePausesdMap(); @@ -272,10 +274,8 @@ namespace Content.Server.Polymorph.Systems } } - if (TryComp(uid, out var mind) && mind.Mind != null) - { - mind.Mind.TransferTo(parent); - } + if (_mindSystem.TryGetMind(uid, out var mind)) + _mindSystem.TransferTo(mind, parent); _popup.PopupEntity(Loc.GetString("polymorph-revert-popup-generic", ("parent", Identity.Entity(uid, EntityManager)), diff --git a/Content.Server/Revenant/EntitySystems/EssenceSystem.cs b/Content.Server/Revenant/EntitySystems/EssenceSystem.cs index a466573344..5f95da95e8 100644 --- a/Content.Server/Revenant/EntitySystems/EssenceSystem.cs +++ b/Content.Server/Revenant/EntitySystems/EssenceSystem.cs @@ -67,7 +67,7 @@ public sealed class EssenceSystem : EntitySystem switch (mob.CurrentState) { case MobState.Alive: - if (TryComp(uid, out var mind) && mind.Mind != null) + if (TryComp(uid, out var mind) && mind.Mind != null) component.EssenceAmount = _random.NextFloat(75f, 100f); else component.EssenceAmount = _random.NextFloat(45f, 70f); diff --git a/Content.Server/Roles/AddRoleCommand.cs b/Content.Server/Roles/AddRoleCommand.cs index 2240d091da..b172873fdf 100644 --- a/Content.Server/Roles/AddRoleCommand.cs +++ b/Content.Server/Roles/AddRoleCommand.cs @@ -6,12 +6,15 @@ using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.Prototypes; using System.Linq; +using Content.Server.Mind; namespace Content.Server.Roles { [AdminCommand(AdminFlags.Admin)] public sealed class AddRoleCommand : IConsoleCommand { + [Dependency] private readonly EntityManager _entityManager = default!; + public string Command => "addrole"; public string Description => "Adds a role to a player's mind."; @@ -54,7 +57,8 @@ namespace Content.Server.Roles } var role = new Job(mind, jobPrototype); - mind.AddRole(role); + var mindSystem = _entityManager.System(); + mindSystem.AddRole(mind, role); } } } diff --git a/Content.Server/Roles/Job.cs b/Content.Server/Roles/Job.cs index cd81dea484..c5c7403600 100644 --- a/Content.Server/Roles/Job.cs +++ b/Content.Server/Roles/Job.cs @@ -2,6 +2,7 @@ using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; using Content.Shared.Roles; using System.Globalization; +using Content.Server.Mind; namespace Content.Server.Roles { @@ -35,8 +36,11 @@ namespace Content.Server.Roles public override void Greet() { base.Greet(); + + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); - if (Mind.TryGetSession(out var session)) + if (mindSystem.TryGetSession(Mind, out var session)) { var chatMgr = IoCManager.Resolve(); chatMgr.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name", diff --git a/Content.Server/Roles/RemoveRoleCommand.cs b/Content.Server/Roles/RemoveRoleCommand.cs index ae0a0f8f4a..00b6492404 100644 --- a/Content.Server/Roles/RemoveRoleCommand.cs +++ b/Content.Server/Roles/RemoveRoleCommand.cs @@ -1,4 +1,5 @@ using Content.Server.Administration; +using Content.Server.Mind; using Content.Server.Players; using Content.Shared.Administration; using Content.Shared.Roles; @@ -12,6 +13,7 @@ namespace Content.Server.Roles public sealed class RemoveRoleCommand : IConsoleCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; public string Command => "rmrole"; @@ -43,7 +45,8 @@ namespace Content.Server.Roles } var role = new Job(mind, _prototypeManager.Index(args[1])); - mind.RemoveRole(role); + var mindSystem = _entityManager.System(); + mindSystem.RemoveRole(mind, role); } } } diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index 8c2e06cf48..ca8c254f7d 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -86,7 +86,7 @@ public sealed class BluespaceLockerSystem : EntitySystem if (component.BehaviorProperties.TransportEntities || component.BehaviorProperties.TransportSentient) foreach (var entity in target.Value.storageComponent.Contents.ContainedEntities.ToArray()) { - if (EntityManager.HasComponent(entity)) + if (EntityManager.HasComponent(entity)) { if (!component.BehaviorProperties.TransportSentient) continue; @@ -297,7 +297,7 @@ public sealed class BluespaceLockerSystem : EntitySystem if (component.BehaviorProperties.TransportEntities || component.BehaviorProperties.TransportSentient) foreach (var entity in entityStorageComponent.Contents.ContainedEntities.ToArray()) { - if (EntityManager.HasComponent(entity)) + if (EntityManager.HasComponent(entity)) { if (!component.BehaviorProperties.TransportSentient) continue; diff --git a/Content.Server/Store/Conditions/BuyerAntagCondition.cs b/Content.Server/Store/Conditions/BuyerAntagCondition.cs index d0f4292dbe..9e16cd1be7 100644 --- a/Content.Server/Store/Conditions/BuyerAntagCondition.cs +++ b/Content.Server/Store/Conditions/BuyerAntagCondition.cs @@ -28,7 +28,7 @@ public sealed class BuyerAntagCondition : ListingCondition { var ent = args.EntityManager; - if (!ent.TryGetComponent(args.Buyer, out var mind) || mind.Mind == null) + if (!ent.TryGetComponent(args.Buyer, out var mind) || mind.Mind == null) return true; if (Blacklist != null) diff --git a/Content.Server/Store/Conditions/BuyerJobCondition.cs b/Content.Server/Store/Conditions/BuyerJobCondition.cs index d867a65d33..b34b6a320b 100644 --- a/Content.Server/Store/Conditions/BuyerJobCondition.cs +++ b/Content.Server/Store/Conditions/BuyerJobCondition.cs @@ -28,7 +28,7 @@ public sealed class BuyerJobCondition : ListingCondition { var ent = args.EntityManager; - if (!ent.TryGetComponent(args.Buyer, out var mind) || mind.Mind == null) + if (!ent.TryGetComponent(args.Buyer, out var mind) || mind.Mind == null) return true; //this is for things like surplus crate if (Blacklist != null) diff --git a/Content.Server/Suspicion/Roles/SuspicionInnocentRole.cs b/Content.Server/Suspicion/Roles/SuspicionInnocentRole.cs index 7c07cbd8df..9703b11fbd 100644 --- a/Content.Server/Suspicion/Roles/SuspicionInnocentRole.cs +++ b/Content.Server/Suspicion/Roles/SuspicionInnocentRole.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Managers; +using Content.Server.Mind; using Content.Shared.Roles; namespace Content.Server.Suspicion.Roles @@ -23,8 +24,10 @@ namespace Content.Server.Suspicion.Roles base.Greet(); var chat = IoCManager.Resolve(); + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); - if (Mind.TryGetSession(out var session)) + if (mindSystem.TryGetSession(Mind, out var session)) { chat.DispatchServerMessage(session, $"You're an {Name}!"); chat.DispatchServerMessage(session, $"Objective: {Objective}"); diff --git a/Content.Server/Suspicion/Roles/SuspicionTraitorRole.cs b/Content.Server/Suspicion/Roles/SuspicionTraitorRole.cs index b42b22da42..87d1fe3593 100644 --- a/Content.Server/Suspicion/Roles/SuspicionTraitorRole.cs +++ b/Content.Server/Suspicion/Roles/SuspicionTraitorRole.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Chat.Managers; +using Content.Server.Mind; using Content.Shared.Roles; namespace Content.Server.Suspicion.Roles @@ -21,7 +22,10 @@ namespace Content.Server.Suspicion.Roles public void GreetSuspicion(List traitors, IChatManager chatMgr) { - if (Mind.TryGetSession(out var session)) + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + + if (mindSystem.TryGetSession(Mind, out var session)) { chatMgr.DispatchServerMessage(session, Loc.GetString("suspicion-role-greeting", ("roleName", Name))); chatMgr.DispatchServerMessage(session, Loc.GetString("suspicion-objective", ("objectiveText", Objective))); diff --git a/Content.Server/Suspicion/SuspicionRoleComponent.cs b/Content.Server/Suspicion/SuspicionRoleComponent.cs index 59964254c8..f95919e3a8 100644 --- a/Content.Server/Suspicion/SuspicionRoleComponent.cs +++ b/Content.Server/Suspicion/SuspicionRoleComponent.cs @@ -67,13 +67,13 @@ namespace Content.Server.Suspicion public void SyncRoles() { - if (!_entMan.TryGetComponent(Owner, out MindComponent? mind) || + if (!_entMan.TryGetComponent(Owner, out MindContainerComponent? mind) || !mind.HasMind) { return; } - Role = mind.Mind!.AllRoles.First(role => role is SuspicionRole); + Role = mind.Mind.AllRoles.First(role => role is SuspicionRole); } public void AddAlly(SuspicionRoleComponent ally) diff --git a/Content.Server/Teleportation/PortalSystem.cs b/Content.Server/Teleportation/PortalSystem.cs index 1730a48ac0..dabcefa9b9 100644 --- a/Content.Server/Teleportation/PortalSystem.cs +++ b/Content.Server/Teleportation/PortalSystem.cs @@ -14,7 +14,7 @@ public sealed class PortalSystem : SharedPortalSystem protected override void LogTeleport(EntityUid portal, EntityUid subject, EntityCoordinates source, EntityCoordinates target) { - if (HasComp(subject)) + if (HasComp(subject)) _adminLogger.Add(LogType.Teleport, LogImpact.Low, $"{ToPrettyString(subject):player} teleported via {ToPrettyString(portal)} from {source} to {target}"); } } diff --git a/Content.Server/Traitor/TraitorRole.cs b/Content.Server/Traitor/TraitorRole.cs index 4e23122e7d..ca16f1a527 100644 --- a/Content.Server/Traitor/TraitorRole.cs +++ b/Content.Server/Traitor/TraitorRole.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Managers; +using Content.Server.Mind; using Content.Server.Roles; using Content.Shared.Roles; @@ -20,7 +21,10 @@ namespace Content.Server.Traitor public void GreetTraitor(string[] codewords) { - if (Mind.TryGetSession(out var session)) + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + + if (mindSystem.TryGetSession(Mind, out var session)) { var chatMgr = IoCManager.Resolve(); chatMgr.DispatchServerMessage(session, Loc.GetString("traitor-role-greeting")); diff --git a/Content.Server/TraitorDeathMatch/TraitorDeathMatchRedemptionSystem.cs b/Content.Server/TraitorDeathMatch/TraitorDeathMatchRedemptionSystem.cs index 9def8fc316..ef81894bc2 100644 --- a/Content.Server/TraitorDeathMatch/TraitorDeathMatchRedemptionSystem.cs +++ b/Content.Server/TraitorDeathMatch/TraitorDeathMatchRedemptionSystem.cs @@ -26,7 +26,7 @@ public sealed class TraitorDeathMatchRedemptionSystem : EntitySystem private void OnInteractUsing(EntityUid uid, TraitorDeathMatchRedemptionComponent component, InteractUsingEvent args) { - if (!EntityManager.TryGetComponent(args.User, out var userMindComponent)) + if (!EntityManager.TryGetComponent(args.User, out var userMindComponent)) { _popup.PopupEntity(Loc.GetString( "traitor-death-match-redemption-component-interact-using-main-message", diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs index 1994b98a7a..572ab6f592 100644 --- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -24,6 +24,7 @@ using Content.Shared.Popups; using Content.Server.Atmos.Miasma; using Content.Server.Humanoid; using Content.Server.IdentityManagement; +using Content.Server.Mind; using Content.Shared.Humanoid; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; @@ -54,6 +55,7 @@ namespace Content.Server.Zombies [Dependency] private readonly EmoteOnDamageSystem _emoteOnDamage = default!; [Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { @@ -196,16 +198,16 @@ namespace Content.Server.Zombies _identity.QueueIdentityUpdate(target); //He's gotta have a mind - var mindcomp = EnsureComp(target); - if (mindcomp.Mind != null && mindcomp.Mind.TryGetSession(out var session)) + var mindComp = EnsureComp(target); + if (_mindSystem.TryGetMind(target, out var mind, mindComp) && _mindSystem.TryGetSession(mind, out var session)) { //Zombie role for player manifest - mindcomp.Mind.AddRole(new TraitorRole(mindcomp.Mind, _proto.Index(zombiecomp.ZombieRoleId))); + _mindSystem.AddRole(mind, new TraitorRole(mind, _proto.Index(zombiecomp.ZombieRoleId))); //Greeting message for new bebe zombers _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); } - if (!HasComp(target) && !mindcomp.HasMind) //this specific component gives build test trouble so pop off, ig + if (!HasComp(target) && !mindComp.HasMind) //this specific component gives build test trouble so pop off, ig { //yet more hardcoding. Visit zombie.ftl for more information. EntityManager.EnsureComponent(target, out var ghostcomp); diff --git a/Resources/Locale/en-US/mind/components/mind-component.ftl b/Resources/Locale/en-US/mind/components/mind-component.ftl index 0711958b70..7374dcd163 100644 --- a/Resources/Locale/en-US/mind/components/mind-component.ftl +++ b/Resources/Locale/en-US/mind/components/mind-component.ftl @@ -1,13 +1,10 @@ -# MindComponent localization +# MindContainerComponent localization comp-mind-ghosting-prevented = You are not able to ghost right now. ## Messages displayed when a body is examined and in a certain state + comp-mind-examined-catatonic = { CAPITALIZE(SUBJECT($ent)) } { CONJUGATE-BE($ent) } totally catatonic. The stresses of life in deep-space must have been too much for { OBJECT($ent) }. Any recovery is unlikely. comp-mind-examined-dead = { CAPITALIZE(POSS-ADJ($ent)) } soul has departed. comp-mind-examined-ssd = { CAPITALIZE(SUBJECT($ent)) } { CONJUGATE-HAVE($ent) } a blank, absent-minded stare and appears completely unresponsive to anything. { CAPITALIZE(SUBJECT($ent)) } may snap out of it soon. - - -mind-component-no-mind-and-alive-text = { CAPITALIZE(POSS-ADJ($ent)) } is totally catatonic. The stresses of life in deep-space must have been too much for them. Any recovery is unlikely. -mind-component-no-mind-and-dead-text = { CAPITALIZE(POSS-ADJ($ent)) } soul has departed and moved on. Any recovery is unlikely. -mind-component-mind-and-no-session-text = { CAPITALIZE(POSS-ADJ($ent)) } { CONJUGATE-HAVE($ent) } a blank, absent-minded stare and appears completely unresponsive to anything. { CAPITALIZE(POSS-ADJ($ent)) } may snap out of it soon. +comp-mind-examined-dead-and-ssd = { CAPITALIZE(POSS-ADJ($ent)) } soul has departed and moved on. Any recovery is unlikely. diff --git a/Resources/Maps/barratry.yml b/Resources/Maps/barratry.yml index d1708d005d..14e1f4ac71 100644 --- a/Resources/Maps/barratry.yml +++ b/Resources/Maps/barratry.yml @@ -56763,7 +56763,7 @@ entities: description: Alas poor Yorick... name: captain's skull type: GhostTakeoverAvailable - - type: Mind + - type: MindContainer - type: InputMover - type: MobMover - type: MovementAlwaysTouching diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index ace5fd81ec..ba22125b13 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -3,7 +3,7 @@ name: revenant description: A spooky ghostie. components: - - type: Mind + - type: MindContainer - type: InputMover - type: MobMover - type: Input diff --git a/Resources/Prototypes/Entities/Mobs/Player/diona.yml b/Resources/Prototypes/Entities/Mobs/Player/diona.yml index 79dfcdcf4b..97c97d0214 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/diona.yml @@ -11,7 +11,7 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: Mind + - type: MindContainer showExamineInfo: true - type: Input context: "human" diff --git a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml index e0d1a3a246..a9dd505bb4 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml @@ -12,7 +12,7 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: Mind + - type: MindContainer showExamineInfo: true - type: Input context: "human" diff --git a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml index d8477281ed..0e887fc40e 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml @@ -21,7 +21,7 @@ - type: Access tags: - Chapel - - type: Mind + - type: MindContainer showExamineInfo: true - type: Alerts - type: Familiar @@ -72,7 +72,7 @@ - type: Access tags: - Chapel - - type: Mind + - type: MindContainer showExamineInfo: true - type: Familiar - type: Vocal diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 60f86a10b8..19fe958bfe 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -11,7 +11,7 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: Mind + - type: MindContainer showExamineInfo: true - type: Input context: "human" diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 8ef363de41..81fc4b3670 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -5,7 +5,7 @@ save: false description: Boo! components: - - type: Mind + - type: MindContainer - type: Clickable - type: InteractionOutline - type: Physics diff --git a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml index 4890c8b6a8..d05950dd18 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml @@ -11,7 +11,7 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: Mind + - type: MindContainer showExamineInfo: true - type: Input context: "human" diff --git a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml index 054e50b921..3764e686e1 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml @@ -8,7 +8,7 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: Mind + - type: MindContainer showExamineInfo: true - type: Input context: "human" diff --git a/Resources/Prototypes/Entities/Mobs/Player/slime.yml b/Resources/Prototypes/Entities/Mobs/Player/slime.yml index c379eeb95e..c56ae9a092 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/slime.yml @@ -10,7 +10,7 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: Mind + - type: MindContainer showExamineInfo: true - type: Input context: "human" diff --git a/Resources/Prototypes/Entities/Mobs/Player/vox.yml b/Resources/Prototypes/Entities/Mobs/Player/vox.yml index a74e95680c..870297ae80 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/vox.yml @@ -11,7 +11,7 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: Mind + - type: MindContainer showExamineInfo: true - type: Input context: "human" @@ -32,4 +32,3 @@ damageRecovery: types: Asphyxiation: -1.0 -