diff --git a/CHANGELOG.md b/CHANGELOG.md
index e04ddf7..c24f4d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,4 +41,9 @@
# v0.1.8 | Changed dependencies
-- Added ModConfig and PeakPresence (my mod), Updated BepInEx
\ No newline at end of file
+- Added ModConfig and PeakPresence (my mod), Updated BepInEx
+
+# v0.1.9 | AudioSyncWorker & Dynamic BingBong voices
+
+- Centralized Audio loading in Worker class
+- Now load BingBong voicelines dynamically after Sound reloads.
\ No newline at end of file
diff --git a/src/JordanMod/JordanMod.csproj b/src/JordanMod/JordanMod.csproj
index 07a81ed..93badc5 100644
--- a/src/JordanMod/JordanMod.csproj
+++ b/src/JordanMod/JordanMod.csproj
@@ -8,7 +8,7 @@
JordanMod
- 0.1.8
+ 0.1.9
diff --git a/src/JordanMod/modules/better_bugle/BetterBugleModule.cs b/src/JordanMod/modules/better_bugle/BetterBugleModule.cs
index 22bec4a..e070f1d 100644
--- a/src/JordanMod/modules/better_bugle/BetterBugleModule.cs
+++ b/src/JordanMod/modules/better_bugle/BetterBugleModule.cs
@@ -24,19 +24,6 @@ class BetterBugleModule : Module
public override string ModuleName => "BetterBugle";
public static readonly string bugleItemName = "Bugle";
- public static readonly string SoundsDirectory = Path.Combine(BepInEx.Paths.BepInExRootPath, "bugleSounds");
- public static readonly Dictionary AudioTypes = new()
- {
- { "wav", AudioType.WAV },
- { "mp3", AudioType.MPEG },
- { "ogg", AudioType.OGGVORBIS },
- { "aiff", AudioType.AIFF },
- };
-
- public static bool IsLoading { get; private set; } = false;
- public static bool IsSyncing { get; private set; } = false;
- public static int CurrentSongIndex { get; set; } = 0;
- public static string CurrentSongName { get; set; } = "None";
public static bool HadConfirmation { get; set; } = false;
public static bool IsPlaying = false;
@@ -51,9 +38,10 @@ class BetterBugleModule : Module
{
if (Instance != null) return;
Instance = this;
- SceneManager.sceneLoaded += OnSceneLoaded;
ManageLocalizedText();
- GetAudioClips();
+ SceneManager.sceneLoaded += OnSceneLoaded;
+ AudioSyncWorker.OnAudioLoadComplete += OnAllAudioClipsLoaded;
+ AudioSyncWorker.GetAudioClips();
base.Initialize();
}
@@ -61,13 +49,13 @@ class BetterBugleModule : Module
{
if (Input.GetKeyDown(ConfigHandler.SyncAudioRepository.Value))
{
- Instance?.TrySyncAndLoadAudioClips();
+ AudioSyncWorker.TrySyncAndLoadAudioClips();
}
if (Input.GetKeyDown(ConfigHandler.FavoriteSongToggleKey.Value))
{
if (Character.localCharacter == null) return;
if (Song.Songs.Count == 0) return;
- if (!Song.Songs.ContainsKey(CurrentSongName)) return;
+ if (!Song.Songs.ContainsKey(AudioSyncWorker.CurrentSongName)) return;
Character character = Character.localCharacter;
Optionable selectedSlot = character.refs.items.currentSelectedSlot;
@@ -82,7 +70,7 @@ class BetterBugleModule : Module
List supportedItemNames = ["Bugle", "Bugle_Magic", "Megaphone"];
if (!supportedItemNames.Contains(item.UIData.itemName)) return;
- Song? currentSong = Song.Songs.GetValueOrDefault(CurrentSongName);
+ Song? currentSong = Song.Songs.GetValueOrDefault(AudioSyncWorker.CurrentSongName);
if (currentSong == null) return;
if (Song.FavoriteSongs.Contains(currentSong.Name))
@@ -113,7 +101,7 @@ class BetterBugleModule : Module
public override void Destroy()
{
- ClearAudioClips();
+ AudioSyncService.ClearAudioClips();
base.Destroy();
}
@@ -129,105 +117,6 @@ class BetterBugleModule : Module
LocalizedText.mainTable.Add("CHANGE_SONG", scrollActionLocalizations);
}
- public void GetAudioClips()
- {
- if (IsLoading || IsSyncing) return;
- if (!Directory.Exists(SoundsDirectory)) return;
- IsLoading = true;
- Plugin.Instance.StartCoroutine(LoadAllAudioClipsCoroutine(SoundsDirectory));
- }
- private void ClearAudioClips()
- {
- foreach (Song song in Song.Songs.Values.ToList())
- {
- song.Dispose();
- }
- Song.Sounds.Clear();
- Song.SoundsByHash.Clear();
- Song.Songs.Clear();
- Song.BB_VoiceLines.Clear();
- GC.Collect();
- }
- private IEnumerator LoadAllAudioClipsCoroutine(string directoryPath, string[]? forceReload = null)
- {
- List<(string filePath, string ext, string name)> filesToLoad = new();
-
- foreach (var ext in AudioTypes.Keys)
- {
- var files = Directory.GetFiles(directoryPath, $"*.{ext}");
- foreach (var file in files)
- {
- string name = Path.GetFileNameWithoutExtension(file);
- bool shouldForceReload = forceReload != null && forceReload.Contains($"{name}.{ext}");
- if (!Song.Songs.ContainsKey(name) || shouldForceReload)
- {
- filesToLoad.Add((file, ext, name));
- }
- }
- }
-
- const int BATCH_SIZE = 2;
- int loadedCount = 0;
-
- for (int i = 0; i < filesToLoad.Count; i += BATCH_SIZE)
- {
- List loadCoroutines = new();
-
- for (int j = i; j < i + Math.Min(BATCH_SIZE, filesToLoad.Count - i) && j < filesToLoad.Count; j++)
- {
- var (filePath, ext, name) = filesToLoad[j];
- bool forceReloadClip = forceReload != null && forceReload.Contains($"{name}.{ext}");
- Coroutine loadCoroutine = Plugin.Instance.StartCoroutine(LoadAudioClipCoroutine(filePath, ext, name, forceReloadClip));
- loadCoroutines.Add(loadCoroutine);
- }
-
- foreach (var coroutine in loadCoroutines) yield return coroutine;
- loadedCount += loadCoroutines.Count;
- BetterBugleUI.Instance?.ShowActionbar($"Loading audio clips... {loadedCount}/{filesToLoad.Count}");
- }
- OnAllAudioClipsLoaded();
- }
- private IEnumerator LoadAudioClipCoroutine(string filePath, string ext, string name, bool forceReload = false)
- {
-
- Debug.Log($"Loading audio clip: {name}.{ext} from {filePath}" + (forceReload ? " (forced reload)" : ""));
-
- using UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip($"file://{filePath}", AudioTypes[ext]);
- yield return www.SendWebRequest();
-
- if (www.result != UnityWebRequest.Result.Success)
- {
- Debug.LogError($"Failed to load audio clip from {filePath}: {www.error}");
- yield break;
- }
-
- bool songExists = Song.Songs.ContainsKey(name);
-
- Debug.Log($"Audio clip '{name}' exists: {songExists}. Force reload: {forceReload}");
-
- if (songExists && !forceReload)
- {
- Debug.LogWarning($"Audio clip with name '{name}' already exists. Skipping duplicate.");
- yield break;
- }
-
- if (songExists && forceReload)
- {
- Song? previousSong = Song.Songs.TryGetValue(name, out var existingSong) ? existingSong : null;
- previousSong?.Dispose();
- }
-
- AudioClip audioClip = DownloadHandlerAudioClip.GetContent(www);
- if (audioClip == null)
- {
- Debug.LogError($"Failed to load audio clip from {filePath}: {www.error}");
- yield break;
- }
-
- Song song = new(name, ext, filePath, audioClip);
- song.Register();
- Debug.Log($"Loaded audio clip: {name} from {filePath}");
- }
private void OnAllAudioClipsLoaded()
{
if (Song.Songs.Count == 0) Debug.LogWarning("No songs loaded. Please ensure audio files are in the Sounds directory.");
@@ -239,86 +128,10 @@ class BetterBugleModule : Module
if (Song.Songs.ContainsKey(songKeyName) && !Song.FavoriteSongs.Contains(songKeyName))
Song.FavoriteSongs.Add(songKeyName);
- if (!Song.Songs.ContainsKey(CurrentSongName))
- CurrentSongName = Song.GetSongNames_Alphabetically()[CurrentSongIndex];
-
- IsLoading = false;
+ if (!Song.Songs.ContainsKey(AudioSyncWorker.CurrentSongName))
+ AudioSyncWorker.CurrentSongName = Song.GetSongNames_Alphabetically()[AudioSyncWorker.CurrentSongIndex];
}
- public void TrySyncAndLoadAudioClips()
- {
- if (IsLoading || IsSyncing) return;
- Task.Run(() =>
- {
- SyncAndLoadAudioClipsCoroutine().GetAwaiter().GetResult();
- });
- }
- private async Task SyncAndLoadAudioClipsCoroutine()
- {
- if (IsLoading || IsSyncing) return;
- IsSyncing = true;
- AudioSyncService audioSyncService = AudioSyncService.GetInstance();
- Dictionary toDownload = new();
-
- string[] existingSongNames = Song.Songs.Keys.ToArray();
- AudioSyncService.APIAudioFormat[] existingAPIFormats = [.. audioSyncService.GetAudioClips()];
- string[] apiExistingNames = [.. existingAPIFormats.Select(apiAudio => apiAudio.Filename)];
-
- var songsToRemove = existingSongNames.Except(apiExistingNames).ToArray();
- foreach (var songName in songsToRemove)
- {
- if (Song.Songs.TryGetValue(songName, out var songToDispose))
- {
- songToDispose.Dispose();
- songToDispose.DeleteFile();
- }
- }
-
-
- foreach (AudioSyncService.APIAudioFormat apiAudio in existingAPIFormats)
- {
- Song? existingSong = Song.SoundsByHash.GetValueOrDefault(apiAudio.Hash);
- if (existingSong == null || existingSong.Hash != apiAudio.Hash)
- {
- toDownload.Add(apiAudio, existingSong);
- }
- }
-
- BetterBugleUI.Instance?.ShowActionbar($"Syncing audio bank... {toDownload.Count} changed/new files found.");
-
- string[] filesToOverload = [];
-
- foreach (AudioSyncService.APIAudioFormat apiAudio in toDownload.Keys)
- {
- bool success = await DownloadAPIAudio(apiAudio, toDownload[apiAudio]);
- if (success)
- {
- Debug.Log($"Successfully downloaded audio: {apiAudio.Filename}.{apiAudio.Extension}, adding to forceload");
- filesToOverload = [.. filesToOverload, $"{apiAudio.Filename}.{apiAudio.Extension}"];
- }
- }
- IsSyncing = false;
- IsLoading = true;
- Plugin.Instance.StartCoroutine(LoadAllAudioClipsCoroutine(SoundsDirectory, filesToOverload));
- }
- private async Task DownloadAPIAudio(AudioSyncService.APIAudioFormat apiAudio, Song? existingSong = null)
- {
- bool success = true;
- try
- {
- if (existingSong != null && apiAudio.Filename != existingSong.Name)
- {
- File.Delete(Path.Combine(SoundsDirectory, $"{existingSong.Name}.{existingSong.Extension}"));
- }
- await apiAudio.DownloadToFolder(SoundsDirectory);
- }
- catch (Exception ex)
- {
- Debug.LogError($"Failed to download API audio: {ex.Message}");
- success = false;
- }
- return success;
- }
}
public class BetterBugleSFX : MonoBehaviourPun
@@ -347,7 +160,7 @@ public class BetterBugleSFX : MonoBehaviourPun
audioSource.volume = 0f;
audioSource.loop = true;
if (IsLocal()) BetterBugleModule.CurrentAudioSource = audioSource;
- song = Song.Songs.GetValueOrDefault(BetterBugleModule.CurrentSongName);
+ song = Song.Songs.GetValueOrDefault(AudioSyncWorker.CurrentSongName);
}
private bool IsLocal()
@@ -393,7 +206,7 @@ public class BetterBugleSFX : MonoBehaviourPun
if (flag != hold)
{
- if (flag) photonView.RPC("RPC_StartBetterToot", RpcTarget.All, BetterBugleModule.CurrentSongName);
+ if (flag) photonView.RPC("RPC_StartBetterToot", RpcTarget.All, AudioSyncWorker.CurrentSongName);
else photonView.RPC("RPC_StopBetterToot", RpcTarget.All);
hold = flag;
}
@@ -526,7 +339,7 @@ public class BetterBugleUI : MonoBehaviour
{
if (customStyle == null) return;
if (BetterBugleModule.CurrentAudioSource == null || !BetterBugleModule.IsPlaying) return;
- Song? currentAudio = Song.Songs.FirstOrDefault(s => s.Value.Name == BetterBugleModule.CurrentSongName).Value;
+ Song? currentAudio = Song.Songs.FirstOrDefault(s => s.Value.Name == AudioSyncWorker.CurrentSongName).Value;
if (currentAudio == null || BetterBugleModule.CurrentAudioSource.clip == null) return;
float MAX_WIDTH = Screen.width - (offsetX * 2);
diff --git a/src/JordanMod/modules/replace_bingbong/ReplaceBingBongModule.cs b/src/JordanMod/modules/replace_bingbong/ReplaceBingBongModule.cs
index 3ed5aa5..4d5e249 100644
--- a/src/JordanMod/modules/replace_bingbong/ReplaceBingBongModule.cs
+++ b/src/JordanMod/modules/replace_bingbong/ReplaceBingBongModule.cs
@@ -1,4 +1,9 @@
using System;
+using System.Collections;
+using System.Collections.Generic;
+using JordanMod.Utils;
+using UnityEngine;
+using UnityEngine.InputSystem;
namespace JordanMod.Modules.ReplaceBingBong;
@@ -6,6 +11,9 @@ namespace JordanMod.Modules.ReplaceBingBong;
class ReplaceBingBongModule : Module
{
public override string ModuleName => "Replace BingBong Module";
+
+ public static bool HasReplacedSounds = false;
+ public static BingBongResponseData[] OriginalResponsesData = [];
public override Type[] GetPatches()
{
@@ -16,5 +24,124 @@ class ReplaceBingBongModule : Module
{
base.Initialize();
LocalizedText.mainTable.Add("idk_funny", ["Test subtitle!"]);
+ AudioSyncWorker.OnAudioLoadComplete += OnAudioLoadComplete;
}
+
+ public override void Update()
+ {
+ base.Update();
+ if (Input.GetKeyDown(KeyCode.P))
+ {
+ Helper.FindItemByName("BingBong_Prop Variant", out Item? item);
+ if (item == null) return;
+ Debug.Log($"Found item: {item.name} in scene {item.gameObject.scene.name}");
+ }
+ }
+
+ private static void OnAudioLoadComplete()
+ {
+ if (!HasReplacedSounds) return;
+ Action_AskBingBong[] allBingBongActions = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None);
+ foreach (Action_AskBingBong askBingBong in allBingBongActions) {
+ ReplaceBingBongResponses(askBingBong);
+ }
+ }
+
+ public static void ReplaceBingBongResponses(Action_AskBingBong askBingBong)
+ {
+ Action_AskBingBong.BingBongResponse[] currentResponses = new Action_AskBingBong.BingBongResponse[OriginalResponsesData.Length];
+ for (int index = 0; index < OriginalResponsesData.Length; index++)
+ {
+ currentResponses[index] = OriginalResponsesData[index].ToBingBongResponse();
+ }
+
+ askBingBong.responses = [];
+
+ Dictionary sfxDict = new();
+ for (int i = 0; i < currentResponses.Length; i++)
+ {
+ Action_AskBingBong.BingBongResponse response = currentResponses[i];
+ if (response.sfx != null && response.sfx.clips != null && response.sfx.clips.Length > 0)
+ {
+ foreach (AudioClip clip in response.sfx.clips)
+ {
+ sfxDict[response.sfx.name] = response.sfx;
+ }
+ }
+ }
+
+ List voices = [.. Song.BB_VoiceLines.Values];
+
+ foreach (Song voice in voices)
+ {
+ AudioClip clip = voice.AudioClip;
+
+ bool isNew = !sfxDict.ContainsKey(voice.Name);
+ if (isNew)
+ {
+ SFX_Instance sFX_Instance = new()
+ {
+ clips = [clip]
+ };
+ Action_AskBingBong.BingBongResponse newResponse = new()
+ {
+ sfx = sFX_Instance,
+ subtitleID = "idk_funny",
+ mouthCurve = null,
+ mouthCurveTime = 1f
+ };
+ currentResponses = [.. currentResponses, newResponse];
+ }
+ else
+ {
+ sfxDict[voice.Name].clips = [clip];
+ }
+ }
+
+ askBingBong.responses = new Action_AskBingBong.BingBongResponse[currentResponses.Length];
+ for (int i = 0; i < currentResponses.Length; i++)
+ {
+ askBingBong.responses[i] = currentResponses[i];
+ }
+ }
+
+
+
+
+}
+
+public class BingBongResponseData
+{
+ public AudioClip[] Clips { get; set; } = [];
+ public string SfxName { get; set; } = "";
+ public string SubtitleID { get; set; } = "";
+ public AnimationCurve? MouthCurve { get; set; } = null;
+ public float MouthCurveTime { get; set; } = 0f;
+
+ public Action_AskBingBong.BingBongResponse ToBingBongResponse()
+ {
+ return new Action_AskBingBong.BingBongResponse
+ {
+ sfx = new SFX_Instance
+ {
+ name = SfxName,
+ clips = (AudioClip[])Clips.Clone()
+ },
+ subtitleID = SubtitleID,
+ mouthCurve = MouthCurve,
+ mouthCurveTime = MouthCurveTime
+ };
+ }
+
+ public static BingBongResponseData FromBingBongResponse(Action_AskBingBong.BingBongResponse response)
+ {
+ return new BingBongResponseData
+ {
+ Clips = (AudioClip[])response.sfx.clips.Clone(),
+ SfxName = response.sfx.name,
+ SubtitleID = response.subtitleID,
+ MouthCurve = response.mouthCurve,
+ MouthCurveTime = response.mouthCurveTime
+ };
+ }
}
\ No newline at end of file
diff --git a/src/JordanMod/patches/BetterBuglePatch.cs b/src/JordanMod/patches/BetterBuglePatch.cs
index a122915..e8c0ac1 100644
--- a/src/JordanMod/patches/BetterBuglePatch.cs
+++ b/src/JordanMod/patches/BetterBuglePatch.cs
@@ -45,7 +45,7 @@ public class BetterBuglePatch
// BetterBugleUI.Instance?.ShowActionbar("No songs available.");
// return;
// }
- if (BetterBugleModule.IsLoading) return;
+ if (AudioSyncWorker.IsLoading) return;
if (!BetterBugleModule.HadConfirmation)
{
BetterBugleUI.Instance?.ShowActionbar("Are you sure you want to refresh songs ? Right-click again to reload.");
@@ -57,7 +57,7 @@ public class BetterBuglePatch
{
BetterBugleModule.HadConfirmation = false; // Reset confirmation state
BetterBugleUI.Instance?.ShowActionbar("Refreshing songs...");
- BetterBugleModule.Instance?.GetAudioClips();
+ AudioSyncService.GetAudioClips();
}
}
@@ -72,7 +72,7 @@ public class BetterBuglePatch
private static void OnScroll(float scrollDelta)
{
- if (BetterBugleModule.IsLoading) return;
+ if (AudioSyncWorker.IsLoading) return;
bool isNext = scrollDelta > 0;
if (Song.Songs.Count == 0)
{
@@ -80,15 +80,15 @@ public class BetterBuglePatch
return;
}
- if (isNext && BetterBugleModule.CurrentSongIndex < Song.Songs.Count - 1) BetterBugleModule.CurrentSongIndex++;
- else if (isNext && BetterBugleModule.CurrentSongIndex == Song.Songs.Count - 1) BetterBugleModule.CurrentSongIndex = 0;
- else if (!isNext && BetterBugleModule.CurrentSongIndex > 0) BetterBugleModule.CurrentSongIndex--;
- else BetterBugleModule.CurrentSongIndex = Song.Songs.Count - 1;
- BetterBugleModule.CurrentSongName = Song.GetSongNames_Alphabetically()[BetterBugleModule.CurrentSongIndex];
+ if (isNext && AudioSyncWorker.CurrentSongIndex < Song.Songs.Count - 1) AudioSyncWorker.CurrentSongIndex++;
+ else if (isNext && AudioSyncWorker.CurrentSongIndex == Song.Songs.Count - 1) AudioSyncWorker.CurrentSongIndex = 0;
+ else if (!isNext && AudioSyncWorker.CurrentSongIndex > 0) AudioSyncWorker.CurrentSongIndex--;
+ else AudioSyncWorker.CurrentSongIndex = Song.Songs.Count - 1;
+ AudioSyncWorker.CurrentSongName = Song.GetSongNames_Alphabetically()[AudioSyncWorker.CurrentSongIndex];
- Song currentSong = Song.Songs[BetterBugleModule.CurrentSongName];
+ Song currentSong = Song.Songs[AudioSyncWorker.CurrentSongName];
- bool isFavorite = Song.FavoriteSongs.Contains(BetterBugleModule.CurrentSongName);
+ bool isFavorite = Song.FavoriteSongs.Contains(AudioSyncWorker.CurrentSongName);
BetterBugleUI.Instance?.ShowActionbar($" {(isFavorite ? "★" : " ")} {currentSong.RealIndex} | {currentSong.Name.Replace("_", " ")}");
}
@@ -104,10 +104,10 @@ public class BetterBuglePatch
if (currentItem.itemState != ItemState.Held) return;
if (currentItem.TryGetComponent(out var bugleSFX))
{
- Song? song = Song.Songs.GetValueOrDefault(BetterBugleModule.CurrentSongName);
+ Song? song = Song.Songs.GetValueOrDefault(AudioSyncWorker.CurrentSongName);
if (song == null) return;
- bool isFavorite = Song.FavoriteSongs.Contains(BetterBugleModule.CurrentSongName);
+ bool isFavorite = Song.FavoriteSongs.Contains(AudioSyncWorker.CurrentSongName);
BetterBugleUI.Instance?.ShowActionbar($"{(isFavorite ? "★" : " ")} {song.RealIndex} | {song.Name}");
}
};
diff --git a/src/JordanMod/patches/ReplaceBingBongPatch.cs b/src/JordanMod/patches/ReplaceBingBongPatch.cs
index b21a33e..ffba091 100644
--- a/src/JordanMod/patches/ReplaceBingBongPatch.cs
+++ b/src/JordanMod/patches/ReplaceBingBongPatch.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using HarmonyLib;
using JordanMod.Utils;
@@ -8,6 +9,14 @@ namespace JordanMod.Modules.ReplaceBingBong;
public class ReplaceBingBongPatch
{
+ [HarmonyPatch(typeof(Item), "Start")]
+ [HarmonyPrefix]
+ static void OnItemStart(Item __instance)
+ {
+ if (__instance.name != "BingBong_Prop Variant") return;
+ Debug.Log($"Item {__instance.name} Start in scene {__instance.gameObject.scene.name} ({__instance.gameObject.scene.buildIndex})");
+ }
+
[HarmonyPatch(typeof(ItemActionBase), "OnEnable")]
[HarmonyPrefix]
static bool PreActionAskBingBongConstructorFix(ItemActionBase __instance)
@@ -16,54 +25,18 @@ public class ReplaceBingBongPatch
return true;
Action_AskBingBong.BingBongResponse[] currentResponses = [..askBingBong.responses];
- // Each response has a .sfx which has a Object.name, store a ref to the sfx with key being sfx name
- Dictionary sfxDict = new();
- for (int i = 0; i < currentResponses.Length; i++)
+
+ if (!ReplaceBingBongModule.HasReplacedSounds)
{
- Action_AskBingBong.BingBongResponse response = currentResponses[i];
- if (response.sfx != null && response.sfx.clips != null && response.sfx.clips.Length > 0)
+ ReplaceBingBongModule.OriginalResponsesData = new BingBongResponseData[currentResponses.Length];
+ for (int index = 0; index < currentResponses.Length; index++)
{
- foreach (AudioClip clip in response.sfx.clips)
- {
- sfxDict[response.sfx.name] = response.sfx;
- }
+ ReplaceBingBongModule.OriginalResponsesData[index] = BingBongResponseData.FromBingBongResponse(currentResponses[index]);
}
+ ReplaceBingBongModule.HasReplacedSounds = true;
}
- List voices = [.. Song.BB_VoiceLines.Values];
-
- foreach (Song voice in voices)
- {
- AudioClip clip = voice.AudioClip;
-
- bool isNew = !sfxDict.ContainsKey(voice.Name);
- if (isNew)
- {
- SFX_Instance sFX_Instance = new()
- {
- clips = [clip]
- };
- Action_AskBingBong.BingBongResponse newResponse = new()
- {
- sfx = sFX_Instance,
- subtitleID = "idk_funny",
- mouthCurve = null,
- mouthCurveTime = 1f
- };
- currentResponses = [.. currentResponses, newResponse];
- }
- else
- {
- sfxDict[voice.Name].clips = [clip];
- }
- }
-
- askBingBong.responses = new Action_AskBingBong.BingBongResponse[currentResponses.Length];
- for (int i = 0; i < currentResponses.Length; i++)
- {
- askBingBong.responses[i] = currentResponses[i];
- }
-
+ ReplaceBingBongModule.ReplaceBingBongResponses(askBingBong);
return true;
}
diff --git a/src/JordanMod/utils/AudioSyncService.cs b/src/JordanMod/utils/AudioSyncService.cs
index 98de8f2..e7c3eea 100644
--- a/src/JordanMod/utils/AudioSyncService.cs
+++ b/src/JordanMod/utils/AudioSyncService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -15,14 +16,26 @@ class AudioSyncService
{
public static string API_BASE_URL => ConfigHandler.BugleSoundAPIURL.Value;
- private static AudioSyncService? Instance { get; set; }
- public static AudioSyncService GetInstance()
+ public async static Task DownloadAPIAudio(APIAudioFormat apiAudio, string SoundsDirectory, Song? existingSong = null)
{
- Instance ??= new AudioSyncService();
- return Instance;
+ bool success = true;
+ try
+ {
+ if (existingSong != null && apiAudio.Filename != existingSong.Name)
+ {
+ File.Delete(Path.Combine(SoundsDirectory, $"{existingSong.Name}.{existingSong.Extension}"));
+ }
+ await apiAudio.DownloadToFolder(SoundsDirectory);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Failed to download API audio: {ex.Message}");
+ success = false;
+ }
+ return success;
}
- public List GetAudioClips()
+ public static List GetAudioClips()
{
List audioClips = [];
@@ -48,6 +61,19 @@ class AudioSyncService
return audioClips;
}
+ public static void ClearAudioClips()
+ {
+ foreach (Song song in Song.Sounds.Values.ToList())
+ {
+ song.Dispose();
+ }
+ Song.Sounds.Clear();
+ Song.SoundsByHash.Clear();
+ Song.Songs.Clear();
+ Song.BB_VoiceLines.Clear();
+ GC.Collect();
+ }
+
public class APIAudioFormat
{
[JsonProperty("_id")]
@@ -105,6 +131,164 @@ class AudioSyncService
}
+class AudioSyncWorker
+{
+
+ private static AudioSyncWorker? Instance { get; set; }
+ public static AudioSyncWorker GetInstance()
+ {
+ Instance ??= new AudioSyncWorker();
+ return Instance;
+ }
+
+ public static readonly string SoundsDirectory = Path.Combine(BepInEx.Paths.BepInExRootPath, "bugleSounds");
+ public static readonly Dictionary AudioTypes = new()
+ {
+ { "wav", AudioType.WAV },
+ { "mp3", AudioType.MPEG },
+ { "ogg", AudioType.OGGVORBIS },
+ { "aiff", AudioType.AIFF },
+ };
+
+ public static bool IsLoading = false;
+ public static bool IsSyncing = false;
+
+ public static int CurrentSongIndex = 0;
+ public static string CurrentSongName = "None";
+
+ public static Action? OnAudioLoadComplete;
+
+ public static void GetAudioClips()
+ {
+ if (IsLoading || IsSyncing) return;
+ if (!Directory.Exists(SoundsDirectory)) return;
+ IsLoading = true;
+ Plugin.Instance.StartCoroutine(LoadAllAudioClipsCoroutine(SoundsDirectory));
+ }
+
+ public static void TrySyncAndLoadAudioClips()
+ {
+ if (IsLoading || IsSyncing) return;
+ Task.Run(() =>
+ {
+ SyncAndLoadAudioClipsCoroutine().GetAwaiter().GetResult();
+ });
+ }
+
+ private static IEnumerator LoadAllAudioClipsCoroutine(string directoryPath, string[]? forceReload = null)
+ {
+ List<(string filePath, string ext, string name)> filesToLoad = new();
+
+ foreach (var ext in AudioTypes.Keys)
+ {
+ var files = Directory.GetFiles(directoryPath, $"*.{ext}");
+ foreach (var file in files)
+ {
+ string name = Path.GetFileNameWithoutExtension(file);
+ bool shouldForceReload = forceReload != null && forceReload.Contains($"{name}.{ext}");
+ if (!Song.Sounds.ContainsKey(name) || shouldForceReload)
+ {
+ filesToLoad.Add((file, ext, name));
+ }
+ }
+ }
+
+ const int BATCH_SIZE = 2;
+ int loadedCount = 0;
+
+ for (int i = 0; i < filesToLoad.Count; i += BATCH_SIZE)
+ {
+ List loadCoroutines = [];
+
+ for (int j = i; j < i + Math.Min(BATCH_SIZE, filesToLoad.Count - i) && j < filesToLoad.Count; j++)
+ {
+ var (filePath, ext, name) = filesToLoad[j];
+ bool forceReloadClip = forceReload != null && forceReload.Contains($"{name}.{ext}");
+ Coroutine loadCoroutine = Plugin.Instance.StartCoroutine(LoadAudioClipCoroutine(filePath, ext, name, forceReloadClip));
+ loadCoroutines.Add(loadCoroutine);
+ }
+
+ foreach (var coroutine in loadCoroutines) yield return coroutine;
+ loadedCount += loadCoroutines.Count;
+ BetterBugleUI.Instance?.ShowActionbar($"Loading audio clips... {loadedCount}/{filesToLoad.Count}");
+ }
+ OnAudioLoadComplete?.Invoke();
+ IsLoading = false;
+ }
+
+ private static IEnumerator LoadAudioClipCoroutine(string filePath, string ext, string name, bool forceReload = false)
+ {
+ using UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip($"file://{filePath}", AudioTypes[ext]);
+ yield return www.SendWebRequest();
+
+ if (www.result != UnityWebRequest.Result.Success) yield break;
+
+ bool songExists = Song.Sounds.ContainsKey(name);
+
+ if (songExists && !forceReload) yield break;
+
+ if (songExists && forceReload)
+ {
+ Song? previousSong = Song.Sounds.TryGetValue(name, out var existingSong) ? existingSong : null;
+ previousSong?.Dispose();
+ }
+
+ AudioClip audioClip = DownloadHandlerAudioClip.GetContent(www);
+ if (audioClip == null) yield break;
+
+ Song song = new(name, ext, filePath, audioClip);
+ song.Register();
+ }
+
+ private static async Task SyncAndLoadAudioClipsCoroutine()
+ {
+ if (IsLoading || IsSyncing) return;
+ IsSyncing = true;
+ Dictionary toDownload = new();
+
+ string[] existingSongNames = [.. Song.Sounds.Keys];
+ AudioSyncService.APIAudioFormat[] existingAPIFormats = [.. AudioSyncService.GetAudioClips()];
+ string[] apiExistingNames = [.. existingAPIFormats.Select(apiAudio => apiAudio.Filename)];
+
+ var songsToRemove = existingSongNames.Except(apiExistingNames).ToArray();
+ foreach (var songName in songsToRemove)
+ {
+ if (Song.Sounds.TryGetValue(songName, out var songToDispose))
+ {
+ songToDispose.Dispose();
+ songToDispose.DeleteFile();
+ }
+ }
+
+
+ foreach (AudioSyncService.APIAudioFormat apiAudio in existingAPIFormats)
+ {
+ Song? existingSong = Song.SoundsByHash.GetValueOrDefault(apiAudio.Hash);
+ if (existingSong == null || existingSong.Hash != apiAudio.Hash)
+ {
+ toDownload.Add(apiAudio, existingSong);
+ }
+ }
+
+ BetterBugleUI.Instance?.ShowActionbar($"Syncing audio bank... {toDownload.Count} changed/new files found.");
+
+ string[] filesToOverload = [];
+
+ foreach (AudioSyncService.APIAudioFormat apiAudio in toDownload.Keys)
+ {
+ bool success = await AudioSyncService.DownloadAPIAudio(apiAudio, SoundsDirectory, toDownload[apiAudio]);
+ if (success)
+ {
+ Debug.Log($"Successfully downloaded audio: {apiAudio.Filename}.{apiAudio.Extension}, adding to forceload");
+ filesToOverload = [.. filesToOverload, $"{apiAudio.Filename}.{apiAudio.Extension}"];
+ }
+ }
+ IsSyncing = false;
+ IsLoading = true;
+ Plugin.Instance.StartCoroutine(LoadAllAudioClipsCoroutine(SoundsDirectory, filesToOverload));
+ }
+}
+
public class Song : IDisposable
{
public static readonly Dictionary Sounds = new();
@@ -180,7 +364,7 @@ public class Song : IDisposable
public void DeleteFile()
{
if (AudioClip == null) return;
- var filePath = Path.Combine(BetterBugleModule.SoundsDirectory, $"{Name}.{Extension}");
+ var filePath = Path.Combine(AudioSyncWorker.SoundsDirectory, $"{Name}.{Extension}");
if (File.Exists(filePath))
{
File.Delete(filePath);