From fd5a4d9b8a587451b638df70fe5b0324e7933c6b Mon Sep 17 00:00:00 2001 From: pathetic meowmeow Date: Sat, 22 Feb 2025 11:29:47 -0500 Subject: [PATCH] Refactor audio system to send collection IDs over the network (#5540) This is important groundwork for future features such as captioning, as a caption and other data can be associated with the collection prototype instead of passing extra data everywhere with the sound. --- .../Animations/AnimationTrackPlaySound.cs | 12 +-- Robust.Client/Audio/AudioSystem.cs | 100 ++++++++++-------- Robust.Server/Audio/AudioSystem.cs | 46 ++++---- Robust.Shared/Audio/ResolvedSoundSpecifier.cs | 90 ++++++++++++++++ Robust.Shared/Audio/SoundSpecifier.cs | 6 ++ .../Audio/Systems/SharedAudioSystem.cs | 90 ++++++++++------ 6 files changed, 237 insertions(+), 107 deletions(-) create mode 100644 Robust.Shared/Audio/ResolvedSoundSpecifier.cs diff --git a/Robust.Client/Animations/AnimationTrackPlaySound.cs b/Robust.Client/Animations/AnimationTrackPlaySound.cs index be5051a4d8..11f6a6e743 100644 --- a/Robust.Client/Animations/AnimationTrackPlaySound.cs +++ b/Robust.Client/Animations/AnimationTrackPlaySound.cs @@ -40,11 +40,7 @@ namespace Robust.Client.Animations var keyFrame = KeyFrames[keyFrameIndex]; var audioParams = keyFrame.AudioParamsFunc.Invoke(); - var audio = new SoundPathSpecifier(keyFrame.Resource) - { - Params = audioParams - }; - IoCManager.Resolve().GetEntitySystem().PlayEntity(audio, Filter.Local(), entity, true); + IoCManager.Resolve().GetEntitySystem().PlayEntity(keyFrame.Specifier, Filter.Local(), entity, true, audioParams); } return (keyFrameIndex, playingTime); @@ -55,7 +51,7 @@ namespace Robust.Client.Animations /// /// The RSI state to play when this keyframe gets triggered. /// - public readonly string Resource; + public readonly ResolvedSoundSpecifier Specifier; /// /// A function that returns the audio parameter to be used. @@ -69,9 +65,9 @@ namespace Robust.Client.Animations /// public readonly float KeyTime; - public KeyFrame(string resource, float keyTime, Func? audioParams = null) + public KeyFrame(ResolvedSoundSpecifier specifier, float keyTime, Func? audioParams = null) { - Resource = resource; + Specifier = specifier; KeyTime = keyTime; AudioParamsFunc = audioParams ?? (() => AudioParams.Default); } diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs index ff6f702dd0..51105cbc63 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -415,6 +415,16 @@ public sealed partial class AudioSystem : SharedAudioSystem return occlusion; } + private bool TryGetAudio(ResolvedSoundSpecifier specifier, [NotNullWhen(true)] out AudioResource? audio) + { + var filename = GetAudioPath(specifier); + if (_resourceCache.TryGetResource(new ResPath(filename), out audio)) + return true; + + Log.Error($"Server tried to play audio file {filename} which does not exist."); + return false; + } + private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio) { if (_resourceCache.TryGetResource(new ResPath(filename), out audio)) @@ -433,15 +443,15 @@ public sealed partial class AudioSystem : SharedAudioSystem return false; } - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates, + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates, AudioParams? audioParams = null) { - return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams); + return PlayStatic(specifier, Filter.Local(), coordinates, true, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityUid uid, AudioParams? audioParams = null) { - return PlayEntity(filename, Filter.Local(), uid, true, audioParams); + return PlayEntity(specifier, Filter.Local(), uid, true, audioParams); } /// @@ -477,21 +487,21 @@ public sealed partial class AudioSystem : SharedAudioSystem /// /// The resource path to the OGG Vorbis file to play. /// - private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true) + private (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null, bool recordReplay = true) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; if (recordReplay && _replayRecording.IsRecording) { _replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage { - FileName = filename, + Specifier = specifier, AudioParams = audioParams ?? AudioParams.Default }); } - return TryGetAudio(filename, out var audio) ? PlayGlobal(audio, audioParams) : default; + return TryGetAudio(specifier, out var audio) ? PlayGlobal(audio, specifier, audioParams) : default; } /// @@ -499,9 +509,9 @@ public sealed partial class AudioSystem : SharedAudioSystem /// /// The audio stream to play. /// - public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null) + public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null) { - var (entity, component) = CreateAndStartPlayingStream(audioParams, stream); + var (entity, component) = CreateAndStartPlayingStream(audioParams, specifier, stream); component.Global = true; component.Source.Global = true; DirtyField(entity, component, nameof(AudioComponent.Global)); @@ -513,22 +523,22 @@ public sealed partial class AudioSystem : SharedAudioSystem /// /// The resource path to the OGG Vorbis file to play. /// The entity "emitting" the audio. - private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true) + private (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; if (recordReplay && _replayRecording.IsRecording) { _replayRecording.RecordReplayMessage(new PlayAudioEntityMessage { - FileName = filename, + Specifier = specifier, NetEntity = GetNetEntity(entity), AudioParams = audioParams ?? AudioParams.Default }); } - return TryGetAudio(filename, out var audio) ? PlayEntity(audio, entity, audioParams) : default; + return TryGetAudio(specifier, out var audio) ? PlayEntity(audio, entity, specifier, audioParams) : default; } /// @@ -537,7 +547,7 @@ public sealed partial class AudioSystem : SharedAudioSystem /// The audio stream to play. /// The entity "emitting" the audio. /// - public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null) + public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null) { if (TerminatingOrDeleted(entity)) { @@ -545,7 +555,7 @@ public sealed partial class AudioSystem : SharedAudioSystem return null; } - var playing = CreateAndStartPlayingStream(audioParams, stream); + var playing = CreateAndStartPlayingStream(audioParams, specifier, stream); _xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero)); return playing; @@ -557,22 +567,22 @@ public sealed partial class AudioSystem : SharedAudioSystem /// The resource path to the OGG Vorbis file to play. /// The coordinates at which to play the audio. /// - private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true) + private (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; if (recordReplay && _replayRecording.IsRecording) { _replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage { - FileName = filename, + Specifier = specifier, Coordinates = GetNetCoordinates(coordinates), AudioParams = audioParams ?? AudioParams.Default }); } - return TryGetAudio(filename, out var audio) ? PlayStatic(audio, coordinates, audioParams) : default; + return TryGetAudio(specifier, out var audio) ? PlayStatic(audio, coordinates, specifier, audioParams) : default; } /// @@ -581,7 +591,7 @@ public sealed partial class AudioSystem : SharedAudioSystem /// The audio stream to play. /// The coordinates at which to play the audio. /// - public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null) + public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null) { if (TerminatingOrDeleted(coordinates.EntityId)) { @@ -589,33 +599,33 @@ public sealed partial class AudioSystem : SharedAudioSystem return null; } - var playing = CreateAndStartPlayingStream(audioParams, stream); + var playing = CreateAndStartPlayingStream(audioParams, specifier, stream); _xformSys.SetCoordinates(playing.Entity, coordinates); return playing; } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { - return PlayGlobal(filename, audioParams); + return PlayGlobal(specifier, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null) { - return PlayEntity(filename, entity, audioParams); + return PlayEntity(specifier, entity, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) { - return PlayStatic(filename, coordinates, audioParams); + return PlayStatic(specifier, coordinates, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, ICommonSession recipient, AudioParams? audioParams = null) { - return PlayGlobal(filename, audioParams); + return PlayGlobal(specifier, audioParams); } public override void LoadStream(Entity entity, T stream) @@ -629,39 +639,39 @@ public sealed partial class AudioSystem : SharedAudioSystem } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, EntityUid recipient, AudioParams? audioParams = null) { - return PlayGlobal(filename, audioParams); + return PlayGlobal(specifier, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) { - return PlayEntity(filename, uid, audioParams); + return PlayEntity(specifier, uid, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) { - return PlayEntity(filename, uid, audioParams); + return PlayEntity(specifier, uid, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { - return PlayStatic(filename, coordinates, audioParams); + return PlayStatic(specifier, coordinates, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { - return PlayStatic(filename, coordinates, audioParams); + return PlayStatic(specifier, coordinates, audioParams); } - private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream) + private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, ResolvedSoundSpecifier? specifier, AudioStream stream) { var audioP = audioParams ?? AudioParams.Default; - var entity = SetupAudio(null, audioP, initialize: false, length: stream.Length); + var entity = SetupAudio(specifier, audioP, initialize: false, length: stream.Length); LoadStream(entity, stream); EntityManager.InitializeAndStartEntity(entity); var comp = entity.Comp; @@ -694,17 +704,17 @@ public sealed partial class AudioSystem : SharedAudioSystem private void OnEntityCoordinates(PlayAudioPositionalMessage ev) { - PlayStatic(ev.FileName, GetCoordinates(ev.Coordinates), ev.AudioParams, false); + PlayStatic(ev.Specifier, GetCoordinates(ev.Coordinates), ev.AudioParams, false); } private void OnEntityAudio(PlayAudioEntityMessage ev) { - PlayEntity(ev.FileName, GetEntity(ev.NetEntity), ev.AudioParams, false); + PlayEntity(ev.Specifier, GetEntity(ev.NetEntity), ev.AudioParams, false); } private void OnGlobalAudio(PlayAudioGlobalMessage ev) { - PlayGlobal(ev.FileName, ev.AudioParams, false); + PlayGlobal(ev.Specifier, ev.AudioParams, false); } protected override TimeSpan GetAudioLengthImpl(string filename) diff --git a/Robust.Server/Audio/AudioSystem.cs b/Robust.Server/Audio/AudioSystem.cs index 163df02f5b..e453f4b1c4 100644 --- a/Robust.Server/Audio/AudioSystem.cs +++ b/Robust.Server/Audio/AudioSystem.cs @@ -81,27 +81,27 @@ public sealed partial class AudioSystem : SharedAudioSystem } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; - var entity = SetupAudio(filename, audioParams); + var entity = SetupAudio(specifier, audioParams); AddAudioFilter(entity, entity.Comp, playerFilter); entity.Comp.Global = true; return (entity, entity.Comp); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; if (TerminatingOrDeleted(uid)) return null; - var entity = SetupAudio(filename, audioParams); + var entity = SetupAudio(specifier, audioParams); // Move it after setting it up XformSystem.SetCoordinates(entity, new EntityCoordinates(uid, Vector2.Zero)); @@ -115,24 +115,24 @@ public sealed partial class AudioSystem : SharedAudioSystem } /// - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityUid uid, AudioParams? audioParams = null) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; if (TerminatingOrDeleted(uid)) return null; - var entity = SetupAudio(filename, audioParams); + var entity = SetupAudio(specifier, audioParams); XformSystem.SetCoordinates(entity, new EntityCoordinates(uid, Vector2.Zero)); return (entity, entity.Comp); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; if (TerminatingOrDeleted(coordinates.EntityId)) @@ -144,7 +144,7 @@ public sealed partial class AudioSystem : SharedAudioSystem if (!coordinates.IsValid(EntityManager)) return null; - var entity = SetupAudio(filename, audioParams); + var entity = SetupAudio(specifier, audioParams); XformSystem.SetCoordinates(entity, coordinates); AddAudioFilter(entity, entity.Comp, playerFilter); @@ -152,10 +152,10 @@ public sealed partial class AudioSystem : SharedAudioSystem } /// - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates, + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates, AudioParams? audioParams = null) { - if (string.IsNullOrEmpty(filename)) + if (specifier is null) return null; if (TerminatingOrDeleted(coordinates.EntityId)) @@ -168,7 +168,7 @@ public sealed partial class AudioSystem : SharedAudioSystem return null; // TODO: Transform TryFindGridAt mess + optimisation required. - var entity = SetupAudio(filename, audioParams); + var entity = SetupAudio(specifier, audioParams); XformSystem.SetCoordinates(entity, coordinates); return (entity, entity.Comp); @@ -191,7 +191,7 @@ public sealed partial class AudioSystem : SharedAudioSystem if (sound == null) return null; - var audio = PlayPvs(GetSound(sound), source, audioParams ?? sound.Params); + var audio = PlayPvs(ResolveSound(sound), source, audioParams ?? sound.Params); if (audio == null) return null; @@ -206,7 +206,7 @@ public sealed partial class AudioSystem : SharedAudioSystem if (sound == null) return null; - var audio = PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params); + var audio = PlayPvs(ResolveSound(sound), coordinates, audioParams ?? sound.Params); if (audio == null) return null; @@ -215,12 +215,12 @@ public sealed partial class AudioSystem : SharedAudioSystem return audio; } - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, ICommonSession recipient, AudioParams? audioParams = null) { return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, EntityUid recipient, AudioParams? audioParams = null) { if (TryComp(recipient, out ActorComponent? actor)) return PlayGlobal(filename, actor.PlayerSession, audioParams); @@ -228,12 +228,12 @@ public sealed partial class AudioSystem : SharedAudioSystem return null; } - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) { return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) { if (TryComp(recipient, out ActorComponent? actor)) return PlayEntity(filename, actor.PlayerSession, uid, audioParams); @@ -241,12 +241,12 @@ public sealed partial class AudioSystem : SharedAudioSystem return null; } - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { if (TryComp(recipient, out ActorComponent? actor)) return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams); diff --git a/Robust.Shared/Audio/ResolvedSoundSpecifier.cs b/Robust.Shared/Audio/ResolvedSoundSpecifier.cs new file mode 100644 index 0000000000..e32e553dbc --- /dev/null +++ b/Robust.Shared/Audio/ResolvedSoundSpecifier.cs @@ -0,0 +1,90 @@ +using System; +using JetBrains.Annotations; +using Robust.Shared.Utility; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Prototypes; + +namespace Robust.Shared.Audio; + +/// +/// Represents a path to a sound resource, either as a literal path or as a collection ID and index. +/// +/// +/// +[Serializable, NetSerializable] +public abstract partial class ResolvedSoundSpecifier { + [Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")] + public static implicit operator ResolvedSoundSpecifier(string s) => new ResolvedPathSpecifier(s); + [Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")] + public static implicit operator ResolvedSoundSpecifier(ResPath s) => new ResolvedPathSpecifier(s); + + /// + /// Returns whether s is null, or if it contains an empty path/collection ID. + /// + public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s) { + return s switch { + null => true, + ResolvedPathSpecifier path => path.Path.ToString() == "", + ResolvedCollectionSpecifier collection => string.IsNullOrEmpty(collection.Collection), + _ => throw new ArgumentOutOfRangeException("s", s, "argument is not a ResolvedPathSpecifier or a ResolvedCollectionSpecifier"), + }; + } +} + +/// +/// Represents a path to a sound resource as a literal path. +/// +/// +[Serializable, NetSerializable] +public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier { + /// + /// The resource path of the sound. + /// + public ResPath Path { get; private set; } + + override public string ToString() => + $"ResolvedPathSpecifier({Path})"; + + [UsedImplicitly] + private ResolvedPathSpecifier() + { + } + public ResolvedPathSpecifier(ResPath path) + { + Path = path; + } + public ResolvedPathSpecifier(string path) : this(new ResPath(path)) + { + } +} + +/// +/// Represents a path to a sound resource as a collection ID and index. +/// +/// +[Serializable, NetSerializable] +public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier { + /// + /// The ID of the sound collection to look up. + /// + public ProtoId? Collection { get; private set; } + /// + /// The index of the file in the associated sound collection to play. + /// + public int Index { get; private set; } + + override public string ToString() => + $"ResolvedCollectionSpecifier({Collection}, {Index})"; + + [UsedImplicitly] + private ResolvedCollectionSpecifier() + { + } + + public ResolvedCollectionSpecifier(string collection, int index) + { + Collection = collection; + Index = index; + } +} diff --git a/Robust.Shared/Audio/SoundSpecifier.cs b/Robust.Shared/Audio/SoundSpecifier.cs index f88254edc2..3378d34f8a 100644 --- a/Robust.Shared/Audio/SoundSpecifier.cs +++ b/Robust.Shared/Audio/SoundSpecifier.cs @@ -27,6 +27,9 @@ public sealed partial class SoundPathSpecifier : SoundSpecifier [DataField(Node, customTypeSerializer: typeof(ResPathSerializer), required: true)] public ResPath Path { get; private set; } + override public string ToString() => + $"SoundPathSpecifier({Path})"; + [UsedImplicitly] private SoundPathSpecifier() { @@ -52,6 +55,9 @@ public sealed partial class SoundCollectionSpecifier : SoundSpecifier [DataField(Node, customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] public string? Collection { get; private set; } + override public string ToString() => + $"SoundCollectionSpecifier({Collection})"; + [UsedImplicitly] public SoundCollectionSpecifier() { } diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index 42d003ddd8..3d51de8893 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -283,33 +283,60 @@ public abstract partial class SharedAudioSystem : EntitySystem } /// - /// Resolves the filepath to a sound file. + /// Resolve a sound specifier so it can be consistently played back on all clients. /// - public string GetSound(SoundSpecifier specifier) + public ResolvedSoundSpecifier ResolveSound(SoundSpecifier specifier) { switch (specifier) { case SoundPathSpecifier path: - return path.Path == default ? string.Empty : path.Path.ToString(); + return new ResolvedPathSpecifier(path.Path == default ? string.Empty : path.Path.ToString()); case SoundCollectionSpecifier collection: { if (collection.Collection == null) - return string.Empty; + return new ResolvedPathSpecifier(string.Empty); var soundCollection = ProtoMan.Index(collection.Collection); - return RandMan.Pick(soundCollection.PickFiles).ToString(); + var index = RandMan.Next(soundCollection.PickFiles.Count); + return new ResolvedCollectionSpecifier(collection.Collection, index); } } - return string.Empty; + return new ResolvedPathSpecifier(string.Empty); + } + + /// + /// Resolves the filepath to a sound file. + /// + [Obsolete("Use ResolveSound() and pass around resolved sound specifiers instead.")] + public string GetSound(SoundSpecifier specifier) + { + var resolved = ResolveSound(specifier); + return GetAudioPath(resolved); } #region AudioParams - protected Entity SetupAudio(string? fileName, AudioParams? audioParams, bool initialize = true, TimeSpan? length = null) + [return: NotNullIfNotNull(nameof(specifier))] + public string? GetAudioPath(ResolvedSoundSpecifier? specifier) + { + return specifier switch { + ResolvedPathSpecifier path => + path.Path.ToString(), + ResolvedCollectionSpecifier collection => + collection.Collection is null ? + string.Empty : + ProtoMan.Index(collection.Collection).PickFiles[collection.Index].ToString(), + null => null, + _ => throw new ArgumentOutOfRangeException("specifier", specifier, "argument is not a ResolvedPathSpecifier or a ResolvedCollectionSpecifier"), + }; + } + + protected Entity SetupAudio(ResolvedSoundSpecifier? specifier, AudioParams? audioParams, bool initialize = true, TimeSpan? length = null) { var uid = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace); + var fileName = GetAudioPath(specifier); DebugTools.Assert(!string.IsNullOrEmpty(fileName) || length is not null); MetadataSys.SetEntityName(uid, $"Audio ({fileName})", raiseEvents: false); audioParams ??= AudioParams.Default; @@ -395,8 +422,9 @@ public abstract partial class SharedAudioSystem : EntitySystem /// /// Gets the timespan of the specified audio. /// - public TimeSpan GetAudioLength(string filename) + public TimeSpan GetAudioLength(ResolvedSoundSpecifier specifier) { + var filename = GetAudioPath(specifier) ?? string.Empty; if (!filename.StartsWith("/")) throw new ArgumentException("Path must be rooted"); @@ -429,7 +457,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// /// The resource path to the OGG Vorbis file to play. /// The set of players that will hear the sound. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null); /// /// Play an audio file globally, without position. @@ -438,7 +466,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The set of players that will hear the sound. public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { - return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params); + return sound == null ? null : PlayGlobal(ResolveSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params); } /// @@ -446,7 +474,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, ICommonSession recipient, AudioParams? audioParams = null); /// /// Play an audio file globally, without position. @@ -455,7 +483,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The player that will hear the sound. public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient, AudioParams? audioParams = null) { - return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); + return sound == null ? null : PlayGlobal(ResolveSound(sound), recipient, audioParams ?? sound.Params); } public abstract void LoadStream(Entity entity, T stream); @@ -465,7 +493,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, EntityUid recipient, AudioParams? audioParams = null); /// /// Play an audio file globally, without position. @@ -474,7 +502,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The player that will hear the sound. public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null) { - return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); + return sound == null ? null : PlayGlobal(ResolveSound(sound), recipient, audioParams ?? sound.Params); } /// @@ -483,7 +511,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The resource path to the OGG Vorbis file to play. /// The set of players that will hear the sound. /// The UID of the entity "emitting" the audio. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null); /// /// Play an audio file following an entity. @@ -491,7 +519,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null); /// /// Play an audio file following an entity. @@ -499,7 +527,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null); /// /// Play an audio file following an entity. @@ -509,7 +537,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The UID of the entity "emitting" the audio. public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) { - return sound == null ? null : PlayEntity(GetSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params); + return sound == null ? null : PlayEntity(ResolveSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params); } /// @@ -520,7 +548,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The UID of the entity "emitting" the audio. public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) { - return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); + return sound == null ? null : PlayEntity(ResolveSound(sound), recipient, uid, audioParams ?? sound.Params); } /// @@ -531,7 +559,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The UID of the entity "emitting" the audio. public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) { - return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); + return sound == null ? null : PlayEntity(ResolveSound(sound), recipient, uid, audioParams ?? sound.Params); } /// @@ -541,7 +569,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The UID of the entity "emitting" the audio. public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null) { - return sound == null ? null : PlayPvs(GetSound(sound), uid, audioParams ?? sound.Params); + return sound == null ? null : PlayPvs(ResolveSound(sound), uid, audioParams ?? sound.Params); } /// @@ -551,7 +579,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The EntityCoordinates to attach the audio source to. public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityCoordinates coordinates, AudioParams? audioParams = null) { - return sound == null ? null : PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params); + return sound == null ? null : PlayPvs(ResolveSound(sound), coordinates, audioParams ?? sound.Params); } /// @@ -559,7 +587,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// /// The sound specifier that points the audio file(s) that should be played. /// The EntityCoordinates to attach the audio source to. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? filename, EntityCoordinates coordinates, AudioParams? audioParams = null); /// @@ -567,7 +595,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// /// The resource path to the OGG Vorbis file to play. /// The UID of the entity "emitting" the audio. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? filename, EntityUid uid, AudioParams? audioParams = null); /// @@ -604,7 +632,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The resource path to the OGG Vorbis file to play. /// The set of players that will hear the sound. /// The coordinates at which to play the audio. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null); /// /// Play an audio file at a static position. @@ -612,7 +640,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The coordinates at which to play the audio. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); /// /// Play an audio file at a static position. @@ -620,7 +648,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The coordinates at which to play the audio. - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); /// /// Play an audio file at a static position. @@ -630,7 +658,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The coordinates at which to play the audio. public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) { - return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay, audioParams); + return sound == null ? null : PlayStatic(ResolveSound(sound), playerFilter, coordinates, recordReplay, audioParams); } /// @@ -641,7 +669,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The coordinates at which to play the audio. public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { - return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); + return sound == null ? null : PlayStatic(ResolveSound(sound), recipient, coordinates, audioParams ?? sound.Params); } /// @@ -652,7 +680,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// The coordinates at which to play the audio. public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { - return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); + return sound == null ? null : PlayStatic(ResolveSound(sound), recipient, coordinates, audioParams ?? sound.Params); } // These are just here for replays now. @@ -665,7 +693,7 @@ public abstract partial class SharedAudioSystem : EntitySystem [NetSerializable, Serializable] protected abstract class AudioMessage : EntityEventArgs { - public string FileName = string.Empty; + public ResolvedSoundSpecifier Specifier = new ResolvedPathSpecifier(string.Empty); public AudioParams AudioParams; }