Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SmoothFishing v1.0.1
SmoothFishing.dll
Decompiled 2 weeks agousing 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)); } } }