diff --git a/Content.IntegrationTests/Tests/Embedding/EmbedTest.cs b/Content.IntegrationTests/Tests/Embedding/EmbedTest.cs
index 5e09b5c482..f9db064163 100644
--- a/Content.IntegrationTests/Tests/Embedding/EmbedTest.cs
+++ b/Content.IntegrationTests/Tests/Embedding/EmbedTest.cs
@@ -1,5 +1,6 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Projectiles;
+using Robust.Shared.GameObjects;
using Robust.Shared.Network;
namespace Content.IntegrationTests.Tests.Embedding;
@@ -88,4 +89,84 @@ public sealed class EmbedTest : InteractionTest
AssertExists(projectile);
await AssertEntityLookup(EmbeddableProtoId);
}
+
+ ///
+ /// Throws two embeddable projectiles at a target, then deletes them
+ /// one at a time, making sure that they are tracked correctly and that
+ /// the is removed once all
+ /// projectiles are gone.
+ ///
+ [Test]
+ public async Task TestDeleteWhileEmbedded()
+ {
+ // Spawn the target we're going to throw at
+ await SpawnTarget(TargetProtoId);
+
+ // Give the player the embeddable to throw
+ var projectile1 = await PlaceInHands(EmbeddableProtoId);
+ Assert.That(TryComp(projectile1, 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();
+
+ // Give the player a second embeddable to throw
+ var projectile2 = await PlaceInHands(EmbeddableProtoId);
+ Assert.That(TryComp(projectile1, out var embedComp2),
+ $"{EmbeddableProtoId} does not have EmbeddableProjectileComponent.");
+
+ // Wait a moment for the projectile 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)),
+ "First projectile not embedded into target.");
+ Assert.That(TryComp(out var containerComp),
+ "Target was not given EmbeddedContainerComponent.");
+ Assert.That(containerComp.EmbeddedObjects, Does.Contain(ToServer(projectile1)),
+ "Target is not tracking the first projectile as embedded.");
+ Assert.That(containerComp.EmbeddedObjects, Has.Count.EqualTo(1),
+ "Target has unexpected EmbeddedObjects count.");
+
+ // Wait for the cooldown between throws
+ await RunSeconds(Hands.ThrowCooldown.Seconds);
+
+ // Throw the second projectile
+ await ThrowItem();
+
+ // Wait a moment for the second projectile to hit and embed
+ await RunSeconds(0.5f);
+
+ Assert.That(embedComp2.EmbeddedIntoUid, Is.EqualTo(ToServer(Target)),
+ "Second projectile not embedded into target");
+ AssertComp();
+ Assert.That(containerComp.EmbeddedObjects, Does.Contain(ToServer(projectile1)),
+ "Target is not tracking the second projectile as embedded.");
+ Assert.That(containerComp.EmbeddedObjects, Has.Count.EqualTo(2),
+ "Target EmbeddedObjects count did not increase with second projectile.");
+
+ // Delete the first projectile
+ await Delete(projectile1);
+
+ Assert.That(containerComp.EmbeddedObjects, Does.Not.Contain(ToServer(projectile1)),
+ "Target did not stop tracking first projectile after it was deleted.");
+ Assert.That(containerComp.EmbeddedObjects, Does.Not.Contain(EntityUid.Invalid),
+ "Target EmbeddedObjects contains an invalid entity.");
+ foreach (var embedded in containerComp.EmbeddedObjects)
+ {
+ Assert.That(!SEntMan.Deleted(embedded),
+ "Target EmbeddedObjects contains a deleted entity.");
+ }
+ Assert.That(containerComp.EmbeddedObjects, Has.Count.EqualTo(1),
+ "Target EmbeddedObjects count did not decrease after deleting first projectile.");
+
+ // Delete the second projectile
+ await Delete(projectile2);
+
+ Assert.That(!SEntMan.HasComponent(ToServer(Target)),
+ "Target did not remove EmbeddedContainerComponent after both projectiles were deleted.");
+ }
}
diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs
index d0cb3b2261..7161a39e0a 100644
--- a/Content.Shared/Projectiles/SharedProjectileSystem.cs
+++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs
@@ -39,6 +39,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem
SubscribeLocalEvent(OnEmbedThrowDoHit);
SubscribeLocalEvent(OnEmbedActivate);
SubscribeLocalEvent(OnEmbedRemove);
+ SubscribeLocalEvent(OnEmbeddableCompShutdown);
SubscribeLocalEvent(OnEmbeddableTermination);
}
@@ -75,6 +76,11 @@ public abstract partial class SharedProjectileSystem : EntitySystem
_hands.TryPickupAnyHand(args.User, embeddable);
}
+ private void OnEmbeddableCompShutdown(Entity embeddable, ref ComponentShutdown arg)
+ {
+ EmbedDetach(embeddable, embeddable.Comp);
+ }
+
private void OnEmbedThrowDoHit(Entity embeddable, ref ThrowDoHitEvent args)
{
if (!embeddable.Comp.EmbedOnThrow)
@@ -130,16 +136,21 @@ public abstract partial class SharedProjectileSystem : EntitySystem
if (!Resolve(uid, ref component))
return;
- if (component.DeleteOnRemove)
- {
- QueueDel(uid);
- return;
- }
-
if (component.EmbeddedIntoUid is not null)
{
if (TryComp(component.EmbeddedIntoUid.Value, out var embeddedContainer))
+ {
embeddedContainer.EmbeddedObjects.Remove(uid);
+ Dirty(component.EmbeddedIntoUid.Value, embeddedContainer);
+ if (embeddedContainer.EmbeddedObjects.Count == 0)
+ RemCompDeferred(component.EmbeddedIntoUid.Value);
+ }
+ }
+
+ if (component.DeleteOnRemove && _net.IsServer)
+ {
+ QueueDel(uid);
+ return;
}
var xform = Transform(uid);