Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded throwables are unembedded when the parent entity is destroyed #32707

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
20 changes: 20 additions & 0 deletions Content.Server/Projectiles/ProjectileSystem.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
using Content.Server.Administration.Logs;
using Content.Server.Body.Components;
using Content.Server.Destructible;
using Content.Server.Effects;
using Content.Server.Nutrition;
using Content.Server.Nutrition.Components;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Body.Components;
Centronias marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand All @@ -22,6 +28,8 @@ public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ProjectileComponent, StartCollideEvent>(OnStartCollide);
SubscribeLocalEvent<HasProjectilesEmbeddedComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<HasProjectilesEmbeddedComponent, BeingGibbedEvent>(OnDestruction);
}

private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref StartCollideEvent args)
Expand Down Expand Up @@ -77,4 +85,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<TEvent>(Entity<HasProjectilesEmbeddedComponent> embeddee, ref TEvent args)
{
var children = Transform(embeddee).ChildEnumerator;
while (children.MoveNext(out var child))
{
if (TryComp<EmbeddableProjectileComponent>(child, out var component))
{
TryUnEmbedFromParent((child, component), null);
}
}
}
}
23 changes: 23 additions & 0 deletions Content.Shared/Projectiles/HasProjectilesEmbeddedComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Xml;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;

namespace Content.Shared.Projectiles;

/// <summary>
/// This component marks that its entity has projectiles embedded in it, and tracks what those projectile entities are.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class HasProjectilesEmbeddedComponent : Component
{
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)]

private List<EntityUid> _embeddedProjectiles = [];

public void Add(Entity<EmbeddableProjectileComponent> projectile) => _embeddedProjectiles.Add(projectile);
public bool Contains(Entity<EmbeddableProjectileComponent> projectile) => _embeddedProjectiles.Contains(projectile);
public void Remove(Entity<EmbeddableProjectileComponent> projectile) => _embeddedProjectiles.Remove(projectile);
public bool IsEmpty() => _embeddedProjectiles.Count == 0;
Centronias marked this conversation as resolved.
Show resolved Hide resolved
}
117 changes: 79 additions & 38 deletions Content.Shared/Projectiles/SharedProjectileSystem.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using System.Numerics;
using System.Reflection.Metadata.Ecma335;
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 Linguini.Syntax.Ast;
using Microsoft.Extensions.ObjectPool;
Centronias marked this conversation as resolved.
Show resolved Hide resolved
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Network;
Expand Down Expand Up @@ -34,10 +38,10 @@ public override void Initialize()
base.Initialize();

SubscribeLocalEvent<ProjectileComponent, PreventCollideEvent>(PreventCollision);
SubscribeLocalEvent<EmbeddableProjectileComponent, ProjectileHitEvent>(OnEmbedProjectileHit);
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
SubscribeLocalEvent<EmbeddableProjectileComponent, ProjectileHitEvent>(OnEmbedProjectileHit, before: [typeof(SharedDestructibleSystem)]);
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit, before: [typeof(SharedDestructibleSystem)]);
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
SubscribeLocalEvent<EmbeddableProjectileComponent, RemoveEmbeddedProjectileEvent>(OnEmbedRemove);
SubscribeLocalEvent<EmbeddableProjectileComponent, RemoveEmbeddedProjectileEvent>(OnRemoveEmbeddedProjectileEvent);
}

private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args)
Expand All @@ -55,62 +59,42 @@ 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<EmbeddableProjectileComponent> entity, ref RemoveEmbeddedProjectileEvent args)
{
// Whacky prediction issues.
if (args.Cancelled || _netManager.IsClient)
return;

if (component.DeleteOnRemove)
{
QueueDel(uid);
return;
}

var xform = Transform(uid);
TryComp<PhysicsComponent>(uid, out var physics);
_physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform);
_transform.AttachToGridOrMap(uid, xform);

// Reset whether the projectile has damaged anything if it successfully was removed
if (TryComp<ProjectileComponent>(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<EmbeddableProjectileComponent> 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<EmbeddableProjectileComponent> 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<EmbeddableProjectileComponent> projectile, EntityUid target, EntityUid? user)
{
EnsureComp<HasProjectilesEmbeddedComponent>(target, out var embeddeds);
embeddeds.Add(projectile);

var (uid, component) = projectile;

TryComp<PhysicsComponent>(uid, out var physics);
_physics.SetLinearVelocity(uid, Vector2.Zero, body: physics);
_physics.SetBodyType(uid, BodyType.Static, body: physics);
Expand All @@ -131,6 +115,63 @@ private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableP
RaiseLocalEvent(uid, ref ev);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="entity">The entity to make no longer embedded</param>
/// <param name="remover">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.</param>
/// <returns>True if the entity was embedded and removed, otherwise false.</return>
public bool TryUnEmbedFromParent(Entity<EmbeddableProjectileComponent> 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<HasProjectilesEmbeddedComponent>(xform.ParentUid, out var c) && c is { } entitiesEmbeddedInParent && entitiesEmbeddedInParent.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.Remove(entity);
if (entitiesEmbeddedInParent.IsEmpty())
{
EntityManager.RemoveComponent<HasProjectilesEmbeddedComponent>(xform.ParentUid);
}

if (entity.Comp.DeleteOnRemove)
{
QueueDel(entity);
return true;
}

TryComp<PhysicsComponent>(entity, out var physics);
_physics.SetBodyType(entity, BodyType.Dynamic, body: physics, xform: xform);
_transform.AttachToGridOrMap(entity, xform);

// Reset whether the projectile has damaged anything if it successfully was removed
if (TryComp<ProjectileComponent>(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)
{
if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon))
Expand Down
Loading