diff --git a/BombRushRadio.csproj b/BombRushRadio.csproj
index 62995fe..4d3622f 100644
--- a/BombRushRadio.csproj
+++ b/BombRushRadio.csproj
@@ -1,39 +1,55 @@
-
- net461
- BombRushRadio
- Allows adding custom music tracks to Bomb Rush Cyberfunk.
- 1.7
- true
- 11
-
-
-
- $(BRCPath)/Bomb Rush Cyberfunk_Data/Managed
-
+
+ net461
+ BombRushRadio
+ Allows adding custom music tracks to Bomb Rush Cyberfunk.
+ 1.7
+ true
+ 11
+
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
- $(ManagedPath)/Assembly-CSharp.dll
- false
- true
-
-
-
-
-
-
-
+
+
+ DEBUG;TRACE
+ full
+ true
+ false
+
+
+
+
+ TRACE
+ none
+ false
+ true
+
+
+
+ $(BRCPath)/Bomb Rush Cyberfunk_Data/Managed
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+ $(ManagedPath)/Assembly-CSharp.dll
+ false
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Patches/MusicPlayer-Patches.cs b/Patches/MusicPlayer-Patches.cs
index c1dcb8f..756741a 100644
--- a/Patches/MusicPlayer-Patches.cs
+++ b/Patches/MusicPlayer-Patches.cs
@@ -2,6 +2,7 @@
using Reptile;
using Reptile.Phone;
using System;
+using System.Collections.Generic;
using UnityEngine;
namespace BombRushRadio;
@@ -61,34 +62,48 @@ public static void Refresh(MusicPlayer __instance, ChapterMusic chapterMusic, St
{
__instance.musicTrackQueue.ClearTracks();
- Story.ObjectiveInfo currentObjectiveInfo = Story.GetCurrentObjectiveInfo();
- if (stage == Stage.hideout)
+ if (!BombRushRadio.RemoveBaseGameSongs.Value)
{
- MusicTrack musicTrackByID = Core.Instance.AudioManager.MusicLibraryPlayer.GetMusicTrackByID(MusicTrackID.Hideout_Mixtape);
- __instance.AddMusicTrack(musicTrackByID);
- Debug.Log("[BRR] [BASE-GAME] Added " + musicTrackByID.Title + " to the total list.");
+ Story.ObjectiveInfo currentObjectiveInfo = Story.GetCurrentObjectiveInfo();
+ if (stage == Stage.hideout)
+ {
+ MusicTrack musicTrackByID = Core.Instance.AudioManager.MusicLibraryPlayer.GetMusicTrackByID(MusicTrackID.Hideout_Mixtape);
+ __instance.AddMusicTrack(musicTrackByID);
+ Debug.Log("[BRR] [BASE-GAME] Added " + musicTrackByID.Title + " to the total list.");
+ }
+ else
+ {
+ MusicTrack chapterMusic2 = chapterMusic.GetChapterMusic(currentObjectiveInfo.chapter);
+ __instance.AddMusicTrack(chapterMusic2);
+ Debug.Log("[BRR] [BASE-GAME] Added " + chapterMusic2.Title + " to the total list.");
+ }
+ AUnlockable[] unlockables = WorldHandler.instance.GetCurrentPlayer().phone.GetAppInstance().Unlockables;
+ for (int i = 0; i < unlockables.Length; i++)
+ {
+ var musicTrack = unlockables[i] as MusicTrack;
+ if (Core.Instance.Platform.User.GetUnlockableSaveDataFor(musicTrack).IsUnlocked)
+ {
+ musicTrack.isRepeatable = false;
+ __instance.AddMusicTrack(musicTrack);
+ Debug.Log("[BRR] [BASE-GAME] Added " + musicTrack.Title + " to the total list.");
+ }
+ }
}
else
{
- MusicTrack chapterMusic2 = chapterMusic.GetChapterMusic(currentObjectiveInfo.chapter);
- __instance.AddMusicTrack(chapterMusic2);
- Debug.Log("[BRR] [BASE-GAME] Added " + chapterMusic2.Title + " to the total list.");
+ Debug.Log("[BRR] Base game songs removed per config setting.");
}
- AUnlockable[] unlockables = WorldHandler.instance.GetCurrentPlayer().phone.GetAppInstance().Unlockables;
- for (int i = 0; i < unlockables.Length; i++)
+
+ var existingTracks = new HashSet();
+ foreach (var track in __instance.musicTrackQueue.currentMusicTracks)
{
- var musicTrack = unlockables[i] as MusicTrack;
- if (Core.Instance.Platform.User.GetUnlockableSaveDataFor(musicTrack).IsUnlocked)
- {
- musicTrack.isRepeatable = false;
- __instance.AddMusicTrack(musicTrack);
- Debug.Log("[BRR] [BASE-GAME] Added " + musicTrack.Title + " to the total list.");
- }
+ existingTracks.Add($"{track.Artist}|{track.Title}");
}
foreach (MusicTrack track in BombRushRadio.Audios)
{
- if (__instance.musicTrackQueue.currentMusicTracks.Find(m => m.Title == track.Title && m.Artist == track.Artist) != null)
+ string trackKey = $"{track.Artist}|{track.Title}";
+ if (existingTracks.Contains(trackKey))
{
continue;
}
@@ -124,7 +139,8 @@ public class MusicTrackQueue_Patches
{
static bool Prefix(MusicTrack musicTrack) // ignore unlocking for custom stuff
{
- if (BombRushRadio.Audios.Find(m => musicTrack.Artist == m.Artist && musicTrack.Title == m.Title))
+ string trackKey = $"{musicTrack.Artist}|{musicTrack.Title}";
+ if (BombRushRadio.AudioLookup.ContainsKey(trackKey))
{
return false;
}
@@ -177,3 +193,20 @@ static void Prefix(MusicPlayer __instance)
__instance.ForcePaused();
}
}
+
+[HarmonyPatch(typeof(MusicPlayer), nameof(MusicPlayer.EvaluateRepeatingMusicTrack))]
+public class MusicPlayer_Patches_EvaluateRepeatingMusicTrack
+{
+ static void Postfix(ref bool __result)
+ {
+ if (BombRushRadio.Skipping)
+ {
+ Debug.Log($"[BRR] EvaluateRepeatingMusicTrack - was {__result}, forcing to false because skipping");
+ }
+
+ if (BombRushRadio.Skipping)
+ {
+ __result = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Patches/MusicPlayerBuffer-Patches.cs b/Patches/MusicPlayerBuffer-Patches.cs
index f9bc074..edd5588 100644
--- a/Patches/MusicPlayerBuffer-Patches.cs
+++ b/Patches/MusicPlayerBuffer-Patches.cs
@@ -6,7 +6,7 @@ namespace BombRushRadio;
[HarmonyPatch(typeof(MusicPlayerBuffer), nameof(MusicPlayerBuffer.BufferMusicTrack))]
public class MusicPlayerBuffer_BufferMusicTrack_Patches
{
- static bool Prefix(MusicPlayerBuffer __instance, MusicTrack musicTrackToLoad) // the the game to not unload our files please lol
+ static bool Prefix(MusicPlayerBuffer __instance, MusicTrack musicTrackToLoad) // tell the game to not unload our files please lol
{
if (musicTrackToLoad == null || musicTrackToLoad.AudioClip == null)
{
@@ -28,11 +28,11 @@ static bool Prefix(MusicPlayerBuffer __instance, MusicTrack musicTrackToLoad) //
[HarmonyPatch(typeof(MusicPlayerBuffer), nameof(MusicPlayerBuffer.UnloadMusicPlayerData))]
public class MusicPlayerBuffer_Patches
{
- static bool Prefix(MusicPlayerData musicPlayerData) // the the game to not unload our files please lol
+ static bool Prefix(MusicPlayerData musicPlayerData) // tell the game to not unload our files please lol
{
- MusicTrack t = BombRushRadio.Audios.Find(m => musicPlayerData.Artist == m.Artist && musicPlayerData.Title == m.Title);
+ string trackKey = $"{musicPlayerData.Artist}|{musicPlayerData.Title}";
- if (t != null)
+ if (BombRushRadio.AudioLookup.ContainsKey(trackKey))
{
return false;
}
diff --git a/Plugin.cs b/Plugin.cs
index 9656889..d6be638 100644
--- a/Plugin.cs
+++ b/Plugin.cs
@@ -16,20 +16,36 @@ namespace BombRushRadio;
public class BombRushRadio : BaseUnityPlugin
{
public static ConfigEntry ReloadKey;
+ public static ConfigEntry SkipKey;
+ public static ConfigEntry SkipKeyController;
+ public static ConfigEntry RemoveBaseGameSongs;
+ public static ConfigEntry StreamAudio;
+ public static ConfigEntry MaxConcurrentLoads;
public static MusicPlayer MInstance;
public static List Audios = new();
+ public static Dictionary AudioLookup = new();
public int ShouldBeDone;
public int Done;
+ private int ActiveLoads;
private static readonly List Loaded = new();
public static bool InMainMenu = false;
public static bool Loading;
+ public static bool Skipping = false;
private readonly AudioType[] _trackerTypes = new[] { AudioType.IT, AudioType.MOD, AudioType.S3M, AudioType.XM };
private readonly string _songFolder = Path.Combine(Application.streamingAssetsPath, "Mods", "BombRushRadio", "Songs");
+ [System.Diagnostics.Conditional("DEBUG")]
+ private void DebugLog(string message)
+ {
+ Logger.LogInfo(message);
+ }
+
+ public int TotalFileCount;
+
public void SanitizeSongs()
{
if (Core.Instance == null || Core.Instance.audioManager == null)
@@ -39,50 +55,69 @@ public void SanitizeSongs()
if (Core.Instance.audioManager.musicPlayer != null)
{
+ var currentTracks = MInstance.musicTrackQueue.currentMusicTracks;
+ var loadedSet = new HashSet(Loaded);
var toRemove = new List();
int idx = 0;
foreach (MusicTrack tr in Audios)
{
- if (MInstance.musicTrackQueue.currentMusicTracks.Contains(tr))
+ if (currentTracks.Contains(tr))
{
- MInstance.musicTrackQueue.currentMusicTracks.Remove(tr);
+ currentTracks.Remove(tr);
}
else
{
Logger.LogInfo("[BRR] Adding " + tr.Title);
}
- if (Loaded.FirstOrDefault(l => l == Helpers.FormatMetadata(new[] { tr.Artist, tr.Title }, "dash")) == null)
+ string trackKey = Helpers.FormatMetadata(new[] { tr.Artist, tr.Title }, "dash");
+
+ if (!loadedSet.Contains(trackKey))
{
Logger.LogInfo("[BRR] Removing " + tr.Title);
toRemove.Add(tr);
}
- MInstance.musicTrackQueue.currentMusicTracks.Insert(1 + idx, tr);
+ currentTracks.Insert(1 + idx, tr);
idx++;
}
foreach (MusicTrack tr in toRemove)
{
Audios.Remove(tr);
- tr.AudioClip.UnloadAudioData();
+ AudioLookup.Remove($"{tr.Artist}|{tr.Title}");
+ if (tr.AudioClip != null)
+ {
+ tr.AudioClip.UnloadAudioData();
+ }
}
}
}
public IEnumerator LoadAudioFile(string filePath, AudioType type)
{
- string[] metadata = Helpers.GetMetadata(filePath, false);
+ while (ActiveLoads >= MaxConcurrentLoads.Value)
+ {
+ yield return null;
+ }
+ ActiveLoads++;
+ DebugLog($"[BRR] Starting load (active: {ActiveLoads}/{MaxConcurrentLoads.Value}): {Path.GetFileName(filePath)}");
+
+ string[] metadata = Helpers.GetMetadata(filePath, false);
string songName = Helpers.FormatMetadata(metadata, "dash");
+ string songKey = $"{metadata[0]}|{metadata[1]}";
// Escape special characters so we don't get an HTML error when we send the request
filePath = UnityWebRequest.EscapeURL(filePath);
using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file:///" + filePath, type))
{
+ var downloadHandler = (DownloadHandlerAudioClip) www.downloadHandler;
+ downloadHandler.streamAudio = StreamAudio.Value && !_trackerTypes.Contains(type);
+
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.ConnectionError)
@@ -94,37 +129,48 @@ public IEnumerator LoadAudioFile(string filePath, AudioType type)
Done++;
MusicTrack musicTrack = ScriptableObject.CreateInstance();
- musicTrack.AudioClip = null;
musicTrack.Artist = metadata[0];
musicTrack.Title = metadata[1];
musicTrack.isRepeatable = false;
- var downloadHandler = (DownloadHandlerAudioClip) www.downloadHandler;
- downloadHandler.streamAudio = !_trackerTypes.Contains(type);
-
AudioClip myClip = downloadHandler.audioClip;
- myClip.name = filePath;
+ myClip.name = songName;
musicTrack.AudioClip = myClip;
Audios.Add(musicTrack);
+ AudioLookup[songKey] = musicTrack;
Logger.LogInfo($"[BRR] Loaded {Helpers.FormatMetadata(metadata, "by")} ({Done}/{ShouldBeDone})");
Loaded.Add(songName);
}
}
+
+ ActiveLoads--;
+ DebugLog($"[BRR] Finished load (active: {ActiveLoads}/{MaxConcurrentLoads.Value})");
}
public IEnumerator LoadFile(string f)
{
string extension = Path.GetExtension(f).ToLowerInvariant().Substring(1);
string[] metadata = Helpers.GetMetadata(f, false);
+ string songKey = $"{metadata[0]}|{metadata[1]}";
- if (Audios.Find(m => m.Artist == metadata[0] && m.Title == metadata[1]))
+ if (AudioLookup.ContainsKey(songKey))
{
string songName = Helpers.FormatMetadata(metadata, "dash");
Loaded.Add(songName);
- Logger.LogInfo("[BRR] " + songName + " is already loaded, skipping.");
+
+ // prefer MP3 over other formats
+ if (extension == "ogg" || extension == "flac" || extension == "wav")
+ {
+ Logger.LogInfo("[BRR] " + songName + " is already loaded, deleting duplicate " + extension.ToUpper() + " file.");
+ File.Delete(f);
+ }
+ else
+ {
+ Logger.LogInfo("[BRR] " + songName + " is already loaded, skipping.");
+ }
}
else
{
@@ -169,10 +215,27 @@ public IEnumerator SearchDirectories(string path = "")
yield return null;
}
+ public IEnumerator SkipTrack()
+ {
+ DebugLog($"[BRR] Skip requested - index: {MInstance.CurrentTrackIndex}/{MInstance.musicTrackQueue.AmountOfTracks}, track: {MInstance.GetMusicTrack(MInstance.CurrentTrackIndex)?.Title}");
+
+ Skipping = true;
+ MInstance.ForcePaused();
+ MInstance.PlayNext();
+
+ yield return new WaitForSeconds(0.3f);
+ Skipping = false;
+
+ DebugLog($"[BRR] Skip complete - now at index: {MInstance.CurrentTrackIndex}, track: {MInstance.GetMusicTrack(MInstance.CurrentTrackIndex)?.Title}");
+ }
+
public IEnumerator ReloadSongs()
{
+ DebugLog("[BRR] ===== RELOAD STARTED =====");
Loaded.Clear();
+ AudioLookup.Clear();
Loading = true;
+ ActiveLoads = 0;
if (Audios.Count > 0)
{
@@ -188,12 +251,22 @@ public IEnumerator ReloadSongs()
yield return StartCoroutine(SearchDirectories());
- Logger.LogInfo("[BRR] TOTAL SONGS LOADED: " + Audios.Count);
+ DebugLog("[BRR] Waiting for all loads to complete...");
+ while (ActiveLoads > 0)
+ {
+ yield return null;
+ }
+ Logger.LogInfo($"[BRR] TOTAL SONGS LOADED: {Audios.Count}");
Logger.LogInfo("[BRR] Bomb Rush Radio has been loaded!");
+ DebugLog("[BRR] ===== RELOAD COMPLETE =====");
Loading = false;
- Audios.Sort((t, t2) => string.Compare(t.AudioClip.name, t2.AudioClip.name, StringComparison.OrdinalIgnoreCase));
+ Audios.Sort((t1, t2) =>
+ {
+ int artistCompare = string.Compare(t1.Artist, t2.Artist, StringComparison.OrdinalIgnoreCase);
+ return artistCompare != 0 ? artistCompare : string.Compare(t1.Title, t2.Title, StringComparison.OrdinalIgnoreCase);
+ });
SanitizeSongs();
}
@@ -208,6 +281,11 @@ private void Awake()
// bind to config
ReloadKey = Config.Bind("Settings", "Reload Key", KeyCode.F1, "Keybind used for reloading songs.");
+ SkipKey = Config.Bind("Settings", "Skip Key", KeyCode.F2, "Keybind used for skipping to next song.");
+ SkipKeyController = Config.Bind("Settings", "Skip Key (Controller)", KeyCode.JoystickButton9, "Controller button for skipping to next song. R3/Right Stick Click is usually JoystickButton9.");
+ RemoveBaseGameSongs = Config.Bind("Settings", "Remove Base Game Songs", false, "Remove all base game songs from the music player.");
+ StreamAudio = Config.Bind("Settings", "Stream Audio", true, "Whether to stream audio from disk or load at runtime (Streaming is faster but more CPU intensive)");
+ MaxConcurrentLoads = Config.Bind("Settings", "Max Concurrent Loads", 5, "Maximum number of songs to load simultaneously (lower = less stuttering, higher = faster loading)");
// load em
StartCoroutine(ReloadSongs());
@@ -215,13 +293,30 @@ private void Awake()
var harmony = new Harmony("kade.bombrushradio");
harmony.PatchAll();
Logger.LogInfo("[BRR] Patched...");
+ }
+
+ private void Update()
+ {
+ if (Input.GetKeyDown(ReloadKey.Value) && !InMainMenu)
+ {
+ StartCoroutine(ReloadSongs());
+ }
- Core.OnUpdate += () =>
+ // skip to next song - idea by goatgirl
+ // only check controller after phone is loaded (means we're actually in game)
+ bool skipPressed = Input.GetKeyUp(SkipKey.Value);
+ if (WorldHandler.instance?.GetCurrentPlayer()?.phone != null)
+ {
+ skipPressed = skipPressed || Input.GetKeyUp(SkipKeyController.Value);
+ }
+
+ if (skipPressed && !InMainMenu)
{
- if (Input.GetKeyDown(ReloadKey.Value) && !InMainMenu) // reload songs
+ DebugLog($"[BRR] Skip button pressed! Skipping: {Skipping}, IsPlaying: {MInstance?.IsPlaying}");
+ if (MInstance != null && MInstance.IsPlaying && !Skipping)
{
- StartCoroutine(ReloadSongs());
+ StartCoroutine(SkipTrack());
}
- };
+ }
}
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 0c270f1..e598f65 100644
--- a/README.md
+++ b/README.md
@@ -33,8 +33,18 @@ Want to reload songs on the fly? Make some changes (additions, removals, and mod
The keybind can be configured in the mod's config.
+## Skip Song
+
+Don't like the current song? Press **F2** (or click the right stick on your controller) to skip to the next track.
+
+The keybind can be configured in the mod's config. *(Idea by goatgirlclover)*
+
+#### Warning: For reasons I can't explain the right stick option works on some machines perfectly and others it requires a controller reconnect. This matches the situation with "MusicCurator".
+
## Config
+BombRushRadio has several configurable options. Experiencing stuttering with a large library? Lower **Max Concurrent Loads**. Want only your custom tracks? Enable **Remove Base Game Songs**.
+
```
## Settings file was created by plugin BombRushRadio v1.7
## Plugin GUID: BombRushRadio
@@ -50,6 +60,21 @@ Stream Audio = true
# Default value: F1
Reload Key = F1
+## Keybind used for skipping to next song.
+# Setting type: KeyCode
+# Default value: F2
+Skip Key = F2
+
+## Remove all base game songs from the music player.
+# Setting type: Boolean
+# Default value: false
+Remove Base Game Songs = false
+
+## Maximum number of songs to load simultaneously (lower = less stuttering, higher = faster loading)
+# Setting type: Int32
+# Default value: 5
+Max Concurrent Loads = 5
+
```
## Installation
@@ -64,4 +89,4 @@ Make sure to add "https://nuget.bepinex.dev/v3/index.json" as a NuGet source. (i

-Then just build it.
+Then just build it.
\ No newline at end of file