diff --git a/Content.Server/Sound/EmitSoundSystem.cs b/Content.Server/Sound/EmitSoundSystem.cs index bc271a8351..e4f9ebcb43 100644 --- a/Content.Server/Sound/EmitSoundSystem.cs +++ b/Content.Server/Sound/EmitSoundSystem.cs @@ -11,7 +11,9 @@ public sealed class EmitSoundSystem : SharedEmitSoundSystem public override void Update(float frameTime) { base.Update(frameTime); - foreach (var soundSpammer in EntityQuery()) + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var soundSpammer)) { if (!soundSpammer.Enabled) continue; @@ -26,8 +28,8 @@ public sealed class EmitSoundSystem : SharedEmitSoundSystem if (Random.Prob(soundSpammer.PlayChance)) { if (soundSpammer.PopUp != null) - Popup.PopupEntity(Loc.GetString(soundSpammer.PopUp), soundSpammer.Owner); - TryEmitSound(soundSpammer); + Popup.PopupEntity(Loc.GetString(soundSpammer.PopUp), uid); + TryEmitSound(uid, soundSpammer); } } } @@ -40,14 +42,14 @@ public sealed class EmitSoundSystem : SharedEmitSoundSystem SubscribeLocalEvent(HandleEmitSoundOnUIOpen); } - private void HandleEmitSoundOnUIOpen(EntityUid eUI, EmitSoundOnUIOpenComponent component, AfterActivatableUIOpenEvent args) + private void HandleEmitSoundOnUIOpen(EntityUid uid, EmitSoundOnUIOpenComponent component, AfterActivatableUIOpenEvent args) { - TryEmitSound(component, args.User); + TryEmitSound(uid, component, args.User); } private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args) { - TryEmitSound(component); + TryEmitSound(uid, component); args.Handled = true; } } diff --git a/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs index 501195eb79..5b5330a6d3 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs @@ -1,23 +1,22 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Sound.Components +namespace Content.Shared.Sound.Components; + +/// +/// Simple sound emitter that emits sound on ActivateInWorld +/// +[RegisterComponent, NetworkedComponent] +public sealed class EmitSoundOnActivateComponent : BaseEmitSoundComponent { /// - /// Simple sound emitter that emits sound on ActivateInWorld + /// Whether or not to mark an interaction as handled after playing the sound. Useful if this component is + /// used to play sound for some other component with activation functionality. /// - [RegisterComponent, NetworkedComponent] - public sealed class EmitSoundOnActivateComponent : BaseEmitSoundComponent - { - /// - /// Whether or not to mark an interaction as handled after playing the sound. Useful if this component is - /// used to play sound for some other component with activation functionality. - /// - /// - /// If false, you should be confident that the interaction will also be handled by some other system, as - /// otherwise this might enable sound spamming, as use-delays are only initiated if the interaction was - /// handled. - /// - [DataField("handle")] - public bool Handle = true; - } + /// + /// If false, you should be confident that the interaction will also be handled by some other system, as + /// otherwise this might enable sound spamming, as use-delays are only initiated if the interaction was + /// handled. + /// + [DataField("handle")] + public bool Handle = true; } diff --git a/Content.Shared/Sound/Components/EmitSoundOnCollideComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnCollideComponent.cs new file mode 100644 index 0000000000..e800124818 --- /dev/null +++ b/Content.Shared/Sound/Components/EmitSoundOnCollideComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Sound.Components; + +[RegisterComponent, NetworkedComponent] +public sealed class EmitSoundOnCollideComponent : BaseEmitSoundComponent +{ + public static readonly TimeSpan CollideCooldown = TimeSpan.FromSeconds(0.2); + + /// + /// Minimum velocity required for the sound to play. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("minVelocity")] + public float MinimumVelocity = 0.25f; + + /// + /// To avoid sound spam add a cooldown to it. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("nextSound", customTypeSerializer:typeof(TimeOffsetSerializer))] + public TimeSpan NextSound; +} diff --git a/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs index 16a8e9995d..d3c4206e91 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs @@ -1,12 +1,11 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Sound.Components +namespace Content.Shared.Sound.Components; + +/// +/// Simple sound emitter that emits sound on entity drop +/// +[RegisterComponent, NetworkedComponent] +public sealed class EmitSoundOnDropComponent : BaseEmitSoundComponent { - /// - /// Simple sound emitter that emits sound on entity drop - /// - [RegisterComponent, NetworkedComponent] - public sealed class EmitSoundOnDropComponent : BaseEmitSoundComponent - { - } } diff --git a/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs index 8507d2d15e..5bf3416bbe 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs @@ -1,12 +1,11 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Sound.Components +namespace Content.Shared.Sound.Components; + +/// +/// Simple sound emitter that emits sound on LandEvent +/// +[RegisterComponent, NetworkedComponent] +public sealed class EmitSoundOnLandComponent : BaseEmitSoundComponent { - /// - /// Simple sound emitter that emits sound on LandEvent - /// - [RegisterComponent, NetworkedComponent] - public sealed class EmitSoundOnLandComponent : BaseEmitSoundComponent - { - } } diff --git a/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs index 5fbb920f63..2b9cd96d05 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs @@ -1,12 +1,11 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Sound.Components +namespace Content.Shared.Sound.Components; + +/// +/// Simple sound emitter that emits sound on entity pickup +/// +[RegisterComponent, NetworkedComponent] +public sealed class EmitSoundOnPickupComponent : BaseEmitSoundComponent { - /// - /// Simple sound emitter that emits sound on entity pickup - /// - [RegisterComponent, NetworkedComponent] - public sealed class EmitSoundOnPickupComponent : BaseEmitSoundComponent - { - } } diff --git a/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs index d53d691e18..0b76e3305e 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs @@ -1,12 +1,11 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Sound.Components +namespace Content.Shared.Sound.Components; + +/// +/// Simple sound emitter that emits sound on ThrowEvent +/// +[RegisterComponent, NetworkedComponent] +public sealed class EmitSoundOnThrowComponent : BaseEmitSoundComponent { - /// - /// Simple sound emitter that emits sound on ThrowEvent - /// - [RegisterComponent, NetworkedComponent] - public sealed class EmitSoundOnThrowComponent : BaseEmitSoundComponent - { - } } diff --git a/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs index 6a7b1e120e..5bb95a5c11 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs @@ -1,23 +1,22 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Sound.Components +namespace Content.Shared.Sound.Components; + +/// +/// Simple sound emitter that emits sound on UseInHand +/// +[RegisterComponent] +public sealed class EmitSoundOnUseComponent : BaseEmitSoundComponent { /// - /// Simple sound emitter that emits sound on UseInHand + /// Whether or not to mark an interaction as handled after playing the sound. Useful if this component is + /// used to play sound for some other component with on-use functionality /// - [RegisterComponent] - public sealed class EmitSoundOnUseComponent : BaseEmitSoundComponent - { - /// - /// Whether or not to mark an interaction as handled after playing the sound. Useful if this component is - /// used to play sound for some other component with on-use functionality - /// - /// - /// If false, you should be confident that the interaction will also be handled by some other system, as - /// otherwise this might enable sound spamming, as use-delays are only initiated if the interaction was - /// handled. - /// - [DataField("handle")] - public bool Handle = true; - } + /// + /// If false, you should be confident that the interaction will also be handled by some other system, as + /// otherwise this might enable sound spamming, as use-delays are only initiated if the interaction was + /// handled. + /// + [DataField("handle")] + public bool Handle = true; } diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index c7bf5cf137..86433d4967 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -8,104 +8,122 @@ using Content.Shared.Throwing; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Network; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Events; using Robust.Shared.Random; +using Robust.Shared.Timing; -namespace Content.Shared.Sound +namespace Content.Shared.Sound; + +/// +/// Will play a sound on various events if the affected entity has a component derived from BaseEmitSoundComponent +/// +[UsedImplicitly] +public abstract class SharedEmitSoundSystem : EntitySystem { - /// - /// Will play a sound on various events if the affected entity has a component derived from BaseEmitSoundComponent - /// - [UsedImplicitly] - public abstract class SharedEmitSoundSystem : EntitySystem + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _netMan = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; + [Dependency] protected readonly IRobustRandom Random = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; + + public override void Initialize() { - [Dependency] private readonly INetManager _netMan = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; - [Dependency] protected readonly IRobustRandom Random = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] protected readonly SharedPopupSystem Popup = default!; + base.Initialize(); + SubscribeLocalEvent(OnEmitSpawnOnInit); + SubscribeLocalEvent(OnEmitSoundOnLand); + SubscribeLocalEvent(OnEmitSoundOnUseInHand); + SubscribeLocalEvent(OnEmitSoundOnThrown); + SubscribeLocalEvent(OnEmitSoundOnActivateInWorld); + SubscribeLocalEvent(OnEmitSoundOnPickup); + SubscribeLocalEvent(OnEmitSoundOnDrop); + SubscribeLocalEvent(OnEmitSoundOnCollide); + } - public override void Initialize() + private void OnEmitSpawnOnInit(EntityUid uid, EmitSoundOnSpawnComponent component, ComponentInit args) + { + TryEmitSound(uid, component, predict: false); + } + + private void OnEmitSoundOnLand(EntityUid uid, BaseEmitSoundComponent component, ref LandEvent args) + { + if (!TryComp(uid, out var xform) || + !_mapManager.TryGetGrid(xform.GridUid, out var grid)) + return; + + var tile = grid.GetTileRef(xform.Coordinates); + + // Handle maps being grids (we'll still emit the sound). + if (xform.GridUid != xform.MapUid && tile.IsSpace(_tileDefMan)) + return; + + // hand throwing not predicted sadly + TryEmitSound(uid, component, args.User, false); + } + + private void OnEmitSoundOnUseInHand(EntityUid uid, EmitSoundOnUseComponent component, UseInHandEvent args) + { + // Intentionally not checking whether the interaction has already been handled. + TryEmitSound(uid, component, args.User); + + if (component.Handle) + args.Handled = true; + } + + private void OnEmitSoundOnThrown(EntityUid uid, BaseEmitSoundComponent component, ThrownEvent args) + { + TryEmitSound(uid, component, args.User, false); + } + + private void OnEmitSoundOnActivateInWorld(EntityUid uid, EmitSoundOnActivateComponent component, ActivateInWorldEvent args) + { + // Intentionally not checking whether the interaction has already been handled. + TryEmitSound(uid, component, args.User); + + if (component.Handle) + args.Handled = true; + } + + private void OnEmitSoundOnPickup(EntityUid uid, EmitSoundOnPickupComponent component, GotEquippedHandEvent args) + { + TryEmitSound(uid, component, args.User); + } + + private void OnEmitSoundOnDrop(EntityUid uid, EmitSoundOnDropComponent component, DroppedEvent args) + { + TryEmitSound(uid, component, args.User); + } + + protected void TryEmitSound(EntityUid uid, BaseEmitSoundComponent component, EntityUid? user=null, bool predict=true) + { + if (component.Sound == null) + return; + + if (predict) { - base.Initialize(); - SubscribeLocalEvent(HandleEmitSpawnOnInit); - SubscribeLocalEvent(OnEmitSoundOnLand); - SubscribeLocalEvent(HandleEmitSoundOnUseInHand); - SubscribeLocalEvent(HandleEmitSoundOnThrown); - SubscribeLocalEvent(HandleEmitSoundOnActivateInWorld); - SubscribeLocalEvent(HandleEmitSoundOnPickup); - SubscribeLocalEvent(HandleEmitSoundOnDrop); + _audioSystem.PlayPredicted(component.Sound, uid, user); } - - private void HandleEmitSpawnOnInit(EntityUid uid, EmitSoundOnSpawnComponent component, ComponentInit args) + else if (_netMan.IsServer) { - TryEmitSound(component, predict: false); - } - - private void OnEmitSoundOnLand(EntityUid uid, BaseEmitSoundComponent component, ref LandEvent args) - { - if (!TryComp(uid, out var xform) || - !_mapManager.TryGetGrid(xform.GridUid, out var grid)) - return; - - var tile = grid.GetTileRef(xform.Coordinates); - - // Handle maps being grids (we'll still emit the sound). - if (xform.GridUid != xform.MapUid && tile.IsSpace(_tileDefMan)) - return; - - // hand throwing not predicted sadly - TryEmitSound(component, args.User, false); - } - - private void HandleEmitSoundOnUseInHand(EntityUid eUI, EmitSoundOnUseComponent component, UseInHandEvent args) - { - // Intentionally not checking whether the interaction has already been handled. - TryEmitSound(component, args.User); - - if (component.Handle) - args.Handled = true; - } - - private void HandleEmitSoundOnThrown(EntityUid eUI, BaseEmitSoundComponent component, ThrownEvent args) - { - TryEmitSound(component, args.User, false); - } - - private void HandleEmitSoundOnActivateInWorld(EntityUid eUI, EmitSoundOnActivateComponent component, ActivateInWorldEvent args) - { - // Intentionally not checking whether the interaction has already been handled. - TryEmitSound(component, args.User); - - if (component.Handle) - args.Handled = true; - } - - private void HandleEmitSoundOnPickup(EntityUid uid, EmitSoundOnPickupComponent component, GotEquippedHandEvent args) - { - TryEmitSound(component, args.User); - } - - private void HandleEmitSoundOnDrop(EntityUid uid, EmitSoundOnDropComponent component, DroppedEvent args) - { - TryEmitSound(component, args.User); - } - - protected void TryEmitSound(BaseEmitSoundComponent component, EntityUid? user=null, bool predict=true) - { - if (component.Sound == null) - return; - - if (predict) - { - _audioSystem.PlayPredicted(component.Sound, component.Owner, user); - } - else if (_netMan.IsServer) - { - // don't predict sounds that client couldn't have played already - _audioSystem.PlayPvs(component.Sound, component.Owner); - } + // don't predict sounds that client couldn't have played already + _audioSystem.PlayPvs(component.Sound, uid); } } -} + private void OnEmitSoundOnCollide(EntityUid uid, EmitSoundOnCollideComponent component, ref StartCollideEvent args) + { + if (!args.OurFixture.Hard || + !args.OtherFixture.Hard || + !TryComp(uid, out var physics) || + physics.LinearVelocity.Length < component.MinimumVelocity || + _timing.CurTime < component.NextSound) + { + return; + } + + component.NextSound = _timing.CurTime + EmitSoundOnCollideComponent.CollideCooldown; + TryEmitSound(uid, component, predict: false); + } +} diff --git a/Resources/Audio/Effects/licenses.txt b/Resources/Audio/Effects/licenses.txt index 95d0d98f70..719cc77cd1 100644 --- a/Resources/Audio/Effects/licenses.txt +++ b/Resources/Audio/Effects/licenses.txt @@ -50,4 +50,9 @@ box_deploy.ogg and chime.ogg taken from Citadel Station at commit: https://githu license: "Royalty free" copyright: "Sonniss.com - GDC 2016 - Game Audio Bundle - Levan Nadashvili - Soldier Footsteps - FS Concrete Soldier Crouch N02, mixed from stereo to mono" +- files: + - "wall_bonk.ogg" + license: "Royalty free" + copyright: "Sonniss.com - GDC 2023 - Game Audio Bundle - 344 Audio - Nuts and Bolts" + # Do not add to this list, I only did the above because yaml scheme validator doesn't like custom licenses. diff --git a/Resources/Audio/Effects/wall_bonk.ogg b/Resources/Audio/Effects/wall_bonk.ogg new file mode 100644 index 0000000000..b1c5505f61 Binary files /dev/null and b/Resources/Audio/Effects/wall_bonk.ogg differ diff --git a/Resources/Prototypes/Entities/Objects/base_item.yml b/Resources/Prototypes/Entities/Objects/base_item.yml index a9b70dd662..bd17cea822 100644 --- a/Resources/Prototypes/Entities/Objects/base_item.yml +++ b/Resources/Prototypes/Entities/Objects/base_item.yml @@ -8,6 +8,11 @@ - type: Clickable - type: InteractionOutline - type: MovedByPressure + - type: EmitSoundOnCollide + sound: + path: /Audio/Effects/wall_bonk.ogg + params: + volume: 2 - type: EmitSoundOnLand sound: path: /Audio/Effects/drop.ogg