diff --git a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs
index 3fd46f6efb..cf9c15a10d 100644
--- a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs
+++ b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs
@@ -1,4 +1,5 @@
using System.Numerics;
+using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Projectiles;
@@ -33,4 +34,10 @@ public sealed partial class EmbeddableProjectileComponent : Component
///
[ViewVariables(VVAccess.ReadWrite), DataField("offset"), AutoNetworkedField]
public Vector2 Offset = Vector2.Zero;
+
+ ///
+ /// Sound to play after embedding into a hit target.
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("sound"), AutoNetworkedField]
+ public SoundSpecifier? Sound;
}
diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs
index 541a2dfbee..9365956601 100644
--- a/Content.Shared/Projectiles/SharedProjectileSystem.cs
+++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs
@@ -5,6 +5,7 @@ using Content.Shared.Projectiles;
using Content.Shared.Sound.Components;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Ranged.Components;
+using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
@@ -13,145 +14,151 @@ using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
-namespace Content.Shared.Projectiles
+namespace Content.Shared.Projectiles;
+
+public abstract class SharedProjectileSystem : EntitySystem
{
- public abstract class SharedProjectileSystem : EntitySystem
+ public const string ProjectileFixture = "projectile";
+
+ [Dependency] private readonly INetManager _netManager = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public override void Initialize()
{
- public const string ProjectileFixture = "projectile";
+ base.Initialize();
- [Dependency] private readonly INetManager _netManager = default!;
- [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
+ SubscribeLocalEvent(PreventCollision);
+ SubscribeLocalEvent(OnEmbedProjectileHit);
+ SubscribeLocalEvent(OnEmbedThrowDoHit);
+ SubscribeLocalEvent(OnEmbedActivate);
+ SubscribeLocalEvent(OnEmbedRemove);
+ }
- public override void Initialize()
+ private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args)
+ {
+ // Nuh uh
+ if (component.RemovalTime == null)
+ return;
+
+ if (args.Handled || !TryComp(uid, out var physics) || physics.BodyType != BodyType.Static)
+ return;
+
+ args.Handled = true;
+
+ _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.RemovalTime.Value,
+ new RemoveEmbeddedProjectileEvent(), eventTarget: uid, target: uid)
{
- base.Initialize();
- SubscribeLocalEvent(PreventCollision);
- SubscribeLocalEvent(OnEmbedProjectileHit);
- SubscribeLocalEvent(OnEmbedThrowDoHit);
- SubscribeLocalEvent(OnEmbedActivate);
- SubscribeLocalEvent(OnEmbedRemove);
+ 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;
}
- private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args)
+ 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 OnEmbedProjectileHit(EntityUid uid, EmbeddableProjectileComponent component, ref ProjectileHitEvent args)
+ {
+ Embed(uid, args.Target, component);
+
+ // Raise a specific event for projectiles.
+ if (TryComp(uid, out var projectile))
{
- // Nuh uh
- if (component.RemovalTime == null)
- return;
+ var ev = new ProjectileEmbedEvent(projectile.Shooter, projectile.Weapon, args.Target);
+ RaiseLocalEvent(uid, ref ev);
+ }
+ }
- if (args.Handled || !TryComp(uid, out var physics) || physics.BodyType != BodyType.Static)
- return;
+ 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);
- args.Handled = true;
-
- _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.RemovalTime.Value,
- new RemoveEmbeddedProjectileEvent(), eventTarget: uid, target: uid)
- {
- DistanceThreshold = SharedInteractionSystem.InteractionRange,
- });
+ if (component.Offset != Vector2.Zero)
+ {
+ _transform.SetLocalPosition(xform, xform.LocalPosition + xform.LocalRotation.RotateVec(component.Offset));
}
- private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent component, RemoveEmbeddedProjectileEvent args)
+ if (component.Sound != null)
{
- // 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);
+ _audio.PlayPredicted(component.Sound, uid, null);
}
+ }
- private void OnEmbedThrowDoHit(EntityUid uid, EmbeddableProjectileComponent component, ThrowDoHitEvent args)
+ private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
+ {
+ if (component.IgnoreShooter && args.OtherEntity == component.Shooter)
{
- Embed(uid, args.Target, component);
+ args.Cancelled = true;
}
+ }
- private void OnEmbedProjectileHit(EntityUid uid, EmbeddableProjectileComponent component, ref ProjectileHitEvent args)
- {
- Embed(uid, args.Target, component);
+ public void SetShooter(ProjectileComponent component, EntityUid uid)
+ {
+ if (component.Shooter == uid)
+ return;
- // Raise a specific event for projectiles.
- if (TryComp(uid, out var projectile))
- {
- var ev = new ProjectileEmbedEvent(projectile.Shooter, projectile.Weapon, args.Target);
- 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)
- {
- if (component.IgnoreShooter && args.OtherEntity == component.Shooter)
- {
- args.Cancelled = true;
- }
- }
-
- public void SetShooter(ProjectileComponent component, EntityUid uid)
- {
- if (component.Shooter == uid)
- return;
-
- component.Shooter = uid;
- Dirty(uid, component);
- }
-
- [Serializable, NetSerializable]
- private sealed class RemoveEmbeddedProjectileEvent : DoAfterEvent
- {
- public override DoAfterEvent Clone() => this;
- }
+ component.Shooter = uid;
+ Dirty(uid, component);
}
[Serializable, NetSerializable]
- public sealed class ImpactEffectEvent : EntityEventArgs
+ private sealed class RemoveEmbeddedProjectileEvent : DoAfterEvent
{
- public string Prototype;
- public EntityCoordinates Coordinates;
-
- public ImpactEffectEvent(string prototype, EntityCoordinates coordinates)
- {
- Prototype = prototype;
- Coordinates = coordinates;
- }
+ public override DoAfterEvent Clone() => this;
}
-
- ///
- /// Raised when entity is just about to be hit with projectile but can reflect it
- ///
- [ByRefEvent]
- public record struct ProjectileReflectAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled);
-
- ///
- /// Raised when projectile hits other entity
- ///
- [ByRefEvent]
- public readonly record struct ProjectileHitEvent(EntityUid Target);
}
+
+[Serializable, NetSerializable]
+public sealed class ImpactEffectEvent : EntityEventArgs
+{
+ public string Prototype;
+ public EntityCoordinates Coordinates;
+
+ public ImpactEffectEvent(string prototype, EntityCoordinates coordinates)
+ {
+ Prototype = prototype;
+ Coordinates = coordinates;
+ }
+}
+
+///
+/// Raised when entity is just about to be hit with projectile but can reflect it
+///
+[ByRefEvent]
+public record struct ProjectileReflectAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled);
+
+///
+/// Raised when projectile hits other entity
+///
+[ByRefEvent]
+public readonly record struct ProjectileHitEvent(EntityUid Target);
diff --git a/Resources/Audio/Weapons/attributions.yml b/Resources/Audio/Weapons/attributions.yml
index 5eaba3cacb..6e59d34d59 100644
--- a/Resources/Audio/Weapons/attributions.yml
+++ b/Resources/Audio/Weapons/attributions.yml
@@ -19,3 +19,12 @@
copyright: "User Mystovski on freesound.org. Modified by LankLTE on github."
source: "https://freesound.org/people/Mystovski/sounds/201111/"
+- files: ["genhit3.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Taken from tgstation."
+ source: "https://github.com/tgstation/tgstation/blob/a7f525bce9a359ab5282fc754078cd4b5678a006/sound/weapons/genhit3.ogg"
+
+- files: ["star_hit.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Based on genhit3.ogg from tgstation, modified by deltanedas on github."
+ source: "https://github.com/deltanedas"
diff --git a/Resources/Audio/Weapons/star_hit.ogg b/Resources/Audio/Weapons/star_hit.ogg
new file mode 100644
index 0000000000..5de4051e8b
Binary files /dev/null and b/Resources/Audio/Weapons/star_hit.ogg differ
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml
new file mode 100644
index 0000000000..198f634c16
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml
@@ -0,0 +1,26 @@
+- type: entity
+ parent: BaseItem
+ id: ThrowingStar
+ name: throwing star
+ description: An ancient weapon still used to this day, due to its ease of lodging itself into its victim's body parts.
+ components:
+ - type: Sprite
+ sprite: Objects/Weapons/Throwable/throwing_star.rsi
+ state: icon
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape: !type:PhysShapeCircle
+ radius: 0.2
+ density: 5
+ mask:
+ - ItemMask
+ restitution: 0.3
+ friction: 0.2
+ - type: EmbeddableProjectile
+ sound: /Audio/Weapons/star_hit.ogg
+ - type: DamageOtherOnHit
+ damage:
+ types:
+ Slash: 8
+ Piercing: 10
diff --git a/Resources/Textures/Objects/Weapons/Throwable/throwing_star.rsi/icon.png b/Resources/Textures/Objects/Weapons/Throwable/throwing_star.rsi/icon.png
new file mode 100644
index 0000000000..239b0304ab
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Throwable/throwing_star.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Weapons/Throwable/throwing_star.rsi/meta.json b/Resources/Textures/Objects/Weapons/Throwable/throwing_star.rsi/meta.json
new file mode 100644
index 0000000000..44d65d9d5e
--- /dev/null
+++ b/Resources/Textures/Objects/Weapons/Throwable/throwing_star.rsi/meta.json
@@ -0,0 +1,14 @@
+{
+ "version": 1,
+ "license": "CC0-1.0",
+ "copyright": "Created for SS14 by deltanedas (github)",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ }
+ ]
+}