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 V0idSounds v1.0.3
V0idSounds.dll
Decompiled 5 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Netcode; using UnityEngine; using UnityEngine.Networking; using V0idSounds.Patches; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("V0idSounds")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3")] [assembly: AssemblyProduct("V0idSounds")] [assembly: AssemblyTitle("V0idSounds")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace V0idSounds { [BepInPlugin("com.v0id.v0idsounds", "V0idSounds", "1.0.3")] public class Plugin : BaseUnityPlugin { public static class PluginInfo { public const string PLUGIN_GUID = "com.v0id.v0idsounds"; public const string PLUGIN_NAME = "V0idSounds"; public const string PLUGIN_VERSION = "1.0.3"; } private static readonly Dictionary<string, ConfigEntry<float>> SoundVolumeConfigs = new Dictionary<string, ConfigEntry<float>>(StringComparer.OrdinalIgnoreCase); private AudioSource _voiceAudioSource; private AudioSource _eventAudioSource; private AudioSource? _shipSpeakerAudioSource; private readonly Dictionary<string, List<AudioClip>> _audioPools = new Dictionary<string, List<AudioClip>>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, int> _playCountPerCategory = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, int> _lastPlayedIndexPerCategory = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private readonly Random _fallbackRandom = new Random(); private string _pluginDirectory = string.Empty; private Coroutine? _fadeCoroutine; public static Plugin Instance { get; private set; } = null; internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger; public static ConfigEntry<bool> ConfigEnableJoinSound { get; private set; } = null; public static ConfigEntry<bool> ConfigEnableLeaveSound { get; private set; } = null; public static ConfigEntry<bool> ConfigPlayForSelfJoin { get; private set; } = null; public static ConfigEntry<bool> ConfigEnableArriveNormal { get; private set; } = null; public static ConfigEntry<bool> ConfigEnableArriveChill { get; private set; } = null; public static ConfigEntry<bool> ConfigEnableLeaveSad { get; private set; } = null; public static ConfigEntry<bool> ConfigEnableLeaveFast { get; private set; } = null; public static ConfigEntry<bool> ConfigEnableLeaveChill { get; private set; } = null; public static ConfigEntry<bool> ConfigEnableTooLate { get; private set; } = null; public static ConfigEntry<bool> ConfigCutOffOnEventChange { get; private set; } = null; public static ConfigEntry<float> ConfigMonsterNearbyDistance { get; private set; } = null; public static ConfigEntry<bool> ConfigPlayEventsThroughShipSpeaker { get; private set; } = null; public static ConfigEntry<float> ConfigShipSpeakerSpatialBlend { get; private set; } = null; public static ConfigEntry<float> ConfigShipSpeakerMinDistance { get; private set; } = null; public static ConfigEntry<float> ConfigShipSpeakerMaxDistance { get; private set; } = null; public static ConfigEntry<float> ConfigMasterVolume { get; private set; } = null; public static ConfigEntry<float> ConfigJoinSoundVolume { get; private set; } = null; public static ConfigEntry<float> ConfigLeaveSoundVolume { get; private set; } = null; public static ConfigEntry<float> ConfigArriveNormalVolume { get; private set; } = null; public static ConfigEntry<float> ConfigArriveChillVolume { get; private set; } = null; public static ConfigEntry<float> ConfigLeaveSadVolume { get; private set; } = null; public static ConfigEntry<float> ConfigLeaveFastVolume { get; private set; } = null; public static ConfigEntry<float> ConfigLeaveChillVolume { get; private set; } = null; public static ConfigEntry<float> ConfigTooLateVolume { get; private set; } = null; private void Awake() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) Instance = this; _pluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? Paths.PluginPath; InitConfig(); SetupAudioSources(); Harmony val = new Harmony("com.v0id.v0idsounds"); val.PatchAll(typeof(PlayerConnectionPatches)); val.PatchAll(typeof(ShipEventPatches)); ((MonoBehaviour)this).StartCoroutine(LoadAllAudioClips()); Log.LogInfo((object)"V0idSounds v1.0.3 initialized successfully!"); } private void InitConfig() { //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Expected O, but got Unknown ConfigEnableJoinSound = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableJoinSound", true, "Whether to play 'User joined your channel' when someone joins your multiplayer game."); ConfigEnableLeaveSound = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableLeaveSound", true, "Whether to play 'User left your channel' when someone leaves your multiplayer game."); ConfigPlayForSelfJoin = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "PlayForSelfJoin", true, "Whether to play 'User joined your channel' when you join someone else's multiplayer game."); ConfigEnableArriveNormal = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableArriveNormal", true, "Whether to play 'arrive_normal*' soundtrack when ship lands on a normal moon."); ConfigEnableArriveChill = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableArriveChill", true, "Whether to play 'arrive_chill*' soundtrack when ship lands on the Company moon."); ConfigEnableLeaveSad = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableLeaveSad", true, "Whether to play 'leave_sad*' soundtrack when all players have died and the ship departs on its own."); ConfigEnableLeaveFast = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableLeaveFast", true, "Whether to play 'leave_fast*' soundtrack when the ship departs and monsters are nearby."); ConfigEnableLeaveChill = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableLeaveChill", true, "Whether to play 'leave_chill*' soundtrack when the ship departs from the Company moon."); ConfigEnableTooLate = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableTooLate", true, "Whether to play 'toolate*' soundtrack when time runs out (midnight) and the ship departs on its own."); ConfigCutOffOnEventChange = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "CutOffOnEventChange", true, "Whether to cut off/stop playing sounds when events change (e.g. ship finishes landing/doors open, ship lifts off/enters orbit, round ends). When false, audio plays to completion."); ConfigMonsterNearbyDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "MonsterNearbyDistance", 65f, "Distance in meters to check for monsters near the ship when deciding whether to play fast departure music."); ConfigPlayEventsThroughShipSpeaker = ((BaseUnityPlugin)this).Config.Bind<bool>("Ship Speaker", "PlayEventsThroughShipSpeaker", true, "Whether to play situational soundtracks (landing, departures, timeout) through the physical ship speaker inside the cabin."); ConfigShipSpeakerSpatialBlend = ((BaseUnityPlugin)this).Config.Bind<float>("Ship Speaker", "ShipSpeakerSpatialBlend", 0.85f, new ConfigDescription("Spatial blend for ship speaker audio (0.0 = pure 2D everywhere, 1.0 = fully 3D from ship speaker).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); ConfigShipSpeakerMinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Ship Speaker", "ShipSpeakerMinDistance", 5f, "Minimum distance in meters for full volume from the ship speaker."); ConfigShipSpeakerMaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Ship Speaker", "ShipSpeakerMaxDistance", 95f, "Maximum distance in meters before ship speaker audio rolls off."); ConfigMasterVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Volume", "MasterVolume", 1f, new ConfigDescription("Master volume multiplier for all V0idSounds audio.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); ConfigJoinSoundVolume = BindSoundVolume("JoinSound", 0.8f, "Volume for 'User joined your channel' sound."); ConfigLeaveSoundVolume = BindSoundVolume("LeaveSound", 0.8f, "Volume for 'User left your channel' sound."); ConfigArriveNormalVolume = BindSoundVolume("ArriveNormal", 0.8f, "Volume for 'arrive_normal*' landing soundtrack."); ConfigArriveChillVolume = BindSoundVolume("ArriveChill", 0.8f, "Volume for 'arrive_chill*' Company landing soundtrack."); ConfigLeaveSadVolume = BindSoundVolume("LeaveSad", 0.8f, "Volume for 'leave_sad*' squad wiped departure soundtrack."); ConfigLeaveFastVolume = BindSoundVolume("LeaveFast", 0.8f, "Volume for 'leave_fast*' emergency/monsters departure soundtrack."); ConfigLeaveChillVolume = BindSoundVolume("LeaveChill", 0.8f, "Volume for 'leave_chill*' Company departure soundtrack."); ConfigTooLateVolume = BindSoundVolume("TooLate", 0.8f, "Volume for 'toolate*' midnight departure soundtrack."); } public ConfigEntry<float> BindSoundVolume(string soundName, float defaultVolume = 0.8f, string description = "") { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown if (SoundVolumeConfigs.TryGetValue(soundName, out ConfigEntry<float> value)) { return value; } string text = (string.IsNullOrWhiteSpace(description) ? ("Volume level for '" + soundName + "' sound (0.0 to 1.0).") : description); ConfigEntry<float> val = ((BaseUnityPlugin)this).Config.Bind<float>("Volume", soundName + "Volume", defaultVolume, new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); SoundVolumeConfigs[soundName] = val; return val; } public float GetEffectiveVolume(string soundName, float fallbackVolume = 0.8f) { ConfigEntry<float> value; float num = (SoundVolumeConfigs.TryGetValue(soundName, out value) ? value.Value : fallbackVolume); return Mathf.Clamp01(ConfigMasterVolume.Value * num); } private void SetupAudioSources() { Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); _voiceAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); _voiceAudioSource.spatialBlend = 0f; _voiceAudioSource.playOnAwake = false; _voiceAudioSource.loop = false; _voiceAudioSource.bypassEffects = true; _voiceAudioSource.bypassListenerEffects = true; _voiceAudioSource.bypassReverbZones = true; _eventAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); _eventAudioSource.spatialBlend = 0f; _eventAudioSource.playOnAwake = false; _eventAudioSource.loop = false; _eventAudioSource.bypassEffects = true; _eventAudioSource.bypassListenerEffects = true; _eventAudioSource.bypassReverbZones = true; } private AudioSource GetActiveEventAudioSource() { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown if (ConfigPlayEventsThroughShipSpeaker.Value && (Object)(object)StartOfRound.Instance != (Object)null) { Transform val = (((Object)(object)StartOfRound.Instance.speakerAudioSource != (Object)null) ? ((Component)StartOfRound.Instance.speakerAudioSource).transform : (((Object)(object)StartOfRound.Instance.shipDoorNode != (Object)null) ? StartOfRound.Instance.shipDoorNode : StartOfRound.Instance.elevatorTransform)); if ((Object)(object)val != (Object)null) { if ((Object)(object)_shipSpeakerAudioSource == (Object)null || !Object.op_Implicit((Object)(object)_shipSpeakerAudioSource)) { Transform val2 = val.Find("V0idShipSpeakerAudioSource"); if ((Object)(object)val2 != (Object)null) { _shipSpeakerAudioSource = ((Component)val2).GetComponent<AudioSource>(); } if ((Object)(object)_shipSpeakerAudioSource == (Object)null) { GameObject val3 = new GameObject("V0idShipSpeakerAudioSource"); val3.transform.SetParent(val, false); _shipSpeakerAudioSource = val3.AddComponent<AudioSource>(); } } _shipSpeakerAudioSource.spatialBlend = ConfigShipSpeakerSpatialBlend.Value; _shipSpeakerAudioSource.minDistance = ConfigShipSpeakerMinDistance.Value; _shipSpeakerAudioSource.maxDistance = ConfigShipSpeakerMaxDistance.Value; _shipSpeakerAudioSource.rolloffMode = (AudioRolloffMode)1; _shipSpeakerAudioSource.playOnAwake = false; _shipSpeakerAudioSource.loop = false; _shipSpeakerAudioSource.dopplerLevel = 0f; return _shipSpeakerAudioSource; } } return _eventAudioSource; } private IEnumerator LoadAllAudioClips() { string path = Path.Combine(_pluginDirectory, "Audio"); List<string> list = new List<string>(); if (Directory.Exists(path)) { list.AddRange(Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)); } if (Directory.Exists(_pluginDirectory)) { list.AddRange(Directory.GetFiles(_pluginDirectory, "*.*", SearchOption.TopDirectoryOnly)); } HashSet<string> supportedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".wav", ".mp3", ".ogg" }; List<string> list2 = list.Where((string f) => supportedExtensions.Contains(Path.GetExtension(f))).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); Log.LogInfo((object)$"Found {list2.Count} candidate audio file(s) on disk."); foreach (string item in list2) { string fileNameWithoutExt = Path.GetFileNameWithoutExtension(item); string category = GetCategoryFromFileName(fileNameWithoutExt); yield return ((MonoBehaviour)this).StartCoroutine(LoadClipFromFile(item, delegate(AudioClip clip) { AddClipToPool(category, clip); Log.LogInfo((object)("Loaded audio '" + fileNameWithoutExt + "' into category '" + category + "'.")); })); } string[] array = new string[8] { "user_joined", "user_left", "arrive_normal", "arrive_chill", "leave_sad", "leave_fast", "leave_chill", "toolate" }; foreach (string text in array) { if (!_audioPools.ContainsKey(text) || _audioPools[text].Count == 0) { TryExtractAndLoadEmbedded(text); } } SortAllPools(); Log.LogInfo((object)"All V0idSounds audio pools initialized successfully."); } private void SortAllPools() { foreach (KeyValuePair<string, List<AudioClip>> audioPool in _audioPools) { audioPool.Value.Sort((AudioClip a, AudioClip b) => string.Compare(((Object)a).name, ((Object)b).name, StringComparison.OrdinalIgnoreCase)); string arg = string.Join(", ", audioPool.Value.Select((AudioClip c) => ((Object)c).name)); Log.LogInfo((object)$"Audio pool '{audioPool.Key}' ({audioPool.Value.Count} tracks): [{arg}]"); } } private string GetCategoryFromFileName(string name) { string text = name.ToLowerInvariant(); if (text.StartsWith("user_joined")) { return "user_joined"; } if (text.StartsWith("user_left")) { return "user_left"; } if (text.StartsWith("arrive_chill")) { return "arrive_chill"; } if (text.StartsWith("arrive_normal")) { return "arrive_normal"; } if (text.StartsWith("leave_sad")) { return "leave_sad"; } if (text.StartsWith("leave_fast")) { return "leave_fast"; } if (text.StartsWith("leave_chill")) { return "leave_chill"; } if (text.StartsWith("toolate")) { return "toolate"; } return text; } private void AddClipToPool(string category, AudioClip clip) { if (!_audioPools.TryGetValue(category, out List<AudioClip> value)) { value = new List<AudioClip>(); _audioPools[category] = value; } value.Add(clip); } private IEnumerator LoadClipFromFile(string fullPath, Action<AudioClip> onLoaded) { AudioType val = (AudioType)(Path.GetExtension(fullPath).ToLowerInvariant() switch { ".wav" => 20, ".mp3" => 13, ".ogg" => 14, _ => 0, }); string uri = "file://" + Path.GetFullPath(fullPath).Replace("\\", "/"); UnityWebRequest uwr = UnityWebRequestMultimedia.GetAudioClip(uri, val); try { yield return uwr.SendWebRequest(); if ((int)uwr.result != 1) { Log.LogError((object)("Error loading audio from '" + uri + "': " + uwr.error)); yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(uwr); if ((Object)(object)content != (Object)null) { ((Object)content).name = Path.GetFileNameWithoutExtension(fullPath); onLoaded(content); } else { Log.LogError((object)("AudioClip from '" + uri + "' was null.")); } } finally { ((IDisposable)uwr)?.Dispose(); } } private void TryExtractAndLoadEmbedded(string category) { string[] obj = new string[3] { ".wav", ".mp3", ".ogg" }; Assembly executingAssembly = Assembly.GetExecutingAssembly(); string[] array = obj; foreach (string ext in array) { string name = "V0idSounds.Audio." + category + ext; using Stream stream = executingAssembly.GetManifestResourceStream(name); if (stream != null) { string text = Path.Combine(_pluginDirectory, "Audio", category + ext); string directoryName = Path.GetDirectoryName(text); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } using (FileStream destination = new FileStream(text, FileMode.Create, FileAccess.Write)) { stream.CopyTo(destination); } ((MonoBehaviour)this).StartCoroutine(LoadClipFromFile(text, delegate(AudioClip clip) { AddClipToPool(category, clip); SortAllPools(); Log.LogInfo((object)("Extracted and loaded embedded audio '" + category + ext + "'.")); })); break; } } } public AudioClip? GetSyncedClip(string category, int extraSalt = 0) { if (_audioPools.TryGetValue(category, out List<AudioClip> value) && value.Count > 0) { if (value.Count == 1) { return value[0]; } int value2; int num = (_playCountPerCategory.TryGetValue(category, out value2) ? value2 : 0); _playCountPerCategory[category] = num + 1; int num2 = 0; if ((Object)(object)StartOfRound.Instance != (Object)null) { num2 = StartOfRound.Instance.randomMapSeed; if (StartOfRound.Instance.gameStats != null) { num2 += StartOfRound.Instance.gameStats.daysSpent * 7919; } } if (num2 == 0 && (Object)(object)TimeOfDay.Instance != (Object)null) { num2 = (int)TimeOfDay.Instance.globalTime; } if (num2 == 0) { num2 = 1337; } int hashCode = category.GetHashCode(); Random random = new Random(num2 + num * 31337 + hashCode + extraSalt); int num3 = random.Next(value.Count); if (value.Count > 1 && _lastPlayedIndexPerCategory.TryGetValue(category, out var value3) && num3 == value3) { num3 = (num3 + 1 + random.Next(value.Count - 1)) % value.Count; } _lastPlayedIndexPerCategory[category] = num3; Log.LogInfo((object)$"Shuffled '{category}' -> '{((Object)value[num3]).name}' (Pool size: {value.Count}, Index: {num3}, PlayCount: {num + 1})."); return value[num3]; } return null; } public void PlayJoinSound(ulong clientId = 0uL) { if (ConfigEnableJoinSound.Value) { AudioClip syncedClip = GetSyncedClip("user_joined", (int)clientId * 19); if ((Object)(object)syncedClip != (Object)null && (Object)(object)_voiceAudioSource != (Object)null) { float effectiveVolume = GetEffectiveVolume("JoinSound"); Log.LogInfo((object)$"Playing synchronized 'user_joined' sound ({((Object)syncedClip).name}) at volume {effectiveVolume:F2}."); _voiceAudioSource.PlayOneShot(syncedClip, effectiveVolume); } else { Log.LogWarning((object)"No audio clip available for 'user_joined'."); } } } public void PlayLeaveSound(ulong clientId = 0uL) { if (ConfigEnableLeaveSound.Value) { AudioClip syncedClip = GetSyncedClip("user_left", (int)clientId * 23); if ((Object)(object)syncedClip != (Object)null && (Object)(object)_voiceAudioSource != (Object)null) { float effectiveVolume = GetEffectiveVolume("LeaveSound"); Log.LogInfo((object)$"Playing synchronized 'user_left' sound ({((Object)syncedClip).name}) at volume {effectiveVolume:F2}."); _voiceAudioSource.PlayOneShot(syncedClip, effectiveVolume); } else { Log.LogWarning((object)"No audio clip available for 'user_left'."); } } } public void PlayEventSound(string category, string volumeKey, int eventSalt = 0, bool isEnabled = true) { if (!isEnabled) { return; } if (ShipEventPatches.IsTimeRanOutLeaving && !category.Equals("toolate", StringComparison.OrdinalIgnoreCase)) { Log.LogInfo((object)("Suppressed event sound '" + category + "' because time ran out ('toolate') is active.")); return; } AudioClip syncedClip = GetSyncedClip(category, eventSalt); if ((Object)(object)syncedClip == (Object)null) { Log.LogWarning((object)("No audio clips found in pool for event '" + category + "'.")); return; } if (_fadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_fadeCoroutine); _fadeCoroutine = null; } AudioSource activeEventAudioSource = GetActiveEventAudioSource(); float effectiveVolume = GetEffectiveVolume(volumeKey); Log.LogInfo((object)string.Format("Playing synchronized event sound '{0}' ({1}) through {2} at volume {3:F2}.", category, ((Object)syncedClip).name, ((Object)(object)activeEventAudioSource == (Object)(object)_shipSpeakerAudioSource) ? "Ship Speaker" : "2D Event Audio", effectiveVolume)); activeEventAudioSource.Stop(); activeEventAudioSource.clip = syncedClip; activeEventAudioSource.volume = effectiveVolume; activeEventAudioSource.Play(); } public void StopEventSound(bool fade = true) { if (!ConfigCutOffOnEventChange.Value) { Log.LogInfo((object)"CutOffOnEventChange is disabled; sound will continue to play."); return; } AudioSource[] array = (AudioSource[])(object)new AudioSource[2] { _eventAudioSource, _shipSpeakerAudioSource }; foreach (AudioSource val in array) { if (!((Object)(object)val != (Object)null) || !val.isPlaying) { continue; } if (fade) { if (_fadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_fadeCoroutine); } _fadeCoroutine = ((MonoBehaviour)this).StartCoroutine(FadeOutAudioSource(val, 1.2f)); } else { val.Stop(); } } } private IEnumerator FadeOutAudioSource(AudioSource source, float duration) { float startVol = source.volume; float elapsed = 0f; while (elapsed < duration && (Object)(object)source != (Object)null && source.isPlaying) { elapsed += Time.deltaTime; source.volume = Mathf.Lerp(startVol, 0f, elapsed / duration); yield return null; } if ((Object)(object)source != (Object)null) { source.Stop(); source.volume = startVol; } _fadeCoroutine = null; } public void PlaySound(AudioClip clip, string soundName, float defaultVolume = 0.8f) { if (!((Object)(object)clip == (Object)null) && !((Object)(object)_voiceAudioSource == (Object)null)) { BindSoundVolume(soundName, defaultVolume); float effectiveVolume = GetEffectiveVolume(soundName, defaultVolume); Log.LogInfo((object)$"Playing custom sound '{soundName}' at volume {effectiveVolume:F2}."); _voiceAudioSource.PlayOneShot(clip, effectiveVolume); } } } } namespace V0idSounds.Patches { [HarmonyPatch(typeof(StartOfRound))] public static class PlayerConnectionPatches { [HarmonyPatch("OnPlayerConnectedClientRpc")] [HarmonyPostfix] public static void Postfix_OnPlayerConnectedClientRpc(ulong clientId) { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null)) { if (clientId != NetworkManager.Singleton.LocalClientId) { Plugin.Log.LogInfo((object)$"Player connected with ClientId #{clientId}. Triggering join sound."); Plugin.Instance.PlayJoinSound(clientId); } else if (Plugin.ConfigPlayForSelfJoin.Value) { Plugin.Log.LogInfo((object)$"Local player joined game (ClientId #{clientId}). Triggering join sound."); Plugin.Instance.PlayJoinSound(clientId); } } } catch (Exception arg) { Plugin.Log.LogError((object)$"Exception occurred in Postfix_OnPlayerConnectedClientRpc: {arg}"); } } [HarmonyPatch("OnPlayerDC")] [HarmonyPostfix] public static void Postfix_OnPlayerDC(int playerObjectNumber, ulong clientId) { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && (!((Object)(object)GameNetworkManager.Instance != (Object)null) || !GameNetworkManager.Instance.isDisconnecting) && clientId != NetworkManager.Singleton.LocalClientId) { Plugin.Log.LogInfo((object)$"Player #{playerObjectNumber} (ClientId #{clientId}) disconnected. Triggering leave sound."); Plugin.Instance.PlayLeaveSound(clientId); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Exception occurred in Postfix_OnPlayerDC: {arg}"); } } } [HarmonyPatch(typeof(StartOfRound))] public static class ShipEventPatches { public static bool IsTimeRanOutLeaving { get; set; } [HarmonyPatch("openingDoorsSequence")] [HarmonyPrefix] public static void Prefix_openingDoorsSequence(StartOfRound __instance) { try { IsTimeRanOutLeaving = false; if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.currentLevel == (Object)null)) { if (IsCompanyMoon(__instance.currentLevel)) { Plugin.Log.LogInfo((object)"Ship landing on Company moon -> Playing 'arrive_chill'."); Plugin.Instance.PlayEventSound("arrive_chill", "ArriveChill", 1122867, Plugin.ConfigEnableArriveChill.Value); } else { Plugin.Log.LogInfo((object)("Ship landing on moon '" + __instance.currentLevel.PlanetName + "' -> Playing 'arrive_normal'.")); Plugin.Instance.PlayEventSound("arrive_normal", "ArriveNormal", 4478310, Plugin.ConfigEnableArriveNormal.Value); } } } catch (Exception arg) { Plugin.Log.LogError((object)$"Error in Prefix_openingDoorsSequence: {arg}"); } } [HarmonyPatch("OnShipLandedMiscEvents")] [HarmonyPostfix] public static void Postfix_OnShipLandedMiscEvents() { try { Plugin.Log.LogInfo((object)"Ship doors opened / landed -> Stopping landing event sound (if cutoff enabled)."); Plugin.Instance.StopEventSound(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Error in Postfix_OnShipLandedMiscEvents: {arg}"); } } [HarmonyPatch("ShipLeave")] [HarmonyPrefix] public static void Prefix_ShipLeave(StartOfRound __instance) { try { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.currentLevel == (Object)null)) { if (IsTimeRanOutLeaving || __instance.shipLeftAutomatically) { Plugin.Log.LogInfo((object)"Ship is leaving automatically (time ran out / squad wipe); skipping manual ShipLeave sounds."); } else if (IsCompanyMoon(__instance.currentLevel)) { Plugin.Log.LogInfo((object)"Ship departing from Company moon -> Playing 'leave_chill'."); Plugin.Instance.PlayEventSound("leave_chill", "LeaveChill", 7833753, Plugin.ConfigEnableLeaveChill.Value); } else if (AreMonstersNearby(__instance)) { Plugin.Log.LogInfo((object)"Ship departing with monsters nearby -> Playing 'leave_fast'."); Plugin.Instance.PlayEventSound("leave_fast", "LeaveFast", 11189196, Plugin.ConfigEnableLeaveFast.Value); } else { Plugin.Log.LogInfo((object)"Ship departing normal moon -> Playing departure sound ('leave_fast')."); Plugin.Instance.PlayEventSound("leave_fast", "LeaveFast", 11189196, Plugin.ConfigEnableLeaveFast.Value); } } } catch (Exception arg) { Plugin.Log.LogError((object)$"Error in Prefix_ShipLeave: {arg}"); } } [HarmonyPatch("ShipLeaveAutomatically")] [HarmonyPrefix] public static void Prefix_ShipLeaveAutomatically(StartOfRound __instance, bool leavingOnMidnight = false) { try { if (!((Object)(object)__instance == (Object)null)) { if (leavingOnMidnight || (!__instance.allPlayersDead && __instance.livingPlayers > 0)) { IsTimeRanOutLeaving = true; Plugin.Log.LogInfo((object)"Time ran out (midnight) and ship is departing -> Playing 'toolate' ONLY."); Plugin.Instance.PlayEventSound("toolate", "TooLate", 14544639, Plugin.ConfigEnableTooLate.Value); } else { IsTimeRanOutLeaving = false; Plugin.Log.LogInfo((object)"All players have died and ship is departing -> Playing 'leave_sad'."); Plugin.Instance.PlayEventSound("leave_sad", "LeaveSad", 10061943, Plugin.ConfigEnableLeaveSad.Value); } } } catch (Exception arg) { Plugin.Log.LogError((object)$"Error in Prefix_ShipLeaveAutomatically: {arg}"); } } [HarmonyPatch("ShipHasLeft")] [HarmonyPostfix] public static void Postfix_ShipHasLeft() { try { Plugin.Log.LogInfo((object)"Ship has left moon -> Stopping departure sound (if cutoff enabled)."); Plugin.Instance.StopEventSound(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Error in Postfix_ShipHasLeft: {arg}"); } } [HarmonyPatch("EndOfGameClientRpc")] [HarmonyPostfix] public static void Postfix_EndOfGameClientRpc() { try { IsTimeRanOutLeaving = false; Plugin.Instance.StopEventSound(fade: false); } catch (Exception arg) { Plugin.Log.LogError((object)$"Error in Postfix_EndOfGameClientRpc: {arg}"); } } private static bool IsCompanyMoon(SelectableLevel level) { if ((Object)(object)level == (Object)null) { return false; } if (level.levelID == 3) { return true; } if (!string.IsNullOrEmpty(level.PlanetName) && level.PlanetName.IndexOf("Gordion", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (!level.planetHasTime && !level.spawnEnemiesAndScrap) { return true; } return false; } private static bool AreMonstersNearby(StartOfRound sor) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) try { Vector3 val = (((Object)(object)sor.shipDoorNode != (Object)null) ? sor.shipDoorNode.position : (((Object)(object)sor.elevatorTransform != (Object)null) ? sor.elevatorTransform.position : Vector3.zero)); float value = Plugin.ConfigMonsterNearbyDistance.Value; if ((Object)(object)RoundManager.Instance != (Object)null && RoundManager.Instance.SpawnedEnemies != null) { foreach (EnemyAI spawnedEnemy in RoundManager.Instance.SpawnedEnemies) { if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead && Vector3.Distance(((Component)spawnedEnemy).transform.position, val) <= value) { return true; } } } EnemyAI[] array = Object.FindObjectsOfType<EnemyAI>(); if (array != null) { EnemyAI[] array2 = array; foreach (EnemyAI val2 in array2) { if (!((Object)(object)val2 == (Object)null) && !val2.isEnemyDead && Vector3.Distance(((Component)val2).transform.position, val) <= value) { return true; } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error checking nearby monsters: " + ex.Message)); } return false; } } }