diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs
index cb83916420..8e9cb6651f 100644
--- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs
+++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs
@@ -435,7 +435,6 @@ public sealed partial class ExplosionSystem : EntitySystem
physics,
xform,
projectileQuery,
- tagQuery,
throwForce);
}
diff --git a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs
index d5cd1ef76f..3fd46f6efb 100644
--- a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs
+++ b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs
@@ -1,12 +1,36 @@
+using System.Numerics;
using Robust.Shared.GameStates;
-namespace Content.Shared.Weapons.Ranged.Components;
+namespace Content.Shared.Projectiles;
///
/// Embeds this entity inside of the hit target.
///
-[RegisterComponent, NetworkedComponent]
-public sealed class EmbeddableProjectileComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EmbeddableProjectileComponent : Component
{
+ ///
+ /// Minimum speed of the projectile to embed.
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("minimumSpeed"), AutoNetworkedField]
+ public float MinimumSpeed = 5f;
+ ///
+ /// Delete the entity on embedded removal?
+ /// Does nothing if there's no RemovalTime.
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("deleteOnRemove"), AutoNetworkedField]
+ public bool DeleteOnRemove;
+
+ ///
+ /// How long it takes to remove the embedded object.
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("removalTime"), AutoNetworkedField]
+ public float? RemovalTime = 3f;
+
+ ///
+ /// How far into the entity should we offset (0 is wherever we collided).
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("offset"), AutoNetworkedField]
+ public Vector2 Offset = Vector2.Zero;
}
diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs
index e5e51080a1..15a4a52ae5 100644
--- a/Content.Shared/Projectiles/SharedProjectileSystem.cs
+++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs
@@ -1,8 +1,14 @@
using System.Numerics;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
using Content.Shared.Projectiles;
+using Content.Shared.Sound.Components;
+using Content.Shared.Throwing;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Shared.Map;
+using Robust.Shared.Network;
using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
@@ -13,6 +19,8 @@ namespace Content.Shared.Projectiles
{
public const string ProjectileFixture = "projectile";
+ [Dependency] private readonly INetManager _netManager = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
@@ -20,19 +28,77 @@ namespace Content.Shared.Projectiles
{
base.Initialize();
SubscribeLocalEvent(PreventCollision);
- SubscribeLocalEvent(OnEmbedCollide);
+ SubscribeLocalEvent(OnEmbedProjectileCollide);
+ SubscribeLocalEvent(OnEmbedThrowDoHit);
+ SubscribeLocalEvent(OnEmbedActivate);
+ SubscribeLocalEvent(OnEmbedRemove);
}
- private void OnEmbedCollide(EntityUid uid, EmbeddableProjectileComponent component, ref StartCollideEvent args)
+ private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args)
{
- if (!TryComp(uid, out var projectile))
+ // Nuh uh
+ if (component.RemovalTime == null)
return;
- _physics.SetLinearVelocity(uid, Vector2.Zero, body: args.OurBody);
- _physics.SetBodyType(uid, BodyType.Static, body: args.OurBody);
- _transform.SetParent(uid, args.OtherEntity);
- var ev = new ProjectileEmbedEvent(projectile.Shooter, projectile.Weapon, args.OtherEntity);
- RaiseLocalEvent(uid, ref ev);
+ _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.RemovalTime.Value,
+ new RemoveEmbeddedProjectileEvent(), eventTarget: uid, target: uid)
+ {
+ DistanceThreshold = SharedInteractionSystem.InteractionRange,
+ });
+ }
+
+ private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent component, 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);
+
+ // Land it just coz uhhh yeah
+ var landEv = new LandEvent(args.User, true);
+ RaiseLocalEvent(uid, ref landEv);
+ _physics.WakeBody(uid, body: physics);
+ }
+
+ private void OnEmbedThrowDoHit(EntityUid uid, EmbeddableProjectileComponent component, ThrowDoHitEvent args)
+ {
+ Embed(uid, args.Target, component);
+ }
+
+ private void OnEmbedProjectileCollide(EntityUid uid, EmbeddableProjectileComponent component, ref ProjectileCollideEvent args)
+ {
+ Embed(uid, args.OtherEntity, component);
+
+ // Raise a specific event for projectiles.
+ if (TryComp(uid, out var projectile))
+ {
+ var ev = new ProjectileEmbedEvent(projectile.Shooter, projectile.Weapon, args.OtherEntity);
+ RaiseLocalEvent(uid, ref ev);
+ }
+ }
+
+ private void Embed(EntityUid uid, EntityUid target, EmbeddableProjectileComponent component)
+ {
+ 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);
+
+ if (component.Offset != Vector2.Zero)
+ {
+ _transform.SetLocalPosition(xform, xform.LocalPosition + xform.LocalRotation.RotateVec(component.Offset));
+ }
}
private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
@@ -49,7 +115,13 @@ namespace Content.Shared.Projectiles
return;
component.Shooter = uid;
- Dirty(component);
+ Dirty(uid, component);
+ }
+
+ [Serializable, NetSerializable]
+ private sealed class RemoveEmbeddedProjectileEvent : DoAfterEvent
+ {
+ public override DoAfterEvent Clone() => this;
}
}
diff --git a/Content.Shared/Throwing/LandEvent.cs b/Content.Shared/Throwing/LandEvent.cs
index 1bf1f20371..f1326bb9ac 100644
--- a/Content.Shared/Throwing/LandEvent.cs
+++ b/Content.Shared/Throwing/LandEvent.cs
@@ -1,5 +1,3 @@
-using JetBrains.Annotations;
-
namespace Content.Shared.Throwing
{
///
diff --git a/Content.Shared/Throwing/ThrowingAngleComponent.cs b/Content.Shared/Throwing/ThrowingAngleComponent.cs
new file mode 100644
index 0000000000..508810028c
--- /dev/null
+++ b/Content.Shared/Throwing/ThrowingAngleComponent.cs
@@ -0,0 +1,19 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Throwing;
+
+///
+/// When thrown applies a specific angle to the thrown entity.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ThrowingAngleComponent : Component
+{
+ ///
+ /// Do we apply throwing spin to the entity.
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("angularVelocity"), AutoNetworkedField]
+ public bool AngularVelocity;
+
+ [ViewVariables(VVAccess.ReadWrite), DataField("angle"), AutoNetworkedField]
+ public Angle Angle;
+}
diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs
index 07b35ca8b7..95232ae565 100644
--- a/Content.Shared/Throwing/ThrowingSystem.cs
+++ b/Content.Shared/Throwing/ThrowingSystem.cs
@@ -75,7 +75,6 @@ public sealed class ThrowingSystem : EntitySystem
physics,
Transform(uid),
projectileQuery,
- tagQuery,
strength,
user,
pushbackRatio,
@@ -94,7 +93,6 @@ public sealed class ThrowingSystem : EntitySystem
PhysicsComponent physics,
TransformComponent transform,
EntityQuery projectileQuery,
- EntityQuery tagQuery,
float strength = 1.0f,
EntityUid? user = null,
float pushbackRatio = PushbackDefault,
@@ -105,7 +103,7 @@ public sealed class ThrowingSystem : EntitySystem
if ((physics.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0)
{
- Logger.Warning($"Tried to throw entity {ToPrettyString(uid)} but can't throw {physics.BodyType} bodies!");
+ Log.Warning($"Tried to throw entity {ToPrettyString(uid)} but can't throw {physics.BodyType} bodies!");
return;
}
@@ -114,12 +112,21 @@ public sealed class ThrowingSystem : EntitySystem
var comp = EnsureComp(uid);
comp.Thrower = user;
+ ThrowingAngleComponent? throwingAngle = null;
// Give it a l'il spin.
- if (physics.InvI > 0f && (!tagQuery.TryGetComponent(uid, out var tag) || !_tagSystem.HasTag(tag, "NoSpinOnThrow")))
+ if (physics.InvI > 0f && (!TryComp(uid, out throwingAngle) || throwingAngle.AngularVelocity))
+ {
_physics.ApplyAngularImpulse(uid, ThrowAngularImpulse / physics.InvI, body: physics);
+ }
else
- transform.LocalRotation = direction.ToWorldAngle() - Math.PI;
+ {
+ Resolve(uid, ref throwingAngle, false);
+ var gridRot = _transform.GetWorldRotation(transform.ParentUid);
+ var angle = direction.ToWorldAngle() - gridRot;
+ var offset = throwingAngle?.Angle ?? Angle.Zero;
+ _transform.SetLocalRotation(uid, angle + offset);
+ }
if (user != null)
_interactionSystem.ThrownInteraction(user.Value, uid);
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml
index 4124cb7db5..b911c67a5b 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml
@@ -25,7 +25,6 @@
count: 4
- type: Tag
tags:
- - NoSpinOnThrow
- Pie
- type: entity
@@ -50,9 +49,6 @@
Quantity: 1.2
- ReagentId: Vitamin
Quantity: 1
- - type: Tag
- tags:
- - NoSpinOnThrow
# Pie
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
index ce257fd329..89a5f6af07 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
@@ -705,7 +705,9 @@
noSpawn: true
components:
- type: EmbeddableProjectile
+ deleteOnRemove: true
- type: Clickable
+ - type: InteractionOutline
- type: Sprite
noRot: false
sprite: Objects/Weapons/Guns/Launchers/grappling_gun.rsi
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml
index d3c674341b..250c3bd29b 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml
@@ -4,9 +4,27 @@
id: Spear
description: Definition of a Classic. Keeping murder affordable since 200,000 BCE.
components:
+ - type: EmbeddableProjectile
+ offset: 0.15,0.15
+ - type: ThrowingAngle
+ angle: 225
- type: Tag
tags:
- Spear
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape: !type:PolygonShape
+ vertices:
+ - -0.40,-0.30
+ - -0.30,-0.40
+ - 0.40,0.30
+ - 0.30,0.40
+ density: 20
+ mask:
+ - ItemMask
+ restitution: 0.3
+ friction: 0.2
- type: Sharp
- type: Sprite
sprite: Objects/Weapons/Melee/spear.rsi
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index acbfa1fb2e..22e5677b31 100644
--- a/Resources/Prototypes/tags.yml
+++ b/Resources/Prototypes/tags.yml
@@ -589,8 +589,6 @@
- type: Tag
id: Multitool
-- type: Tag
- id: NoSpinOnThrow
- type: Tag
id: NoBlockAnchoring