* Add tests for ghost spawn position * Make ghosts spawn immediately * Format mind system * Move ghost spawning to GhostSystem * Spawn ghost on grid or map This fixes the ghosts being attached the parent entity instead of the grid. * Move logging out of the ghost system * Make round start observer spawn using GhostSystem * Move GameTicker ghost spawning to GhostSystem Moved the more robust character name selection code over. Moved the TimeOfDeath code over. Added canReturn logic. * Add overrides and default for ghost spawn coordinates * Add warning log to ghost spawn fail * Clean up test * Dont spawn ghost on map delete * Minor changes to the role test * Fix role test failing to spawn ghost It was failing the map check due to using Nullspace * Fix ghost tests when running in parallel Not sure what happened, but it seems to be because they were running simultaneously and overwriting values. * Clean up ghost tests * Test that map deletion does not spawn ghosts * Spawn ghost on the next available map * Disallow spawning on deleted maps * Fix map deletion ghost test * Cleanup
315 lines
12 KiB
C#
315 lines
12 KiB
C#
#nullable enable
|
|
using System.Linq;
|
|
using Content.Server.GameTicking;
|
|
using Content.Shared.Ghost;
|
|
using Content.Shared.Mind;
|
|
using Content.Shared.Players;
|
|
using Robust.Server.Console;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Server.Player;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.Map;
|
|
|
|
namespace Content.IntegrationTests.Tests.Minds;
|
|
|
|
// Tests various scenarios where an entity that is associated with a player's mind is deleted.
|
|
public sealed partial class MindTests
|
|
{
|
|
// This test will do the following:
|
|
// - spawn a player
|
|
// - visit some entity
|
|
// - delete the entity being visited
|
|
// - assert that player returns to original entity
|
|
[Test]
|
|
public async Task TestDeleteVisiting()
|
|
{
|
|
await using var pair = await SetupPair();
|
|
var server = pair.Server;
|
|
|
|
var entMan = server.ResolveDependency<IServerEntityManager>();
|
|
var playerMan = server.ResolveDependency<IPlayerManager>();
|
|
|
|
var mindSystem = entMan.EntitySysManager.GetEntitySystem<SharedMindSystem>();
|
|
|
|
EntityUid playerEnt = default;
|
|
EntityUid visitEnt = default;
|
|
EntityUid mindId = default!;
|
|
MindComponent mind = default!;
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
var player = playerMan.Sessions.Single();
|
|
|
|
playerEnt = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
|
visitEnt = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
|
|
|
mindId = mindSystem.CreateMind(player.UserId);
|
|
mind = entMan.GetComponent<MindComponent>(mindId);
|
|
mindSystem.TransferTo(mindId, playerEnt);
|
|
mindSystem.Visit(mindId, visitEnt);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(player.AttachedEntity, Is.EqualTo(visitEnt));
|
|
Assert.That(mind.VisitingEntity, Is.EqualTo(visitEnt));
|
|
});
|
|
});
|
|
|
|
await pair.RunTicksSync(5);
|
|
await server.WaitPost(() => entMan.DeleteEntity(visitEnt));
|
|
await pair.RunTicksSync(5);
|
|
|
|
#pragma warning disable NUnit2045 // Interdependent assertions.
|
|
Assert.That(mind.VisitingEntity, Is.Null);
|
|
Assert.That(entMan.EntityExists(mind.OwnedEntity));
|
|
Assert.That(mind.OwnedEntity, Is.EqualTo(playerEnt));
|
|
#pragma warning restore NUnit2045
|
|
|
|
// This used to throw so make sure it doesn't.
|
|
await server.WaitPost(() => entMan.DeleteEntity(mind.OwnedEntity!.Value));
|
|
await pair.RunTicksSync(5);
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
|
|
// this is a variant of TestGhostOnDelete that just deletes the whole map.
|
|
[Test]
|
|
public async Task TestGhostOnDeleteMap()
|
|
{
|
|
await using var pair = await SetupPair(dirty: true);
|
|
var server = pair.Server;
|
|
var testMap = await pair.CreateTestMap();
|
|
var testMap2 = await pair.CreateTestMap();
|
|
|
|
var entMan = server.ResolveDependency<IServerEntityManager>();
|
|
var mapManager = server.ResolveDependency<IMapManager>();
|
|
var playerMan = server.ResolveDependency<IPlayerManager>();
|
|
var player = playerMan.Sessions.Single();
|
|
|
|
var mindSystem = entMan.EntitySysManager.GetEntitySystem<SharedMindSystem>();
|
|
|
|
EntityUid playerEnt = default;
|
|
EntityUid mindId = default!;
|
|
MindComponent mind = default!;
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
playerEnt = entMan.SpawnEntity(null, testMap.GridCoords);
|
|
mindId = player.ContentData()!.Mind!.Value;
|
|
mind = entMan.GetComponent<MindComponent>(mindId);
|
|
mindSystem.TransferTo(mindId, playerEnt);
|
|
|
|
Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt));
|
|
});
|
|
|
|
await pair.RunTicksSync(5);
|
|
await server.WaitAssertion(() => mapManager.DeleteMap(testMap.MapId));
|
|
await pair.RunTicksSync(5);
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
#pragma warning disable NUnit2045 // Interdependent assertions.
|
|
// Spawn ghost on the second map
|
|
var attachedEntity = player.AttachedEntity;
|
|
Assert.That(entMan.EntityExists(attachedEntity), Is.True);
|
|
Assert.That(attachedEntity, Is.Not.EqualTo(playerEnt));
|
|
Assert.That(entMan.HasComponent<GhostComponent>(attachedEntity));
|
|
var transform = entMan.GetComponent<TransformComponent>(attachedEntity.Value);
|
|
Assert.That(transform.MapID, Is.Not.EqualTo(MapId.Nullspace));
|
|
Assert.That(transform.MapID, Is.Not.EqualTo(testMap.MapId));
|
|
#pragma warning restore NUnit2045
|
|
});
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that a ghost gets created when the player entity is deleted.
|
|
/// 1. Delete mob
|
|
/// 2. Assert is ghost
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestGhostOnDelete()
|
|
{
|
|
// Client is needed to spawn session
|
|
await using var pair = await SetupPair(dirty: true);
|
|
var server = pair.Server;
|
|
|
|
var entMan = server.ResolveDependency<IServerEntityManager>();
|
|
var playerMan = server.ResolveDependency<IPlayerManager>();
|
|
|
|
var player = playerMan.Sessions.Single();
|
|
|
|
Assert.That(!entMan.HasComponent<GhostComponent>(player.AttachedEntity), "Player was initially a ghost?");
|
|
|
|
// Delete entity
|
|
await server.WaitPost(() => entMan.DeleteEntity(player.AttachedEntity!.Value));
|
|
await pair.RunTicksSync(5);
|
|
|
|
Assert.That(entMan.HasComponent<GhostComponent>(player.AttachedEntity), "Player did not become a ghost");
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that when the original mob gets deleted, the visited ghost does not get deleted.
|
|
/// And that the visited ghost becomes the main mob.
|
|
/// 1. Visit ghost
|
|
/// 2. Delete original mob
|
|
/// 3. Assert is ghost
|
|
/// 4. Assert was not deleted
|
|
/// 5. Assert is main mob
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestOriginalDeletedWhileGhostingKeepsGhost()
|
|
{
|
|
// Client is needed to spawn session
|
|
await using var pair = await SetupPair();
|
|
var server = pair.Server;
|
|
|
|
var entMan = server.ResolveDependency<IServerEntityManager>();
|
|
var playerMan = server.ResolveDependency<IPlayerManager>();
|
|
var mindSystem = entMan.EntitySysManager.GetEntitySystem<SharedMindSystem>();
|
|
var mind = GetMind(pair);
|
|
|
|
var player = playerMan.Sessions.Single();
|
|
#pragma warning disable NUnit2045 // Interdependent assertions.
|
|
Assert.That(player.AttachedEntity, Is.Not.Null);
|
|
Assert.That(entMan.EntityExists(player.AttachedEntity));
|
|
#pragma warning restore NUnit2045
|
|
var originalEntity = player.AttachedEntity.Value;
|
|
|
|
EntityUid ghost = default!;
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
ghost = entMan.SpawnEntity(GameTicker.ObserverPrototypeName, MapCoordinates.Nullspace);
|
|
mindSystem.Visit(mind.Id, ghost);
|
|
});
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(player.AttachedEntity, Is.EqualTo(ghost));
|
|
Assert.That(entMan.HasComponent<GhostComponent>(player.AttachedEntity), "player is not a ghost");
|
|
Assert.That(mind.Comp.VisitingEntity, Is.EqualTo(player.AttachedEntity));
|
|
Assert.That(mind.Comp.OwnedEntity, Is.EqualTo(originalEntity));
|
|
});
|
|
|
|
await pair.RunTicksSync(5);
|
|
await server.WaitAssertion(() => entMan.DeleteEntity(originalEntity));
|
|
await pair.RunTicksSync(5);
|
|
Assert.That(entMan.Deleted(originalEntity));
|
|
|
|
// Check that the player is still in control of the ghost
|
|
mind = GetMind(pair);
|
|
Assert.That(!entMan.Deleted(ghost), "ghost has been deleted");
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(player.AttachedEntity, Is.EqualTo(ghost));
|
|
Assert.That(entMan.HasComponent<GhostComponent>(player.AttachedEntity));
|
|
Assert.That(mind.Comp.VisitingEntity, Is.Null);
|
|
Assert.That(mind.Comp.OwnedEntity, Is.EqualTo(ghost));
|
|
});
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that ghosts can become admin ghosts without issue
|
|
/// 1. Become a ghost
|
|
/// 2. visit an admin ghost
|
|
/// 3. original ghost is deleted, player is an admin ghost.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestGhostToAghost()
|
|
{
|
|
await using var pair = await SetupPair();
|
|
var server = pair.Server;
|
|
var entMan = server.ResolveDependency<IServerEntityManager>();
|
|
var playerMan = server.ResolveDependency<IPlayerManager>();
|
|
var serverConsole = server.ResolveDependency<IServerConsoleHost>();
|
|
|
|
var player = playerMan.Sessions.Single();
|
|
|
|
var ghost = await BecomeGhost(pair);
|
|
|
|
// Player is a normal ghost (not admin ghost).
|
|
Assert.That(entMan.GetComponent<MetaDataComponent>(player.AttachedEntity!.Value).EntityPrototype?.ID, Is.Not.EqualTo(GameTicker.AdminObserverPrototypeName));
|
|
|
|
// Try to become an admin ghost
|
|
await server.WaitAssertion(() => serverConsole.ExecuteCommand(player, "aghost"));
|
|
await pair.RunTicksSync(5);
|
|
|
|
Assert.That(entMan.Deleted(ghost), "old ghost was not deleted");
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(player.AttachedEntity, Is.Not.EqualTo(ghost), "Player is still attached to the old ghost");
|
|
Assert.That(entMan.HasComponent<GhostComponent>(player.AttachedEntity), "Player did not become a new ghost");
|
|
Assert.That(entMan.GetComponent<MetaDataComponent>(player.AttachedEntity!.Value).EntityPrototype?.ID, Is.EqualTo(GameTicker.AdminObserverPrototypeName));
|
|
});
|
|
|
|
var mindId = player.ContentData()?.Mind;
|
|
Assert.That(mindId, Is.Not.Null);
|
|
|
|
var mind = entMan.GetComponent<MindComponent>(mindId.Value);
|
|
Assert.That(mind.VisitingEntity, Is.Null);
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test ghost getting deleted while player is connected spawns another ghost
|
|
/// 1. become ghost
|
|
/// 2. delete ghost
|
|
/// 3. new ghost is spawned
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestGhostDeletedSpawnsNewGhost()
|
|
{
|
|
// Client is needed to spawn session
|
|
await using var pair = await SetupPair();
|
|
var server = pair.Server;
|
|
|
|
var entMan = server.ResolveDependency<IServerEntityManager>();
|
|
var playerMan = server.ResolveDependency<IPlayerManager>();
|
|
var serverConsole = server.ResolveDependency<IServerConsoleHost>();
|
|
|
|
var player = playerMan.Sessions.Single();
|
|
|
|
EntityUid ghost = default!;
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
Assert.That(player.AttachedEntity, Is.Not.EqualTo(null));
|
|
entMan.DeleteEntity(player.AttachedEntity!.Value);
|
|
});
|
|
|
|
await pair.RunTicksSync(5);
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
// Is player a ghost?
|
|
Assert.That(player.AttachedEntity, Is.Not.EqualTo(null));
|
|
ghost = player.AttachedEntity!.Value;
|
|
Assert.That(entMan.HasComponent<GhostComponent>(ghost));
|
|
});
|
|
|
|
await pair.RunTicksSync(5);
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
serverConsole.ExecuteCommand(player, "aghost");
|
|
});
|
|
|
|
await pair.RunTicksSync(5);
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
#pragma warning disable NUnit2045 // Interdependent assertions.
|
|
Assert.That(entMan.Deleted(ghost));
|
|
Assert.That(player.AttachedEntity, Is.Not.EqualTo(ghost));
|
|
Assert.That(entMan.HasComponent<GhostComponent>(player.AttachedEntity!.Value));
|
|
#pragma warning restore NUnit2045
|
|
});
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
}
|