Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LethalMusicSync v1.0.2
LethalMusicSync.dll
Decompiled 6 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace LethalMusicSync; [BepInPlugin("com.foxwood.lethalmusicsync", "LethalMusicSync", "1.0.2")] public class LethalMusicSyncPlugin : BaseUnityPlugin { public const string MOD_GUID = "com.foxwood.lethalmusicsync"; public const string MOD_NAME = "LethalMusicSync"; public const string MOD_VERSION = "1.0.2"; internal static ManualLogSource LoggerInstance; private static LethalMusicSyncPlugin Instance; public static AudioSource CustomShipSpeaker; public static AudioSource CustomShipMusic; public static string CurrentPlayingUrl = ""; public static bool IsCustomBoomboxPlaying = false; public static AudioClip[] OriginalBoomboxClips; public static List<string> SpeakerQueue = new List<string>(); public static List<string> SpeakerQueueTitles = new List<string>(); public static int SpeakerQueueIndex = -1; public static string SpeakerLoopMode = "off"; public static List<string> MusicQueue = new List<string>(); public static List<string> MusicQueueTitles = new List<string>(); public static int MusicQueueIndex = -1; public static string MusicLoopMode = "off"; public static List<string> BoomboxQueue = new List<string>(); public static List<string> BoomboxQueueTitles = new List<string>(); public static int BoomboxQueueIndex = -1; public static string BoomboxLoopMode = "off"; public static bool IsProgrammaticActivation = false; public static bool IsSpeakerPaused = false; public static bool IsMusicPaused = false; public static bool IsBoomboxPaused = false; public static float SpeakerVolumeSetting = 0.8f; public static float MusicVolumeSetting = 0.6f; public static ConfigEntry<string> HudHotkey; public static ConfigEntry<string> MusicControlPermission; public static bool showBoomboxGui = false; private string guiUrlInput = ""; private float lastVolumeBroadcastTime = 0f; private int lastBroadcastedVolumePercent = -1; public static readonly Queue<string> pendingChatBroadcasts = new Queue<string>(); private void Awake() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown Instance = this; LoggerInstance = ((BaseUnityPlugin)this).Logger; LoggerInstance.LogInfo((object)"LethalMusicSync is loading..."); HudHotkey = ((BaseUnityPlugin)this).Config.Bind<string>("General", "HUDHotkey", "F8", "The hotkey used to toggle the Boombox HUD Console dashboard. Can be F8, F9, F10, F11, Tab, etc."); MusicControlPermission = ((BaseUnityPlugin)this).Config.Bind<string>("General", "MusicControlPermission", "everyone", "Who can control the music (speaker, music, boombox). Options: everyone, host."); Harmony val = new Harmony("com.foxwood.lethalmusicsync"); val.PatchAll(); LoggerInstance.LogInfo((object)"LethalMusicSync patches applied successfully!"); } public static Key ParseKey(string keyStr) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { return (Key)Enum.Parse(typeof(Key), keyStr, ignoreCase: true); } catch { LoggerInstance.LogWarning((object)("[LMS] Invalid HUDHotkey config value: '" + keyStr + "'. Falling back to F8.")); return (Key)101; } } public static bool CanLocalPlayerControl() { if (MusicControlPermission == null) { return true; } string text = MusicControlPermission.Value.ToLower(); if (text == "host" || text == "hostonly") { bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } return flag; } return true; } private void Update() { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) while (pendingChatBroadcasts.Count > 0) { string text = pendingChatBroadcasts.Dequeue(); if ((Object)(object)HUDManager.Instance != (Object)null) { try { HUDManager.Instance.AddTextToChatOnServer(text, -1); Log("[LMS] Safely sent GUI command: " + text); } catch (Exception ex) { LoggerInstance.LogError((object)("[LMS] Failed to send GUI command: " + ex.Message)); } } } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null && val.currentlyHeldObjectServer is BoomboxItem) { bool flag = false; try { string keyStr = ((HudHotkey != null && !string.IsNullOrEmpty(HudHotkey.Value)) ? HudHotkey.Value : "F8"); Key val2 = ParseKey(keyStr); if (Keyboard.current != null && ((ButtonControl)Keyboard.current[val2]).wasPressedThisFrame) { flag = true; } } catch (Exception ex) { LoggerInstance.LogError((object)("Error reading InputSystem keyboard: " + ex.Message)); } if (flag) { showBoomboxGui = !showBoomboxGui; if (showBoomboxGui) { Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; val.disableLookInput = true; } else { Cursor.visible = false; Cursor.lockState = (CursorLockMode)1; val.disableLookInput = false; } } } else if (showBoomboxGui) { showBoomboxGui = false; Cursor.visible = false; Cursor.lockState = (CursorLockMode)1; if ((Object)(object)val != (Object)null) { val.disableLookInput = false; } } if (!((Object)(object)val != (Object)null)) { return; } bool flag2 = false; if ((!val.isPlayerDead || !((Object)(object)val.spectatedPlayerScript != (Object)null)) ? val.isInsideFactory : val.spectatedPlayerScript.isInsideFactory) { if ((Object)(object)CustomShipSpeaker != (Object)null && CustomShipSpeaker.volume > 0f) { CustomShipSpeaker.volume = 0f; } if ((Object)(object)CustomShipMusic != (Object)null && CustomShipMusic.volume > 0f) { CustomShipMusic.volume = 0f; } } else { if ((Object)(object)CustomShipSpeaker != (Object)null && CustomShipSpeaker.volume != SpeakerVolumeSetting) { CustomShipSpeaker.volume = SpeakerVolumeSetting; } if ((Object)(object)CustomShipMusic != (Object)null && CustomShipMusic.volume != MusicVolumeSetting) { CustomShipMusic.volume = MusicVolumeSetting; } } } private void OnGUI() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Expected O, but got Unknown //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_03a6: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_0452: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Unknown result type (might be due to invalid IL or missing references) //IL_06ad: Unknown result type (might be due to invalid IL or missing references) //IL_0504: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_06eb: Unknown result type (might be due to invalid IL or missing references) //IL_079e: Unknown result type (might be due to invalid IL or missing references) //IL_07d0: Unknown result type (might be due to invalid IL or missing references) //IL_0808: Unknown result type (might be due to invalid IL or missing references) //IL_062c: Unknown result type (might be due to invalid IL or missing references) //IL_0632: Invalid comparison between Unknown and I4 //IL_08cb: Unknown result type (might be due to invalid IL or missing references) //IL_099f: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val == (Object)null) { return; } GrabbableObject currentlyHeldObjectServer = val.currentlyHeldObjectServer; BoomboxItem val2 = (BoomboxItem)(object)((currentlyHeldObjectServer is BoomboxItem) ? currentlyHeldObjectServer : null); if ((Object)(object)val2 == (Object)null) { return; } GUIStyle val3 = new GUIStyle(GUI.skin.label); val3.normal.textColor = Color.cyan; val3.fontSize = 13; val3.fontStyle = (FontStyle)1; GUI.backgroundColor = new Color(0f, 0.1f, 0.15f, 0.85f); GUI.Box(new Rect(15f, 15f, 250f, 40f), ""); string text = ((HudHotkey != null && !string.IsNullOrEmpty(HudHotkey.Value)) ? HudHotkey.Value : "F8"); GUI.Label(new Rect(25f, 23f, 230f, 25f), "\ud83d\udcfb [LMS HUD] Press " + text + " to Toggle Dashboard", val3); if (!showBoomboxGui) { return; } Rect val4 = default(Rect); ((Rect)(ref val4))..ctor((float)Screen.width / 2f - 200f, (float)Screen.height / 2f - 190f, 400f, 380f); GUI.backgroundColor = new Color(0.08f, 0.12f, 0.08f, 0.98f); GUI.Box(val4, ""); GUIStyle val5 = new GUIStyle(GUI.skin.label); val5.alignment = (TextAnchor)4; val5.fontSize = 15; val5.fontStyle = (FontStyle)1; val5.normal.textColor = Color.green; GUI.Label(new Rect(((Rect)(ref val4)).x, ((Rect)(ref val4)).y + 12f, ((Rect)(ref val4)).width, 25f), "=== \ud83d\udcfb LMS BOOMBOX CONSOLE ===", val5); string text2 = "Idle (Queue Empty)"; if (BoomboxQueueIndex >= 0 && BoomboxQueueIndex < BoomboxQueueTitles.Count) { text2 = BoomboxQueueTitles[BoomboxQueueIndex]; } GUIStyle val6 = new GUIStyle(GUI.skin.label); val6.alignment = (TextAnchor)4; val6.normal.textColor = Color.white; val6.fontSize = 12; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, ((Rect)(ref val4)).y + 45f, ((Rect)(ref val4)).width - 40f, 20f), "Playing: " + text2, val6); GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, ((Rect)(ref val4)).y + 65f, ((Rect)(ref val4)).width - 40f, 20f), "Loop Mode: " + BoomboxLoopMode.ToUpper(), val6); float num = ((Rect)(ref val4)).y + 95f; float num2 = 110f; float num3 = 30f; bool flag = CanLocalPlayerControl(); if (!flag) { GUIStyle val7 = new GUIStyle(GUI.skin.label); val7.alignment = (TextAnchor)4; val7.normal.textColor = Color.red; val7.fontSize = 11; val7.fontStyle = (FontStyle)1; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, ((Rect)(ref val4)).y + 35f, ((Rect)(ref val4)).width - 40f, 15f), "⚠\ufe0f LOBBY OWNER ONLY PERMISSIONS ENABLED", val7); } GUI.enabled = flag; string text3 = (val2.isPlayingMusic ? "PAUSE" : "RESUME"); if (GUI.Button(new Rect(((Rect)(ref val4)).x + 20f, num, num2, num3), text3)) { if (val2.isPlayingMusic) { string item = "[LMS_SYNC] BOOMBOX PAUSE"; pendingChatBroadcasts.Enqueue(item); } else { string item = "[LMS_SYNC] BOOMBOX RESUME"; pendingChatBroadcasts.Enqueue(item); } } if (GUI.Button(new Rect(((Rect)(ref val4)).x + 145f, num, num2, num3), "SKIP")) { string item = "[LMS_SYNC] BOOMBOX SKIP"; pendingChatBroadcasts.Enqueue(item); } if (GUI.Button(new Rect(((Rect)(ref val4)).x + 270f, num, num2, num3), "CLEAR")) { string item = "[LMS_SYNC] BOOMBOX CLEAR"; pendingChatBroadcasts.Enqueue(item); } GUI.enabled = true; float num4 = num + 45f; if ((Object)(object)val2.boomboxAudio != (Object)null) { GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num4, 120f, 20f), "Volume: " + Mathf.RoundToInt(val2.boomboxAudio.volume * 100f) + "%", val6); float num5 = GUI.HorizontalSlider(new Rect(((Rect)(ref val4)).x + 140f, num4 + 5f, 240f, 20f), val2.boomboxAudio.volume, 0f, 1f); int num6 = Mathf.RoundToInt(num5 * 100f); if (Mathf.Abs(num5 - val2.boomboxAudio.volume) > 0.01f) { val2.boomboxAudio.volume = num5; if (flag && num6 != lastBroadcastedVolumePercent && Time.time - lastVolumeBroadcastTime > 0.25f) { lastVolumeBroadcastTime = Time.time; lastBroadcastedVolumePercent = num6; string item = "[LMS_SYNC] BOOMBOX VOLUME " + num6; pendingChatBroadcasts.Enqueue(item); } } if (Event.current != null && (int)Event.current.type == 1 && flag && num6 != lastBroadcastedVolumePercent) { lastVolumeBroadcastTime = Time.time; lastBroadcastedVolumePercent = num6; string item = "[LMS_SYNC] BOOMBOX VOLUME " + num6; pendingChatBroadcasts.Enqueue(item); } } else { GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num4, 360f, 20f), "Volume: [Audio Source Missing]", val6); } GUI.enabled = flag; float num7 = num4 + 30f; if (GUI.Button(new Rect(((Rect)(ref val4)).x + 20f, num7, 360f, 30f), "CYCLE LOOP (Current: " + BoomboxLoopMode.ToUpper() + ")")) { string text4 = "off"; if (BoomboxLoopMode == "off") { text4 = "track"; } else if (BoomboxLoopMode == "track") { text4 = "queue"; } string item = "[LMS_SYNC] BOOMBOX LOOP " + text4; pendingChatBroadcasts.Enqueue(item); } float num8 = num7 + 45f; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num8, 360f, 20f), "Paste Link or type YouTube Search query:", val6); guiUrlInput = GUI.TextField(new Rect(((Rect)(ref val4)).x + 20f, num8 + 20f, 360f, 25f), guiUrlInput); float num9 = num8 + 55f; if (GUI.Button(new Rect(((Rect)(ref val4)).x + 20f, num9, 170f, 35f), "PLAY NOW") && !string.IsNullOrEmpty(guiUrlInput)) { string text5 = guiUrlInput.Trim(); if (!text5.StartsWith("http://") && !text5.StartsWith("https://")) { SearchAndQueueYoutube(text5, "BOOMBOX", "PLAY", viaGui: true); } else { string item = "[LMS_SYNC] BOOMBOX PLAY " + text5; pendingChatBroadcasts.Enqueue(item); } guiUrlInput = ""; } if (GUI.Button(new Rect(((Rect)(ref val4)).x + 210f, num9, 170f, 35f), "ADD TO QUEUE") && !string.IsNullOrEmpty(guiUrlInput)) { string text5 = guiUrlInput.Trim(); if (!text5.StartsWith("http://") && !text5.StartsWith("https://")) { SearchAndQueueYoutube(text5, "BOOMBOX", "QUEUE", viaGui: true); } else { string item = "[LMS_SYNC] BOOMBOX QUEUE " + text5; pendingChatBroadcasts.Enqueue(item); } guiUrlInput = ""; } GUI.enabled = true; float num10 = num9 + 45f; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num10, 360f, 20f), "Total Tracks in Queue: " + BoomboxQueue.Count, val6); } public static void Log(string message) { LoggerInstance.LogInfo((object)message); } public static string ResolveSunoUrl(string inputUrl) { if (string.IsNullOrEmpty(inputUrl)) { return ""; } if (inputUrl.Contains("cdn1.suno.ai") && inputUrl.EndsWith(".mp3")) { return inputUrl; } Match match = Regex.Match(inputUrl, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (match.Success) { string value = match.Groups[1].Value; string text = "https://cdn1.suno.ai/" + value + ".mp3"; Log("Resolved Suno AI URL: " + inputUrl + " -> " + text); return text; } return inputUrl; } public static AudioSource GetOrCreateShipAudioSource(string device) { //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Expected O, but got Unknown //IL_0324: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.Find("HangarShip"); if ((Object)(object)val == (Object)null) { val = GameObject.Find("HangarShip(Clone)"); } if ((Object)(object)val == (Object)null && (Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.shipAnimatorObject != (Object)null) { val = ((Component)StartOfRound.Instance.shipAnimatorObject).gameObject; } if ((Object)(object)val == (Object)null) { Log("HangarShip not found in scene. Cannot spawn audio source."); return null; } PlayerControllerB val2 = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); bool flag = false; if ((Object)(object)val2 != (Object)null) { flag = ((!val2.isPlayerDead || !((Object)(object)val2.spectatedPlayerScript != (Object)null)) ? val2.isInsideFactory : val2.spectatedPlayerScript.isInsideFactory); } if (device == "SPEAKER") { if ((Object)(object)CustomShipSpeaker != (Object)null) { return CustomShipSpeaker; } GameObject val3 = new GameObject("LethalMusicSyncSpeaker3D"); val3.transform.SetParent(val.transform); val3.transform.localPosition = new Vector3(0f, 2.2f, -1f); AudioSource val4 = val3.AddComponent<AudioSource>(); val4.playOnAwake = false; val4.spatialBlend = 1f; val4.rolloffMode = (AudioRolloffMode)0; val4.minDistance = 3f; val4.maxDistance = 18f; val4.volume = (flag ? 0f : SpeakerVolumeSetting); AudioSource componentInChildren = val.GetComponentInChildren<AudioSource>(); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = componentInChildren.outputAudioMixerGroup; Log("[LMS] Routed Speaker through Ship AudioMixerGroup: " + ((Object)componentInChildren.outputAudioMixerGroup).name); } else { AudioSource[] array = Object.FindObjectsOfType<AudioSource>(); AudioSource[] array2 = array; foreach (AudioSource val5 in array2) { if ((Object)(object)val5 != (Object)null && (Object)(object)val5.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = val5.outputAudioMixerGroup; Log("[LMS] Routed Speaker through Fallback AudioMixerGroup: " + ((Object)val5.outputAudioMixerGroup).name); break; } } } CustomShipSpeaker = val4; Log("Custom 3D Spatial Hangar Ship Speaker spawned successfully!"); return CustomShipSpeaker; } if (device == "MUSIC") { if ((Object)(object)CustomShipMusic != (Object)null) { return CustomShipMusic; } GameObject val6 = new GameObject("LethalMusicSyncMusic2D"); val6.transform.SetParent(val.transform); val6.transform.localPosition = Vector3.zero; AudioSource val4 = val6.AddComponent<AudioSource>(); val4.playOnAwake = false; val4.spatialBlend = 0f; val4.volume = (flag ? 0f : MusicVolumeSetting); AudioSource componentInChildren = val.GetComponentInChildren<AudioSource>(); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = componentInChildren.outputAudioMixerGroup; Log("[LMS] Routed Music through Ship AudioMixerGroup: " + ((Object)componentInChildren.outputAudioMixerGroup).name); } else { AudioSource[] array = Object.FindObjectsOfType<AudioSource>(); AudioSource[] array2 = array; foreach (AudioSource val5 in array2) { if ((Object)(object)val5 != (Object)null && (Object)(object)val5.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = val5.outputAudioMixerGroup; Log("[LMS] Routed Music through Fallback AudioMixerGroup: " + ((Object)val5.outputAudioMixerGroup).name); break; } } } CustomShipMusic = val4; Log("Custom 2D Global Ship Background Music spawned successfully!"); return CustomShipMusic; } return null; } public static void PlayAudioFromUrl(AudioSource source, string url, string device, Action<AudioClip> onSuccess = null) { if (!((Object)(object)source == (Object)null) && !string.IsNullOrEmpty(url)) { ((MonoBehaviour)Instance).StartCoroutine(StreamAudioCoroutine(source, url, device, onSuccess)); } } private static IEnumerator StreamAudioCoroutine(AudioSource source, string url, string device, Action<AudioClip> onSuccess) { switch (device) { case "SPEAKER": IsSpeakerPaused = false; break; case "MUSIC": IsMusicPaused = false; break; case "BOOMBOX": IsBoomboxPaused = false; break; } string targetUrl = url; if (url.Contains("youtube.com") || url.Contains("youtu.be")) { Log("[LMS] Resolving YouTube URL via Cobalt API..."); string requestJson = "{\"url\":\"" + url + "\",\"downloadMode\":\"audio\"}"; byte[] requestBytes = Encoding.UTF8.GetBytes(requestJson); string[] cobaltEndpoints = new string[2] { "https://apicobalt.mgytr.top/", "https://api.cobalt.tools/" }; bool resolvedSuccessfully = false; try { string[] array = cobaltEndpoints; foreach (string endpoint in array) { Log("[LMS] Sending request to Cobalt endpoint: " + endpoint); UnityWebRequest resolveRequest = new UnityWebRequest(endpoint, "POST"); try { resolveRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(requestBytes); resolveRequest.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); resolveRequest.SetRequestHeader("Content-Type", "application/json"); resolveRequest.SetRequestHeader("Accept", "application/json"); yield return resolveRequest.SendWebRequest(); if ((int)resolveRequest.result == 2 || (int)resolveRequest.result == 3) { Log("[LMS] Cobalt endpoint error (" + endpoint + "): " + resolveRequest.error); if (resolveRequest.downloadHandler != null && !string.IsNullOrEmpty(resolveRequest.downloadHandler.text)) { Log("[LMS] Server response details: " + resolveRequest.downloadHandler.text); } continue; } string responseText = resolveRequest.downloadHandler.text; if (responseText.Contains("error.api.auth")) { Log("[LMS] Cobalt endpoint (" + endpoint + ") failed due to auth lock. Response: " + responseText); continue; } Match match = Regex.Match(responseText, "\"url\":\"(.*?)\""); if (match.Success) { targetUrl = match.Groups[1].Value; Log("[LMS] Successfully resolved YouTube direct stream URL via " + endpoint); Match match2 = Regex.Match(responseText, "\"title\":\"(.*?)\""); if (match2.Success) { string value = match2.Groups[1].Value; UpdateQueueTitle(url, value, device); } resolvedSuccessfully = true; break; } Log("[LMS] Cobalt response parsing failed for endpoint " + endpoint + ". Response: " + responseText); } finally { ((IDisposable)resolveRequest)?.Dispose(); } } } finally { } if (!resolvedSuccessfully) { Log("[LMS] Failed to resolve YouTube URL through any Cobalt endpoints. Aborting stream."); yield break; } } else if (url.Contains("suno.com/playlist") || url.Contains("suno.com/p/")) { Log("[LMS] Suno Playlist URL detected. Fetching playlist page to extract tracks..."); string playlistId = ""; Match idMatch = Regex.Match(url, "/(?:playlist|p)/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (idMatch.Success) { playlistId = idMatch.Groups[1].Value.ToLower(); } UnityWebRequest pageRequest = UnityWebRequest.Get(url); try { pageRequest.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); pageRequest.SetRequestHeader("Referer", "https://suno.com/"); yield return pageRequest.SendWebRequest(); if ((int)pageRequest.result == 2 || (int)pageRequest.result == 3) { Log("[LMS] Failed to fetch Suno playlist page: " + pageRequest.error); } else { string text = pageRequest.downloadHandler.text; string text2 = ""; try { int num = text.IndexOf("playlist_clips"); if (num != -1) { int num2 = text.LastIndexOf("<script>", num); int num3 = text.IndexOf("</script>", num); if (num2 != -1 && num3 != -1) { string input = text.Substring(num2, num3 - num2); Match match3 = Regex.Match(input, "self\\.__next_f\\.push\\(\\[1,\\s*\"(.*?)\"\\s*\\]\\)", RegexOptions.Singleline); if (match3.Success) { string value2 = match3.Groups[1].Value; string text3 = JsonConvert.DeserializeObject<string>("\"" + value2 + "\""); int num4 = text3.IndexOf("["); if (num4 != -1) { JToken token = JToken.Parse(text3.Substring(num4)); JToken val = FindPlaylistClips(token); if (val != null && (int)val.Type == 2 && val.HasValues) { JToken val2 = val[(object)0][(object)"clip"]; if (val2 != null) { JToken val3 = val2[(object)"id"]; if (val3 != null) { text2 = ((object)val3).ToString(); } } } } } } } } catch { } if (string.IsNullOrEmpty(text2)) { MatchCollection matchCollection = Regex.Matches(text, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); foreach (Match item in matchCollection) { string text4 = item.Value.ToLower(); if (text4 != playlistId && text4 != "019c23e4-1dd3-7d05-8d4f-341d10bb7c55") { text2 = text4; break; } } } if (!string.IsNullOrEmpty(text2)) { targetUrl = "https://cdn1.suno.ai/" + text2 + ".mp3"; Log("[LMS] Successfully resolved Suno Playlist to track direct URL: " + targetUrl); } else { Log("[LMS] Could not find any song UUIDs in Suno playlist page."); } } } finally { ((IDisposable)pageRequest)?.Dispose(); } } else if (url.Contains("suno.com/song") || url.Contains("suno.com/track")) { Match match5 = Regex.Match(url, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (match5.Success) { string value3 = match5.Groups[1].Value; targetUrl = "https://cdn1.suno.ai/" + value3 + ".mp3"; Log("[LMS] Resolved Suno song to direct URL: " + targetUrl); } } else { targetUrl = ResolveSunoUrl(url); } Log("[LMS] Starting stream of URL: " + targetUrl); UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(targetUrl, (AudioType)13); try { if (targetUrl.Contains("suno.ai") || targetUrl.Contains("suno.com")) { www.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); www.SetRequestHeader("Referer", "https://suno.com/"); } yield return www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { Log("[LMS] Stream Error: " + www.error); yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(www); if ((Object)(object)content != (Object)null) { source.clip = content; source.loop = false; source.Play(); Log("[LMS] Playing audio track successfully!"); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { string text5 = "Audio Stream"; if (device == "SPEAKER" && SpeakerQueueIndex >= 0 && SpeakerQueueIndex < SpeakerQueueTitles.Count) { text5 = SpeakerQueueTitles[SpeakerQueueIndex]; } else if (device == "MUSIC" && MusicQueueIndex >= 0 && MusicQueueIndex < MusicQueueTitles.Count) { text5 = MusicQueueTitles[MusicQueueIndex]; } else if (device == "BOOMBOX" && BoomboxQueueIndex >= 0 && BoomboxQueueIndex < BoomboxQueueTitles.Count) { text5 = BoomboxQueueTitles[BoomboxQueueIndex]; } HUDManager.Instance.AddTextToChatOnServer("<color=cyan>[LMS] ▶ Now Playing: " + text5 + " (" + device + ")</color>", -1); } ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, content, device)); onSuccess?.Invoke(content); } else { Log("[LMS] Download succeeded, but clip is null."); } } finally { ((IDisposable)www)?.Dispose(); } } private static JToken FindPlaylistClips(JToken token) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (token == null) { return null; } if ((int)token.Type == 1) { JObject val = (JObject)token; if (val.ContainsKey("playlist_clips")) { return val["playlist_clips"]; } foreach (KeyValuePair<string, JToken> item in val) { JToken val2 = FindPlaylistClips(item.Value); if (val2 != null) { return val2; } } } else if ((int)token.Type == 2) { foreach (JToken item2 in (IEnumerable<JToken>)token) { JToken val2 = FindPlaylistClips(item2); if (val2 != null) { return val2; } } } return null; } public static void SearchAndQueueYoutube(string query, string device, string action, bool viaGui) { ((MonoBehaviour)Instance).StartCoroutine(SearchYoutubeCoroutine(device, action, query, viaGui)); } private static IEnumerator SearchYoutubeCoroutine(string device, string action, string query, bool viaGui) { string escapedQuery = Uri.EscapeDataString(query); string resolvedUrl = null; string resolvedTitle = null; string ytSearchUrl = "https://www.youtube.com/results?search_query=" + escapedQuery; Log("[LMS] Searching YouTube directly via: " + ytSearchUrl); UnityWebRequest ytRequest = UnityWebRequest.Get(ytSearchUrl); try { ytRequest.timeout = 6; ytRequest.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); yield return ytRequest.SendWebRequest(); if ((int)ytRequest.result == 1) { string text = ytRequest.downloadHandler.text; Match match = Regex.Match(text, "\\\"videoRenderer\\\":\\{\\\"videoId\\\":\\\"([a-zA-Z0-9_-]{11})\\\""); if (match.Success) { resolvedUrl = "https://www.youtube.com/watch?v=" + match.Groups[1].Value; Match match2 = Regex.Match(text, "\\\"videoRenderer\\\":\\{\\\"videoId\\\":\\\"([a-zA-Z0-9_-]{11})\\\".*?\\\"title\\\":\\{\\\"runs\\\":\\[\\{\\\"text\\\":\\\"([^\\\"]+)\\\""); if (match2.Success && match2.Groups[1].Value == match.Groups[1].Value) { resolvedTitle = match2.Groups[2].Value; try { resolvedTitle = Regex.Unescape(resolvedTitle); } catch { } } else { resolvedTitle = query; } Log("[LMS] Direct YouTube search succeeded!"); } } } finally { ((IDisposable)ytRequest)?.Dispose(); } if (string.IsNullOrEmpty(resolvedUrl)) { string[] instances = new string[6] { "https://inv.nadeko.net", "https://invidious.nerdvpn.de", "https://iv.melmac.space", "https://invidious.no-logs.com", "https://invidious.flokinet.to", "https://yewtu.be" }; try { string[] array = instances; foreach (string instance in array) { string searchUrl = instance + "/api/v1/search?q=" + escapedQuery; Log("[LMS] Direct search failed. Trying Invidious fallback: " + searchUrl); UnityWebRequest request = UnityWebRequest.Get(searchUrl); try { request.timeout = 5; yield return request.SendWebRequest(); if ((int)request.result == 1) { try { string text2 = request.downloadHandler.text; JArray val = JArray.Parse(text2); foreach (JToken item in val) { if (item[(object)"type"] != null && ((object)item[(object)"type"]).ToString() == "video") { string text3 = ((object)item[(object)"videoId"]).ToString(); resolvedTitle = ((item[(object)"title"] != null) ? ((object)item[(object)"title"]).ToString() : query); resolvedUrl = "https://www.youtube.com/watch?v=" + text3; break; } } } catch (Exception ex) { Log("[LMS] Error parsing search JSON: " + ex.Message); } } } finally { ((IDisposable)request)?.Dispose(); } if (!string.IsNullOrEmpty(resolvedUrl)) { break; } } } finally { } } if (!string.IsNullOrEmpty(resolvedUrl)) { Log("[LMS] Search resolved to: " + resolvedUrl + " (" + resolvedTitle + ")"); if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=cyan>[LMS] \ud83d\udd0d Found: " + resolvedTitle + " (" + device.ToUpper() + ")</color>", -1); } string text4 = "[LMS_SYNC] " + device.ToUpper() + " " + action.ToUpper() + " " + resolvedUrl; if (viaGui) { pendingChatBroadcasts.Enqueue(text4); } else if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer(text4, -1); } } else { string text5 = "YouTube search failed for: \"" + query + "\""; Log("[LMS] " + text5); if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] Search failed. No video found for search query.", -1); } } } public static void ResolveAndQueueSunoPlaylist(string url, string device, bool clearQueue) { ((MonoBehaviour)Instance).StartCoroutine(ResolveSunoPlaylistCoroutine(url, device, clearQueue)); } private static IEnumerator ResolveSunoPlaylistCoroutine(string url, string device, bool clearQueue) { Log("[LMS] Scraping Suno Playlist tracks: " + url); string playlistId = ""; Match idMatch = Regex.Match(url, "/(?:playlist|p)/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (idMatch.Success) { playlistId = idMatch.Groups[1].Value.ToLower(); } UnityWebRequest pageRequest = UnityWebRequest.Get(url); try { pageRequest.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); pageRequest.SetRequestHeader("Referer", "https://suno.com/"); yield return pageRequest.SendWebRequest(); if ((int)pageRequest.result == 2 || (int)pageRequest.result == 3) { Log("[LMS] Failed to scrape playlist: " + pageRequest.error); yield break; } string text = pageRequest.downloadHandler.text; List<string> list = new List<string>(); List<string> list2 = new List<string>(); try { int num = text.IndexOf("playlist_clips"); if (num != -1) { int num2 = text.LastIndexOf("<script>", num); int num3 = text.IndexOf("</script>", num); if (num2 != -1 && num3 != -1) { string input = text.Substring(num2, num3 - num2); Match match = Regex.Match(input, "self\\.__next_f\\.push\\(\\[1,\\s*\"(.*?)\"\\s*\\]\\)", RegexOptions.Singleline); if (match.Success) { string value = match.Groups[1].Value; string text2 = JsonConvert.DeserializeObject<string>("\"" + value + "\""); int num4 = text2.IndexOf("["); if (num4 != -1) { JToken token = JToken.Parse(text2.Substring(num4)); JToken val = FindPlaylistClips(token); if (val != null && (int)val.Type == 2) { JArray val2 = (JArray)val; foreach (JToken item2 in val2) { JToken val3 = item2[(object)"clip"]; if (val3 == null) { continue; } string text3 = ((object)val3[(object)"id"])?.ToString(); string text4 = ((object)val3[(object)"title"])?.ToString(); if (!string.IsNullOrEmpty(text3)) { string item = "https://cdn1.suno.ai/" + text3 + ".mp3"; if (string.IsNullOrEmpty(text4)) { text4 = "Suno Track " + text3.Substring(0, 8); } if (!list.Contains(item)) { list.Add(item); list2.Add(text4); } } } Log("[LMS] Scraped " + list.Count + " tracks via Strategy 1 (RSC Parser) with real titles!"); } } } } } } catch (Exception ex) { Log("[LMS] Strategy 1 (RSC Parser) error: " + ex.Message + ". Falling back to Strategy 2..."); } if (list.Count == 0) { Log("[LMS] Strategy 1 failed or found no clips. Running Strategy 2 broad UUID scan..."); MatchCollection matchCollection = Regex.Matches(text, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); foreach (Match item3 in matchCollection) { string text5 = item3.Value.ToLower(); if (text5 != playlistId && text5 != "019c23e4-1dd3-7d05-8d4f-341d10bb7c55") { string item = "https://cdn1.suno.ai/" + text5 + ".mp3"; if (!list.Contains(item)) { list.Add(item); list2.Add("Suno Track " + text5.Substring(0, 8)); } } } Log("[LMS] Scraped " + list.Count + " tracks via Strategy 2 (Broad UUID Scan)."); } if (list.Count <= 0) { yield break; } switch (device) { case "SPEAKER": { if (clearQueue) { SpeakerQueue.Clear(); SpeakerQueueTitles.Clear(); SpeakerQueueIndex = 0; } int count = SpeakerQueue.Count; for (int i = 0; i < list.Count; i++) { SpeakerQueue.Add(list[i]); SpeakerQueueTitles.Add(list2[i]); } Log("[LMS] Queued " + list.Count + " tracks for SPEAKER."); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=yellow>[LMS] ➕ Queued Playlist: Scraped " + list.Count + " Suno tracks (SPEAKER)</color>", -1); } if (clearQueue || SpeakerQueueIndex == -1) { SpeakerQueueIndex = count; PlayAudioFromUrl(GetOrCreateShipAudioSource("SPEAKER"), SpeakerQueue[SpeakerQueueIndex], "SPEAKER"); } break; } case "MUSIC": { if (clearQueue) { MusicQueue.Clear(); MusicQueueTitles.Clear(); MusicQueueIndex = 0; } int count = MusicQueue.Count; for (int i = 0; i < list.Count; i++) { MusicQueue.Add(list[i]); MusicQueueTitles.Add(list2[i]); } Log("[LMS] Queued " + list.Count + " tracks for MUSIC."); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=yellow>[LMS] ➕ Queued Playlist: Scraped " + list.Count + " Suno tracks (MUSIC)</color>", -1); } if (clearQueue || MusicQueueIndex == -1) { MusicQueueIndex = count; PlayAudioFromUrl(GetOrCreateShipAudioSource("MUSIC"), MusicQueue[MusicQueueIndex], "MUSIC"); } break; } case "BOOMBOX": { BoomboxItem nearest = ChatSyncPatch.GetNearestBoombox(); if (!((Object)(object)nearest != (Object)null)) { break; } if (clearQueue) { BoomboxQueue.Clear(); BoomboxQueueTitles.Clear(); BoomboxQueueIndex = 0; } int count = BoomboxQueue.Count; for (int i = 0; i < list.Count; i++) { BoomboxQueue.Add(list[i]); BoomboxQueueTitles.Add(list2[i]); } Log("[LMS] Queued " + list.Count + " tracks for BOOMBOX."); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=yellow>[LMS] ➕ Queued Playlist: Scraped " + list.Count + " Suno tracks (BOOMBOX)</color>", -1); } if (!clearQueue && BoomboxQueueIndex != -1) { break; } BoomboxQueueIndex = count; PlayAudioFromUrl(nearest.boomboxAudio, BoomboxQueue[BoomboxQueueIndex], "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { IsProgrammaticActivation = false; } }); break; } } } finally { ((IDisposable)pageRequest)?.Dispose(); } } public static void QueueSingleTrack(string url, string device, bool clearQueue) { string text = url; if (url.Contains("youtube.com") || url.Contains("youtu.be")) { text = "YouTube Video"; } else if (url.Contains("suno.ai") || url.Contains("suno.com")) { Match match = Regex.Match(url, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); text = ((!match.Success) ? "Suno Track" : ("Suno Song " + match.Groups[1].Value.Substring(0, 8))); } else { text = "Audio Stream"; } switch (device) { case "SPEAKER": { if (clearQueue) { SpeakerQueue.Clear(); SpeakerQueueTitles.Clear(); } SpeakerQueue.Add(url); SpeakerQueueTitles.Add(text); if (clearQueue || SpeakerQueueIndex == -1) { SpeakerQueueIndex = SpeakerQueue.Count - 1; PlayAudioFromUrl(GetOrCreateShipAudioSource("SPEAKER"), url, "SPEAKER"); break; } bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=yellow>[LMS] ➕ Queued: " + text + " (SPEAKER)</color>", -1); } break; } case "MUSIC": { if (clearQueue) { MusicQueue.Clear(); MusicQueueTitles.Clear(); } MusicQueue.Add(url); MusicQueueTitles.Add(text); if (clearQueue || MusicQueueIndex == -1) { MusicQueueIndex = MusicQueue.Count - 1; PlayAudioFromUrl(GetOrCreateShipAudioSource("MUSIC"), url, "MUSIC"); break; } bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=yellow>[LMS] ➕ Queued: " + text + " (MUSIC)</color>", -1); } break; } case "BOOMBOX": { BoomboxItem nearest = ChatSyncPatch.GetNearestBoombox(); if (!((Object)(object)nearest != (Object)null)) { break; } if (clearQueue) { BoomboxQueue.Clear(); BoomboxQueueTitles.Clear(); } BoomboxQueue.Add(url); BoomboxQueueTitles.Add(text); if (clearQueue || BoomboxQueueIndex == -1) { BoomboxQueueIndex = BoomboxQueue.Count - 1; PlayAudioFromUrl(nearest.boomboxAudio, url, "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { IsProgrammaticActivation = false; } }); } else { bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=yellow>[LMS] ➕ Queued: " + text + " (BOOMBOX)</color>", -1); } } break; } } } private static void UpdateQueueTitle(string url, string newTitle, string device) { switch (device) { case "SPEAKER": { for (int i = 0; i < SpeakerQueue.Count; i++) { if (SpeakerQueue[i] == url) { SpeakerQueueTitles[i] = newTitle; } } break; } case "MUSIC": { for (int i = 0; i < MusicQueue.Count; i++) { if (MusicQueue[i] == url) { MusicQueueTitles[i] = newTitle; } } break; } case "BOOMBOX": { for (int i = 0; i < BoomboxQueue.Count; i++) { if (BoomboxQueue[i] == url) { BoomboxQueueTitles[i] = newTitle; } } break; } } } private static IEnumerator TrackEndTrigger(AudioSource source, AudioClip clip, string device) { float startWaitTime = 0f; bool isPaused = false; while ((Object)(object)source != (Object)null && (Object)(object)source.clip == (Object)(object)clip && !source.isPlaying && startWaitTime < 3f) { switch (device) { case "SPEAKER": isPaused = IsSpeakerPaused; break; case "MUSIC": isPaused = IsMusicPaused; break; case "BOOMBOX": isPaused = IsBoomboxPaused; break; } if (isPaused) { break; } startWaitTime += 0.2f; yield return (object)new WaitForSeconds(0.2f); } while ((Object)(object)source != (Object)null && (Object)(object)source.clip == (Object)(object)clip) { switch (device) { case "SPEAKER": isPaused = IsSpeakerPaused; break; case "MUSIC": isPaused = IsMusicPaused; break; case "BOOMBOX": isPaused = IsBoomboxPaused; break; } if (source.isPlaying || isPaused) { yield return (object)new WaitForSeconds(1f); continue; } break; } if (!((Object)(object)source != (Object)null) || !((Object)(object)source.clip == (Object)(object)clip)) { yield break; } switch (device) { case "SPEAKER": isPaused = IsSpeakerPaused; break; case "MUSIC": isPaused = IsMusicPaused; break; case "BOOMBOX": isPaused = IsBoomboxPaused; break; } if (source.isPlaying || isPaused) { yield break; } if (device == "SPEAKER" && SpeakerQueueIndex != -1) { if (SpeakerLoopMode == "track") { source.Play(); ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, clip, device)); } else { Log("[LMS] Speaker track finished naturally. Advancing queue..."); PlayNextSpeakerTrack(); } } else if (device == "MUSIC" && MusicQueueIndex != -1) { if (MusicLoopMode == "track") { source.Play(); ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, clip, device)); } else { Log("[LMS] Music track finished naturally. Advancing queue..."); PlayNextMusicTrack(); } } else if (device == "BOOMBOX" && BoomboxQueueIndex != -1) { if (BoomboxLoopMode == "track") { source.Play(); ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, clip, device)); } else { Log("[LMS] Boombox track finished naturally. Advancing queue..."); PlayNextBoomboxTrack(); } } } public static void PlayNextSpeakerTrack() { if (SpeakerQueue.Count == 0) { return; } AudioSource orCreateShipAudioSource = GetOrCreateShipAudioSource("SPEAKER"); if ((Object)(object)orCreateShipAudioSource != (Object)null) { orCreateShipAudioSource.Stop(); orCreateShipAudioSource.clip = null; } SpeakerQueueIndex++; if (SpeakerQueueIndex >= SpeakerQueue.Count) { if (!(SpeakerLoopMode == "queue")) { SpeakerQueueIndex = -1; Log("[LMS] Speaker reached end of queue."); return; } SpeakerQueueIndex = 0; } string text = SpeakerQueue[SpeakerQueueIndex]; Log("[LMS] Playing next speaker track in queue (Index " + SpeakerQueueIndex + "): " + text); PlayAudioFromUrl(orCreateShipAudioSource, text, "SPEAKER"); } public static void PlayNextMusicTrack() { if (MusicQueue.Count == 0) { return; } AudioSource orCreateShipAudioSource = GetOrCreateShipAudioSource("MUSIC"); if ((Object)(object)orCreateShipAudioSource != (Object)null) { orCreateShipAudioSource.Stop(); orCreateShipAudioSource.clip = null; } MusicQueueIndex++; if (MusicQueueIndex >= MusicQueue.Count) { if (!(MusicLoopMode == "queue")) { MusicQueueIndex = -1; Log("[LMS] Global Music reached end of queue."); return; } MusicQueueIndex = 0; } string text = MusicQueue[MusicQueueIndex]; Log("[LMS] Playing next music track in queue (Index " + MusicQueueIndex + "): " + text); PlayAudioFromUrl(orCreateShipAudioSource, text, "MUSIC"); } public static void PlayNextBoomboxTrack() { BoomboxItem nearest = ChatSyncPatch.GetNearestBoombox(); if ((Object)(object)nearest == (Object)null || BoomboxQueue.Count == 0) { return; } if ((Object)(object)nearest.boomboxAudio != (Object)null) { nearest.boomboxAudio.Stop(); nearest.boomboxAudio.clip = null; } BoomboxQueueIndex++; if (BoomboxQueueIndex >= BoomboxQueue.Count) { if (!(BoomboxLoopMode == "queue")) { BoomboxQueueIndex = -1; ((GrabbableObject)nearest).isBeingUsed = false; nearest.isPlayingMusic = false; if (OriginalBoomboxClips != null) { nearest.musicAudios = OriginalBoomboxClips; } Log("[LMS] Boombox reached end of queue."); return; } BoomboxQueueIndex = 0; } string text = BoomboxQueue[BoomboxQueueIndex]; Log("[LMS] Playing next boombox track in queue (Index " + BoomboxQueueIndex + "): " + text); PlayAudioFromUrl(nearest.boomboxAudio, text, "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { IsProgrammaticActivation = false; } }); } public static string GetQueueDisplayString(string device) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("\n========================================="); stringBuilder.AppendLine(" LETHAL MUSIC SYNC QUEUE "); stringBuilder.AppendLine("=========================================\n"); List<string> list = ((device == "SPEAKER") ? SpeakerQueue : ((device == "MUSIC") ? MusicQueue : BoomboxQueue)); List<string> list2 = ((device == "SPEAKER") ? SpeakerQueueTitles : ((device == "MUSIC") ? MusicQueueTitles : BoomboxQueueTitles)); int num = ((device == "SPEAKER") ? SpeakerQueueIndex : ((device == "MUSIC") ? MusicQueueIndex : BoomboxQueueIndex)); string text = ((device == "SPEAKER") ? SpeakerLoopMode : ((device == "MUSIC") ? MusicLoopMode : BoomboxLoopMode)); stringBuilder.AppendLine("DEVICE: " + device); stringBuilder.AppendLine("LOOP MODE: " + text.ToUpper()); stringBuilder.AppendLine("TRACKS: " + list.Count + "\n"); if (list.Count == 0) { stringBuilder.AppendLine("[Queue is currently empty.]"); stringBuilder.AppendLine("Use: play [URL] or queue [URL] to add songs!"); } else { for (int i = 0; i < list.Count; i++) { string text2 = " "; string text3 = ""; if (i == num) { text2 = "> "; text3 = " [PLAYING]"; } stringBuilder.AppendLine(text2 + (i + 1) + ". " + list2[i] + text3); } } stringBuilder.AppendLine("\n=========================================\n"); return stringBuilder.ToString(); } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] public static class TerminalPatch { [HarmonyPrefix] public static bool Prefix(Terminal __instance, ref TerminalNode __result) { string text = ""; try { if ((Object)(object)__instance.screenText != (Object)null && !string.IsNullOrEmpty(__instance.screenText.text)) { string text2 = __instance.screenText.text; text = ((text2.Length < __instance.textAdded) ? text2.Trim() : text2.Substring(text2.Length - __instance.textAdded).Trim()); if (text.StartsWith(">")) { text = text.Substring(1).Trim(); } } } catch (Exception ex) { LethalMusicSyncPlugin.Log("Error getting terminal text: " + ex.Message); } if (string.IsNullOrEmpty(text)) { return true; } string text3 = text.Trim().ToLower(); if (text3.StartsWith("boombox ") || text3.StartsWith("speaker ") || text3.StartsWith("music ")) { __result = HandleTerminalCommand(__instance, text3); return false; } return true; } private static TerminalNode HandleTerminalCommand(Terminal terminal, string command) { TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>(); val.clearPreviousText = true; string[] array = command.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { val.displayText = "LethalMusicSync - Invalid Command Format.\nUse: boombox/speaker/music play [URL], queue [URL], skip, loop, list, clear, stop.\n\n"; return val; } string text = array[0]; string text2 = array[1]; switch (text2) { case "queue": if (array.Length < 3) { break; } goto default; default: if (!LethalMusicSyncPlugin.CanLocalPlayerControl()) { val.displayText = "\n=========================================\n LETHAL MUSIC SYNC (v1.0) \n=========================================\n\nDEVICE: " + text.ToUpper() + "\n[ERROR] ONLY THE HOST IS PERMITTED TO CONTROL MUSIC IN THIS LOBBY.\n=========================================\n\n"; return val; } break; case "list": case "qlist": break; } int result; if (text2 == "play" || text2 == "queue" || text2 == "q") { if (array.Length < 3) { val.displayText = "Usage: " + text + " " + text2 + " [URL/Search Query]\n\n"; return val; } string text3 = command.Substring(command.IndexOf(text2) + text2.Length).Trim(); if (!text3.StartsWith("http://") && !text3.StartsWith("https://")) { LethalMusicSyncPlugin.SearchAndQueueYoutube(text3, text, text2, viaGui: false); if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=orange>[LMS] \ud83d\udd0d Searching YouTube for: \"" + text3 + "\" (" + text.ToUpper() + ")...</color>", -1); } val.displayText = "\n=========================================\n LETHAL MUSIC SYNC (v1.0) \n=========================================\n\nDEVICE: " + text.ToUpper() + "\nACTION: " + text2.ToUpper() + "\nSEARCH: " + text3 + "\n\n[Searching YouTube asynchronously...]\n=========================================\n\n"; } else { string text4 = "[LMS_SYNC] " + text.ToUpper() + " " + text2.ToUpper() + " " + text3; HUDManager.Instance.AddTextToChatOnServer(text4, -1); string text5 = ((text2 == "play") ? "Initiating Stream..." : "Queueing Track..."); val.displayText = "\n=========================================\n LETHAL MUSIC SYNC (v1.0) \n=========================================\n\nDEVICE: " + text.ToUpper() + "\nACTION: " + text5 + "\nURL: " + text3 + "\n\n[Broadcasting network sync...]\n=========================================\n\n"; } } else if (text2 == "stop") { string text4 = "[LMS_SYNC] " + text.ToUpper() + " STOP"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Stopping stream...\n\n"; } else if (text2 == "skip" || text2 == "s") { if (array.Length >= 3 && array[2] == "to") { if (array.Length < 4) { val.displayText = "Usage: " + text + " skip to [index]\n\n"; return val; } string text6 = array[3]; if (!int.TryParse(text6, out result)) { val.displayText = "[ERROR] Index '" + text6 + "' must be a valid integer.\n\n"; return val; } int num = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerQueue.Count : ((text == "music") ? LethalMusicSyncPlugin.MusicQueue.Count : LethalMusicSyncPlugin.BoomboxQueue.Count)); if (result < 1 || result > num) { val.displayText = "[ERROR] Index " + text6 + " is out of bounds. The " + text.ToUpper() + " queue has " + num + " track(s).\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIPTO " + text6; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping to track index " + text6 + "...\n\n"; } else if (array.Length >= 3) { string text6 = array[2]; if (int.TryParse(text6, out result)) { int num = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerQueue.Count : ((text == "music") ? LethalMusicSyncPlugin.MusicQueue.Count : LethalMusicSyncPlugin.BoomboxQueue.Count)); if (result < 1 || result > num) { val.displayText = "[ERROR] Index " + text6 + " is out of bounds. The " + text.ToUpper() + " queue has " + num + " track(s).\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIPTO " + text6; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping to track index " + text6 + "...\n\n"; } else { string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIP"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping current track...\n\n"; } } else { string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIP"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping current track...\n\n"; } } else if (text2 == "skipto" || text2 == "goto") { if (array.Length < 3) { val.displayText = "Usage: " + text + " skipto [index]\n\n"; return val; } string text6 = array[2]; if (!int.TryParse(text6, out result)) { val.displayText = "[ERROR] Index '" + text6 + "' must be a valid integer.\n\n"; return val; } int num = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerQueue.Count : ((text == "music") ? LethalMusicSyncPlugin.MusicQueue.Count : LethalMusicSyncPlugin.BoomboxQueue.Count)); if (result < 1 || result > num) { val.displayText = "[ERROR] Index " + text6 + " is out of bounds. The " + text.ToUpper() + " queue has " + num + " track(s).\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIPTO " + text6; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping to track index " + text6 + "...\n\n"; } else { switch (text2) { case "loop": { string text8 = "off"; if (array.Length >= 3) { text8 = array[2].ToLower(); } else { string text9 = "off"; text9 = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerLoopMode : ((!(text == "music")) ? LethalMusicSyncPlugin.BoomboxLoopMode : LethalMusicSyncPlugin.MusicLoopMode)); text8 = ((text9 == "off") ? "track" : ((!(text9 == "track")) ? "off" : "queue")); } if (text8 != "off" && text8 != "track" && text8 != "queue") { val.displayText = "Invalid loop mode. Choose: track, queue, off.\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " LOOP " + text8; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Setting loop mode to '" + text8.ToUpper() + "'...\n\n"; break; } case "clear": { string text4 = "[LMS_SYNC] " + text.ToUpper() + " CLEAR"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Clearing current queue...\n\n"; break; } default: if (!(text2 == "queue")) { if (text2 == "volume" || text2 == "vol") { if (array.Length < 3) { val.displayText = "Usage: " + text + " volume [0-100]\n\n"; return val; } string text7 = array[2]; string text4 = "[LMS_SYNC] " + text.ToUpper() + " VOLUME " + text7; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Adjusting volume to " + text7 + "%\n\n"; } else { val.displayText = "Unknown command. Options: play, queue, list, skip, skipto, loop, clear, stop, volume.\n\n"; } break; } goto case "list"; case "list": case "qlist": val.displayText = LethalMusicSyncPlugin.GetQueueDisplayString(text.ToUpper()); break; } } return val; } } [HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")] public static class ChatSyncPatch { [HarmonyPrefix] public static bool Prefix(ref string chatMessage, int playerId) { if (string.IsNullOrEmpty(chatMessage)) { return true; } if (chatMessage.StartsWith("[LMS_SYNC]")) { if (LethalMusicSyncPlugin.MusicControlPermission != null && LethalMusicSyncPlugin.MusicControlPermission.Value.ToLower() == "host" && playerId != 0 && playerId != -1) { LethalMusicSyncPlugin.Log("[LMS] Ignored sync command from non-host player (ID: " + playerId + "). Permissions locked to Host."); return false; } try { string cmd = chatMessage.Substring(10).Trim(); ExecuteSyncCommand(cmd); } catch (Exception ex) { LethalMusicSyncPlugin.Log("Error executing sync command: " + ex.Message); } return false; } return true; } private static void ExecuteSyncCommand(string cmd) { string[] array = cmd.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { return; } string text = array[0]; string text2 = array[1]; if (text2 == "PLAY" || text2 == "QUEUE") { if (array.Length < 3) { return; } string text3 = cmd.Substring(cmd.IndexOf(text2) + text2.Length).Trim(); bool clearQueue = text2 == "PLAY"; if (text == "BOOMBOX") { LethalMusicSyncPlugin.IsCustomBoomboxPlaying = true; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null && LethalMusicSyncPlugin.OriginalBoomboxClips == null) { LethalMusicSyncPlugin.OriginalBoomboxClips = nearestBoombox.musicAudios; } } if (text3.Contains("suno.com/playlist") || text3.Contains("suno.com/p/")) { LethalMusicSyncPlugin.ResolveAndQueueSunoPlaylist(text3, text, clearQueue); } else { LethalMusicSyncPlugin.QueueSingleTrack(text3, text, clearQueue); } return; } switch (text2) { case "STOP": { bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=red>[LMS] \ud83d\uded1 Stopping playback (" + text + ")...</color>", -1); } switch (text) { case "SPEAKER": { AudioSource orCreateShipAudioSource = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("SPEAKER"); if ((Object)(object)orCreateShipAudioSource != (Object)null) { orCreateShipAudioSource.Stop(); } LethalMusicSyncPlugin.SpeakerQueueIndex = -1; break; } case "MUSIC": { AudioSource orCreateShipAudioSource2 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("MUSIC"); if ((Object)(object)orCreateShipAudioSource2 != (Object)null) { orCreateShipAudioSource2.Stop(); } LethalMusicSyncPlugin.MusicQueueIndex = -1; break; } case "BOOMBOX": { LethalMusicSyncPlugin.IsCustomBoomboxPlaying = false; LethalMusicSyncPlugin.IsBoomboxPaused = false; LethalMusicSyncPlugin.BoomboxQueueIndex = -1; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { ((GrabbableObject)nearestBoombox).isBeingUsed = false; nearestBoombox.isPlayingMusic = false; if ((Object)(object)nearestBoombox.boomboxAudio != (Object)null) { nearestBoombox.boomboxAudio.Stop(); } LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearestBoombox).ItemActivate(false, false); } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } if (LethalMusicSyncPlugin.OriginalBoomboxClips != null) { nearestBoombox.musicAudios = LethalMusicSyncPlugin.OriginalBoomboxClips; } } break; } } break; } case "PAUSE": if (text == "BOOMBOX") { BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { nearestBoombox.isPlayingMusic = false; nearestBoombox.boomboxAudio.Pause(); LethalMusicSyncPlugin.IsCustomBoomboxPlaying = false; LethalMusicSyncPlugin.IsBoomboxPaused = true; } } break; case "RESUME": { if (!(text == "BOOMBOX")) { break; } BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { nearestBoombox.isPlayingMusic = true; nearestBoombox.boomboxAudio.UnPause(); LethalMusicSyncPlugin.IsCustomBoomboxPlaying = true; LethalMusicSyncPlugin.IsBoomboxPaused = false; LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearestBoombox).ItemActivate(true, true); break; } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } } break; } case "SKIP": { bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=cyan>[LMS] ⏭\ufe0f Skipping current track (" + text + ")...</color>", -1); } switch (text) { case "SPEAKER": LethalMusicSyncPlugin.PlayNextSpeakerTrack(); break; case "MUSIC": LethalMusicSyncPlugin.PlayNextMusicTrack(); break; case "BOOMBOX": LethalMusicSyncPlugin.PlayNextBoomboxTrack(); break; } break; } case "SKIPTO": { if (array.Length < 3 || !int.TryParse(array[2], out var result2)) { break; } bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=cyan>[LMS] ⏭\ufe0f Skipping to track #" + result2 + " (" + text + ")...</color>", -1); } result2--; switch (text) { case "SPEAKER": if (result2 >= 0 && result2 < LethalMusicSyncPlugin.SpeakerQueue.Count) { LethalMusicSyncPlugin.Log("[LMS] Execute SKIPTO for SPEAKER index: " + (result2 + 1)); LethalMusicSyncPlugin.SpeakerQueueIndex = result2; AudioSource orCreateShipAudioSource3 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("SPEAKER"); if ((Object)(object)orCreateShipAudioSource3 != (Object)null) { orCreateShipAudioSource3.Stop(); orCreateShipAudioSource3.clip = null; } LethalMusicSyncPlugin.PlayAudioFromUrl(orCreateShipAudioSource3, LethalMusicSyncPlugin.SpeakerQueue[LethalMusicSyncPlugin.SpeakerQueueIndex], "SPEAKER"); } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO SPEAKER failed: Index " + (result2 + 1) + " out of bounds (Count: " + LethalMusicSyncPlugin.SpeakerQueue.Count + ")"); } break; case "MUSIC": if (result2 >= 0 && result2 < LethalMusicSyncPlugin.MusicQueue.Count) { LethalMusicSyncPlugin.Log("[LMS] Execute SKIPTO for MUSIC index: " + (result2 + 1)); LethalMusicSyncPlugin.MusicQueueIndex = result2; AudioSource orCreateShipAudioSource3 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("MUSIC"); if ((Object)(object)orCreateShipAudioSource3 != (Object)null) { orCreateShipAudioSource3.Stop(); orCreateShipAudioSource3.clip = null; } LethalMusicSyncPlugin.PlayAudioFromUrl(orCreateShipAudioSource3, LethalMusicSyncPlugin.MusicQueue[LethalMusicSyncPlugin.MusicQueueIndex], "MUSIC"); } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO MUSIC failed: Index " + (result2 + 1) + " out of bounds (Count: " + LethalMusicSyncPlugin.MusicQueue.Count + ")"); } break; case "BOOMBOX": if (result2 >= 0 && result2 < LethalMusicSyncPlugin.BoomboxQueue.Count) { LethalMusicSyncPlugin.Log("[LMS] Execute SKIPTO for BOOMBOX index: " + (result2 + 1)); LethalMusicSyncPlugin.BoomboxQueueIndex = result2; BoomboxItem nearest = GetNearestBoombox(); if ((Object)(object)nearest != (Object)null) { if ((Object)(object)nearest.boomboxAudio != (Object)null) { nearest.boomboxAudio.Stop(); nearest.boomboxAudio.clip = null; } LethalMusicSyncPlugin.PlayAudioFromUrl(nearest.boomboxAudio, LethalMusicSyncPlugin.BoomboxQueue[LethalMusicSyncPlugin.BoomboxQueueIndex], "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } }); } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO BOOMBOX failed: No boombox nearby found!"); } } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO BOOMBOX failed: Index " + (result2 + 1) + " out of bounds (Count: " + LethalMusicSyncPlugin.BoomboxQueue.Count + ")"); } break; } break; } case "LOOP": { if (array.Length < 3) { break; } string text4 = array[2].ToLower(); bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=pink>[LMS] \ud83d\udd01 Loop mode set to: " + text4.ToUpper() + " (" + text + ")</color>", -1); } switch (text) { case "SPEAKER": LethalMusicSyncPlugin.SpeakerLoopMode = text4; if ((Object)(object)LethalMusicSyncPlugin.CustomShipSpeaker != (Object)null) { LethalMusicSyncPlugin.CustomShipSpeaker.loop = text4 == "track"; } break; case "MUSIC": LethalMusicSyncPlugin.MusicLoopMode = text4; if ((Object)(object)LethalMusicSyncPlugin.CustomShipMusic != (Object)null) { LethalMusicSyncPlugin.CustomShipMusic.loop = text4 == "track"; } break; case "BOOMBOX": { LethalMusicSyncPlugin.BoomboxLoopMode = text4; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null && (Object)(object)nearestBoombox.boomboxAudio != (Object)null) { nearestBoombox.boomboxAudio.loop = text4 == "track"; } break; } } break; } case "CLEAR": { bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("<color=red>[LMS] \ud83e\uddf9 Queue cleared (" + text + ")...</color>", -1); } switch (text) { case "SPEAKER": LethalMusicSyncPlugin.SpeakerQueue.Clear(); LethalMusicSyncPlugin.SpeakerQueueTitles.Clear(); LethalMusicSyncPlugin.SpeakerQueueIndex = -1; if ((Object)(object)LethalMusicSyncPlugin.CustomShipSpeaker != (Object)null) { LethalMusicSyncPlugin.CustomShipSpeaker.Stop(); } break; case "MUSIC": LethalMusicSyncPlugin.MusicQueue.Clear(); LethalMusicSyncPlugin.MusicQueueTitles.Clear(); LethalMusicSyncPlugin.MusicQueueIndex = -1; if ((Object)(object)LethalMusicSyncPlugin.CustomShipMusic != (Object)null) { LethalMusicSyncPlugin.CustomShipMusic.Stop(); } break; case "BOOMBOX": { LethalMusicSyncPlugin.BoomboxQueue.Clear(); LethalMusicSyncPlugin.BoomboxQueueTitles.Clear(); LethalMusicSyncPlugin.IsBoomboxPaused = false; LethalMusicSyncPlugin.BoomboxQueueIndex = -1; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { ((GrabbableObject)nearestBoombox).isBeingUsed = false; nearestBoombox.isPlayingMusic = false; if ((Object)(object)nearestBoombox.boomboxAudio != (Object)null) { nearestBoombox.boomboxAudio.Stop(); } LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearestBoombox).ItemActivate(false, false); } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } if (LethalMusicSyncPlugin.OriginalBoomboxClips != null) { nearestBoombox.musicAudios = LethalMusicSyncPlugin.OriginalBoomboxClips; } } break; } } break; } case "VOLUME": { if (array.Length < 3 || !float.TryParse(array[2], out var result)) { break; } switch (text) { case "SPEAKER": { LethalMusicSyncPlugin.SpeakerVolumeSetting = result / 100f; AudioSource orCreateShipAudioSource = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("SPEAKER"); PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); bool flag = false; if ((Object)(object)val != (Object)null) { flag = ((!val.isPlayerDead || !((Object)(object)val.spectatedPlayerScript != (Object)null)) ? val.isInsideFactory : val.spectatedPlayerScript.isInsideFactory); } if ((Object)(object)orCreateShipAudioSource != (Object)null && !flag) { orCreateShipAudioSource.volume = LethalMusicSyncPlugin.SpeakerVolumeSetting; } break; } case "MUSIC": { LethalMusicSyncPlugin.MusicVolumeSetting = result / 100f; AudioSource orCreateShipAudioSource2 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("MUSIC"); PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); bool flag = false; if ((Object)(object)val != (Object)null) { flag = ((!val.isPlayerDead || !((Object)(object)val.spectatedPlayerScript != (Object)null)) ? val.isInsideFactory : val.spectatedPlayerScript.isInsideFactory); } if ((Object)(object)orCreateShipAudioSource2 != (Object)null && !flag) { orCreateShipAudioSource2.volume = LethalMusicSyncPlugin.MusicVolumeSetting; } break; } case "BOOMBOX": { BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { nearestBoombox.boomboxAudio.volume = result / 100f; } break; } } break; } } } public static BoomboxItem GetNearestBoombox() { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) BoomboxItem[] array = Object.FindObjectsOfType<BoomboxItem>(); if (array == null || array.Length == 0) { return null; } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); BoomboxItem[] array2; if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform != (Object)null) { GrabbableObject currentlyHeldObjectServer = val.currentlyHeldObjectServer; BoomboxItem val2 = (BoomboxItem)(object)((currentlyHeldObjectServer is BoomboxItem) ? currentlyHeldObjectServer : null); if ((Object)(object)val2 != (Object)null && (Object)(object)((Component)val2).transform != (Object)null) { return val2; } BoomboxItem result = null; float num = float.MaxValue; array2 = array; foreach (BoomboxItem val3 in array2) { if (!((Object)(object)val3 == (Object)null) && !((Object)(object)((Component)val3).gameObject == (Object)null) && !((Object)(object)((Component)val3).transform == (Object)null)) { float num2 = Vector3.Distance(((Component)val).transform.position, ((Component)val3).transform.position); if (num2 < num) { num = num2; result = val3; } } } return result; } array2 = array; foreach (BoomboxItem val3 in array2) { if ((Object)(object)val3 != (Object)null && (Object)(object)((Component)val3).gameObject != (Object)null && (Object)(object)((Component)val3).transform != (Object)null) { return val3; } } return null; } } public static class BoomboxPocketHelper { public static void ProcessPocket(GrabbableObject item) { BoomboxItem val = (BoomboxItem)(object)((item is BoomboxItem) ? item : null); if ((Object)(object)val != (Object)null && LethalMusicSyncPlugin.BoomboxQueueIndex != -1) { LethalMusicSyncPlugin.Log("[LMS] Intercepted Boombox PocketItem (" + ((object)item).GetType().Name + "). Pausing stream to preserve track time..."); ((GrabbableObject)val).isPocketed = true; ((GrabbableObject)val).EnableItemMeshes(false); if ((Object)(object)((GrabbableObject)val).playerHeldBy != (Object)null) { ((GrabbableObject)val).parentObject = ((GrabbableObject)val).playerHeldBy.localItemHolder; } ((GrabbableObject)val).isBeingUsed = false; val.isPlayingMusic = false; val.boomboxAudio.Pause(); LethalMusicSyncPlugin.IsCustomBoomboxPlaying = false; LethalMusicSyncPlugin.IsBoomboxPaused = true; PlayerControllerB val2 = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val2 != (Object)null && (Object)(object)((GrabbableObject)val).playerHeldBy == (Object)(object)val2) { LethalMusicSyncPlugin.pendingChatBroadcasts.Enqueue("[LMS_SYNC] BOOMBOX PAUSE"); } } } } [HarmonyPatch(typeof(GrabbableObject), "PocketItem")] public static class GrabbablePocketPatch { [HarmonyPrefix] public static bool Prefix(GrabbableObject __instance) { if (__instance is BoomboxItem) { BoomboxPocketHelper.ProcessPocket(__instance); return false; } return true; } } [HarmonyPatch(typeof(GrabbableObject), "DiscardItem")] public static class BoomboxDiscardPatch { [HarmonyPrefix] public static void Prefix(GrabbableObject __instance) { BoomboxItem val = (BoomboxItem)(object)((__instance is BoomboxItem) ? __instance : null); if ((Object)(object)val != (Object)null && LethalMusicSyncPlugin.BoomboxQueueIndex != -1) { LethalMusicSyncPlugin.IsProgrammaticActivation = true; LethalMusicSyncPlugin.Log("[LMS] Boombox dropped — blocking discard-triggered ItemActivate."); } } [HarmonyPostfix] public static void Postfix(GrabbableObject __instance) { BoomboxItem val = (BoomboxItem)(object)((__instance is BoomboxItem) ? __instance : null); if (!((Object)(object)val != (Object)null) || LethalMusicSyncPlugin.BoomboxQueueIndex == -1) { return; } LethalMusicSyncPlugin.IsProgrammaticActivation = false; if (LethalMusicSyncPlugin.IsBoomboxPaused) { ((GrabbableObject)val).isBeingUsed = false; val.isPlayingMusic = false; if ((Object)(object)val.boomboxAudio != (Object)null) { val.boomboxAudio.Pause(); } LethalMusicSyncPlugin.Log("[LMS] Boombox dropped while paused — ensuring it remains paused."); } else { ((GrabbableObject)val).isBeingUsed = true; val.isPlayingMusic = true; if ((Object)(object)val.boomboxAudio != (Object)null && !val.boomboxAudio.isPlaying) { val.boomboxAudio.Play(); } LethalMusicSyncPlugin.Log("[LMS] Boombox dropped while playing — ensuring it keeps playing."); } } } [HarmonyPatch(typeof(BoomboxItem), "ItemActivate")] public static class BoomboxOverridePatch { [HarmonyPrefix] public static bool Prefix(BoomboxItem __instance, bool used, bool buttonDown) { if (LethalMusicSyncPlugin.showBoomboxGui) { return false; } if (LethalMusicSyncPlugin.BoomboxQueueIndex != -1) { if (!LethalMusicSyncPlugin.IsProgrammaticActivation) { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null && (Object)(object)((GrabbableObject)__instance).playerHeldBy == (Object)(object)val) { if (!used) { LethalMusicSyncPlugin.pendingChatBroadcasts.Enqueue("[LMS_SYNC] BOOMBOX PAUSE"); } else { LethalMusicSyncPlugin.pendingChatBroadcasts.Enqueue("[LMS_SYNC] BOOMBOX RESUME"); } } } return false; } return true; } } [HarmonyPatch(typeof(Terminal), "Start")] public static class TerminalHelpPatch { [HarmonyPostfix] public static void Postfix(Terminal __instance) { if (!((Object)(object)__instance != (Object)null)) { return; } if ((Object)(object)__instance.screenText != (Object)null) { __instance.screenText.characterLimit = 9999; LethalMusicSyncPlugin.Log("[LMS] Increased Terminal input characterLimit to 9999!"); } if (!((Object)(object)__instance.terminalNodes != (Object)null) || __instance.terminalNodes.allKeywords == null) { return; } TerminalKeyword[] allKeywords = __instance.terminalNodes.allKeywords; foreach (TerminalKeyword val in allKeywords) { if ((Object)(object)val != (Object)null && val.word == "help" && (Object)(object)val.specialKeywordResult != (Object)null) { TerminalNode specialKeywordResult = val.specialKeywordResult; specialKeywordResult.displayText += "\n\n>MUSIC CONTROLS\nUse speaker/music/boombox: play [URL], queue [URL], list, skip, skip to [index], loop [track/queue/off], stop, clear, vol [0-100].\n* Speaker: 3D Spatial directional Audio from ship ceiling!\n* Music: 2D Global Ambient flat Background Music!\n* Boombox: Local Portable 3D Audio!\n\n"; LethalMusicSyncPlugin.Log("[LMS] Injected music commands into the Terminal Help menu!"); break; } } } } [HarmonyPatch(typeof(Terminal), "TextChanged")] public static class TerminalTextChangedPatch { [HarmonyPrefix] public static void Prefix(Terminal __instance) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.currentNode != (Object)null && __instance.currentNode.maxCharactersToType < 9999) { __instance.currentNode.maxCharactersToType = 9999; } } }