diff --git a/Content.Server/Storage/Components/SpawnTableOnUseComponent.cs b/Content.Server/Storage/Components/SpawnTableOnUseComponent.cs
new file mode 100644
index 00000000000000..a7cbac725439fa
--- /dev/null
+++ b/Content.Server/Storage/Components/SpawnTableOnUseComponent.cs
@@ -0,0 +1,17 @@
+using Content.Server.Storage.EntitySystems;
+using Content.Shared.EntityTable.EntitySelectors;
+
+namespace Content.Server.Storage.Components;
+
+///
+/// Spawns items from an entity table when used in hand.
+///
+[RegisterComponent, Access(typeof(SpawnTableOnUseSystem))]
+public sealed partial class SpawnTableOnUseComponent : Component
+{
+ ///
+ /// The entity table to select entities from.
+ ///
+ [DataField(required: true)]
+ public EntityTableSelector Table = default!;
+}
diff --git a/Content.Server/Storage/EntitySystems/SpawnTableOnUseSystem.cs b/Content.Server/Storage/EntitySystems/SpawnTableOnUseSystem.cs
new file mode 100644
index 00000000000000..96556ed7b25624
--- /dev/null
+++ b/Content.Server/Storage/EntitySystems/SpawnTableOnUseSystem.cs
@@ -0,0 +1,41 @@
+using Content.Server.Administration.Logs;
+using Content.Server.Storage.Components;
+using Content.Shared.Database;
+using Content.Shared.EntityTable;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction.Events;
+
+namespace Content.Server.Storage.EntitySystems;
+
+public sealed class SpawnTableOnUseSystem : EntitySystem
+{
+ [Dependency] private readonly EntityTableSystem _entityTable = default!;
+ [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnUseInHand);
+ }
+
+ private void OnUseInHand(Entity ent, ref UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+
+ var coords = Transform(ent).Coordinates;
+ var spawns = _entityTable.GetSpawns(ent.Comp.Table);
+ foreach (var id in spawns)
+ {
+ var spawned = Spawn(id, coords);
+ _adminLogger.Add(LogType.EntitySpawn, LogImpact.Low, $"{ToPrettyString(args.User):user} used {ToPrettyString(ent):spawner} which spawned {ToPrettyString(spawned)}");
+ _hands.TryPickupAnyHand(args.User, spawned);
+ }
+
+ Del(ent);
+ }
+}
diff --git a/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs b/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs
index 4e6ebb23d21644..870d20457ef761 100644
--- a/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs
+++ b/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs
@@ -14,4 +14,11 @@ public abstract partial class BaseEmitSoundComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
[DataField(required: true)]
public SoundSpecifier? Sound;
+
+ ///
+ /// Play the sound at the position instead of parented to the source entity.
+ /// Useful if the entity is deleted after.
+ ///
+ [DataField]
+ public bool Positional;
}
diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs
index 3e051fff317770..30744b68644cbc 100644
--- a/Content.Shared/Sound/SharedEmitSoundSystem.cs
+++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs
@@ -145,14 +145,22 @@ protected void TryEmitSound(EntityUid uid, BaseEmitSoundComponent component, Ent
if (component.Sound == null)
return;
- if (predict)
+ if (component.Positional)
{
- _audioSystem.PlayPredicted(component.Sound, uid, user);
+ var coords = Transform(uid).Coordinates;
+ if (predict)
+ _audioSystem.PlayPredicted(component.Sound, coords, user);
+ else if (_netMan.IsServer)
+ // don't predict sounds that client couldn't have played already
+ _audioSystem.PlayPvs(component.Sound, coords);
}
- else if (_netMan.IsServer)
+ else
{
- // don't predict sounds that client couldn't have played already
- _audioSystem.PlayPvs(component.Sound, uid);
+ if (predict)
+ _audioSystem.PlayPredicted(component.Sound, uid, user);
+ else if (_netMan.IsServer)
+ // don't predict sounds that client couldn't have played already
+ _audioSystem.PlayPvs(component.Sound, uid);
}
}