Decompiled source of FishCues v0.1.1

FishCues.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("FishCues")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+71e76beb6276bed7b59df336ec1182615c484abf")]
[assembly: AssemblyProduct("FishCues")]
[assembly: AssemblyTitle("FishCues")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FishCues
{
	internal static class Clips
	{
		private const float RescanSeconds = 15f;

		private static Dictionary<string, AudioClip> _byName;

		private static float _nextScan = -1f;

		private static readonly HashSet<string> Warned = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		public static AudioClip Find(string preferred, params string[] fallbacks)
		{
			Dictionary<string, AudioClip> dictionary = Map();
			if (dictionary == null)
			{
				return null;
			}
			if (!string.IsNullOrWhiteSpace(preferred) && dictionary.TryGetValue(preferred.Trim(), out var value))
			{
				return value;
			}
			if (fallbacks != null)
			{
				foreach (string key in fallbacks)
				{
					if (dictionary.TryGetValue(key, out value))
					{
						return value;
					}
				}
			}
			return null;
		}

		public static void WarnMissing(string role, string configKey, string preferred)
		{
			if (Warned.Add(role) && FishCuesPlugin.Log != null)
			{
				FishCuesPlugin.Log.LogWarning((object)("FishCues: no game sound called '" + preferred + "', so the " + role + " cue is silent. Set " + configKey + " to any sound name from the game."));
			}
		}

		private static Dictionary<string, AudioClip> Map()
		{
			if (_byName != null && _byName.Count > 0)
			{
				return _byName;
			}
			if (Time.time < _nextScan)
			{
				return null;
			}
			_nextScan = Time.time + 15f;
			AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
			if (array == null || array.Length == 0)
			{
				return null;
			}
			Dictionary<string, AudioClip> dictionary = new Dictionary<string, AudioClip>(array.Length, StringComparer.OrdinalIgnoreCase);
			foreach (AudioClip val in array)
			{
				if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(((Object)val).name) && !dictionary.ContainsKey(((Object)val).name))
				{
					dictionary[((Object)val).name] = val;
				}
			}
			_byName = dictionary;
			ManualLogSource log = FishCuesPlugin.Log;
			if (log != null)
			{
				log.LogDebug((object)$"FishCues: indexed {dictionary.Count} game sounds.");
			}
			return dictionary;
		}
	}
	[HarmonyPatch(typeof(FishingFloat), "RPC_Nibble")]
	internal static class NibblePatch
	{
		private static void Prefix(FishingFloat __instance, out float __state)
		{
			__state = __instance.m_nibbleTime;
		}

		private static void Postfix(FishingFloat __instance, bool correctBait, float __state)
		{
			try
			{
				if (correctBait && __instance.m_nibbleTime != __state)
				{
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer != (Object)null && FishCuesPlugin.IsMine(__instance, localPlayer))
					{
						FishCuesPlugin.Plink();
					}
				}
			}
			catch (Exception e)
			{
				FishCuesPlugin.WarnOnce("NibblePatch", e);
			}
		}
	}
	[BepInPlugin("online.buddycloud.fishcues", "FishCues", "0.1.1")]
	public class FishCuesPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "online.buddycloud.fishcues";

		public const string PluginName = "FishCues";

		public const string PluginVersion = "0.1.1";

		internal static ManualLogSource Log;

		internal static FishCuesPlugin Self;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> PlinkClip;

		internal static ConfigEntry<float> PlinkVolume;

		internal static ConfigEntry<string> StruggleClip;

		internal static ConfigEntry<float> StruggleVolume;

		internal static ConfigEntry<float> StrugglePitch;

		private AudioSource _plinkSource;

		private AudioSource _toneSource;

		private string _plinkName;

		private string _struggleName;

		private static readonly string[] PlinkFallbacks = new string[3] { "Splash_Water_Small3", "Splash_Water_Small1", "UI_Craft_Finish_01" };

		private static readonly string[] StruggleFallbacks = new string[3] { "Tar_Bubble_Loop", "Boil_CauldronBubble_Loop", "Amb_Caves_WaterDrop_01" };

		private static readonly HashSet<string> Warned = new HashSet<string>();

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Self = this;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Play the fishing audio cues.");
			PlinkClip = ((BaseUnityPlugin)this).Config.Bind<string>("General", "PlinkClip", "Splash_Water_Small2", "Name of any vanilla game sound to play when a fish bites. Defaults to a small water splash - wet rather than alarming, and it belongs to fishing rather than to the crafting menu. Free text: the game holds thousands of sounds.");
			PlinkVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PlinkVolume", 0.15f, "Volume of the 'plink' played when a fish bites (0 disables just the plink). The splash peaks near 0 dBFS so it reads louder than the number: 0.3 is +6 dB, 0.1 -3.5 dB.");
			StruggleClip = ((BaseUnityPlugin)this).Config.Bind<string>("General", "StruggleClip", "Items_Bathtub_Bubbles_Loop", "Looped sound played while the hooked fish struggles. Defaults to a bathtub's bubbles: 21 s long, so no audible cycle, and the bathtub is the only thing in the game that plays it - you will never hear it by accident while fishing.");
			StruggleVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StruggleVolume", 0.25f, "Volume of the tone played while the hooked fish struggles (0 disables just the tone). Silence means the fish is calm.");
			StrugglePitch = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StrugglePitch", 1f, "Pitch of the struggle tone, 0.5 to 2. Raise it if the tone hides under water and wind.");
			Harmony.CreateAndPatchAll(typeof(FishCuesPlugin).Assembly, "online.buddycloud.fishcues");
			Log.LogInfo((object)"FishCues 0.1.1: plink on the bite, tone while the fish struggles.");
		}

		private void Update()
		{
			try
			{
				EnsureSources();
				if (!Enabled.Value || StruggleVolume.Value <= 0f)
				{
					StopTone();
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					StopTone();
					return;
				}
				Fish val = MyCatch(localPlayer);
				if ((Object)(object)val != (Object)null && val.IsEscaping())
				{
					StartTone();
				}
				else
				{
					StopTone();
				}
			}
			catch (Exception e)
			{
				WarnOnce("Update", e);
			}
		}

		private void OnDestroy()
		{
			StopTone();
		}

		private void EnsureSources()
		{
			if ((Object)(object)_plinkSource == (Object)null)
			{
				_plinkSource = ((Component)this).gameObject.AddComponent<AudioSource>();
				_plinkSource.playOnAwake = false;
				_plinkSource.loop = false;
				_plinkSource.spatialBlend = 0f;
			}
			if ((Object)(object)_toneSource == (Object)null)
			{
				_toneSource = ((Component)this).gameObject.AddComponent<AudioSource>();
				_toneSource.playOnAwake = false;
				_toneSource.loop = true;
				_toneSource.spatialBlend = 0f;
			}
			RefreshClips();
			if ((Object)(object)AudioMan.instance != (Object)null)
			{
				if ((Object)(object)_plinkSource.outputAudioMixerGroup == (Object)null)
				{
					_plinkSource.outputAudioMixerGroup = AudioMan.instance.m_guiMixer;
				}
				if ((Object)(object)_toneSource.outputAudioMixerGroup == (Object)null)
				{
					_toneSource.outputAudioMixerGroup = AudioMan.instance.m_guiMixer;
				}
			}
		}

		private void RefreshClips()
		{
			if ((Object)(object)_plinkSource.clip == (Object)null || !string.Equals(_plinkName, PlinkClip.Value, StringComparison.OrdinalIgnoreCase))
			{
				_plinkName = PlinkClip.Value;
				_plinkSource.clip = Clips.Find(_plinkName, PlinkFallbacks);
				if ((Object)(object)_plinkSource.clip == (Object)null)
				{
					Clips.WarnMissing("bite", "PlinkClip", _plinkName);
				}
			}
			if ((Object)(object)_toneSource.clip == (Object)null || !string.Equals(_struggleName, StruggleClip.Value, StringComparison.OrdinalIgnoreCase))
			{
				_struggleName = StruggleClip.Value;
				_toneSource.clip = Clips.Find(_struggleName, StruggleFallbacks);
				if ((Object)(object)_toneSource.clip == (Object)null)
				{
					Clips.WarnMissing("struggle", "StruggleClip", _struggleName);
				}
			}
		}

		private void StartTone()
		{
			if (!((Object)(object)_toneSource.clip == (Object)null))
			{
				_toneSource.volume = Mathf.Clamp01(StruggleVolume.Value);
				_toneSource.pitch = Mathf.Clamp(StrugglePitch.Value, 0.5f, 2f);
				if (!_toneSource.isPlaying)
				{
					_toneSource.Play();
					Log.LogDebug((object)"Struggle tone on.");
				}
			}
		}

		private void StopTone()
		{
			if ((Object)(object)_toneSource != (Object)null && _toneSource.isPlaying)
			{
				_toneSource.Stop();
				Log.LogDebug((object)"Struggle tone off.");
			}
		}

		private static Fish MyCatch(Player player)
		{
			foreach (FishingFloat allInstance in FishingFloat.GetAllInstances())
			{
				if (IsMine(allInstance, player))
				{
					Fish val = allInstance.GetCatch();
					if ((Object)(object)val != (Object)null)
					{
						return val;
					}
				}
			}
			return null;
		}

		internal static bool IsMine(FishingFloat ff, Player player)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ff == (Object)null || (Object)(object)ff.m_nview == (Object)null || !ff.m_nview.IsValid())
			{
				return false;
			}
			long num = ff.m_nview.GetZDO().GetLong(ZDOVars.s_rodOwner, 0L);
			ZDOID zDOID = ((Character)player).GetZDOID();
			return num == ((ZDOID)(ref zDOID)).UserID;
		}

		internal static void Plink()
		{
			try
			{
				FishCuesPlugin self = Self;
				if (!((Object)(object)self == (Object)null) && Enabled.Value && !(PlinkVolume.Value <= 0f))
				{
					self.EnsureSources();
					if ((Object)(object)self._plinkSource != (Object)null && (Object)(object)self._plinkSource.clip != (Object)null)
					{
						self._plinkSource.PlayOneShot(self._plinkSource.clip, Mathf.Clamp01(PlinkVolume.Value));
						Log.LogDebug((object)"Bite plink.");
					}
				}
			}
			catch (Exception e)
			{
				WarnOnce("Plink", e);
			}
		}

		internal static void WarnOnce(string key, Exception e)
		{
			if (Warned.Add(key))
			{
				Log.LogWarning((object)("FishCues: " + key + " failed (" + e.GetType().Name + ": " + e.Message + "). That cue is disabled; the game is unaffected."));
				Log.LogDebug((object)e.ToString());
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}