using 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 BepInEx; using BepInEx.Configuration; using Microsoft.CodeAnalysis; using SailingBagpipes.Logging; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("SailingBagpipes")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d5b1b3e35b1c15db102ba9706f1e9199cfd561bb")] [assembly: AssemblyProduct("SailingBagpipes")] [assembly: AssemblyTitle("SailingBagpipes")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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; } } } namespace SailingBagpipes { [BepInPlugin("eh.mataeo.valheim.slowsailingbagpipes", "SlowSailingBagpipes", "1.0.5")] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class d__67 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; public float fadeDuration; public float targetVolume; public Action onComplete; private float 5__2; private float 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__67(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; if ((Object)(object)plugin._audioSource == (Object)null) { return false; } if (fadeDuration <= 0f) { plugin._audioSource.volume = targetVolume; plugin._fadeRoutine = null; onComplete?.Invoke(); return false; } 5__2 = plugin._audioSource.volume; 5__3 = 0f; break; case 1: <>1__state = -1; break; } if (5__3 < fadeDuration) { if ((Object)(object)plugin._audioSource == (Object)null) { return false; } 5__3 += Time.deltaTime; float num2 = Mathf.Clamp01(5__3 / fadeDuration); plugin._audioSource.volume = Mathf.Lerp(5__2, targetVolume, num2); <>2__current = null; <>1__state = 1; return true; } if ((Object)(object)plugin._audioSource != (Object)null) { plugin._audioSource.volume = targetVolume; } plugin._fadeRoutine = null; onComplete?.Invoke(); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__71 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; public string trackPath; private UnityWebRequest 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__71(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } 5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Invalid comparison between Unknown and I4 bool result; try { int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: result = false; break; case 0: { <>1__state = -1; plugin.LogInfo("Loading track " + Path.GetFileName(trackPath) + "."); Uri uri = new Uri(trackPath); AudioType audioTypeForExtension = GetAudioTypeForExtension(Path.GetExtension(trackPath)); 5__2 = UnityWebRequestMultimedia.GetAudioClip(uri.AbsoluteUri, audioTypeForExtension); <>1__state = -3; <>2__current = 5__2.SendWebRequest(); <>1__state = 1; result = true; break; } case 1: <>1__state = -3; if ((int)5__2.result != 1) { plugin.LogError("Failed to load " + trackPath + ": " + 5__2.error); plugin._pendingPlayback = false; plugin._pendingTrackPath = null; plugin._clipLoadRoutine = null; result = false; } else { AudioClip content = DownloadHandlerAudioClip.GetContent(5__2); ((Object)content).name = Path.GetFileNameWithoutExtension(trackPath); plugin._clipCache[trackPath] = content; plugin.LogInfo($"Loaded clip metadata: length={content.length:F1}s channels={content.channels} frequency={content.frequency}."); if (!plugin._pendingPlayback || plugin._pendingTrackPath != trackPath) { plugin._clipLoadRoutine = null; result = false; } else { plugin.PlayClip(content, trackPath); plugin._clipLoadRoutine = null; result = false; } } <>m__Finally1(); break; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (5__2 != null) { ((IDisposable)5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string PluginGuid = "eh.mataeo.valheim.slowsailingbagpipes"; private const string PluginName = "SlowSailingBagpipes"; private const string PluginVersion = "1.0.5"; private const float PaddleThresholdSeconds = 0.1f; private const float ResumeGraceSeconds = 10f; private const float FadeInDuration = 1.5f; private const float FadeOutDuration = 0.5f; private const string PlaceholderTrackName = "ghost_bagpipe_track.mp3"; private const string DefaultTrackDirectoryName = "BagPipesTracks"; private static readonly string[] SupportedExtensions = new string[3] { ".mp3", ".ogg", ".wav" }; private static readonly FieldInfo? ShipPlayersField = typeof(Ship).GetField("m_players", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo? MusicManMusicSourceField = typeof(MusicMan).GetField("m_musicSource", BindingFlags.Instance | BindingFlags.NonPublic); private ConfigEntry? _enabled; private ConfigEntry? _volume; private ConfigEntry? _trackDirectoryConfig; private PluginLogger? _fileLogger; private GameObject? _audioHolder; private AudioSource? _audioSource; private readonly Dictionary _clipCache = new Dictionary(); private readonly Random _rng = new Random(); private string? _pluginDirectory; private string? _trackDirectory; private List _trackPaths = new List(); private Coroutine? _clipLoadRoutine; private Coroutine? _fadeRoutine; private string? _pendingTrackPath; private bool _pendingPlayback; private bool _isPlaying; private bool _isPausedForGrace; private float _paddleTimer; private float _resumeUntil; private float _storedMusicVolume = 1f; private bool _musicManSuppressed; private bool _musicManWasPlaying; private bool _audioSourceConfiguredFromMusicMan; private int _lastControlledShipId = -1; private string _lastControlledShipName = "None"; private Speed? _lastSpeedSetting; private bool _lastEligibleRowingState; private string _lastControllerDescription = "None"; private bool? _lastAttachedToShip; private void Awake() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown _pluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? Paths.PluginPath; _fileLogger = new PluginLogger("SlowSailingBagpipes", _pluginDirectory); _enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master toggle for the Slow Sailing Bagpipes mod."); _volume = ((BaseUnityPlugin)this).Config.Bind("General", "Volume", 0.85f, new ConfigDescription("Playback volume for the bagpipe loop.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); _trackDirectoryConfig = ((BaseUnityPlugin)this).Config.Bind("Audio", "TrackDirectory", "BagPipesTracks", "Directory (absolute or relative to the plugin folder) that contains bagpipe audio clips."); UpdateTrackDirectory(_trackDirectoryConfig.Value); _audioSource = CreateAudioSource(); ((BaseUnityPlugin)this).Config.SettingChanged += OnConfigSettingChanged; LogInfo("Initialized. Watching track directory: " + _trackDirectory); } private void OnDestroy() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnConfigSettingChanged; StopBagpipes(immediate: true); ClearClipCache(); if ((Object)(object)_audioHolder != (Object)null) { Object.Destroy((Object)(object)_audioHolder); _audioHolder = null; } } private void Update() { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) ConfigEntry enabled = _enabled; if (enabled == null || !enabled.Value) { if (_isPlaying || _isPausedForGrace || _pendingPlayback) { StopBagpipes(immediate: true); } return; } if ((Object)(object)_audioSource == (Object)null) { LogWarn("Audio source not initialized; skipping update."); return; } SyncAudioSourceWithMusicMan(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { if (_isPlaying || _isPausedForGrace || _pendingPlayback) { StopBagpipes(immediate: true); } LogControlState(null, null, null, "None"); return; } string controllerDescription; Ship val = ResolveControlledShip(localPlayer, out controllerDescription); Speed? speedSetting = ((val != null) ? new Speed?(val.GetSpeedSetting()) : null); LogControlState(localPlayer, val, speedSetting, controllerDescription); if (speedSetting.HasValue && IsRowingAtTriggerSpeed(speedSetting.Value)) { HandleRowingState(val, speedSetting.Value); } else { HandleNonRowingState(val, speedSetting); } } private void OnConfigSettingChanged(object? sender, SettingChangedEventArgs args) { if ((Object)(object)_audioSource == (Object)null) { return; } if (args.ChangedSetting == _volume) { if (_isPlaying) { _audioSource.volume = _volume.Value; } } else if (_trackDirectoryConfig != null && args.ChangedSetting == _trackDirectoryConfig) { LogInfo("Track directory changed to " + _trackDirectoryConfig.Value + "; reloading library."); StopBagpipes(immediate: true); ClearClipCache(); UpdateTrackDirectory(_trackDirectoryConfig.Value); } } private static bool IsRowingAtTriggerSpeed(Speed speedSetting) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)speedSetting != 2) { return (int)speedSetting == 1; } return true; } private void HandleRowingState(Ship ship, Speed speedSetting) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if (!_lastEligibleRowingState) { LogInfo($"Detected eligible rowing on {GetShipDisplayName(ship)} at speed setting {speedSetting}; waiting for {0.1f:F1}s threshold."); } _lastEligibleRowingState = true; _paddleTimer += Time.deltaTime; if (_isPausedForGrace && Time.time <= _resumeUntil) { LogInfo("Resuming paused clip on " + GetShipDisplayName(ship) + " within grace window."); ResumeBagpipes(); return; } bool flag = _resumeUntil > 0f && Time.time <= _resumeUntil; if (!_isPlaying && !_pendingPlayback && !_isPausedForGrace && (_paddleTimer >= 0.1f || flag)) { LogInfo($"Rowing threshold satisfied on {GetShipDisplayName(ship)} at speed setting {speedSetting}; starting bagpipes."); StartBagpipes(); } } private void HandleNonRowingState(Ship? ship, Speed? speedSetting) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (_lastEligibleRowingState) { string arg = (((Object)(object)ship != (Object)null) ? GetShipDisplayName(ship) : _lastControlledShipName); object obj; if (!speedSetting.HasValue) { obj = null; } else { Speed valueOrDefault = speedSetting.GetValueOrDefault(); obj = ((object)(Speed)(ref valueOrDefault)).ToString(); } if (obj == null) { obj = "None"; } string arg2 = (string)obj; LogInfo($"Eligible rowing ended on {arg}; current speed setting {arg2}; timer reached {_paddleTimer:F2}s."); } _lastEligibleRowingState = false; _paddleTimer = 0f; if (_pendingPlayback) { CancelPendingPlayback(); } if (_isPlaying) { _resumeUntil = Time.time + 10f; PauseBagpipesForGrace(); } else if (_isPausedForGrace && Time.time > _resumeUntil) { StopBagpipes(immediate: true); } else if (!_isPausedForGrace && Time.time > _resumeUntil) { _resumeUntil = 0f; } } private AudioSource CreateAudioSource() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _audioHolder = new GameObject("SlowSailingBagpipes_AudioCarrier"); Object.DontDestroyOnLoad((Object)(object)_audioHolder); AudioSource obj = _audioHolder.AddComponent(); obj.playOnAwake = false; obj.loop = true; obj.volume = 0f; obj.spatialBlend = 0f; obj.priority = 64; return obj; } private void SyncAudioSourceWithMusicMan() { if (!((Object)(object)_audioSource == (Object)null) && !_audioSourceConfiguredFromMusicMan) { AudioSource musicManAudioSource = GetMusicManAudioSource(); if (!((Object)(object)musicManAudioSource == (Object)null)) { _audioSource.outputAudioMixerGroup = musicManAudioSource.outputAudioMixerGroup; _audioSource.priority = musicManAudioSource.priority; _audioSource.pitch = 1f; _audioSource.panStereo = 0f; _audioSource.reverbZoneMix = musicManAudioSource.reverbZoneMix; _audioSource.bypassEffects = musicManAudioSource.bypassEffects; _audioSource.bypassListenerEffects = musicManAudioSource.bypassListenerEffects; _audioSource.bypassReverbZones = musicManAudioSource.bypassReverbZones; _audioSource.ignoreListenerPause = musicManAudioSource.ignoreListenerPause; _audioSource.ignoreListenerVolume = musicManAudioSource.ignoreListenerVolume; _audioSource.mute = false; _audioSource.spatialBlend = 0f; _audioSource.dopplerLevel = 0f; _audioSource.spread = 0f; _audioSourceConfiguredFromMusicMan = true; LogInfo("Audio source synced to Valheim music mixer settings."); } } } private void LogControlState(Player? player, Ship? ship, Speed? speedSetting, string controllerDescription) { //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { if (_lastControlledShipId != -1 || _lastSpeedSetting.HasValue || _lastControllerDescription != "None" || _lastAttachedToShip.HasValue) { LogInfo("No local player is active; cleared ship control state."); } _lastControlledShipId = -1; _lastControlledShipName = "None"; _lastSpeedSetting = null; _lastControllerDescription = "None"; _lastAttachedToShip = null; return; } bool flag = ((Character)player).IsAttachedToShip(); if ((Object)(object)ship == (Object)null) { if (_lastControlledShipId != -1 || _lastSpeedSetting.HasValue || controllerDescription != _lastControllerDescription || flag != _lastAttachedToShip) { LogInfo($"No controlled ship resolved. AttachedToShip={flag} controller={controllerDescription}."); } _lastControlledShipId = -1; _lastControlledShipName = "None"; _lastSpeedSetting = null; _lastControllerDescription = controllerDescription; _lastAttachedToShip = flag; return; } int instanceID = ((Object)ship).GetInstanceID(); string shipDisplayName = GetShipDisplayName(ship); if (instanceID != _lastControlledShipId || speedSetting != _lastSpeedSetting || controllerDescription != _lastControllerDescription || flag != _lastAttachedToShip) { LogInfo($"Ship control state: ship={shipDisplayName} speedSetting={speedSetting} speed={ship.GetSpeed():F2} rudder={ship.GetRudder():F2} attached={flag} controller={controllerDescription}."); } _lastControlledShipId = instanceID; _lastControlledShipName = shipDisplayName; _lastSpeedSetting = speedSetting; _lastControllerDescription = controllerDescription; _lastAttachedToShip = flag; } private static string GetShipDisplayName(Ship ship) { string text = (((Object)(object)((Component)ship).gameObject != (Object)null) ? ((Object)((Component)ship).gameObject).name : ((Object)ship).name); if (!string.IsNullOrWhiteSpace(text)) { return text; } return ((object)ship).GetType().Name; } private static Ship? ResolveControlledShip(Player player, out string controllerDescription) { Ship controlledShip = player.GetControlledShip(); if ((Object)(object)controlledShip != (Object)null) { controllerDescription = "Player.GetControlledShip"; return controlledShip; } IDoodadController doodadController = player.GetDoodadController(); if (doodadController == null) { controllerDescription = "None"; return null; } Component controlledComponent = doodadController.GetControlledComponent(); controllerDescription = (((Object)(object)controlledComponent == (Object)null) ? ((object)doodadController).GetType().Name : (((object)doodadController).GetType().Name + "->" + ((object)controlledComponent).GetType().Name)); Ship val = (Ship)(object)((controlledComponent is Ship) ? controlledComponent : null); if (val != null) { return val; } if ((Object)(object)controlledComponent == (Object)null) { return ResolveAttachedShip(player, ref controllerDescription); } object obj = controlledComponent.GetComponent() ?? controlledComponent.GetComponentInParent(); if (obj == null) { obj = ResolveAttachedShip(player, ref controllerDescription); } return (Ship?)obj; } private static Ship? ResolveAttachedShip(Player player, ref string controllerDescription) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) Ship val = null; float num = float.MaxValue; Ship[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Ship val2 in array) { if (ShipContainsPlayer(val2, player)) { controllerDescription = AppendResolutionSource(controllerDescription, "Ship.m_players"); return val2; } if (((Character)player).IsAttachedToShip()) { float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)val2).transform.position); if (num2 < num) { num = num2; val = val2; } } } if ((Object)(object)val != (Object)null && num <= 25f) { controllerDescription = AppendResolutionSource(controllerDescription, $"NearestAttachedShip({num:F1}m)"); return val; } return null; } private static bool ShipContainsPlayer(Ship ship, Player player) { if (!(ShipPlayersField?.GetValue(ship) is List list)) { return false; } return list.Contains(player); } private static string AppendResolutionSource(string currentDescription, string source) { if (string.IsNullOrWhiteSpace(currentDescription) || currentDescription == "None") { return source; } return currentDescription + "+" + source; } private void EnsurePlaceholderTrackExists(string trackDirectory) { string path = Path.Combine(trackDirectory, "ghost_bagpipe_track.mp3"); if (!File.Exists(path)) { File.WriteAllBytes(path, Array.Empty()); LogInfo("Created placeholder track file. Replace it with a real MP3, OGG, or WAV file."); } } private void RefreshTrackLibrary() { if (_trackDirectory != null) { _trackPaths = (from path in Directory.EnumerateFiles(_trackDirectory, "*.*", SearchOption.TopDirectoryOnly) where SupportedExtensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase) where new FileInfo(path).Length > 0 select path).OrderBy((string path) => path, StringComparer.OrdinalIgnoreCase).ToList(); if (_trackPaths.Count == 0) { LogWarn("No playable bagpipe tracks found in " + _trackDirectory + ". Add non-empty MP3, OGG, or WAV files."); } else { LogInfo($"Loaded {_trackPaths.Count} playable bagpipe track(s) from {_trackDirectory}."); } } } private void UpdateTrackDirectory(string directorySetting) { if (string.IsNullOrWhiteSpace(directorySetting)) { directorySetting = "BagPipesTracks"; if (_trackDirectoryConfig != null) { _trackDirectoryConfig.Value = "BagPipesTracks"; } } string text = (_trackDirectory = ResolveTrackDirectory(directorySetting)); Directory.CreateDirectory(text); if (IsDefaultTrackDirectory(directorySetting)) { EnsurePlaceholderTrackExists(text); } RefreshTrackLibrary(); } private string ResolveTrackDirectory(string configuredPath) { if (Path.IsPathRooted(configuredPath)) { return configuredPath; } return Path.Combine(_pluginDirectory ?? Paths.PluginPath, configuredPath); } private static bool IsDefaultTrackDirectory(string directorySetting) { return string.Equals(directorySetting, "BagPipesTracks", StringComparison.OrdinalIgnoreCase); } private void StartBagpipes() { if ((Object)(object)_audioSource == (Object)null) { LogWarn("Audio source missing; cannot start playback."); return; } SyncAudioSourceWithMusicMan(); if (_trackPaths.Count == 0) { RefreshTrackLibrary(); if (_trackPaths.Count == 0) { return; } } string text = SelectRandomTrack(); if (text == null) { LogWarn("Track selection returned no result."); return; } _pendingPlayback = true; _pendingTrackPath = text; if (_clipCache.TryGetValue(text, out AudioClip value) && (Object)(object)value != (Object)null) { PlayClip(value, text); return; } CancelFade(); if (_clipLoadRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_clipLoadRoutine); } _clipLoadRoutine = ((MonoBehaviour)this).StartCoroutine(LoadClipCoroutine(text)); } private void PauseBagpipesForGrace() { if ((Object)(object)_audioSource == (Object)null || (Object)(object)_audioSource.clip == (Object)null || _isPausedForGrace) { return; } _isPlaying = false; _isPausedForGrace = true; StartFade(0f, 0.5f, delegate { if (!((Object)(object)_audioSource == (Object)null)) { if (_isPausedForGrace && Time.time <= _resumeUntil) { _audioSource.Pause(); } else { _audioSource.Stop(); _audioSource.clip = null; _isPausedForGrace = false; } SetGameMusicSuppressed(shouldSuppress: false); } }); } private void ResumeBagpipes() { if ((Object)(object)_audioSource == (Object)null || (Object)(object)_audioSource.clip == (Object)null) { _isPausedForGrace = false; StartBagpipes(); return; } CancelFade(); if (!_audioSource.isPlaying) { _audioSource.UnPause(); } _isPausedForGrace = false; _isPlaying = true; SetGameMusicSuppressed(shouldSuppress: true); StartFade(_volume.Value, 1.5f); } private void StopBagpipes(bool immediate = false) { CancelPendingPlayback(); if ((Object)(object)_audioSource == (Object)null) { _isPlaying = false; _isPausedForGrace = false; _resumeUntil = 0f; return; } CancelFade(); _isPlaying = false; _isPausedForGrace = false; _resumeUntil = 0f; if (immediate) { _audioSource.Stop(); _audioSource.clip = null; _audioSource.volume = 0f; SetGameMusicSuppressed(shouldSuppress: false); return; } StartFade(0f, 0.5f, delegate { if (!((Object)(object)_audioSource == (Object)null)) { _audioSource.Stop(); _audioSource.clip = null; SetGameMusicSuppressed(shouldSuppress: false); } }); } private void StartFade(float targetVolume, float fadeDuration, Action? onComplete = null) { CancelFade(); _fadeRoutine = ((MonoBehaviour)this).StartCoroutine(FadeVolume(targetVolume, fadeDuration, onComplete)); } private void CancelFade() { if (_fadeRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_fadeRoutine); _fadeRoutine = null; } } [IteratorStateMachine(typeof(d__67))] private IEnumerator FadeVolume(float targetVolume, float fadeDuration, Action? onComplete = null) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__67(0) { <>4__this = this, targetVolume = targetVolume, fadeDuration = fadeDuration, onComplete = onComplete }; } private void SetGameMusicSuppressed(bool shouldSuppress) { AudioSource musicManAudioSource = GetMusicManAudioSource(); if ((Object)(object)musicManAudioSource == (Object)null) { return; } if (shouldSuppress && !_musicManSuppressed) { _storedMusicVolume = musicManAudioSource.volume; _musicManWasPlaying = musicManAudioSource.isPlaying; musicManAudioSource.volume = 0f; if (_musicManWasPlaying) { musicManAudioSource.Pause(); } _musicManSuppressed = true; } else if (!shouldSuppress && _musicManSuppressed) { musicManAudioSource.volume = _storedMusicVolume; if (_musicManWasPlaying) { musicManAudioSource.UnPause(); } _musicManSuppressed = false; _musicManWasPlaying = false; } } private AudioSource? GetMusicManAudioSource() { MusicMan instance = MusicMan.instance; if ((Object)(object)instance == (Object)null || MusicManMusicSourceField == null) { return null; } try { object? value = MusicManMusicSourceField.GetValue(instance); return (AudioSource?)((value is AudioSource) ? value : null); } catch (Exception ex) { LogWarn("Unable to resolve MusicMan audio source via reflection: " + ex.GetType().Name + ": " + ex.Message); return null; } } private string? SelectRandomTrack() { if (_trackPaths.Count == 0) { return null; } int index = _rng.Next(0, _trackPaths.Count); return _trackPaths[index]; } [IteratorStateMachine(typeof(d__71))] private IEnumerator LoadClipCoroutine(string trackPath) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__71(0) { <>4__this = this, trackPath = trackPath }; } private void PlayClip(AudioClip clip, string trackPath) { if ((Object)(object)_audioSource == (Object)null) { LogWarn("Audio source missing during playback start."); return; } LogInfo("Starting clip " + Path.GetFileName(trackPath) + "."); CancelFade(); _pendingPlayback = false; _pendingTrackPath = null; _isPausedForGrace = false; _isPlaying = true; _audioSource.clip = clip; _audioSource.volume = 0f; _audioSource.mute = false; _audioSource.Play(); SetGameMusicSuppressed(shouldSuppress: true); StartFade(_volume.Value, 1.5f); } private void CancelPendingPlayback() { _pendingPlayback = false; _pendingTrackPath = null; if (_clipLoadRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_clipLoadRoutine); _clipLoadRoutine = null; } } private void ClearClipCache() { foreach (AudioClip value in _clipCache.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } _clipCache.Clear(); } private static AudioType GetAudioTypeForExtension(string extension) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) return (AudioType)(extension.ToLowerInvariant() switch { ".mp3" => 13, ".ogg" => 14, ".wav" => 20, _ => 0, }); } private void LogDebug(string message) { ((BaseUnityPlugin)this).Logger.LogDebug((object)message); _fileLogger?.Debug(message); } private void LogInfo(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); _fileLogger?.Info(message); } private void LogWarn(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); _fileLogger?.Warn(message); } private void LogError(string message) { ((BaseUnityPlugin)this).Logger.LogError((object)message); _fileLogger?.Error(message); } } } namespace SailingBagpipes.Logging { internal sealed class PluginLogger { private const int MaxLogFiles = 7; private readonly string _projectName; private readonly string _logDirectory; private readonly string _logPath; private readonly object _gate = new object(); internal PluginLogger(string projectName, string pluginDirectory) { _projectName = projectName; _logDirectory = Path.Combine(pluginDirectory, "Logs"); Directory.CreateDirectory(_logDirectory); _logPath = Path.Combine(_logDirectory, $"{_projectName}-{DateTime.Now:yyyy-MM-dd-HH-mm}.log"); CleanupOldLogs(); } internal void Debug(string message) { Write("DEBUG", message); } internal void Info(string message) { Write("INFO", message); } internal void Warn(string message) { Write("WARN", message); } internal void Error(string message) { Write("ERROR", message); } private void Write(string level, string message) { string text = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}][{level}] {message}"; lock (_gate) { File.AppendAllText(_logPath, text + Environment.NewLine); } } private void CleanupOldLogs() { foreach (string item in Directory.GetFiles(_logDirectory, _projectName + "-*.log").OrderByDescending(File.GetCreationTimeUtc).ToList() .Skip(7)) { try { File.Delete(item); } catch (IOException) { } } } } }