Decompiled source of SmoothFishing v1.0.1

SmoothFishing.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("SmoothFishing")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SmoothFishing")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("7194ee0b-89ee-4b45-86cd-4ec02fd31dae")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SmoothFishing;

internal static class BiteSound
{
	private static AudioClip _clip;

	private static AudioSource _source;

	private static string _loadedFile;

	internal static void Init(Plugin plugin)
	{
		_source = ((Component)plugin).gameObject.AddComponent<AudioSource>();
		_source.playOnAwake = false;
		_source.spatialBlend = 0f;
		((MonoBehaviour)plugin).StartCoroutine(Load(plugin));
	}

	private static IEnumerator Load(Plugin plugin)
	{
		string fileName = Plugin.SoundFile.Value;
		if (string.IsNullOrWhiteSpace(fileName))
		{
			yield break;
		}
		string dir = Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location);
		string path = Path.Combine(dir ?? "", fileName);
		if (!File.Exists(path))
		{
			Plugin.Log.LogWarning((object)("Sound file not found: " + path + " (the mod keeps working without sound)"));
			yield break;
		}
		AudioType type = TypeFor(path);
		string url = new Uri(path).AbsoluteUri;
		UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(url, type);
		try
		{
			yield return req.SendWebRequest();
			if ((int)req.result != 1)
			{
				Plugin.Log.LogWarning((object)("Sound file could not be loaded: " + req.error));
				yield break;
			}
			try
			{
				_clip = DownloadHandlerAudioClip.GetContent(req);
				_loadedFile = fileName;
				Plugin.Debug("Sound loaded: " + fileName);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Sound file is not a valid audio format: " + ex.Message));
			}
		}
		finally
		{
			((IDisposable)req)?.Dispose();
		}
	}

	private static AudioType TypeFor(string path)
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch
		{
			".wav" => 20, 
			".ogg" => 14, 
			".mp3" => 13, 
			_ => 0, 
		});
	}

	internal static void Play()
	{
		if ((Object)(object)_clip == (Object)null || (Object)(object)_source == (Object)null)
		{
			return;
		}
		try
		{
			_source.PlayOneShot(_clip, Mathf.Clamp01(Plugin.SoundVolume.Value));
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Sound could not be played: " + ex.Message));
		}
	}
}
internal static class FishPicker
{
	private sealed class Entry
	{
		public GameObject Prefab;

		public int Biomes = -1;

		public string RequiredKey;

		public float MinDepth;

		public float MaxDepth;

		public readonly Dictionary<string, float> Baits = new Dictionary<string, float>();
	}

	private static List<Entry> _entries;

	internal static void Reset()
	{
		_entries = null;
	}

	internal static GameObject Pick(Vector3 floatPos, float depth, string bait)
	{
		//IL_0040: 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_0047: Expected I4, but got Unknown
		if (_entries == null)
		{
			Build();
		}
		if (_entries == null || _entries.Count == 0)
		{
			return null;
		}
		int num = (int)Heightmap.FindBiome(floatPos);
		List<Entry> list = new List<Entry>();
		foreach (Entry entry in _entries)
		{
			if ((entry.Biomes & num) != 0 && (string.IsNullOrEmpty(entry.RequiredKey) || !((Object)(object)ZoneSystem.instance != (Object)null) || ZoneSystem.instance.GetGlobalKey(entry.RequiredKey)) && (!(entry.MaxDepth > entry.MinDepth) || (!(depth < entry.MinDepth) && !(depth > entry.MaxDepth))))
			{
				list.Add(entry);
			}
		}
		if (list.Count == 0)
		{
			return null;
		}
		string baitKey = GameApi.Normalize(bait);
		bool flag = false;
		if (baitKey.Length > 0)
		{
			List<Entry> list2 = list.FindAll((Entry e) => e.Baits.ContainsKey(baitKey));
			if (list2.Count > 0)
			{
				list = list2;
				flag = true;
			}
		}
		float num2 = 0f;
		float[] array = new float[list.Count];
		for (int num3 = 0; num3 < list.Count; num3++)
		{
			num2 += (array[num3] = (flag ? Mathf.Max(0.01f, list[num3].Baits[baitKey]) : 1f));
		}
		float num4 = Random.value * num2;
		for (int num5 = 0; num5 < list.Count; num5++)
		{
			num4 -= array[num5];
			if (num4 <= 0f)
			{
				return list[num5].Prefab;
			}
		}
		return list[list.Count - 1].Prefab;
	}

	private static void Build()
	{
		try
		{
			SpawnSystem[] array = Object.FindObjectsOfType<SpawnSystem>();
			if (array.Length == 0)
			{
				return;
			}
			List<Entry> list = new List<Entry>();
			if (GameApi.GetField(array[0], "m_spawnLists") is IEnumerable enumerable)
			{
				foreach (object item in enumerable)
				{
					if (!(GameApi.GetField(item, "m_spawners") is IEnumerable enumerable2))
					{
						continue;
					}
					foreach (object item2 in enumerable2)
					{
						object field = GameApi.GetField(item2, "m_prefab");
						GameObject val = (GameObject)((field is GameObject) ? field : null);
						if (!((Object)(object)val == (Object)null) && !((Object)(object)val.GetComponent<Fish>() == (Object)null) && (!(GameApi.GetField(item2, "m_enabled") is bool flag) || flag))
						{
							Entry entry = new Entry
							{
								Prefab = val
							};
							object field2 = GameApi.GetField(item2, "m_biome");
							if (field2 != null)
							{
								entry.Biomes = Convert.ToInt32(field2);
							}
							entry.RequiredKey = GameApi.GetField(item2, "m_requiredGlobalKey") as string;
							if (GameApi.GetField(item2, "m_minOceanDepth") is float minDepth)
							{
								entry.MinDepth = minDepth;
							}
							if (GameApi.GetField(item2, "m_maxOceanDepth") is float maxDepth)
							{
								entry.MaxDepth = maxDepth;
							}
							ReadBaits(val, entry);
							list.Add(entry);
						}
					}
				}
			}
			if (list.Count == 0)
			{
				Plugin.Log.LogWarning((object)"No fish spawn data found - using fallback over all fish prefabs.");
				if ((Object)(object)ZNetScene.instance != (Object)null && GameApi.GetField(ZNetScene.instance, "m_prefabs") is IEnumerable enumerable3)
				{
					foreach (object item3 in enumerable3)
					{
						GameObject val2 = (GameObject)((item3 is GameObject) ? item3 : null);
						if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.GetComponent<Fish>() == (Object)null))
						{
							Entry entry2 = new Entry
							{
								Prefab = val2
							};
							ReadBaits(val2, entry2);
							list.Add(entry2);
						}
					}
				}
			}
			_entries = list;
			Plugin.Debug("FishPicker: " + list.Count + " fish entries loaded.");
		}
		catch (Exception ex)
		{
			_entries = new List<Entry>();
			Plugin.Log.LogError((object)("FishPicker.Build failed: " + ex));
		}
	}

	private static void ReadBaits(GameObject prefab, Entry entry)
	{
		try
		{
			Fish component = prefab.GetComponent<Fish>();
			if (!(GameApi.GetField(component, "m_baits") is IEnumerable enumerable))
			{
				return;
			}
			foreach (object item in enumerable)
			{
				object field = GameApi.GetField(item, "m_bait");
				Component val = (Component)((field is Component) ? field : null);
				if (!((Object)(object)val == (Object)null))
				{
					float value = ((GameApi.GetField(item, "m_chance") is float num) ? num : 1f);
					entry.Baits[GameApi.Normalize(((Object)val.gameObject).name)] = value;
				}
			}
		}
		catch
		{
		}
	}
}
internal sealed class FloatTracker
{
	private sealed class FloatState
	{
		public Vector3 LastPos;

		public float SettledSince = -1f;

		public float NextSpawnAt = -1f;

		public int SpawnCount;

		public bool Seen;
	}

	private sealed class Tracked
	{
		public GameObject Go;

		public Fish Fish;

		public float SpawnTime;

		public int FloatId;

		public float OrphanSince = -1f;
	}

	private const float TickInterval = 0.25f;

	private const float SettleTime = 1f;

	private const float StillThresholdSqr = 0.0064f;

	private readonly Dictionary<int, FloatState> _states = new Dictionary<int, FloatState>();

	private readonly List<Tracked> _fish = new List<Tracked>();

	private readonly List<int> _goneBuffer = new List<int>();

	private float _nextTick;

	private float _cooldownUntil;

	private int _errors;

	private bool _disabled;

	private int _groundMask = -1;

	internal void Tick()
	{
		if (_disabled)
		{
			return;
		}
		float time = Time.time;
		if (time < _nextTick)
		{
			return;
		}
		_nextTick = time + 0.25f;
		try
		{
			TickInner(time);
		}
		catch (Exception ex)
		{
			_errors++;
			if (_errors <= 3)
			{
				Plugin.Log.LogError((object)("Error in FloatTracker: " + ex));
			}
			if (_errors >= 10)
			{
				_disabled = true;
				Plugin.Log.LogError((object)"Too many errors - fish spawning is disabled for safety (the game keeps running normally).");
			}
		}
	}

	private void TickInner(float now)
	{
		//IL_0139: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
		{
			if (_states.Count > 0 || _fish.Count > 0)
			{
				_states.Clear();
				_fish.Clear();
				FishPicker.Reset();
			}
			return;
		}
		foreach (FloatState value2 in _states.Values)
		{
			value2.Seen = false;
		}
		foreach (FishingFloat @float in GameApi.GetFloats())
		{
			if ((Object)(object)@float == (Object)null)
			{
				continue;
			}
			ZNetView component = ((Component)@float).GetComponent<ZNetView>();
			if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner())
			{
				int instanceID = ((Object)@float).GetInstanceID();
				if (!_states.TryGetValue(instanceID, out var value))
				{
					value = new FloatState
					{
						LastPos = ((Component)@float).transform.position
					};
					_states[instanceID] = value;
				}
				value.Seen = true;
				UpdateFloat(@float, instanceID, value, now);
			}
		}
		_goneBuffer.Clear();
		foreach (KeyValuePair<int, FloatState> state in _states)
		{
			if (!state.Value.Seen)
			{
				_goneBuffer.Add(state.Key);
			}
		}
		foreach (int item in _goneBuffer)
		{
			_states.Remove(item);
			foreach (Tracked item2 in _fish)
			{
				if (item2.FloatId == item && item2.OrphanSince < 0f)
				{
					item2.OrphanSince = now;
				}
			}
		}
		UpdateFish(now);
	}

	private void UpdateFloat(FishingFloat f, int id, FloatState st, float now)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		Vector3 position = ((Component)f).transform.position;
		float num = position.x - st.LastPos.x;
		float num2 = position.z - st.LastPos.z;
		st.LastPos = position;
		if (num * num + num2 * num2 > 0.0064f)
		{
			st.SettledSince = -1f;
			st.NextSpawnAt = -1f;
			return;
		}
		if (st.SettledSince < 0f)
		{
			st.SettledSince = now;
		}
		if (now - st.SettledSince < 1f)
		{
			return;
		}
		if (GameApi.FloatHasCatch(f))
		{
			st.NextSpawnAt = -1f;
		}
		else
		{
			if (st.SpawnCount >= Plugin.MaxSpawnsPerCast.Value)
			{
				return;
			}
			if (st.NextSpawnAt < 0f)
			{
				st.NextSpawnAt = now + RandomDelay();
			}
			else if (!(now < st.NextSpawnAt))
			{
				st.NextSpawnAt = now + RandomDelay();
				if (TrySpawn(f, id, now))
				{
					st.SpawnCount++;
				}
			}
		}
	}

	private static float RandomDelay()
	{
		float num = Mathf.Max(0.5f, Mathf.Min(Plugin.MinDelay.Value, Plugin.MaxDelay.Value));
		float num2 = Mathf.Max(num, Plugin.MaxDelay.Value);
		return Random.Range(num, num2);
	}

	private bool TrySpawn(FishingFloat f, int floatId, float now)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_014e: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0172: Unknown result type (might be due to invalid IL or missing references)
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		if (now < _cooldownUntil)
		{
			return false;
		}
		if (_fish.Count >= Plugin.MaxPendingFish.Value)
		{
			return false;
		}
		Vector3 position = ((Component)f).transform.position;
		float num = GameApi.WaterLevel();
		if (position.y > num + 0.8f || position.y < num - 3f)
		{
			return false;
		}
		if (CountNearbyFish(position, Plugin.NearbyRadius.Value) >= Plugin.MaxFishNearFloat.Value)
		{
			return false;
		}
		string bait = GameApi.GetBait(f);
		float num2 = GameApi.BaitRange(f);
		float num3 = Mathf.Min(Plugin.MaxDistance.Value, num2 - 1f);
		if (num3 < 3f)
		{
			return false;
		}
		float minD = Mathf.Clamp(Plugin.MinDistance.Value, 2f, num3 - 1f);
		for (int i = 0; i < 10; i++)
		{
			if (!TryPickSpot(position, num, minD, num3, out var spot, out var depth))
			{
				continue;
			}
			GameObject val = FishPicker.Pick(position, depth, bait);
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = position - spot;
				val2.y = 0f;
				Quaternion val3 = ((((Vector3)(ref val2)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(val2) : Quaternion.identity);
				GameObject val4 = Object.Instantiate<GameObject>(val, spot, val3);
				if ((Object)(object)val4 == (Object)null)
				{
					return false;
				}
				_fish.Add(new Tracked
				{
					Go = val4,
					Fish = val4.GetComponent<Fish>(),
					SpawnTime = now,
					FloatId = floatId
				});
				_cooldownUntil = now + Mathf.Max(0f, Plugin.SpawnCooldown.Value);
				Plugin.Debug("Fish spawned: " + ((Object)val).name + " (depth " + depth.ToString("0.0") + " m, bait '" + bait + "')");
				return true;
			}
		}
		return false;
	}

	private bool TryPickSpot(Vector3 floatPos, float water, float minD, float maxD, out Vector3 spot, out float depth)
	{
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0168: Unknown result type (might be due to invalid IL or missing references)
		//IL_016d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_011b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		if (_groundMask == -1)
		{
			_groundMask = LayerMask.GetMask(new string[3] { "terrain", "static_solid", "Default" });
		}
		float num = Mathf.Max(0.5f, Plugin.MinWaterDepth.Value);
		Vector3 val = default(Vector3);
		RaycastHit val2 = default(RaycastHit);
		Vector3 val3 = default(Vector3);
		for (int i = 0; i < 8; i++)
		{
			float num2 = Random.value * (float)Math.PI * 2f;
			float num3 = Random.Range(minD, maxD);
			float num4 = floatPos.x + Mathf.Cos(num2) * num3;
			float num5 = floatPos.z + Mathf.Sin(num2) * num3;
			((Vector3)(ref val))..ctor(num4, water + 5f, num5);
			float num6 = ((!Physics.Raycast(val, Vector3.down, ref val2, 200f, _groundMask, (QueryTriggerInteraction)1)) ? (water - 200f) : ((RaycastHit)(ref val2)).point.y);
			depth = water - num6;
			if (!(depth < num))
			{
				spot = new Vector3(num4, water - Mathf.Min(0.8f, depth * 0.5f), num5);
				((Vector3)(ref val3))..ctor(floatPos.x, water - 0.2f, floatPos.z);
				if (!Physics.Linecast(val3, spot, _groundMask, (QueryTriggerInteraction)1))
				{
					return true;
				}
			}
		}
		spot = Vector3.zero;
		depth = 0f;
		return false;
	}

	private static int CountNearbyFish(Vector3 pos, float radius)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		float num = radius * radius;
		int num2 = 0;
		Fish[] array = Object.FindObjectsOfType<Fish>();
		foreach (Fish val in array)
		{
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = ((Component)val).transform.position - pos;
				if (!(((Vector3)(ref val2)).sqrMagnitude > num) && GameApi.IsFishHooked(val) != true)
				{
					num2++;
				}
			}
		}
		return num2;
	}

	private void UpdateFish(float now)
	{
		float num = Mathf.Max(10f, Plugin.FishLifetime.Value);
		float num2 = Mathf.Max(0f, Plugin.OrphanGrace.Value);
		for (int num3 = _fish.Count - 1; num3 >= 0; num3--)
		{
			Tracked tracked = _fish[num3];
			if ((Object)(object)tracked.Go == (Object)null)
			{
				_fish.RemoveAt(num3);
			}
			else if (GameApi.IsFishHooked(tracked.Fish) != false)
			{
				_fish.RemoveAt(num3);
			}
			else
			{
				bool flag = now - tracked.SpawnTime > num;
				bool flag2 = tracked.OrphanSince >= 0f && now - tracked.OrphanSince > num2;
				if (flag || flag2)
				{
					Despawn(tracked.Go);
					_fish.RemoveAt(num3);
				}
			}
		}
	}

	private static void Despawn(GameObject go)
	{
		try
		{
			ZNetView component = go.GetComponent<ZNetView>();
			if ((Object)(object)component != (Object)null && component.IsValid())
			{
				if (!component.IsOwner())
				{
					component.ClaimOwnership();
				}
				ZNetScene.instance.Destroy(go);
			}
			else
			{
				Object.Destroy((Object)(object)go);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Fish could not be removed: " + ex.Message));
		}
	}
}
internal static class GameApi
{
	private static readonly MethodInfo GetAllFloats = Safe(() => AccessTools.Method(typeof(FishingFloat), "GetAllInstances", (Type[])null, (Type[])null));

	private static readonly MethodInfo GetCatchMethod = Safe(() => AccessTools.Method(typeof(FishingFloat), "GetCatch", (Type[])null, (Type[])null));

	private static readonly FieldInfo FloatRange = Safe(() => AccessTools.Field(typeof(FishingFloat), "m_range"));

	private static readonly MethodInfo IsHookedMethod = Safe(() => AccessTools.Method(typeof(Fish), "IsHooked", (Type[])null, (Type[])null));

	private static readonly FieldInfo WaterLevelField = Safe(() => AccessTools.Field(typeof(ZoneSystem), "m_waterLevel"));

	private static readonly Dictionary<string, FieldInfo> FieldCache = new Dictionary<string, FieldInfo>();

	internal static T Safe<T>(Func<T> f) where T : class
	{
		try
		{
			return f();
		}
		catch
		{
			return null;
		}
	}

	internal static MethodInfo FindMethod(Type type, string name)
	{
		return Safe(() => AccessTools.Method(type, name, (Type[])null, (Type[])null));
	}

	internal static object GetField(object obj, string name)
	{
		if (obj == null)
		{
			return null;
		}
		try
		{
			Type type = obj.GetType();
			string key = type.FullName + "." + name;
			if (!FieldCache.TryGetValue(key, out var value))
			{
				value = AccessTools.Field(type, name);
				FieldCache[key] = value;
			}
			return value?.GetValue(obj);
		}
		catch
		{
			return null;
		}
	}

	internal static List<FishingFloat> GetFloats()
	{
		try
		{
			if (GetAllFloats != null && GetAllFloats.Invoke(null, null) is IEnumerable<FishingFloat> collection)
			{
				return new List<FishingFloat>(collection);
			}
		}
		catch
		{
		}
		return new List<FishingFloat>(Object.FindObjectsOfType<FishingFloat>());
	}

	internal static bool FloatHasCatch(FishingFloat f)
	{
		if ((Object)(object)f == (Object)null || GetCatchMethod == null)
		{
			return false;
		}
		try
		{
			object? obj = GetCatchMethod.Invoke(f, null);
			return (Object)((obj is Object) ? obj : null) != (Object)null;
		}
		catch
		{
			return false;
		}
	}

	internal static float BaitRange(FishingFloat f)
	{
		try
		{
			if (FloatRange != null && FloatRange.GetValue(f) is float num && num > 2f)
			{
				return num;
			}
		}
		catch
		{
		}
		return 10f;
	}

	internal static bool? IsFishHooked(Fish fish)
	{
		if ((Object)(object)fish == (Object)null || IsHookedMethod == null)
		{
			return null;
		}
		try
		{
			return IsHookedMethod.Invoke(fish, null) as bool?;
		}
		catch
		{
			return null;
		}
	}

	internal static float WaterLevel()
	{
		try
		{
			if (WaterLevelField != null && (Object)(object)ZoneSystem.instance != (Object)null && WaterLevelField.GetValue(ZoneSystem.instance) is float result)
			{
				return result;
			}
		}
		catch
		{
		}
		return 30f;
	}

	internal static string GetBait(FishingFloat f)
	{
		try
		{
			ZNetView component = ((Component)f).GetComponent<ZNetView>();
			ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
			return (val != null) ? val.GetString("bait", "") : "";
		}
		catch
		{
			return "";
		}
	}

	internal static string Normalize(string name)
	{
		return string.IsNullOrEmpty(name) ? "" : name.Replace("(Clone)", "").Trim().ToLowerInvariant();
	}
}
internal enum SoundTrigger
{
	Nibble,
	Hooked,
	Both
}
[BepInPlugin("com.smoothfishing.valheim", "SmoothFishing", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "com.smoothfishing.valheim";

	public const string PluginName = "SmoothFishing";

	public const string PluginVersion = "1.0.0";

	internal static Plugin Instance;

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<float> MinDelay;

	internal static ConfigEntry<float> MaxDelay;

	internal static ConfigEntry<float> MinDistance;

	internal static ConfigEntry<float> MaxDistance;

	internal static ConfigEntry<float> MinWaterDepth;

	internal static ConfigEntry<int> MaxFishNearFloat;

	internal static ConfigEntry<float> NearbyRadius;

	internal static ConfigEntry<float> SpawnCooldown;

	internal static ConfigEntry<int> MaxPendingFish;

	internal static ConfigEntry<int> MaxSpawnsPerCast;

	internal static ConfigEntry<float> FishLifetime;

	internal static ConfigEntry<float> OrphanGrace;

	internal static ConfigEntry<bool> SoundEnabled;

	internal static ConfigEntry<string> SoundFile;

	internal static ConfigEntry<float> SoundVolume;

	internal static ConfigEntry<SoundTrigger> SoundMode;

	internal static ConfigEntry<float> SoundMinInterval;

	internal static ConfigEntry<bool> DebugLog;

	private Harmony _harmony;

	private FloatTracker _tracker;

	private void Awake()
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfig();
		_tracker = new FloatTracker();
		BiteSound.Init(this);
		_harmony = new Harmony("com.smoothfishing.valheim");
		BitePatches.Apply(_harmony);
		Log.LogInfo((object)"SmoothFishing 1.0.0 loaded.");
	}

	private void Update()
	{
		if (Enabled.Value)
		{
			_tracker.Tick();
		}
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}

	internal static void Debug(string msg)
	{
		if (DebugLog != null && DebugLog.Value)
		{
			Log.LogInfo((object)("[Debug] " + msg));
		}
	}

	private void BindConfig()
	{
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Expected O, but got Unknown
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Expected O, but got Unknown
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Expected O, but got Unknown
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_0110: Expected O, but got Unknown
		//IL_0143: Unknown result type (might be due to invalid IL or missing references)
		//IL_014d: Expected O, but got Unknown
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		//IL_017f: Expected O, but got Unknown
		//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Expected O, but got Unknown
		//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f9: Expected O, but got Unknown
		//IL_0221: Unknown result type (might be due to invalid IL or missing references)
		//IL_022b: Expected O, but got Unknown
		//IL_0253: Unknown result type (might be due to invalid IL or missing references)
		//IL_025d: Expected O, but got Unknown
		//IL_0290: Unknown result type (might be due to invalid IL or missing references)
		//IL_029a: Expected O, but got Unknown
		//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d7: Expected O, but got Unknown
		//IL_034e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0358: Expected O, but got Unknown
		//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b5: Expected O, but got Unknown
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 General", "Enabled", true, "Turns the automatic fish spawning on or off.");
		MinDelay = ((BaseUnityPlugin)this).Config.Bind<float>("2 Spawning", "DelayMin", 3f, new ConfigDescription("Shortest wait (seconds) until a fish appears after you cast.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 120f), Array.Empty<object>()));
		MaxDelay = ((BaseUnityPlugin)this).Config.Bind<float>("2 Spawning", "DelayMax", 8f, new ConfigDescription("Longest wait (seconds) until a fish appears after you cast.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 120f), Array.Empty<object>()));
		MinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("2 Spawning", "DistanceMin", 5f, new ConfigDescription("Minimum distance (meters) between the float and the spawned fish.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 30f), Array.Empty<object>()));
		MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("2 Spawning", "DistanceMax", 9f, new ConfigDescription("Maximum distance (meters). Automatically capped by the game's bait range (about 10 m), because normal fish would not notice the bait from farther away.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 30f), Array.Empty<object>()));
		MinWaterDepth = ((BaseUnityPlugin)this).Config.Bind<float>("2 Spawning", "MinWaterDepth", 1.5f, new ConfigDescription("Fish only spawn in water at least this deep (meters).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), Array.Empty<object>()));
		MaxFishNearFloat = ((BaseUnityPlugin)this).Config.Bind<int>("3 Protection", "MaxFishNearby", 2, new ConfigDescription("If this many fish (including normal ones) are already near the float, no further fish are spawned.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
		NearbyRadius = ((BaseUnityPlugin)this).Config.Bind<float>("3 Protection", "NearbyRadius", 15f, new ConfigDescription("Radius in meters used to count the fish near the float.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>()));
		SpawnCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("3 Protection", "SpawnCooldown", 6f, new ConfigDescription("Minimum seconds between two spawned fish (prevents cast spamming).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
		MaxPendingFish = ((BaseUnityPlugin)this).Config.Bind<int>("3 Protection", "MaxPendingFish", 2, new ConfigDescription("Maximum number of spawned fish per player that have not bitten yet.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
		MaxSpawnsPerCast = ((BaseUnityPlugin)this).Config.Bind<int>("3 Protection", "MaxSpawnsPerCast", 3, new ConfigDescription("Maximum number of fish a single cast may create (prevents endless AFK fishing).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
		FishLifetime = ((BaseUnityPlugin)this).Config.Bind<float>("3 Protection", "FishLifetime", 120f, new ConfigDescription("Seconds until a spawned fish that has not bitten disappears again.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 1800f), Array.Empty<object>()));
		OrphanGrace = ((BaseUnityPlugin)this).Config.Bind<float>("3 Protection", "CleanupDelayAfterReelIn", 20f, new ConfigDescription("Seconds unused fish stay in the water after you reel in your line.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
		SoundEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("4 Sound", "Enabled", true, "Plays your own sound when a fish bites.");
		SoundFile = ((BaseUnityPlugin)this).Config.Bind<string>("4 Sound", "FileName", "bite.wav", "Name of the sound file. It must be in the same folder as SmoothFishing.dll. Recommended: WAV, alternatively OGG or MP3.");
		SoundVolume = ((BaseUnityPlugin)this).Config.Bind<float>("4 Sound", "Volume", 0.7f, new ConfigDescription("Volume of the bite sound from 0 (silent) to 1 (full).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		SoundMode = ((BaseUnityPlugin)this).Config.Bind<SoundTrigger>("4 Sound", "Trigger", SoundTrigger.Nibble, "Nibble = when a fish nibbles at the bait (your cue to react), Hooked = when the fish is on the line, Both = both.");
		SoundMinInterval = ((BaseUnityPlugin)this).Config.Bind<float>("4 Sound", "MinIntervalSeconds", 1.5f, new ConfigDescription("Minimum seconds between two sounds, so it does not ring twice or constantly.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
		DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("5 Debug", "DebugLog", false, "Writes extra information to the BepInEx log (set to true if you have problems).");
	}
}
internal static class BitePatches
{
	private static float _lastPlay = -999f;

	internal static void Apply(Harmony harmony)
	{
		try
		{
			PatchFirst(harmony, new string[2] { "RPC_Nibble", "Nibble" }, "OnNibble", "Nibble");
			PatchFirst(harmony, new string[1] { "SetCatch" }, "OnHooked", "Hooked");
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Sound patches could not be applied: " + ex.Message));
		}
	}

	private static void PatchFirst(Harmony harmony, string[] names, string postfix, string label)
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Expected O, but got Unknown
		foreach (string text in names)
		{
			MethodInfo methodInfo = GameApi.FindMethod(typeof(FishingFloat), text);
			if (!(methodInfo == null))
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BitePatches), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.Debug("Sound patch '" + label + "' applied to FishingFloat." + text);
				return;
			}
		}
		Plugin.Log.LogWarning((object)("No matching method found for sound trigger '" + label + "' - this trigger stays silent."));
	}

	private static void OnNibble(FishingFloat __instance)
	{
		Fire(__instance, SoundTrigger.Nibble);
	}

	private static void OnHooked(FishingFloat __instance)
	{
		if (GameApi.FloatHasCatch(__instance))
		{
			Fire(__instance, SoundTrigger.Hooked);
		}
	}

	private static void Fire(FishingFloat f, SoundTrigger trigger)
	{
		try
		{
			if (!Plugin.SoundEnabled.Value || (Object)(object)f == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			SoundTrigger value = Plugin.SoundMode.Value;
			if (value != SoundTrigger.Both && value != trigger)
			{
				return;
			}
			ZNetView component = ((Component)f).GetComponent<ZNetView>();
			if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner())
			{
				float time = Time.time;
				if (!(time - _lastPlay < Plugin.SoundMinInterval.Value))
				{
					_lastPlay = time;
					BiteSound.Play();
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Error while playing the bite sound: " + ex.Message));
		}
	}
}