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