From ef1b41becbe51240cf2ccd1bad31c376dd411f9f Mon Sep 17 00:00:00 2001 From: Janet Blackquill Date: Wed, 27 Nov 2024 15:58:38 -0500 Subject: [PATCH] Refactor audio system to send collection IDs over the network 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 | 42 ++++---- Robust.Shared/Audio/ResolvedSoundSpecifier.cs | 88 +++++++++++++++ Robust.Shared/Audio/SoundSpecifier.cs | 6 ++ .../Audio/Systems/SharedAudioSystem.cs | 54 ++++++---- 6 files changed, 209 insertions(+), 93 deletions(-) create mode 100644 Robust.Shared/Audio/ResolvedSoundSpecifier.cs diff --git a/Robust.Client/Animations/AnimationTrackPlaySound.cs b/Robust.Client/Animations/AnimationTrackPlaySound.cs index be5051a4d8f..11f6a6e7434 100644 --- a/Robust.Client/Animations/AnimationTrackPlaySound.cs +++ b/Robust.Client/Animations/AnimationTrackPlaySound.cs @@ -40,11 +40,7 @@ public override (int KeyFrameIndex, float FramePlayingTime) 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 @@ public struct KeyFrame /// /// 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 @@ public struct KeyFrame /// 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 5dfd954bb4c..2e49b51a452 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -415,6 +415,16 @@ public float GetOcclusion(MapCoordinates listener, Vector2 delta, float distance return occlusion; } + private bool TryGetAudio(ResolvedSoundSpecifier specifier, [NotNullWhen(true)] out AudioResource? audio) + { + var filename = GetAudioPath(specifier) ?? string.Empty; + 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 @@ private bool TryGetAudio(AudioStream stream, [NotNullWhen(true)] out AudioResour 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 override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// /// 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 override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// /// 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; Dirty(entity, component); @@ -513,22 +523,22 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// /// 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 override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// 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 override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun 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 override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// 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 override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// 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 override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun 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 override void LoadStream(Entity entity, T stream) } /// - 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 @@ private void ApplyAudioParams(AudioParams audioParams, IAudioSource source) 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 981c01e770e..bc91a0466db 100644 --- a/Robust.Server/Audio/AudioSystem.cs +++ b/Robust.Server/Audio/AudioSystem.cs @@ -81,27 +81,27 @@ private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filt } /// - 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)); AddAudioFilter(entity, entity.Comp, playerFilter); @@ -110,24 +110,24 @@ public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? } /// - 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)) @@ -139,7 +139,7 @@ public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? 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); @@ -147,10 +147,10 @@ public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? } /// - 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)) @@ -163,7 +163,7 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? fi 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); @@ -210,12 +210,12 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun 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); @@ -223,12 +223,12 @@ public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? 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); @@ -236,12 +236,12 @@ public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? 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 00000000000..c14a23c3351 --- /dev/null +++ b/Robust.Shared/Audio/ResolvedSoundSpecifier.cs @@ -0,0 +1,88 @@ +using System; +using JetBrains.Annotations; +using Robust.Shared.Utility; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +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), + }; + } +} + +/// +/// 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 string? 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 f88254edc23..3378d34f8ad 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 4d79ff0e3d0..5fd14a2a90c 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -273,31 +273,46 @@ public float GetAudioDistance(float length) /// /// Resolves the filepath to a sound file. /// - public string GetSound(SoundSpecifier specifier) + public ResolvedSoundSpecifier GetSound(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); } #region AudioParams - protected Entity SetupAudio(string? fileName, AudioParams? audioParams, bool initialize = true, TimeSpan? length = null) + 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, + }; + } + + 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; @@ -383,8 +398,9 @@ public void SetVolume(EntityUid? entity, float value, AudioComponent? component /// /// 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"); @@ -417,7 +433,7 @@ public TimeSpan GetAudioLength(string filename) /// /// 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. @@ -434,7 +450,7 @@ public TimeSpan GetAudioLength(string filename) /// /// 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. @@ -453,7 +469,7 @@ public TimeSpan GetAudioLength(string filename) /// /// 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. @@ -471,7 +487,7 @@ public TimeSpan GetAudioLength(string filename) /// 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. @@ -479,7 +495,7 @@ public TimeSpan GetAudioLength(string filename) /// 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. @@ -487,7 +503,7 @@ public TimeSpan GetAudioLength(string filename) /// 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. @@ -547,7 +563,7 @@ public TimeSpan GetAudioLength(string filename) /// /// 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); /// @@ -555,7 +571,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// /// 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); /// @@ -592,7 +608,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// 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. @@ -600,7 +616,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// 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. @@ -608,7 +624,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// 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. @@ -653,7 +669,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs [NetSerializable, Serializable] protected abstract class AudioMessage : EntityEventArgs { - public string FileName = string.Empty; + public ResolvedSoundSpecifier Specifier = new ResolvedPathSpecifier(string.Empty); public AudioParams AudioParams; }