From 239adc583bd67a6d528f784a86c66e0ce774eb93 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Mon, 6 Apr 2026 22:23:20 -0500 Subject: [PATCH] Add more audio overrides --- src/Helpers/Utils.cs | 21 ++++ src/Managers/AudioOverrideManager.cs | 116 ++++++++++++++++++++--- src/Patches/Client/AudioOverridePatch.cs | 47 ++++++++- 3 files changed, 170 insertions(+), 14 deletions(-) diff --git a/src/Helpers/Utils.cs b/src/Helpers/Utils.cs index 524783a..487ad1a 100644 --- a/src/Helpers/Utils.cs +++ b/src/Helpers/Utils.cs @@ -575,6 +575,27 @@ internal static class Utils return clip; } + /// + /// Generates a normalized directory path by combining folder names from a delimited string. + /// + /// A string containing folder names separated by the specified separator character. Folder names must not be empty. + /// The character used to separate folder names in the input string. Defaults to '/'. + /// A directory path constructed by combining the non-empty folder names from the input string, using the system's + /// directory separator character. + internal static string GenerateDirectoryPath(string path, char separator = '/') + { + string[] folders = path.Split(separator); + string currentPath = ""; + foreach (string folder in folders) + { + if (!string.IsNullOrEmpty(folder)) + { + currentPath = Path.Combine(currentPath, folder); + } + } + return currentPath; + } + // Platform utilities /// /// Gets the platform name for a player. diff --git a/src/Managers/AudioOverrideManager.cs b/src/Managers/AudioOverrideManager.cs index bb45f6e..db90606 100644 --- a/src/Managers/AudioOverrideManager.cs +++ b/src/Managers/AudioOverrideManager.cs @@ -13,21 +13,37 @@ internal static class AudioOverrideManager /// internal static readonly List Overrides = []; - internal static readonly AudioOverrideData Music_MainMenuSong = new("Music/MainMenuSong.wav"); + internal static readonly AudioOverrideData Music_MainMenuSong = new("Music", "MainMenuSong.wav"); - internal static readonly AudioOverrideData Music_LobbySong = new("Music/LobbySong.wav"); + internal static readonly AudioOverrideData Music_LobbySong = new("Music", "LobbySong.wav"); - internal static readonly AudioOverrideData Sounds_EmergencyMeeting = new("Sounds/Meeting/EmergencyMeeting.wav"); + internal static readonly MapAudioOverrideData Sounds_VentEnter = new("Sounds/Gameplay", "VentEnter.wav"); + + internal static readonly MapAudioOverrideData Sounds_VentExit = new("Sounds/Gameplay", "VentExit.wav"); + + internal static readonly MapAudioOverrideData Sounds_SabotageAlert = new("Sounds/Gameplay", "SabotageAlert.wav"); + + internal static readonly AudioOverrideData Sounds_EmergencyMeetingStart = new("Sounds/Meeting", "EmergencyMeetingStart.wav"); + + internal static readonly AudioOverrideData Sounds_EmergencyMeetingVote = new("Sounds/Meeting", "EmergencyMeetingVote.wav"); + + internal static readonly AudioOverrideData Sounds_EmergencyMeetingVoteLockin = new("Sounds/Meeting", "EmergencyMeetingLockin.wav"); + + internal static readonly AudioOverrideData Sounds_EmergencyMeetingEnd = new("Sounds/Meeting", "EmergencyMeetingEnd.wav"); /// - /// Initializes the AudioOverrideManager by discovering all AudioOverrideData fields, + /// Initializes the AudioOverrideManager by discovering all AudioOverrideData fields and loading their audio clips. /// + /// + /// This method scans all static fields in AudioOverrideManager that inherit from AudioOverrideData, + /// adds them to the Overrides collection, and loads their associated audio files from disk. + /// internal static void Initialize() { // Discover all AudioOverrideInfo fields var infoFields = typeof(AudioOverrideManager) .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Where(f => f.FieldType == typeof(AudioOverrideData)) + .Where(f => typeof(AudioOverrideData).IsAssignableFrom(f.FieldType)) .Select(f => f.GetValue(null) as AudioOverrideData) .Where(v => v != null); @@ -48,22 +64,25 @@ internal static class AudioOverrideManager /// /// Represents an audio override file with its path and loaded AudioClip. + /// Provides functionality for loading custom audio files from the file system. /// - /// The relative path to the audio file within the audio overrides folder. - internal sealed class AudioOverrideData(string path) + /// The relative folder path to the audio file within the audio overrides directory. + /// The name of the audio file, including extension. + internal class AudioOverrideData(string folderPath, string fileName) { - private readonly string _path = path; + private readonly string _path = Path.Combine(Utils.GenerateDirectoryPath(folderPath), fileName); private AudioClip? audioClip; /// - /// Gets or sets the original audio clip associated with this instance. + /// Gets or sets the original audio clip that this override replaces. /// + /// The original game audio clip, or null if not set. internal AudioClip? Original; /// /// Loads the audio clip from disk. Creates the file and directory structure if they don't exist. /// - internal void Load() + internal virtual void Load() { if (string.IsNullOrEmpty(_path)) return; @@ -89,9 +108,9 @@ internal static class AudioOverrideManager /// /// Attempts to retrieve the loaded AudioClip. /// - /// The AudioClip if loaded and available; otherwise, null. - /// True if the AudioClip is available; otherwise, false. - internal bool TryGetAudioClip(out AudioClip? clip) + /// When this method returns, contains the loaded AudioClip if successful; otherwise, null. + /// True if the AudioClip is available and loaded; otherwise, false. + internal virtual bool TryGetAudioClip(out AudioClip? clip) { if (audioClip != null) { @@ -102,5 +121,76 @@ internal static class AudioOverrideManager clip = null; return false; } + + /// + /// Determines whether an audio clip is currently associated with this instance. + /// + /// if an audio clip is loaded and present; otherwise, . + internal virtual bool HasAudioClip() + { + return audioClip != null; + } + } + + /// + /// Represents a map specific audio override that loads different audio files for different maps. + /// + /// The relative folder path to the audio files within the audio overrides directory. + /// The base name of the audio file, which will be prefixed with map names. + internal sealed class MapAudioOverrideData(string folderPath, string fileName) : AudioOverrideData(folderPath, fileName) + { + /// + /// Enumeration of map types for audio override categorization. + /// + private enum MapTyped + { + Ships, + MiraHQ, + Polus, + Fungle + } + + private readonly string _folderPath = folderPath; + private readonly string _fileName = fileName; + private readonly Dictionary audioOverrideDatas = []; + + /// + /// Loads map specific audio overrides for all supported map types. + /// + internal override void Load() + { + foreach (var map in Enum.GetValues(typeof(ShipStatus.MapType))) + { + if (map is ShipStatus.MapType mapType) + { + var audioData = new AudioOverrideData(Path.Combine(Utils.GenerateDirectoryPath(_folderPath), Path.GetFileNameWithoutExtension(_fileName)), $"{(MapTyped)mapType}_" + _fileName); + audioOverrideDatas[(MapTyped)mapType] = audioData; + audioData.Load(); + } + } + } + + /// + /// Attempts to retrieve the map appropriate AudioClip based on the currently active map. + /// + /// When this method returns, contains the map specific AudioClip if successful; otherwise, null. + /// True if a map-specific AudioClip is available for the current map; otherwise, false. + internal override bool TryGetAudioClip(out AudioClip? clip) + { + if (ShipStatus.Instance != null) + { + if (audioOverrideDatas.TryGetValue((MapTyped)ShipStatus.Instance.Type, out var audioData)) + { + if (audioData.TryGetAudioClip(out var audio)) + { + clip = audio; + return true; + } + } + } + + clip = null; + return false; + } } } \ No newline at end of file diff --git a/src/Patches/Client/AudioOverridePatch.cs b/src/Patches/Client/AudioOverridePatch.cs index 4360c42..77e3360 100644 --- a/src/Patches/Client/AudioOverridePatch.cs +++ b/src/Patches/Client/AudioOverridePatch.cs @@ -32,9 +32,54 @@ internal static class AudioOverridePatch [HarmonyPrefix] private static void MeetingCalledAnimation_CoShow_Prefix(MeetingCalledAnimation __instance) { - if (AudioOverrideManager.Sounds_EmergencyMeeting.TryGetAudioClip(out var audio)) + if (AudioOverrideManager.Sounds_EmergencyMeetingStart.TryGetAudioClip(out var audio)) { __instance.Stinger = audio; } } + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Awake))] + [HarmonyPrefix] + private static void MeetingHud_Awake_Prefix(MeetingHud __instance) + { + if (AudioOverrideManager.Sounds_EmergencyMeetingVote.TryGetAudioClip(out var vote)) + { + __instance.VoteSound = vote; + } + + if (AudioOverrideManager.Sounds_EmergencyMeetingVoteLockin.TryGetAudioClip(out var lockin)) + { + __instance.VoteLockinSound = lockin; + } + + if (AudioOverrideManager.Sounds_EmergencyMeetingEnd.TryGetAudioClip(out var end)) + { + __instance.VoteEndingSound = end; + } + } + + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Awake))] + [HarmonyPostfix] + private static void ShipStatus_Awake_Postfix(ShipStatus __instance) + { + if (__instance.VentExitSound == null) + { + __instance.VentExitSound = __instance.VentEnterSound; + } + + if (AudioOverrideManager.Sounds_VentEnter.TryGetAudioClip(out var ventEnter)) + { + __instance.VentEnterSound = ventEnter; + } + + if (AudioOverrideManager.Sounds_VentExit.TryGetAudioClip(out var ventExit)) + { + __instance.VentExitSound = ventExit; + } + + if (AudioOverrideManager.Sounds_SabotageAlert.TryGetAudioClip(out var sabo)) + { + __instance.SabotageSound = sabo; + } + } }