Decompiled source of SlowSailingBagpipes v1.0.7

plugins/SailingBagpipes.dll

Decompiled a month ago
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.7.0")]
[assembly: AssemblyInformationalVersion("1.0.7+03821e47e0b46beeaf244887b10685d972ece905")]
[assembly: AssemblyProduct("SailingBagpipes")]
[assembly: AssemblyTitle("SailingBagpipes")]
[assembly: AssemblyVersion("1.0.7.0")]
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;
		}
	}
}
namespace SailingBagpipes
{
	[BepInPlugin("eh.mataeo.valheim.slowsailingbagpipes", "SlowSailingBagpipes", "1.0.7")]
	public class Plugin : BaseUnityPlugin
	{
		private const string PluginGuid = "eh.mataeo.valheim.slowsailingbagpipes";

		private const string PluginName = "SlowSailingBagpipes";

		private const string PluginVersion = "1.0.7";

		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<bool>? _enabled;

		private ConfigEntry<float>? _volume;

		private ConfigEntry<string>? _trackDirectoryConfig;

		private PluginLogger? _fileLogger;

		private GameObject? _audioHolder;

		private AudioSource? _audioSource;

		private readonly Dictionary<string, AudioClip> _clipCache = new Dictionary<string, AudioClip>();

		private readonly Random _rng = new Random();

		private string? _pluginDirectory;

		private string? _trackDirectory;

		private List<string> _trackPaths = new List<string>();

		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 bool _musicManSuppressed;

		private bool _audioSourceConfiguredFromMusicMan;

		private float _fadeLevel;

		private int _syncedMusicManId;

		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<bool>("General", "Enabled", true, "Master toggle for the Slow Sailing Bagpipes mod.");
			_volume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Volume", 0.85f, new ConfigDescription("Bagpipe volume, applied on top of the in-game Master and Music volume sliders (1.0 = as loud as those sliders allow).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			_trackDirectoryConfig = ((BaseUnityPlugin)this).Config.Bind<string>("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_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<bool> 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();
			ReassertMusicSuppression();
			ApplyEffectiveVolume();
			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()) : ((Speed?)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))
			{
				if ((object)args.ChangedSetting == _volume)
				{
					ApplyEffectiveVolume();
				}
				else if (_trackDirectoryConfig != null && (object)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);
				string arg2 = (speedSetting.HasValue ? ((object)speedSetting.GetValueOrDefault()/*cast due to .constrained prefix*/).ToString() : null) ?? "None";
				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<AudioSource>();
			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)
			{
				return;
			}
			MusicMan instance = MusicMan.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)instance).GetInstanceID();
			if (!_audioSourceConfiguredFromMusicMan || instanceID != _syncedMusicManId)
			{
				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;
					_syncedMusicManId = instanceID;
					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<Ship>() ?? controlledComponent.GetComponentInParent<Ship>();
			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<Ship>((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<Player> 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<byte>());
				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<string>(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase)
					where new FileInfo(path).Length > 0
					select path).OrderBy<string, string>((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(1f, 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;
				_fadeLevel = 0f;
				ApplyEffectiveVolume();
				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 targetLevel, float fadeDuration, Action? onComplete = null)
		{
			CancelFade();
			_fadeRoutine = ((MonoBehaviour)this).StartCoroutine(FadeVolume(targetLevel, fadeDuration, onComplete));
		}

		private void CancelFade()
		{
			if (_fadeRoutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_fadeRoutine);
				_fadeRoutine = null;
			}
		}

		private IEnumerator FadeVolume(float targetLevel, float fadeDuration, Action? onComplete = null)
		{
			if ((Object)(object)_audioSource == (Object)null)
			{
				yield break;
			}
			if (fadeDuration <= 0f)
			{
				_fadeLevel = targetLevel;
				ApplyEffectiveVolume();
				_fadeRoutine = null;
				onComplete?.Invoke();
				yield break;
			}
			float startLevel = _fadeLevel;
			float elapsed = 0f;
			while (elapsed < fadeDuration)
			{
				if ((Object)(object)_audioSource == (Object)null)
				{
					yield break;
				}
				elapsed += Time.deltaTime;
				float num = Mathf.Clamp01(elapsed / fadeDuration);
				_fadeLevel = Mathf.Lerp(startLevel, targetLevel, num);
				ApplyEffectiveVolume();
				yield return null;
			}
			_fadeLevel = targetLevel;
			ApplyEffectiveVolume();
			_fadeRoutine = null;
			onComplete?.Invoke();
		}

		private void ApplyEffectiveVolume()
		{
			if (!((Object)(object)_audioSource == (Object)null) && _volume != null)
			{
				float num = Mathf.Clamp01(_volume.Value);
				_audioSource.volume = _fadeLevel * num * GetGameMusicVolume();
			}
		}

		private static float GetGameMusicVolume()
		{
			return Mathf.Clamp01(MusicMan.m_masterMusicVolume);
		}

		private void SetGameMusicSuppressed(bool shouldSuppress)
		{
			AudioSource musicManAudioSource = GetMusicManAudioSource();
			if ((Object)(object)musicManAudioSource == (Object)null)
			{
				_musicManSuppressed = false;
				return;
			}
			musicManAudioSource.mute = shouldSuppress;
			_musicManSuppressed = shouldSuppress;
		}

		private void ReassertMusicSuppression()
		{
			if (_musicManSuppressed)
			{
				AudioSource musicManAudioSource = GetMusicManAudioSource();
				if ((Object)(object)musicManAudioSource != (Object)null && !musicManAudioSource.mute)
				{
					musicManAudioSource.mute = true;
				}
			}
		}

		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];
		}

		private IEnumerator LoadClipCoroutine(string trackPath)
		{
			LogInfo("Loading track " + Path.GetFileName(trackPath) + ".");
			Uri uri = new Uri(trackPath);
			AudioType audioTypeForExtension = GetAudioTypeForExtension(Path.GetExtension(trackPath));
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(uri.AbsoluteUri, audioTypeForExtension);
			try
			{
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					LogError("Failed to load " + trackPath + ": " + request.error);
					_pendingPlayback = false;
					_pendingTrackPath = null;
					_clipLoadRoutine = null;
					yield break;
				}
				AudioClip content = DownloadHandlerAudioClip.GetContent(request);
				((Object)content).name = Path.GetFileNameWithoutExtension(trackPath);
				_clipCache[trackPath] = content;
				LogInfo($"Loaded clip metadata: length={content.length:F1}s channels={content.channels} frequency={content.frequency}.");
				if (!_pendingPlayback || _pendingTrackPath != trackPath)
				{
					_clipLoadRoutine = null;
					yield break;
				}
				PlayClip(content, trackPath);
				_clipLoadRoutine = null;
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		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;
			_fadeLevel = 0f;
			ApplyEffectiveVolume();
			_audioSource.mute = false;
			_audioSource.Play();
			SetGameMusicSuppressed(shouldSuppress: true);
			StartFade(1f, 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);
		}
	}
	internal static class GeneratedModVersion
	{
		public const string Value = "1.0.7";
	}
}
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)
				{
				}
			}
		}
	}
}