Add ability for audio overrides

This commit is contained in:
D1GQ committed 2026-04-06 19:51:55 -05:00
1 parent 476441ec71
commit 99b8ecab04
5 files changed
+227

No files matched your search

+2
View File
@@ -9,6 +9,7 @@ using BetterAmongUs.Data.Config;
using BetterAmongUs.Data.Json;
using BetterAmongUs.Enums;
using BetterAmongUs.Helpers;
using BetterAmongUs.Managers;
using BetterAmongUs.Modules;
using BetterAmongUs.Modules.OptionItems;
using BetterAmongUs.Modules.Support;
@@ -153,6 +154,7 @@ internal class BAUPlugin : BasePlugin
GithubAPI.Connect();
BAUConfigs.LoadConfigs();
BetterDataManager.Initialize();
AudioOverrideManager.Initialize();
Translator.Initialize();
Harmony.PatchAll();
GameSettingsPatch.SetupSettings(true);
+5
View File
@@ -25,6 +25,11 @@ internal static class BetterDataManager
/// </summary>
internal static readonly string starLightDataFolderPath = Environment.GetEnvironmentVariable("STAR_DATA_PATH") ?? string.Empty;
/// <summary>
/// Directory for audio overrides files.
/// </summary>
internal static readonly string audioOverridesFolderPath = Path.Combine(fileFolderPath, $"AudioOverrides");
/// <summary>
/// Directory for save information files.
/// </summary>
+74
View File
@@ -501,6 +501,80 @@ internal static class Utils
}
}
/// <summary>
/// Loads a WAV audio clip from a file on disk.
/// </summary>
/// <param name="filePath">The full path to the WAV file.</param>
/// <returns>An AudioClip containing the loaded WAV data, or null if loading fails.</returns>
internal static AudioClip? LoadWavFromDisk(string filePath)
{
if (!File.Exists(filePath))
{
Logger_.Error($"File not found: {filePath}");
return null;
}
if (Path.GetExtension(filePath).ToLower() != ".wav")
{
Logger_.Error("Only .wav files are supported.");
return null;
}
try
{
byte[] wavBytes = File.ReadAllBytes(filePath);
if (wavBytes.Length == 0)
{
return null;
}
var audio = ToAudioClip(wavBytes);
return audio;
}
catch (Exception ex)
{
Logger_.Error($"Failed to load WAV: {ex}");
return null;
}
}
/// <summary>
/// Converts raw WAV byte data into an AudioClip.
/// </summary>
/// <param name="wavBytes">The raw WAV file bytes.</param>
/// <returns>An AudioClip containing the converted WAV data.</returns>
internal static AudioClip ToAudioClip(byte[] wavBytes)
{
int sampleRate = BitConverter.ToInt32(wavBytes, 24);
int channels = BitConverter.ToInt16(wavBytes, 22);
int samples = BitConverter.ToInt32(wavBytes, 40) / 2;
int offset = 36;
while (offset < wavBytes.Length - 8)
{
if (wavBytes[offset] == 'd' && wavBytes[offset + 1] == 'a' &&
wavBytes[offset + 2] == 't' && wavBytes[offset + 3] == 'a')
{
offset += 4;
offset += 4;
break;
}
offset++;
}
float[] floatData = new float[samples];
for (int i = 0; i < samples; i++)
{
short sample = BitConverter.ToInt16(wavBytes, offset + i * 2);
floatData[i] = sample / 32768f;
}
AudioClip clip = AudioClip.Create("LoadedWav", samples / channels, channels, sampleRate, false);
clip.SetData(floatData, 0);
clip.hideFlags = HideFlags.HideAndDontSave;
return clip;
}
// Platform utilities
/// <summary>
/// Gets the platform name for a player.
+106
View File
@@ -0,0 +1,106 @@
using BetterAmongUs.Data;
using BetterAmongUs.Helpers;
using System.Reflection;
using UnityEngine;
/// <summary>
/// Manages audio override files and their corresponding AudioClip data.
/// </summary>
internal static class AudioOverrideManager
{
/// <summary>
/// Collection of all discovered audio override data instances.
/// </summary>
internal static readonly List<AudioOverrideData> Overrides = [];
internal static readonly AudioOverrideData Music_MainMenuSong = new("Music/MainMenuSong.wav");
internal static readonly AudioOverrideData Music_LobbySong = new("Music/LobbySong.wav");
internal static readonly AudioOverrideData Sounds_EmergencyMeeting = new("Sounds/Meeting/EmergencyMeeting.wav");
/// <summary>
/// Initializes the AudioOverrideManager by discovering all AudioOverrideData fields,
/// </summary>
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))
.Select(f => f.GetValue(null) as AudioOverrideData)
.Where(v => v != null);
Overrides.Clear();
foreach (var overrideData in infoFields)
{
if (overrideData != null)
{
Overrides.Add(overrideData);
}
}
foreach (var overrideInfo in Overrides)
{
overrideInfo.Load();
}
}
/// <summary>
/// Represents an audio override file with its path and loaded AudioClip.
/// </summary>
/// <param name="path">The relative path to the audio file within the audio overrides folder.</param>
internal sealed class AudioOverrideData(string path)
{
private readonly string _path = path;
private AudioClip? audioClip;
/// <summary>
/// Gets or sets the original audio clip associated with this instance.
/// </summary>
internal AudioClip? Original;
/// <summary>
/// Loads the audio clip from disk. Creates the file and directory structure if they don't exist.
/// </summary>
internal void Load()
{
if (string.IsNullOrEmpty(_path)) return;
var fullPath = Path.Combine(BetterDataManager.Folders.audioOverridesFolderPath, _path);
if (!Directory.Exists(fullPath))
{
string? directoryPath = Path.GetDirectoryName(fullPath);
if (directoryPath == null) return;
Directory.CreateDirectory(directoryPath);
}
if (!File.Exists(fullPath))
{
File.Create(fullPath).Close();
}
audioClip = Utils.LoadWavFromDisk(fullPath);
if (audioClip != null)
{
audioClip.name = "Override_" + Path.GetFileNameWithoutExtension(_path);
}
}
/// <summary>
/// Attempts to retrieve the loaded AudioClip.
/// </summary>
/// <param name="clip">The AudioClip if loaded and available; otherwise, null.</param>
/// <returns>True if the AudioClip is available; otherwise, false.</returns>
internal bool TryGetAudioClip(out AudioClip? clip)
{
if (audioClip != null)
{
clip = audioClip;
return true;
}
clip = null;
return false;
}
}
}
+40
View File
@@ -0,0 +1,40 @@
using HarmonyLib;
namespace BetterAmongUs.Patches.Client;
[HarmonyPatch]
internal static class AudioOverridePatch
{
[HarmonyPatch(typeof(SoundStarter), nameof(SoundStarter.Awake))]
[HarmonyPrefix]
private static void SoundStarter_Awake_Prefix(SoundStarter __instance)
{
if (__instance.gameObject.name == "MainMenuBgMusic")
{
if (AudioOverrideManager.Music_MainMenuSong.TryGetAudioClip(out var audio))
{
__instance.SoundToPlay = audio;
}
}
}
[HarmonyPatch(typeof(LobbyBehaviour), nameof(LobbyBehaviour.Start))]
[HarmonyPrefix]
private static void LobbyBehaviour_Start_Prefix(LobbyBehaviour __instance)
{
if (AudioOverrideManager.Music_LobbySong.TryGetAudioClip(out var audio))
{
__instance.MapTheme = audio;
}
}
[HarmonyPatch(typeof(MeetingCalledAnimation), nameof(MeetingCalledAnimation.CoShow))]
[HarmonyPrefix]
private static void MeetingCalledAnimation_CoShow_Prefix(MeetingCalledAnimation __instance)
{
if (AudioOverrideManager.Sounds_EmergencyMeeting.TryGetAudioClip(out var audio))
{
__instance.Stinger = audio;
}
}
}