diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index 970536f42b0272..48092423c46a3e 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -1,9 +1,11 @@ using Content.Server.Administration.Logs; +using Content.Server.Body.Components; using Content.Server.Effects; using Content.Server.Weapons.Ranged.Systems; using Content.Shared.Camera; using Content.Shared.Damage; using Content.Shared.Database; +using Content.Shared.Destructible; using Content.Shared.Projectiles; using Robust.Shared.Physics.Events; using Robust.Shared.Player; @@ -22,6 +24,8 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnDestruction); + SubscribeLocalEvent(OnDestruction); } private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref StartCollideEvent args) @@ -77,4 +81,16 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, GetNetCoordinates(xform.Coordinates)), Filter.Pvs(xform.Coordinates, entityMan: EntityManager)); } } + + private void OnDestruction(Entity embeddee, ref TEvent args) + { + var children = Transform(embeddee).ChildEnumerator; + while (children.MoveNext(out var child)) + { + if (TryComp(child, out var component)) + { + TryUnEmbedFromParent((child, component), null); + } + } + } } diff --git a/Content.Shared/Projectiles/HasProjectilesEmbeddedComponent.cs b/Content.Shared/Projectiles/HasProjectilesEmbeddedComponent.cs new file mode 100644 index 00000000000000..3a1fb4c6401706 --- /dev/null +++ b/Content.Shared/Projectiles/HasProjectilesEmbeddedComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Projectiles; + +/// +/// This component marks that its entity has projectiles embedded in it, and tracks what those projectile entities are. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class HasProjectilesEmbeddedComponent : Component +{ + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)] + public List EmbeddedProjectiles = []; +} diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 85e75d6d291324..a678759210c26b 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -1,17 +1,15 @@ using System.Numerics; -using Content.Shared.CombatMode.Pacification; using Content.Shared.Damage; +using Content.Shared.Destructible; using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; -using Content.Shared.Mobs.Components; using Content.Shared.Throwing; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; @@ -34,10 +32,10 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(PreventCollision); - SubscribeLocalEvent(OnEmbedProjectileHit); - SubscribeLocalEvent(OnEmbedThrowDoHit); + SubscribeLocalEvent(OnEmbedProjectileHit, before: [typeof(SharedDestructibleSystem)]); + SubscribeLocalEvent(OnEmbedThrowDoHit, before: [typeof(SharedDestructibleSystem)]); SubscribeLocalEvent(OnEmbedActivate); - SubscribeLocalEvent(OnEmbedRemove); + SubscribeLocalEvent(OnRemoveEmbeddedProjectileEvent); } private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args) @@ -55,84 +53,119 @@ private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent compon new RemoveEmbeddedProjectileEvent(), eventTarget: uid, target: uid)); } - private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent component, RemoveEmbeddedProjectileEvent args) + private void OnRemoveEmbeddedProjectileEvent(Entity entity, ref RemoveEmbeddedProjectileEvent args) { // Whacky prediction issues. if (args.Cancelled || _netManager.IsClient) return; - if (component.DeleteOnRemove) - { - QueueDel(uid); - return; - } - - var xform = Transform(uid); - TryComp(uid, out var physics); - _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform); - _transform.AttachToGridOrMap(uid, xform); - component.EmbeddedIntoUid = null; - Dirty(uid, component); - - // Reset whether the projectile has damaged anything if it successfully was removed - if (TryComp(uid, out var projectile)) - { - projectile.Shooter = null; - projectile.Weapon = null; - projectile.DamagedEntity = false; - } - - // Land it just coz uhhh yeah - var landEv = new LandEvent(args.User, true); - RaiseLocalEvent(uid, ref landEv); - _physics.WakeBody(uid, body: physics); - - // try place it in the user's hand - _hands.TryPickupAnyHand(args.User, uid); + TryUnEmbedFromParent(entity, args.User); } - private void OnEmbedThrowDoHit(EntityUid uid, EmbeddableProjectileComponent component, ThrowDoHitEvent args) + private void OnEmbedThrowDoHit(Entity entity, ref ThrowDoHitEvent args) { - if (!component.EmbedOnThrow) + if (!entity.Comp.EmbedOnThrow) return; - Embed(uid, args.Target, null, component); + Embed(entity, args.Target, null); } - private void OnEmbedProjectileHit(EntityUid uid, EmbeddableProjectileComponent component, ref ProjectileHitEvent args) + private void OnEmbedProjectileHit(Entity entity, ref ProjectileHitEvent args) { - Embed(uid, args.Target, args.Shooter, component); + Embed(entity, args.Target, args.Shooter); // Raise a specific event for projectiles. - if (TryComp(uid, out ProjectileComponent? projectile)) + if (TryComp(entity.Owner, out ProjectileComponent? projectile)) { var ev = new ProjectileEmbedEvent(projectile.Shooter!.Value, projectile.Weapon!.Value, args.Target); - RaiseLocalEvent(uid, ref ev); + RaiseLocalEvent(entity.Owner, ref ev); } } - private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableProjectileComponent component) + private void Embed(Entity projectile, EntityUid target, EntityUid? user) { - TryComp(uid, out var physics); - _physics.SetLinearVelocity(uid, Vector2.Zero, body: physics); - _physics.SetBodyType(uid, BodyType.Static, body: physics); - var xform = Transform(uid); - _transform.SetParent(uid, xform, target); + EnsureComp(target, out var embeddeds); + embeddeds.EmbeddedProjectiles.Add(projectile); + + TryComp(projectile, out var physics); + _physics.SetLinearVelocity(projectile, Vector2.Zero, body: physics); + _physics.SetBodyType(projectile, BodyType.Static, body: physics); + var xform = Transform(projectile); + _transform.SetParent(projectile, xform, target); - if (component.Offset != Vector2.Zero) + if (projectile.Comp.Offset != Vector2.Zero) { var rotation = xform.LocalRotation; - if (TryComp(uid, out var throwingAngleComp)) + if (TryComp(projectile, out var throwingAngleComp)) rotation += throwingAngleComp.Angle; - _transform.SetLocalPosition(uid, xform.LocalPosition + rotation.RotateVec(component.Offset), + _transform.SetLocalPosition(projectile, xform.LocalPosition + rotation.RotateVec(projectile.Comp.Offset), xform); } - _audio.PlayPredicted(component.Sound, uid, null); - component.EmbeddedIntoUid = target; + _audio.PlayPredicted(projectile.Comp.Sound, projectile, null); + projectile.Comp.EmbeddedIntoUid = target; var ev = new EmbedEvent(user, target); - RaiseLocalEvent(uid, ref ev); - Dirty(uid, component); + RaiseLocalEvent(projectile, ref ev); + Dirty(projectile); + } + + /// + /// Makes the specified entity not be embedded in whatever it's embedded in. In the case that the specified entity + /// is not embedded in anything, this function does nothing. + /// + /// The entity to make no longer embedded + /// The entity which is removing the embedded entity. If not null, we'll try to put the + /// embedded object in its hands. If null, there's no specific remover, eg. if the embeddee object is destroyed. + /// True if the entity was embedded and removed, otherwise false. + public bool TryUnEmbedFromParent(Entity entity, EntityUid? remover) + { + var xform = Transform(entity); + + // Check that the projectile's parent has any embedded projectiles and that this projectile is one of them. + if (!(TryComp(xform.ParentUid, out var c) && c is { } entitiesEmbeddedInParent && entitiesEmbeddedInParent.EmbeddedProjectiles.Contains(entity))) + { + return false; + } + + // Remove `entity` from the parent's embedded projectiles, and clean up the parent's embedding component if it's empty. + entitiesEmbeddedInParent.EmbeddedProjectiles.Remove(entity); + if (entitiesEmbeddedInParent.EmbeddedProjectiles.Count == 0) + { + EntityManager.RemoveComponent(xform.ParentUid); + } + + if (entity.Comp.DeleteOnRemove) + { + QueueDel(entity); + return true; + } + + TryComp(entity, out var physics); + _physics.SetBodyType(entity, BodyType.Dynamic, body: physics, xform: xform); + _transform.AttachToGridOrMap(entity, xform); + entity.Comp.EmbeddedIntoUid = null; + Dirty(entity); + + // Reset whether the projectile has damaged anything if it successfully was removed + if (TryComp(entity, out var projectile)) + { + projectile.Shooter = null; + projectile.Weapon = null; + projectile.DamagedEntity = false; + } + + // Land it just coz uhhh yeah + var landEv = new LandEvent(remover, true); + RaiseLocalEvent(entity, ref landEv); + _physics.WakeBody(entity, body: physics); + + // try place it in the user's hand + if (remover is EntityUid user) + { + _hands.TryPickupAnyHand(user, entity); + } + + return true; } private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)