diff --git a/Content.IntegrationTests/Tests/Embedding/EmbedTest.cs b/Content.IntegrationTests/Tests/Embedding/EmbedTest.cs new file mode 100644 index 0000000000..5e09b5c482 --- /dev/null +++ b/Content.IntegrationTests/Tests/Embedding/EmbedTest.cs @@ -0,0 +1,91 @@ +using Content.IntegrationTests.Tests.Interaction; +using Content.Shared.Projectiles; +using Robust.Shared.Network; + +namespace Content.IntegrationTests.Tests.Embedding; + +public sealed class EmbedTest : InteractionTest +{ + /// + /// Embeddable entity that will be thrown at the target. + /// + private const string EmbeddableProtoId = "SurvivalKnife"; + + /// + /// Target entity that the thrown item will embed into. + /// + private const string TargetProtoId = "AirlockGlass"; + + /// + /// Embeds an entity with a into a target, + /// then disconnects the client. Intended to reveal any clientside issues that might + /// occur due to reparenting during cleanup. + /// + [Test] + public async Task TestDisconnectWhileEmbedded() + { + // Spawn the target we're going to throw at + await SpawnTarget(TargetProtoId); + + // Give the player the embeddable to throw + var projectile = await PlaceInHands(EmbeddableProtoId); + Assert.That(TryComp(projectile, out var embedComp), + $"{EmbeddableProtoId} does not have EmbeddableProjectileComponent"); + // Make sure the projectile isn't already embedded into anything + Assert.That(embedComp.EmbeddedIntoUid, Is.Null, + $"Projectile already embedded into {SEntMan.ToPrettyString(embedComp.EmbeddedIntoUid)}"); + + // Have the player throw the embeddable at the target + await ThrowItem(); + + // Wait a moment for the item to hit and embed + await RunSeconds(0.5f); + + // Make sure the projectile is embedded into the target + Assert.That(embedComp.EmbeddedIntoUid, Is.EqualTo(ToServer(Target)), + "Projectile not embedded into target"); + + // Disconnect the client + var cNetMgr = Client.ResolveDependency(); + await Client.WaitPost(Client.EntMan.FlushEntities); + await Pair.RunTicksSync(1); + } + + /// + /// Embeds an entity with a into a target, + /// then deletes the target and makes sure the embeddable is not deleted. + /// + [Test] + public async Task TestEmbedDetach() + { + // Spawn the target we're going to throw at + await SpawnTarget(TargetProtoId); + + // Give the player the embeddable to throw + var projectile = await PlaceInHands(EmbeddableProtoId); + Assert.That(TryComp(projectile, out var embedComp), + $"{EmbeddableProtoId} does not have EmbeddableProjectileComponent"); + // Make sure the projectile isn't already embedded into anything + Assert.That(embedComp.EmbeddedIntoUid, Is.Null, + $"Projectile already embedded into {SEntMan.ToPrettyString(embedComp.EmbeddedIntoUid)}"); + + // Have the player throw the embeddable at the target + await ThrowItem(); + + // Wait a moment for the item to hit and embed + await RunSeconds(0.5f); + + // Make sure the projectile is embedded into the target + Assert.That(embedComp.EmbeddedIntoUid, Is.EqualTo(ToServer(Target)), + "Projectile not embedded into target"); + + // Delete the target + await Delete(Target.Value); + + await RunTicks(1); + + // Make sure the embeddable wasn't deleted with the target + AssertExists(projectile); + await AssertEntityLookup(EmbeddableProtoId); + } +} diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index be86fd1af2..d0cb3b2261 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -143,6 +143,8 @@ public abstract partial class SharedProjectileSystem : EntitySystem } var xform = Transform(uid); + if (TerminatingOrDeleted(xform.GridUid) && TerminatingOrDeleted(xform.MapUid)) + return; TryComp(uid, out var physics); _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform); _transform.AttachToGridOrMap(uid, xform);