Decompiled source of SlowSailingBagpipes v1.0.5

plugins/SailingBagpipes.dll

Decompiled 2 weeks 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.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 <FadeVolume>d__67 : IEnumerator<object>, 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 <startVolume>5__2;

			private float <elapsed>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <FadeVolume>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;
					}
					<startVolume>5__2 = plugin._audioSource.volume;
					<elapsed>5__3 = 0f;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if (<elapsed>5__3 < fadeDuration)
				{
					if ((Object)(object)plugin._audioSource == (Object)null)
					{
						return false;
					}
					<elapsed>5__3 += Time.deltaTime;
					float num2 = Mathf.Clamp01(<elapsed>5__3 / fadeDuration);
					plugin._audioSource.volume = Mathf.Lerp(<startVolume>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 <LoadClipCoroutine>d__71 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public Plugin <>4__this;

			public string trackPath;

			private UnityWebRequest <request>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadClipCoroutine>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();
					}
				}
				<request>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));
						<request>5__2 = UnityWebRequestMultimedia.GetAudioClip(uri.AbsoluteUri, audioTypeForExtension);
						<>1__state = -3;
						<>2__current = <request>5__2.SendWebRequest();
						<>1__state = 1;
						result = true;
						break;
					}
					case 1:
						<>1__state = -3;
						if ((int)<request>5__2.result != 1)
						{
							plugin.LogError("Failed to load " + trackPath + ": " + <request>5__2.error);
							plugin._pendingPlayback = false;
							plugin._pendingTrackPath = null;
							plugin._clipLoadRoutine = null;
							result = false;
						}
						else
						{
							AudioClip content = DownloadHandlerAudioClip.GetContent(<request>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 (<request>5__2 != null)
				{
					((IDisposable)<request>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<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 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<bool>("General", "Enabled", true, "Master toggle for the Slow Sailing Bagpipes mod.");
			_volume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Volume", 0.85f, new ConfigDescription("Playback volume for the bagpipe loop.", (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_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<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();
			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<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) && !_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<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(_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(<FadeVolume>d__67))]
		private IEnumerator FadeVolume(float targetVolume, float fadeDuration, Action? onComplete = null)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <FadeVolume>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(<LoadClipCoroutine>d__71))]
		private IEnumerator LoadClipCoroutine(string trackPath)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadClipCoroutine>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)
				{
				}
			}
		}
	}
}