diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
index 6b8760bf59..0f1690d453 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
@@ -38,7 +38,7 @@ namespace Content.IntegrationTests.Tests.Interaction;
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
public abstract partial class InteractionTest
{
- protected virtual string PlayerPrototype => "MobInteractionTestObserver";
+ protected virtual string PlayerPrototype => "InteractionTestMob";
protected PairTracker PairTracker = default!;
protected TestMapData MapData = default!;
@@ -115,38 +115,27 @@ public abstract partial class InteractionTest
public float TickPeriod => (float)STiming.TickPeriod.TotalSeconds;
- [SetUp]
- public virtual async Task Setup()
- {
- const string TestPrototypes = @"
+
+ // Simple mob that has one hand and can perform misc interactions.
+ public const string TestPrototypes = @"
- type: entity
- id: MobInteractionTestObserver
- name: observer
- noSpawn: true
- save: false
- description: Boo!
+ id: InteractionTestMob
components:
- - type: Access
- groups:
- - AllAccess
- type: Body
prototype: Aghost
- type: DoAfter
- - type: Ghost
- canInteract: true
- type: Hands
- - type: Mind
+ - type: MindContainer
- type: Stripping
- type: Tag
tags:
- CanPilot
- - BypassInteractionRangeChecks
- - type: Thieving
- stripTimeReduction: 9999
- stealthy: true
- type: UserInterface
";
+ [SetUp]
+ public virtual async Task Setup()
+ {
PairTracker = await PoolManager.GetServerClient(new PoolSettings{ExtraPrototypes = TestPrototypes});
// server dependencies
diff --git a/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs
new file mode 100644
index 0000000000..bd6ce81d1a
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs
@@ -0,0 +1,103 @@
+#nullable enable
+using System.Threading.Tasks;
+using Content.Server.Ghost.Roles;
+using Content.Server.Ghost.Roles.Components;
+using Content.Server.Mind;
+using Content.Server.Players;
+using NUnit.Framework;
+using Robust.Shared.Console;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.IntegrationTests.Tests.Minds;
+
+[TestFixture]
+public sealed class GhostRoleTests
+{
+ private const string Prototypes = @"
+- type: entity
+ id: GhostRoleTestEntity_Player
+ components:
+ - type: MindContainer
+
+- type: entity
+ id: GhostRoleTestEntity_Role
+ components:
+ - type: MindContainer
+ - type: GhostRole
+ - type: GhostTakeoverAvailable
+";
+
+ ///
+ /// This is a simple test that just checks if a player can take a ghost roll and then regain control of their
+ /// original entity without encountering errors.
+ ///
+ [Test]
+ public async Task TakeRoleAndReturn()
+ {
+ await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings {ExtraPrototypes = Prototypes});
+ var server = pairTracker.Pair.Server;
+ var client = pairTracker.Pair.Client;
+
+ var entMan = server.ResolveDependency();
+ var sPlayerMan = server.ResolveDependency();
+ var conHost = client.ResolveDependency();
+ var cPlayerMan = client.ResolveDependency();
+ var mindSystem = entMan.System();
+
+ // Get player data
+ if (cPlayerMan.LocalPlayer?.Session == null)
+ Assert.Fail("No player");
+
+ var clientSession = cPlayerMan.LocalPlayer!.Session!;
+ var session = sPlayerMan.GetSessionByUserId(clientSession.UserId);
+
+ // Spawn player entity & attach
+ EntityUid originalMob = default;
+ await server.WaitPost(() =>
+ {
+ originalMob = entMan.SpawnEntity("GhostRoleTestEntity_Player", MapCoordinates.Nullspace);
+ mindSystem.TransferTo(session.ContentData()!.Mind!, originalMob, true);
+ });
+
+ // Check player got attached.
+ await PoolManager.RunTicksSync(pairTracker.Pair, 10);
+ Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.EqualTo(originalMob));
+
+ // Use the ghost command
+ conHost.ExecuteCommand("ghost");
+ await PoolManager.RunTicksSync(pairTracker.Pair, 10);
+ Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.Not.EqualTo(originalMob));
+
+ // Spawn ghost takeover entity.
+ EntityUid ghostRole = default;
+ await server.WaitPost(() => ghostRole = entMan.SpawnEntity("GhostRoleTestEntity_Role", MapCoordinates.Nullspace));
+
+ // Take the ghost role
+ await server.WaitPost(() =>
+ {
+ var id = entMan.GetComponent(ghostRole).Identifier;
+ entMan.EntitySysManager.GetEntitySystem().Takeover(session, id);
+ });
+
+ // Check player got attached to ghost role.
+ await PoolManager.RunTicksSync(pairTracker.Pair, 10);
+ Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.EqualTo(ghostRole));
+
+ // Ghost again.
+ conHost.ExecuteCommand("ghost");
+ await PoolManager.RunTicksSync(pairTracker.Pair, 10);
+ Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.Not.EqualTo(originalMob));
+ Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.Not.EqualTo(ghostRole));
+
+ // Next, control the original entity again:
+ await server.WaitPost(() =>
+ {
+ mindSystem.TransferTo(session.ContentData()!.Mind!, originalMob, true);
+ });
+ await PoolManager.RunTicksSync(pairTracker.Pair, 10);
+ Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.EqualTo(originalMob));
+
+ await pairTracker.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs b/Content.IntegrationTests/Tests/Minds/MindEntityDeletionTest.cs
similarity index 84%
rename from Content.IntegrationTests/Tests/MindEntityDeletionTest.cs
rename to Content.IntegrationTests/Tests/Minds/MindEntityDeletionTest.cs
index 93c5fe41f1..82baf4810d 100644
--- a/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs
+++ b/Content.IntegrationTests/Tests/Minds/MindEntityDeletionTest.cs
@@ -1,7 +1,7 @@
+#nullable enable
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 +10,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,9 +26,11 @@ 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;
+ Mind mind = default!;
var map = await PoolManager.CreateTestMap(pairTracker);
await server.WaitAssertion(() =>
@@ -39,11 +41,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));
@@ -54,11 +54,6 @@ namespace Content.IntegrationTests.Tests
await server.WaitAssertion(() =>
{
entMan.DeleteEntity(visitEnt);
- if (mind == null)
- {
- Assert.Fail("Mind was null");
- return;
- }
if (mind.VisitingEntity != null)
{
@@ -91,10 +86,13 @@ 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;
- Mind mind = null;
+ Mind mind = default!;
await server.WaitAssertion(() =>
{
var player = playerMan.ServerSessions.Single();
@@ -103,10 +101,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 +132,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;
+ Mind mind = default!;
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..68c1617554
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs
@@ -0,0 +1,456 @@
+#nullable enable
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Content.Server.Ghost;
+using Content.Server.Ghost.Roles;
+using Content.Server.Mind;
+using Content.Server.Mind.Commands;
+using Content.Server.Mind.Components;
+using Content.Server.Players;
+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.Console;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
+using IPlayerManager = Robust.Server.Player.IPlayerManager;
+
+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();
+ }
+
+ [Test]
+ public async Task TestPlayerCanGhost()
+ {
+ // Client is needed to spawn session
+ await using var pairTracker = await PoolManager.GetServerClient();
+ var server = pairTracker.Pair.Server;
+
+ var entMan = server.ResolveDependency();
+ var playerMan = server.ResolveDependency();
+
+ var mindSystem = entMan.EntitySysManager.GetEntitySystem();
+ var ghostSystem = entMan.EntitySysManager.GetEntitySystem();
+
+ EntityUid entity = default!;
+ Mind mind = default!;
+ IPlayerSession player = playerMan.ServerSessions.Single();
+
+ await server.WaitAssertion(() =>
+ {
+ entity = entMan.SpawnEntity(null, new MapCoordinates());
+ var mindComp = entMan.EnsureComponent(entity);
+
+ mind = mindSystem.CreateMind(player.UserId, "Mindy McThinker");
+
+ Assert.That(mind.UserId, Is.EqualTo(player.UserId));
+
+ mindSystem.TransferTo(mind, entity);
+ Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind));
+ });
+
+ await PoolManager.RunTicksSync(pairTracker.Pair, 5);
+
+ await server.WaitAssertion(() =>
+ {
+ entMan.DeleteEntity(entity);
+ });
+
+ await PoolManager.RunTicksSync(pairTracker.Pair, 5);
+
+ EntityUid mob = default!;
+ Mind mobMind = default!;
+
+ await server.WaitAssertion(() =>
+ {
+ Assert.That(mind.OwnedEntity != null);
+
+ mob = entMan.SpawnEntity(null, new MapCoordinates());
+
+ MakeSentientCommand.MakeSentient(mob, IoCManager.Resolve());
+ mobMind = mindSystem.CreateMind(player.UserId, "Mindy McThinker the Second");
+
+ mindSystem.ChangeOwningPlayer(mobMind, player.UserId);
+ mindSystem.TransferTo(mobMind, mob);
+ });
+
+ await PoolManager.RunTicksSync(pairTracker.Pair, 5);
+
+ await server.WaitAssertion(() =>
+ {
+ var m = player.ContentData()?.Mind;
+ Assert.That(m, Is.Not.EqualTo(null));
+ Assert.That(m!.OwnedEntity, Is.EqualTo(mob));
+ Assert.That(m, Is.Not.EqualTo(mind));
+ });
+
+ await pairTracker.CleanReturnAsync();
+ }
+
+ [Test]
+ public async Task TestPlayerCanReturnFromGhostWhenDead()
+ {
+ // TODO Implement
+ }
+
+ [Test]
+ public async Task TestGhostDoesNotInfiniteLoop()
+ {
+ // Client is needed to spawn session
+ await using var pairTracker = await PoolManager.GetServerClient();
+ var server = pairTracker.Pair.Server;
+
+ var entMan = server.ResolveDependency();
+ var playerMan = server.ResolveDependency();
+ var serverConsole = server.ResolveDependency();
+
+ var mindSystem = entMan.EntitySysManager.GetEntitySystem();
+
+ EntityUid entity = default!;
+ EntityUid mouse = default!;
+ EntityUid ghost = default!;
+ Mind mind = default!;
+ IPlayerSession player = playerMan.ServerSessions.Single();
+
+ await server.WaitAssertion(() =>
+ {
+ // entity = entMan.SpawnEntity(null, new MapCoordinates());
+ // var mindComp = entMan.EnsureComponent(entity);
+
+ // mind = mindSystem.CreateMind(player.UserId, "Mindy McThinker");
+ //
+ // Assert.That(mind.UserId, Is.EqualTo(player.UserId));
+ //
+ // mindSystem.TransferTo(mind, entity);
+ // Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind));
+
+ var data = player.ContentData();
+
+ Assert.That(data?.Mind, Is.Not.EqualTo(null));
+ mind = data!.Mind!;
+
+ Assert.That(mind.OwnedEntity != null);
+
+ mouse = entMan.SpawnEntity("MobMouse", new MapCoordinates());
+ });
+
+ await PoolManager.RunTicksSync(pairTracker.Pair, 120);
+
+ await server.WaitAssertion(() =>
+ {
+ serverConsole.ExecuteCommand(player, "aghost");
+ });
+
+ await PoolManager.RunTicksSync(pairTracker.Pair, 120);
+
+ await server.WaitAssertion(() =>
+ {
+ entMan.EntitySysManager.GetEntitySystem().Takeover(player, 0);
+ });
+
+ await PoolManager.RunTicksSync(pairTracker.Pair, 120);
+
+ await server.WaitAssertion(() =>
+ {
+ var data = player.ContentData()!;
+ Assert.That(data.Mind!.OwnedEntity == mouse);
+
+ serverConsole.ExecuteCommand(player, "aghost");
+ Assert.That(player.AttachedEntity != null);
+ ghost = player.AttachedEntity!.Value;
+ });
+
+ await PoolManager.RunTicksSync(pairTracker.Pair, 60);
+
+ await server.WaitAssertion(() =>
+ {
+ Assert.That(player.AttachedEntity != null);
+ Assert.That(ghost == player.AttachedEntity!.Value);
+ });
+
+ 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..97b1c48ae1 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,11 +34,15 @@ 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))
+ if (mind.VisitingEntity != default && _entities.TryGetComponent(mind.VisitingEntity, out var oldGhostComponent))
{
- player.ContentData()!.Mind?.UnVisit();
- return;
+ mindSystem.UnVisit(mind);
+ // If already an admin ghost, then return to body.
+ if (oldGhostComponent.CanGhostInteract)
+ return;
}
var canReturn = mind.CurrentEntity != null
@@ -56,12 +61,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 7b6ca14ea1..c7e66dc77b 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;
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs
index 5b3c1efc8b..ce5d479851 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs
@@ -8,6 +8,7 @@ using Content.Server.Disposal.Tube;
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;
@@ -53,6 +54,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();
@@ -245,7 +247,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
@@ -279,7 +286,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()
{
@@ -342,7 +349,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 0d2ec47c20..57603c35dc 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;
@@ -29,6 +30,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()
{
@@ -41,16 +43,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 97a76a64d1..0a8dd5dc6a 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;
@@ -13,6 +14,7 @@ namespace Content.Server.Body.Systems
public sealed class BrainSystem : EntitySystem
{
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+ [Dependency] private readonly MindSystem _mindSystem = default!;
public override void Initialize()
{
@@ -38,8 +40,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))
@@ -55,7 +57,10 @@ namespace Content.Server.Body.Systems
_movementSpeed.ChangeBaseSpeed(newEntity, 0, 0 , 0, move);
}
- 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