diff --git a/.gitignore b/.gitignore index fcdef46..a5b3a3f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ riderModule.iml /_ReSharper.Caches/ .idea/ *.dll +.vs/ +dist/ MuzikaGromche.sln.DotSettings.user +MuzikaGromche.zip diff --git a/Assets/.gitattributes b/Assets/.gitattributes index 6b9df02..58b97d0 100644 --- a/Assets/.gitattributes +++ b/Assets/.gitattributes @@ -1 +1,3 @@ *.mp3 filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text diff --git a/Assets/GodModeLoop.ogg b/Assets/GodModeLoop.ogg new file mode 100644 index 0000000..2206c0e --- /dev/null +++ b/Assets/GodModeLoop.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6d288d905154b81d0897a284f226ed23db2fc4d45716afbc4533534315df65e +size 484599 diff --git a/Assets/GodModeStart.ogg b/Assets/GodModeStart.ogg new file mode 100644 index 0000000..c64b651 --- /dev/null +++ b/Assets/GodModeStart.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c8ebf87264d0a56b4b3bb0e1ab41d28f82c5de42a7bc2843f3595bcc6ef4858 +size 587503 diff --git a/Assets/MuzikaGromcheLoop.mp3 b/Assets/MuzikaGromcheLoop.mp3 deleted file mode 100644 index 4990181..0000000 --- a/Assets/MuzikaGromcheLoop.mp3 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3454f660cfd49a74e0447a2d21949711db7e62533a1064e4dcb25ac98f5f6034 -size 373234 diff --git a/Assets/MuzikaGromcheLoop.ogg b/Assets/MuzikaGromcheLoop.ogg new file mode 100644 index 0000000..66a8fa6 --- /dev/null +++ b/Assets/MuzikaGromcheLoop.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735ab968262f0582a411127e6e0cb9a1d6e3c855f772f7c0a799613992c6775a +size 203194 diff --git a/Assets/MuzikaGromcheStart.mp3 b/Assets/MuzikaGromcheStart.mp3 deleted file mode 100644 index 9544d2e..0000000 --- a/Assets/MuzikaGromcheStart.mp3 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a21599a64f1780b49e1cd057c817a90740c71482e3c5518c5265622d9f721e05 -size 1143589 diff --git a/Assets/MuzikaGromcheStart.ogg b/Assets/MuzikaGromcheStart.ogg new file mode 100644 index 0000000..f6c5065 --- /dev/null +++ b/Assets/MuzikaGromcheStart.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fd6d690c921c4388bcfd4b79a15fbf79d5db4ced37c47e5a629c3c938557c2c +size 693016 diff --git a/Assets/PeretasovkaLoop.ogg b/Assets/PeretasovkaLoop.ogg new file mode 100644 index 0000000..a6207cf --- /dev/null +++ b/Assets/PeretasovkaLoop.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69c5ff517b55c143ac5021201063daae928d8c677b9158167b06a38796ad1a28 +size 207710 diff --git a/Assets/PeretasovkaStart.ogg b/Assets/PeretasovkaStart.ogg new file mode 100644 index 0000000..0446cda --- /dev/null +++ b/Assets/PeretasovkaStart.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dce39da7799e0cf6e715a32ca6e016b5378848bbc159d90663765cedb8d271a9 +size 858033 diff --git a/Assets/RiseAndShineLoop.ogg b/Assets/RiseAndShineLoop.ogg new file mode 100644 index 0000000..3dc40ee --- /dev/null +++ b/Assets/RiseAndShineLoop.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d39362f218e1cbb644c2cf2cf7e53a869cde3bcf2dbc60f2ef18e3c4090fe2cc +size 386251 diff --git a/Assets/RiseAndShineStart.ogg b/Assets/RiseAndShineStart.ogg new file mode 100644 index 0000000..3a8f95c --- /dev/null +++ b/Assets/RiseAndShineStart.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:791378625a6c202624f24793cb7e897250b88ad65220ff8eaf57752089ecd60e +size 808735 diff --git a/MuzikaGromche/Plugin.cs b/MuzikaGromche/Plugin.cs index 159cf2c..207216c 100644 --- a/MuzikaGromche/Plugin.cs +++ b/MuzikaGromche/Plugin.cs @@ -10,20 +10,21 @@ using UnityEngine.Networking; namespace MuzikaGromche { - [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] - public class Plugin : BaseUnityPlugin - { + [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + public class Plugin : BaseUnityPlugin + { public static Track[] Tracks = [ new Track { Name = "MuzikaGromche", WindUpTimer = 46.3f, Bpm = 130f, + AudioType = AudioType.OGGVORBIS, }, new Track { Name = "VseVZale", - WindUpTimer = 39f, + WindUpTimer = 39f, Bpm = 138f, }, new Track @@ -49,20 +50,89 @@ namespace MuzikaGromche Name = "Durochka", WindUpTimer = 37f, Bpm = 130f, - } + }, + new Track + { + Name = "GodMode", + WindUpTimer = 40.38f, + Bpm = 108f, + AudioType = AudioType.OGGVORBIS, + }, + new Track + { + Name = "RiseAndShine", + WindUpTimer = 59.87f, + Bpm = 137.36f, + AudioType = AudioType.OGGVORBIS, + }, + new Track + { + Name = "Peretasovka", + WindUpTimer = 59.04f, + Bpm = 130f, + AudioType = AudioType.OGGVORBIS, + }, ]; public static Coroutine JesterLightSwitching; public static Track CurrentTrack; + public static void StartLightSwitching(MonoBehaviour __instance) + { + StopLightSwitching(__instance); + JesterLightSwitching = __instance.StartCoroutine(rotateColors()); + } + + public static void StopLightSwitching(MonoBehaviour __instance) + { + if (JesterLightSwitching != null) { + __instance.StopCoroutine(JesterLightSwitching); + JesterLightSwitching = null; + } + } + + public static void SetLightColor(Color color) + { + foreach (var light in RoundManager.Instance.allPoweredLights) + { + light.color = color; + } + } + + public static void ResetLightColor() + { + SetLightColor(Color.white); + } + + // TODO: Move to Track class to make them customizable per-song + static List colors = [Color.magenta, Color.cyan, Color.green, Color.yellow]; + + public static IEnumerator rotateColors() + { + Debug.Log("Starting color rotation"); + var i = 0; + while (true) + { + var color = colors[i]; + Debug.Log("Chose color " + color); + SetLightColor(color); + i = (i + 1) % colors.Count; + if (CurrentTrack != null) { + yield return new WaitForSeconds(60f / CurrentTrack.Bpm); + } else { + yield break; + } + } + } + private void Awake() { string text = Info.Location.TrimEnd((PluginInfo.PLUGIN_NAME + ".dll").ToCharArray()); UnityWebRequest[] requests = new UnityWebRequest[Tracks.Length * 2]; for (int i = 0; i < Tracks.Length; i++) { Track track = Tracks[i]; - requests[i * 2] = UnityWebRequestMultimedia.GetAudioClip($"File://{text}{track.Name}Start.mp3", AudioType.MPEG); - requests[i * 2 + 1] = UnityWebRequestMultimedia.GetAudioClip($"File://{text}{track.Name}Loop.mp3", AudioType.MPEG); + requests[i * 2] = UnityWebRequestMultimedia.GetAudioClip($"File://{text}{track.FileNameStart}", track.AudioType); + requests[i * 2 + 1] = UnityWebRequestMultimedia.GetAudioClip($"File://{text}{track.FileNameLoop}", track.AudioType); requests[i * 2].SendWebRequest(); requests[i * 2 + 1].SendWebRequest(); } @@ -71,141 +141,130 @@ namespace MuzikaGromche if (requests.All(request => request.result == UnityWebRequest.Result.Success)) { for (int i = 0; i < Tracks.Length; i++) { - Tracks[i].LoadedStart = DownloadHandlerAudioClip.GetContent(requests[i * 2]); - Tracks[i].LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]); + Track track = Tracks[i]; + track.LoadedStart = DownloadHandlerAudioClip.GetContent(requests[i * 2]); + track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]); } new Harmony(PluginInfo.PLUGIN_NAME).PatchAll(typeof(JesterPatch)); } else { Logger.LogError("Could not load audio file"); } } - } + } - public class Track - { - public string Name; - public float WindUpTimer; - public float Bpm; - public AudioClip LoadedStart; - public AudioClip LoadedLoop; - } + public class Track + { + public string Name; + // Wind-up time can be shorter than the Start audio track, so that + // the "pop" effect can be baked in the Start audio and kept away + // from the looped part. + public float WindUpTimer; + // BPM for light switching in sync with the music. There is no offset, + // so the Loop track should start precisely on a beat. + public float Bpm; - [HarmonyPatch(typeof(JesterAI))] - internal class JesterPatch - { - [HarmonyPatch("Update")] - [HarmonyPrefix] - public static void DoNotStopTheMusicPrefix(JesterAI __instance, out State __state) - { - __state = new State(); - __state.prevStateindex = __instance.previousState; - if (__instance.currentBehaviourStateIndex == 2 && __instance.previousBehaviourStateIndex != 2) { - // if just popped out - // then override farAudio so that vanilla logic does not stop the music - __state.farAudio = __instance.farAudio; - __instance.farAudio = __instance.creatureVoice; - } - } - - static List colors = [Color.magenta, Color.cyan, Color.green, Color.yellow]; + // MPEG is basically mp3, and it can produce gaps at the start. + // WAV is OK, but takes a lot of space. Try OGGVORBIS instead. + public AudioType AudioType = AudioType.MPEG; - public static IEnumerator rotateColors() - { - Debug.Log("Starting color rotation"); - var i = 0; - while (true) - { - var color = colors[i]; - Debug.Log("Chose color " + color); - foreach (var light in RoundManager.Instance.allPoweredLights) - { - light.color = color; - } + public AudioClip LoadedStart; + public AudioClip LoadedLoop; - i += 1; - if (i >= colors.Count) i = 0; - yield return new WaitForSeconds(60f / Plugin.CurrentTrack.Bpm); - } - } + public string FileNameStart => $"{Name}Start.{ext}"; + public string FileNameLoop => $"{Name}Loop.{ext}"; + private string ext => AudioType switch + { + AudioType.MPEG => "mp3", + AudioType.WAV => "wav", + AudioType.OGGVORBIS => "ogg", + _ => "", + }; + } + + [HarmonyPatch(typeof(JesterAI))] + internal class JesterPatch + { + [HarmonyPatch("Update")] + [HarmonyPrefix] + public static void DoNotStopTheMusicPrefix(JesterAI __instance, out State __state) + { + __state = new State(); + __state.prevStateindex = __instance.previousState; + if (__instance.currentBehaviourStateIndex == 2 && __instance.previousBehaviourStateIndex != 2) { + // if just popped out + // then override farAudio so that vanilla logic does not stop the music + __state.farAudio = __instance.farAudio; + __instance.farAudio = __instance.creatureVoice; + } + } [HarmonyPatch("Update")] - [HarmonyPostfix] - public static void DoNotStopTheMusic(JesterAI __instance, State __state) - { - if (__state.farAudio != null) - { - __instance.farAudio = __state.farAudio; - } - - if (__instance.currentBehaviourStateIndex is 1 && __state.prevStateindex != 1) - { - // if just started winding up - // then stop the default music... - __instance.farAudio.Stop(); - __instance.creatureVoice.Stop(); - - // ...and start modded music - var seed = RoundManager.Instance.dungeonGenerator.Generator.ChosenSeed; - var sha = SHA256.Create(); - var hash = sha.ComputeHash(BitConverter.GetBytes(seed)); - var trackId = 0; - foreach (var t in hash) - { - // modulus division on byte array - trackId *= 256 % Plugin.Tracks.Length; - trackId %= Plugin.Tracks.Length; - trackId += t % Plugin.Tracks.Length; - trackId %= Plugin.Tracks.Length; - } - Debug.Log($"Seed is {seed}, chosen track is {trackId} out of {Plugin.Tracks.Length} tracks"); - Plugin.CurrentTrack = Plugin.Tracks[trackId]; - __instance.popUpTimer = Plugin.CurrentTrack.WindUpTimer; - __instance.farAudio.maxDistance = 150; - __instance.farAudio.clip = Plugin.CurrentTrack.LoadedStart; - __instance.farAudio.loop = false; - Debug.Log($"Playing start music: maxDistance: {__instance.farAudio.maxDistance}, minDistance: {__instance.farAudio.minDistance}, volume: {__instance.farAudio.volume}, spread: {__instance.farAudio.spread}"); - __instance.farAudio.Play(); - } + [HarmonyPostfix] + public static void DoNotStopTheMusic(JesterAI __instance, State __state) + { + if (__state.farAudio != null) + { + __instance.farAudio = __state.farAudio; + } - if (__instance.currentBehaviourStateIndex is 2 && __state.prevStateindex != 2) - { - __instance.creatureVoice.Stop(); - - if (Plugin.JesterLightSwitching != null) { - __instance.StopCoroutine(Plugin.JesterLightSwitching); - Plugin.JesterLightSwitching = null; - } - Plugin.JesterLightSwitching = __instance.StartCoroutine(rotateColors()); - } + if (__instance.currentBehaviourStateIndex is 1 && __state.prevStateindex != 1) + { + // if just started winding up + // then stop the default music... + __instance.farAudio.Stop(); + __instance.creatureVoice.Stop(); - if (__instance.currentBehaviourStateIndex != 2 && __state.prevStateindex == 2) - { - if (Plugin.JesterLightSwitching != null) { - __instance.StopCoroutine(Plugin.JesterLightSwitching); - Plugin.JesterLightSwitching = null; - } - foreach (var light in RoundManager.Instance.allPoweredLights) - { - light.color = Color.white; - } - } + // ...and start modded music + var seed = RoundManager.Instance.dungeonGenerator.Generator.ChosenSeed; + var sha = SHA256.Create(); + var hash = sha.ComputeHash(BitConverter.GetBytes(seed)); + var trackId = 0; + foreach (var t in hash) + { + // modulus division on byte array + trackId *= 256 % Plugin.Tracks.Length; + trackId %= Plugin.Tracks.Length; + trackId += t % Plugin.Tracks.Length; + trackId %= Plugin.Tracks.Length; + } + Debug.Log($"Seed is {seed}, chosen track is {trackId} out of {Plugin.Tracks.Length} tracks"); + Plugin.CurrentTrack = Plugin.Tracks[trackId]; + __instance.popUpTimer = Plugin.CurrentTrack.WindUpTimer; + __instance.farAudio.maxDistance = 150; + __instance.farAudio.clip = Plugin.CurrentTrack.LoadedStart; + __instance.farAudio.loop = false; + Debug.Log($"Playing start music: maxDistance: {__instance.farAudio.maxDistance}, minDistance: {__instance.farAudio.minDistance}, volume: {__instance.farAudio.volume}, spread: {__instance.farAudio.spread}"); + __instance.farAudio.Play(); + } - if (__instance.currentBehaviourStateIndex is 2 && !__instance.creatureVoice.isPlaying) - { - __instance.creatureVoice.maxDistance = 150; - __instance.creatureVoice.clip = Plugin.CurrentTrack.LoadedLoop; - var time = __instance.farAudio.time; - var delay = Plugin.CurrentTrack.LoadedStart.length - time; - Debug.Log($"Start length: {Plugin.CurrentTrack.LoadedStart.length}; played time: {time}"); - Debug.Log($"Playing loop music: maxDistance: {__instance.creatureVoice.maxDistance}, minDistance: {__instance.creatureVoice.minDistance}, volume: {__instance.creatureVoice.volume}, spread: {__instance.creatureVoice.spread}, in seconds: {delay}"); - __instance.creatureVoice.PlayDelayed(delay); - } - } - } + if (__instance.currentBehaviourStateIndex is 2 && __state.prevStateindex != 2) + { + __instance.creatureVoice.Stop(); + Plugin.StartLightSwitching(__instance); + } - internal class State - { - public AudioSource farAudio; - public int prevStateindex; - } + if (__instance.currentBehaviourStateIndex != 2 && __state.prevStateindex == 2) + { + Plugin.StopLightSwitching(__instance); + Plugin.ResetLightColor(); + } + + if (__instance.currentBehaviourStateIndex is 2 && !__instance.creatureVoice.isPlaying) + { + __instance.creatureVoice.maxDistance = 150; + __instance.creatureVoice.clip = Plugin.CurrentTrack.LoadedLoop; + var time = __instance.farAudio.time; + var delay = Plugin.CurrentTrack.LoadedStart.length - time; + Debug.Log($"Start length: {Plugin.CurrentTrack.LoadedStart.length}; played time: {time}"); + Debug.Log($"Playing loop music: maxDistance: {__instance.creatureVoice.maxDistance}, minDistance: {__instance.creatureVoice.minDistance}, volume: {__instance.creatureVoice.volume}, spread: {__instance.creatureVoice.spread}, in seconds: {delay}"); + __instance.creatureVoice.PlayDelayed(delay); + } + } + } + + internal class State + { + public AudioSource farAudio; + public int prevStateindex; + } } diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4f4898 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Adds some content to your reverse teleports on Titan \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..e111710 Binary files /dev/null and b/icon.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..a84a944 --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "MuzikaGromche", + "version_number": "13.37.6", + "author": "Oflor", + "description": "Glaza zakryvaj", + "website_url": "https://git.vilunov.me/nikita/muzika-gromche", + "dependencies": [ + "BepInEx-BepInExPack-5.4.2100" + ] + } \ No newline at end of file