From 244d7a629e05a1397d9c2254a77001d2a50cc8fd Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:23:35 +0300 Subject: [PATCH] Fix embeddable projectiles dissapearing (reopening) (#35153) --- .../ItemRecall/SharedItemRecallSystem.cs | 2 +- .../Projectiles/EmbeddedContainerComponent.cs | 13 +++ .../Projectiles/SharedProjectileSystem.cs | 92 +++++++++++++------ 3 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 Content.Shared/Projectiles/EmbeddedContainerComponent.cs diff --git a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs index 63d38203c6..a4a49e9708 100644 --- a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs +++ b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs @@ -81,7 +81,7 @@ public abstract partial class SharedItemRecallSystem : EntitySystem return; if (TryComp(ent, out var projectile)) - _proj.UnEmbed(ent, projectile, actionOwner.Value); + _proj.EmbedDetach(ent, projectile, actionOwner.Value); _popups.PopupPredicted(Loc.GetString("item-recall-item-summon", ("item", ent)), actionOwner.Value, actionOwner.Value); diff --git a/Content.Shared/Projectiles/EmbeddedContainerComponent.cs b/Content.Shared/Projectiles/EmbeddedContainerComponent.cs new file mode 100644 index 0000000000..19dd93bfbd --- /dev/null +++ b/Content.Shared/Projectiles/EmbeddedContainerComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Projectiles; + +/// +/// Stores a list of all stuck entities to release when this entity is deleted. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class EmbeddedContainerComponent : Component +{ + [DataField, AutoNetworkedField] + public HashSet EmbeddedObjects = new(); +} diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 1d0fc16cbd..be86fd1af2 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -15,6 +15,7 @@ using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.Projectiles; @@ -22,7 +23,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem { public const string ProjectileFixture = "projectile"; - [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; @@ -38,62 +39,63 @@ public abstract partial class SharedProjectileSystem : EntitySystem SubscribeLocalEvent(OnEmbedThrowDoHit); SubscribeLocalEvent(OnEmbedActivate); SubscribeLocalEvent(OnEmbedRemove); + + SubscribeLocalEvent(OnEmbeddableTermination); } - private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args) + private void OnEmbedActivate(Entity embeddable, ref ActivateInWorldEvent args) { - // Nuh uh - if (component.RemovalTime == null) + // Unremovable embeddables moment + if (embeddable.Comp.RemovalTime == null) return; - if (args.Handled || !args.Complex || !TryComp(uid, out var physics) || physics.BodyType != BodyType.Static) + if (args.Handled || !args.Complex || !TryComp(embeddable, out var physics) || + physics.BodyType != BodyType.Static) return; args.Handled = true; - _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.RemovalTime.Value, - new RemoveEmbeddedProjectileEvent(), eventTarget: uid, target: uid)); + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, + args.User, + embeddable.Comp.RemovalTime.Value, + new RemoveEmbeddedProjectileEvent(), + eventTarget: embeddable, + target: embeddable)); } - private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent component, RemoveEmbeddedProjectileEvent args) + private void OnEmbedRemove(Entity embeddable, ref RemoveEmbeddedProjectileEvent args) { // Whacky prediction issues. - if (args.Cancelled || _netManager.IsClient) + if (args.Cancelled || _net.IsClient) return; - if (component.DeleteOnRemove) - { - QueueDel(uid); - return; - } - - UnEmbed(uid, component, args.User); + EmbedDetach(embeddable, embeddable.Comp, args.User); // try place it in the user's hand - _hands.TryPickupAnyHand(args.User, uid); + _hands.TryPickupAnyHand(args.User, embeddable); } - private void OnEmbedThrowDoHit(EntityUid uid, EmbeddableProjectileComponent component, ThrowDoHitEvent args) + private void OnEmbedThrowDoHit(Entity embeddable, ref ThrowDoHitEvent args) { - if (!component.EmbedOnThrow) + if (!embeddable.Comp.EmbedOnThrow) return; - Embed(uid, args.Target, null, component); + EmbedAttach(embeddable, args.Target, null, embeddable.Comp); } - private void OnEmbedProjectileHit(EntityUid uid, EmbeddableProjectileComponent component, ref ProjectileHitEvent args) + private void OnEmbedProjectileHit(Entity embeddable, ref ProjectileHitEvent args) { - Embed(uid, args.Target, args.Shooter, component); + EmbedAttach(embeddable, args.Target, args.Shooter, embeddable.Comp); // Raise a specific event for projectiles. - if (TryComp(uid, out ProjectileComponent? projectile)) + if (TryComp(embeddable, out ProjectileComponent? projectile)) { var ev = new ProjectileEmbedEvent(projectile.Shooter!.Value, projectile.Weapon!.Value, args.Target); - RaiseLocalEvent(uid, ref ev); + RaiseLocalEvent(embeddable, ref ev); } } - private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableProjectileComponent component) + private void EmbedAttach(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableProjectileComponent component) { TryComp(uid, out var physics); _physics.SetLinearVelocity(uid, Vector2.Zero, body: physics); @@ -106,8 +108,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem var rotation = xform.LocalRotation; if (TryComp(uid, out var throwingAngleComp)) rotation += throwingAngleComp.Angle; - _transform.SetLocalPosition(uid, xform.LocalPosition + rotation.RotateVec(component.Offset), - xform); + _transform.SetLocalPosition(uid, xform.LocalPosition + rotation.RotateVec(component.Offset), xform); } _audio.PlayPredicted(component.Sound, uid, null); @@ -115,13 +116,32 @@ public abstract partial class SharedProjectileSystem : EntitySystem var ev = new EmbedEvent(user, target); RaiseLocalEvent(uid, ref ev); Dirty(uid, component); + + EnsureComp(target, out var embeddedContainer); + + //Assert that this entity not embed + DebugTools.AssertEqual(embeddedContainer.EmbeddedObjects.Contains(uid), false); + + embeddedContainer.EmbeddedObjects.Add(uid); } - public void UnEmbed(EntityUid uid, EmbeddableProjectileComponent? component, EntityUid? user = null) + public void EmbedDetach(EntityUid uid, EmbeddableProjectileComponent? component, EntityUid? user = null) { 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); + } + var xform = Transform(uid); TryComp(uid, out var physics); _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform); @@ -149,6 +169,22 @@ public abstract partial class SharedProjectileSystem : EntitySystem _physics.WakeBody(uid, body: physics); } + private void OnEmbeddableTermination(Entity container, ref EntityTerminatingEvent args) + { + DetachAllEmbedded(container); + } + + public void DetachAllEmbedded(Entity container) + { + foreach (var embedded in container.Comp.EmbeddedObjects) + { + if (!TryComp(embedded, out var embeddedComp)) + continue; + + EmbedDetach(embedded, embeddedComp); + } + } + private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args) { if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon))