using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Projectiles;
+using Robust.Shared.GameObjects;
using Robust.Shared.Network;
namespace Content.IntegrationTests.Tests.Embedding;
AssertExists(projectile);
await AssertEntityLookup(EmbeddableProtoId);
}
+
+ /// <summary>
+ /// Throws two embeddable projectiles at a target, then deletes them
+ /// one at a time, making sure that they are tracked correctly and that
+ /// the <see cref="EmbeddedContainerComponent"/> is removed once all
+ /// projectiles are gone.
+ /// </summary>
+ [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<EmbeddableProjectileComponent>(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<EmbeddableProjectileComponent>(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<EmbeddedContainerComponent>(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<EmbeddedContainerComponent>();
+ 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<EmbeddedContainerComponent>(ToServer(Target)),
+ "Target did not remove EmbeddedContainerComponent after both projectiles were deleted.");
+ }
}
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
SubscribeLocalEvent<EmbeddableProjectileComponent, RemoveEmbeddedProjectileEvent>(OnEmbedRemove);
+ SubscribeLocalEvent<EmbeddableProjectileComponent, ComponentShutdown>(OnEmbeddableCompShutdown);
SubscribeLocalEvent<EmbeddedContainerComponent, EntityTerminatingEvent>(OnEmbeddableTermination);
}
_hands.TryPickupAnyHand(args.User, embeddable);
}
+ private void OnEmbeddableCompShutdown(Entity<EmbeddableProjectileComponent> embeddable, ref ComponentShutdown arg)
+ {
+ EmbedDetach(embeddable, embeddable.Comp);
+ }
+
private void OnEmbedThrowDoHit(Entity<EmbeddableProjectileComponent> embeddable, ref ThrowDoHitEvent args)
{
if (!embeddable.Comp.EmbedOnThrow)
if (!Resolve(uid, ref component))
return;
- if (component.DeleteOnRemove)
- {
- QueueDel(uid);
- return;
- }
-
if (component.EmbeddedIntoUid is not null)
{
if (TryComp<EmbeddedContainerComponent>(component.EmbeddedIntoUid.Value, out var embeddedContainer))
+ {
embeddedContainer.EmbeddedObjects.Remove(uid);
+ Dirty(component.EmbeddedIntoUid.Value, embeddedContainer);
+ if (embeddedContainer.EmbeddedObjects.Count == 0)
+ RemCompDeferred<EmbeddedContainerComponent>(component.EmbeddedIntoUid.Value);
+ }
+ }
+
+ if (component.DeleteOnRemove && _net.IsServer)
+ {
+ QueueDel(uid);
+ return;
}
var xform = Transform(uid);