Decompiled source of BalancedFisherman v0.1.0

plugins/BalancedFisherman/BalancedFisherman.dll

Decompiled 2 days ago
using System;
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.Bootstrap;
using BepInEx.Configuration;
using ConditionalConfigSync;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("BalancedFisherman.Probe")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("BalancedFisherman")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("BalancedFisherman")]
[assembly: AssemblyTitle("BalancedFisherman")]
[assembly: AssemblyVersion("0.1.0.0")]
[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 Vercadi.BalancedFisherman
{
	internal sealed class CatchRegistry
	{
		internal readonly Dictionary<string, GameObject> Items = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		internal readonly List<Catch> Options = new List<Catch>();

		internal Dictionary<string, long> Weights;

		private readonly Dictionary<string, string> sharedNames = new Dictionary<string, string>(StringComparer.Ordinal);

		internal string Name(ItemData item)
		{
			if (Object.op_Implicit((Object)(object)item.m_dropPrefab))
			{
				return ((Object)item.m_dropPrefab).name;
			}
			if (sharedNames.TryGetValue(item.m_shared.m_name, out var value))
			{
				return value;
			}
			throw new InvalidOperationException("Unresolved inventory item in fish stock snapshot: " + item.m_shared.m_name);
		}

		internal static CatchRegistry Build()
		{
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Invalid comparison between Unknown and I4
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Invalid comparison between Unknown and I4
			CatchRegistry catchRegistry = new CatchRegistry();
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				if (!Object.op_Implicit((Object)(object)item))
				{
					continue;
				}
				ItemDrop component = item.GetComponent<ItemDrop>();
				if (Object.op_Implicit((Object)(object)component))
				{
					catchRegistry.Items[((Object)item).name] = item;
					if (!catchRegistry.sharedNames.ContainsKey(component.m_itemData.m_shared.m_name))
					{
						catchRegistry.sharedNames[component.m_itemData.m_shared.m_name] = ((Object)item).name;
					}
				}
			}
			Catch[] catalog = Rules.Catalog;
			foreach (Catch obj in catalog)
			{
				if (catchRegistry.Items.TryGetValue(obj.Prefab, out var value) && !Object.op_Implicit((Object)(object)value.GetComponent<Character>()))
				{
					catchRegistry.Options.Add(obj);
				}
			}
			foreach (Catch item2 in Rules.Catalog.Where((Catch x) => !x.Prefab.EndsWith("_RtD", StringComparison.Ordinal)))
			{
				if (!catchRegistry.Options.Contains(item2))
				{
					throw new InvalidOperationException("Missing vanilla catch item: " + item2.Prefab);
				}
			}
			List<Conversion> list = new List<Conversion>();
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				if (!Object.op_Implicit((Object)(object)recipe) || !recipe.m_enabled || !Object.op_Implicit((Object)(object)recipe.m_item) || recipe.m_amount <= 0 || recipe.m_resources == null)
				{
					continue;
				}
				SharedData shared = recipe.m_item.m_itemData.m_shared;
				if ((int)shared.m_itemType != 1 && (int)shared.m_itemType != 2)
				{
					continue;
				}
				Conversion conversion = new Conversion
				{
					Output = ((Object)recipe.m_item).name,
					Amount = recipe.m_amount,
					Any = recipe.m_requireOnlyOneIngredient
				};
				foreach (Requirement item3 in recipe.m_resources.Where((Requirement q) => q != null && Object.op_Implicit((Object)(object)q.m_resItem) && q.m_amount > 0))
				{
					string name = ((Object)item3.m_resItem).name;
					conversion.Inputs[name] = checked((conversion.Inputs.TryGetValue(name, out var value2) ? value2 : 0) + item3.m_amount);
				}
				if (conversion.Inputs.Count > 0)
				{
					list.Add(conversion);
				}
			}
			List<Conversion> list2 = new List<Conversion>();
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if (!Object.op_Implicit((Object)(object)prefab))
				{
					continue;
				}
				CookingStation[] componentsInChildren = prefab.GetComponentsInChildren<CookingStation>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					foreach (ItemConversion item4 in componentsInChildren[i].m_conversion)
					{
						if (Object.op_Implicit((Object)(object)item4.m_from) && Object.op_Implicit((Object)(object)item4.m_to))
						{
							list2.Add(One(((Object)item4.m_from).name, ((Object)item4.m_to).name, 1));
						}
					}
				}
				Smelter[] componentsInChildren2 = prefab.GetComponentsInChildren<Smelter>(true);
				for (int i = 0; i < componentsInChildren2.Length; i++)
				{
					foreach (ItemConversion item5 in componentsInChildren2[i].m_conversion)
					{
						if (Object.op_Implicit((Object)(object)item5.m_from) && Object.op_Implicit((Object)(object)item5.m_to))
						{
							list2.Add(One(((Object)item5.m_from).name, ((Object)item5.m_to).name, 1));
						}
					}
				}
				Fermenter[] componentsInChildren3 = prefab.GetComponentsInChildren<Fermenter>(true);
				for (int i = 0; i < componentsInChildren3.Length; i++)
				{
					foreach (ItemConversion item6 in componentsInChildren3[i].m_conversion)
					{
						if (Object.op_Implicit((Object)(object)item6.m_from) && Object.op_Implicit((Object)(object)item6.m_to) && item6.m_producedItems > 0)
						{
							list2.Add(One(((Object)item6.m_from).name, ((Object)item6.m_to).name, item6.m_producedItems));
						}
					}
				}
			}
			string[] seeds = catchRegistry.Options.Select((Catch o) => o.Prefab).Concat(new string[1] { "FishRaw" }).ToArray();
			IEnumerable<string> food = (from p in catchRegistry.Items.Where(delegate(KeyValuePair<string, GameObject> p)
				{
					//IL_0018: Unknown result type (might be due to invalid IL or missing references)
					//IL_001e: Invalid comparison between Unknown and I4
					SharedData shared2 = p.Value.GetComponent<ItemDrop>().m_itemData.m_shared;
					return (int)shared2.m_itemType == 2 && (shared2.m_food > 0f || shared2.m_foodStamina > 0f || shared2.m_foodEitr > 0f);
				})
				select p.Key).Concat(new string[1] { "FeastOceans_Material" });
			HashSet<string> allowed = Rules.FoodChain(seeds, food, list2);
			catchRegistry.Weights = Rules.FoodWeights(seeds, from c in list.Concat(list2)
				where allowed.Contains(c.Output)
				select c);
			if (!catchRegistry.Weights.ContainsKey("FishCooked"))
			{
				throw new InvalidOperationException("Cooked-fish stock mapping is missing.");
			}
			return catchRegistry;
		}

		private static Conversion One(string from, string to, int amount)
		{
			return new Conversion
			{
				Output = to,
				Amount = amount,
				Inputs = new Dictionary<string, int> { { from, 1 } }
			};
		}

		internal void Export(string path)
		{
			List<string> list = new List<string> { "Kind\tPrefab\tName\tWeight\tBoss requirement\tStock units" };
			foreach (Catch option in Options)
			{
				string[] obj = new string[10]
				{
					"catch\t",
					option.Prefab,
					"\t",
					Localization.instance.Localize(Items[option.Prefab].GetComponent<ItemDrop>().m_itemData.m_shared.m_name),
					"\t",
					null,
					null,
					null,
					null,
					null
				};
				int weight = option.Weight;
				obj[5] = weight.ToString();
				obj[6] = "\t";
				obj[7] = option.Key;
				obj[8] = "\t";
				obj[9] = Weights[option.Prefab].ToString();
				list.Add(string.Concat(obj));
			}
			foreach (KeyValuePair<string, long> item in Weights.OrderBy((KeyValuePair<string, long> x) => x.Key))
			{
				list.Add("stock\t" + item.Key + "\t\t\t\t" + item.Value);
			}
			File.WriteAllLines(path, list);
		}
	}
	internal static class Compatibility
	{
		private const BindingFlags Fields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		internal static bool SupportedVersion(Version version)
		{
			if (version != null && version.Major == 0 && version.Minor == 8 && version.Build == 5)
			{
				return version.Revision <= 0;
			}
			return false;
		}

		internal static void RequireApi(Type dock, Type container, Type view, Type gameObject)
		{
			Field(dock, "m_targetContainer", container);
			Field(dock, "m_productionEffect", gameObject);
			Method(dock, "UpdateFishProduction", typeof(void));
			Field(container, "m_nview", view);
			Method(container, "Load", typeof(bool));
			Method(container, "Save", typeof(void));
			Method(container, "Awake", typeof(void));
			Method(container, "OnDestroyed", typeof(void));
			Method(container, "GetHoverText", typeof(string));
		}

		private static void Field(Type type, string name, Type expected)
		{
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null || field.FieldType != expected)
			{
				throw new NotSupportedException("Unsupported fishing API field: " + type.Name + "." + name);
			}
		}

		private static void Method(Type type, string name, Type expected)
		{
			MethodInfo method = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method == null || method.ReturnType != expected)
			{
				throw new NotSupportedException("Unsupported fishing API method: " + type.Name + "." + name);
			}
		}
	}
	[BepInPlugin("vercadi.BalancedFisherman", "Balanced Fisherman", "0.1.0")]
	[BepInDependency("marlthon.OdinShipPlus", "0.8.5")]
	[BepInDependency("_shudnal.ConditionalConfigSync", "1.0.9")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal sealed class Clock
		{
			internal float Last;

			internal bool Owner;

			internal bool Active;
		}

		private sealed class StockSnapshot
		{
			internal byte[] Data;

			internal CatchRegistry Registry;

			internal long Count;
		}

		public const string Id = "vercadi.BalancedFisherman";

		public const string Version = "0.1.0";

		internal static Plugin Instance;

		internal const string ProgressKey = "vercadi_fisher_progress";

		internal const string StatusKey = "vercadi_fisher_status";

		private Harmony harmony;

		private ConfigSync sync;

		internal bool Faulted;

		private ConfigEntry<bool> productionEnabled;

		private ConfigEntry<float> interval;

		private ConfigEntry<float> chance;

		private ConfigEntry<float> radius;

		private ConfigEntry<int> cap;

		internal readonly HashSet<Container> Containers = new HashSet<Container>();

		internal readonly ConditionalWeakTable<OdinFishingDock, Clock> Clocks = new ConditionalWeakTable<OdinFishingDock, Clock>();

		internal CatchRegistry Registry;

		private ObjectDB preparedDb;

		private ZNetScene preparedScene;

		private float nextRegistryCheck;

		private float registryReadyAt;

		private string waiting;

		private readonly ConditionalWeakTable<Container, StockSnapshot> stockCache = new ConditionalWeakTable<Container, StockSnapshot>();

		private void Awake()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Expected O, but got Unknown
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Expected O, but got Unknown
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Expected O, but got Unknown
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Expected O, but got Unknown
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Expected O, but got Unknown
			Instance = this;
			sync = new ConfigSync("vercadi.BalancedFisherman")
			{
				DisplayName = "Balanced Fisherman",
				CurrentVersion = "0.1.0",
				MinimumRequiredVersion = "0.1.0",
				ModRequired = true
			};
			((ConditionalConfigSync)sync).AddLockingConfigEntry<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("General", "Lock server settings", true, "Use the server's fishing settings on every player."));
			productionEnabled = Bind("Fishing", "Production enabled", value: true, "Pause fishing when off. Does not restore the original fast producer.");
			interval = Bind("Fishing", "Seconds per attempt", 180f, "Active daylight seconds per fishing attempt.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 3600f));
			chance = Bind("Fishing", "Catch probability", 0.5f, "Chance of one size-one fish or one seafood ingredient per attempt.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
			cap = Bind("Stock", "Fish portion limit", 40, "Pause when nearby raw fish and fish-derived foods meet this conservative portion budget.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500));
			radius = Bind("Stock", "Storage radius", 32f, "Metres around the dock to count loaded containers. Player inventories and unloaded/distant storage are not included.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 64f));
			try
			{
				VerifyShip();
				harmony = new Harmony("vercadi.BalancedFisherman");
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Container), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "ContainerAwake", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Container), "OnDestroyed", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "ContainerDestroyed", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Container), "GetHoverText", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "Hover", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(OdinFishingDock), "UpdateFishProduction", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "Production", (Type[])null)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"ACTIVE: original dock production suppressed; balanced catches wait for world data. Matching helper required on clients and server.");
			}
			catch (Exception ex)
			{
				Faulted = true;
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				((Behaviour)this).enabled = false;
				((BaseUnityPlugin)this).Logger.LogError((object)("Balanced fishing disabled during initialization; original dock production retained. " + ex));
			}
		}

		private ConfigEntry<T> Bind<T>(string section, string key, T value, string desc, AcceptableValueBase range = null)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(section, key, value, new ConfigDescription(desc, range, Array.Empty<object>()));
			((ConditionalConfigSync)sync).AddConfigEntry<T>(val, (ConfigSyncMode)0);
			return val;
		}

		private static void VerifyShip()
		{
			if (!Chainloader.PluginInfos.TryGetValue("marlthon.OdinShipPlus", out var value) || !Compatibility.SupportedVersion(value.Metadata.Version))
			{
				throw new NotSupportedException("This release requires OdinShipPlus 0.8.5. Newer versions need a compatibility review.");
			}
			Compatibility.RequireApi(typeof(OdinFishingDock), typeof(Container), typeof(ZNetView), typeof(GameObject));
		}

		private void Update()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (Faulted || realtimeSinceStartup < nextRegistryCheck)
			{
				return;
			}
			nextRegistryCheck = realtimeSinceStartup + 3f;
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance) || !Object.op_Implicit((Object)(object)ZoneSystem.instance) || ObjectDB.instance.m_recipes.Count == 0)
			{
				string text = "Waiting for world data: ObjectDB=" + Object.op_Implicit((Object)(object)ObjectDB.instance) + ", scene=" + Object.op_Implicit((Object)(object)ZNetScene.instance) + ", zone=" + Object.op_Implicit((Object)(object)ZoneSystem.instance) + ", recipes=" + (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.m_recipes.Count : 0);
				if (text != waiting)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)text);
					waiting = text;
				}
				Registry = null;
				registryReadyAt = 0f;
			}
			else
			{
				if ((Object)(object)ObjectDB.instance == (Object)(object)preparedDb && (Object)(object)ZNetScene.instance == (Object)(object)preparedScene)
				{
					return;
				}
				Registry = null;
				if (registryReadyAt == 0f)
				{
					registryReadyAt = realtimeSinceStartup + 10f;
				}
				else if (!(realtimeSinceStartup < registryReadyAt))
				{
					try
					{
						Registry = CatchRegistry.Build();
						preparedDb = ObjectDB.instance;
						preparedScene = ZNetScene.instance;
						registryReadyAt = 0f;
						((BaseUnityPlugin)this).Logger.LogInfo((object)("READY: " + Registry.Options.Count + " reviewed catches; " + Registry.Weights.Count + " raw/processed stock mappings. No villagers or additional creatures added."));
						Registry.Export(Path.Combine(Paths.BepInExRootPath, "BalancedFisherman-catalog.tsv"));
					}
					catch (Exception ex)
					{
						Faulted = true;
						((BaseUnityPlugin)this).Logger.LogError((object)("Fishing paused: catch/recipe registration failed. " + ex));
					}
				}
			}
		}

		private static bool Production(OdinFishingDock __instance)
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return false;
			}
			try
			{
				Instance.Tick(__instance);
			}
			catch (Exception ex)
			{
				Instance.Faulted = true;
				((BaseUnityPlugin)Instance).Logger.LogError((object)("Fishing paused after an unexpected error; no automatic retry. " + ex));
			}
			return false;
		}

		private void Tick(OdinFishingDock dock)
		{
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)dock))
			{
				return;
			}
			bool flag = Object.op_Implicit((Object)(object)EnvMan.instance) && EnvMan.IsDaylight();
			if (Object.op_Implicit((Object)(object)dock.m_productionEffect))
			{
				dock.m_productionEffect.SetActive(flag);
			}
			ZNetView component = ((Component)dock).GetComponent<ZNetView>();
			Clock value = Clocks.GetValue(dock, (OdinFishingDock _) => new Clock());
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			bool flag2 = Object.op_Implicit((Object)(object)component) && component.IsValid() && component.IsOwner();
			double elapsed = ((value.Owner && value.Active) ? (realtimeSinceStartup - value.Last) : 0f);
			value.Last = realtimeSinceStartup;
			value.Owner = flag2;
			value.Active = false;
			if (!flag2)
			{
				return;
			}
			ZDO zDO = component.GetZDO();
			zDO.Set("lastTime", ZNet.instance.GetTime().Ticks);
			zDO.Set("production_progress", 0f);
			string text = (Faulted ? "Paused: check mod log" : ((!productionEnabled.Value) ? "Paused" : ((Registry == null) ? "Preparing fishing supplies" : ((!flag) ? "Resting until daylight" : ((!Object.op_Implicit((Object)(object)Player.GetClosestPlayer(((Component)dock).transform.position, 64f))) ? "Waiting for a nearby player" : null)))));
			Container targetContainer = dock.m_targetContainer;
			if (text == null && (!Object.op_Implicit((Object)(object)targetContainer) || targetContainer.IsInUse()))
			{
				text = "Waiting for the dock chest to close";
			}
			ZNetView val = (Object.op_Implicit((Object)(object)targetContainer) ? Access.View(targetContainer) : null);
			if (text == null && (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || !val.IsOwner()))
			{
				text = "Waiting for chest synchronization";
			}
			if (text != null)
			{
				SetStatus(zDO, text);
				return;
			}
			Access.Load(targetContainer);
			Inventory inventory = targetContainer.GetInventory();
			if (inventory == null || !Access.Current(targetContainer))
			{
				SetStatus(zDO, "Waiting for chest synchronization");
				return;
			}
			if (!TryStock(dock, targetContainer, out var total))
			{
				SetStatus(zDO, "Waiting for nearby storage");
				return;
			}
			ItemData val2;
			int num2;
			bool num3;
			int num4;
			checked
			{
				long limit = unchecked((long)cap.Value) * 1000L;
				if (!Rules.Fits(total, limit, 1000L))
				{
					SetStatus(zDO, "Fish stocks full (" + Math.Ceiling((double)total / 1000.0) + "/" + cap.Value + ")");
					return;
				}
				Catch[] array = Registry.Options.Where((Catch c) => (c.Key.Length == 0 || ZoneSystem.instance.GetGlobalKey(c.Key)) && inventory.CanAddItem(Registry.Items[c.Prefab], 1)).ToArray();
				if (array.Length == 0)
				{
					SetStatus(zDO, "Dock chest full or catches locked");
					return;
				}
				value.Active = true;
				double num = Rules.Advance(zDO.GetFloat("vercadi_fisher_progress", 0f), elapsed, interval.Value, active: true);
				zDO.Set("vercadi_fisher_progress", (float)num);
				if (num < (double)interval.Value)
				{
					SetStatus(zDO, "Fishing: next attempt in " + Math.Ceiling((double)interval.Value - num) + "s");
					return;
				}
				zDO.Set("vercadi_fisher_progress", 0f);
				if (Random.value >= chance.Value)
				{
					SetStatus(zDO, "No catch this time");
					return;
				}
				string name = Rules.Choose(array, Math.Min(Random.value, (float)Math.PI * 113f / 355f));
				if (!component.IsOwner() || !val.IsOwner() || targetContainer.IsInUse() || !Access.Current(targetContainer))
				{
					return;
				}
				val2 = Registry.Items[name].GetComponent<ItemDrop>().m_itemData.Clone();
				val2.m_stack = 1;
				val2.m_quality = 1;
				val2.m_variant = 0;
				val2.m_dropPrefab = Registry.Items[name];
				num2 = (from i in inventory.GetAllItems()
					where Object.op_Implicit((Object)(object)i.m_dropPrefab) && ((Object)i.m_dropPrefab).name == name
					select i).Sum((ItemData i) => i.m_stack);
				if (!inventory.CanAddItem(val2, 1))
				{
					SetStatus(zDO, "Dock chest full");
					return;
				}
				num3 = inventory.AddItem(val2);
				num4 = (from i in inventory.GetAllItems()
					where Object.op_Implicit((Object)(object)i.m_dropPrefab) && ((Object)i.m_dropPrefab).name == name
					select i).Sum((ItemData i) => i.m_stack);
			}
			if (!num3 || num4 != num2 + 1)
			{
				throw new InvalidOperationException("Fish insertion did not produce exactly one item.");
			}
			Access.Save(targetContainer);
			if (!Access.Current(targetContainer))
			{
				throw new InvalidOperationException("Fish chest persistence did not verify.");
			}
			SetStatus(zDO, "Caught: " + Localization.instance.Localize(val2.m_shared.m_name));
		}

		private bool TryStock(OdinFishingDock dock, Container target, out long total)
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Expected O, but got Unknown
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Expected O, but got Unknown
			Containers.RemoveWhere((Container c) => !Object.op_Implicit((Object)(object)c));
			total = 0L;
			HashSet<ZDOID> hashSet = new HashSet<ZDOID>();
			List<Container> list = Containers.Where(delegate(Container c)
			{
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_0036: Unknown result type (might be due to invalid IL or missing references)
				if (Object.op_Implicit((Object)(object)c))
				{
					if (!((Object)(object)c == (Object)(object)target))
					{
						Vector3 val3 = ((Component)c).transform.position - ((Component)dock).transform.position;
						return ((Vector3)(ref val3)).sqrMagnitude <= radius.Value * radius.Value;
					}
					return true;
				}
				return false;
			}).ToList();
			if (!list.Contains(target))
			{
				list.Add(target);
			}
			checked
			{
				foreach (Container item in list)
				{
					ZNetView val = Access.View(item);
					if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || !hashSet.Add(val.GetZDO().m_uid))
					{
						continue;
					}
					if (item.IsInUse())
					{
						return false;
					}
					byte[] byteArray = val.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null);
					if (byteArray == null)
					{
						if (item.GetInventory() != null && item.GetInventory().NrOfItems() > 0)
						{
							return false;
						}
						continue;
					}
					StockSnapshot value = stockCache.GetValue(item, (Container _) => new StockSnapshot());
					if (value.Registry != Registry || value.Data == null || !value.Data.SequenceEqual(byteArray))
					{
						Inventory val2 = new Inventory("Fisherman stock snapshot", (Sprite)null, Math.Max(1, item.m_width), Math.Max(1, item.m_height));
						val2.Load(new ZPackage(byteArray));
						value.Count = Rules.Stock(from i in val2.GetAllItems()
							select new KeyValuePair<string, int>(Registry.Name(i), i.m_stack), Registry.Weights);
						value.Data = (byte[])byteArray.Clone();
						value.Registry = Registry;
					}
					total += value.Count;
				}
				return true;
			}
		}

		private static void SetStatus(ZDO z, string text)
		{
			if (z.GetString("vercadi_fisher_status", "") != text)
			{
				z.Set("vercadi_fisher_status", text);
			}
		}

		private static void ContainerAwake(Container __instance)
		{
			if (Object.op_Implicit((Object)(object)__instance))
			{
				Instance?.Containers.Add(__instance);
			}
		}

		private static void ContainerDestroyed(Container __instance)
		{
			Instance?.Containers.Remove(__instance);
		}

		private static void Hover(Container __instance, ref string __result)
		{
			if (!Object.op_Implicit((Object)(object)__instance))
			{
				return;
			}
			OdinFishingDock componentInParent = ((Component)__instance).GetComponentInParent<OdinFishingDock>();
			if (!Object.op_Implicit((Object)(object)componentInParent))
			{
				return;
			}
			ZNetView component = ((Component)componentInParent).GetComponent<ZNetView>();
			if (Object.op_Implicit((Object)(object)component) && component.IsValid())
			{
				string text = component.GetZDO().GetString("vercadi_fisher_status", "");
				if (text.Length > 0)
				{
					__result = __result + "\n" + text;
				}
			}
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	internal static class Access
	{
		private static readonly FieldInfo ViewField = AccessTools.Field(typeof(Container), "m_nview");

		private static readonly MethodInfo LoadMethod = AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null);

		private static readonly MethodInfo SaveMethod = AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null);

		internal static ZNetView View(Container c)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			return (ZNetView)ViewField.GetValue(c);
		}

		internal static void Load(Container c)
		{
			LoadMethod.Invoke(c, null);
		}

		internal static void Save(Container c)
		{
			SaveMethod.Invoke(c, null);
		}

		internal static bool Current(Container c)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			ZNetView val = View(c);
			if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || !val.IsOwner() || c.IsInUse())
			{
				return false;
			}
			byte[] byteArray = val.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null);
			if (byteArray == null)
			{
				return c.GetInventory().NrOfItems() == 0;
			}
			ZPackage val2 = new ZPackage();
			c.GetInventory().Save(val2);
			return val2.GetArray().SequenceEqual(byteArray);
		}
	}
	internal sealed class Catch
	{
		internal readonly string Prefab;

		internal readonly string Key;

		internal readonly int Weight;

		internal Catch(string prefab, int weight, string key = "")
		{
			Prefab = prefab;
			Weight = weight;
			Key = key;
		}
	}
	internal sealed class Conversion
	{
		internal string Output;

		internal int Amount;

		internal bool Any;

		internal Dictionary<string, int> Inputs = new Dictionary<string, int>(StringComparer.Ordinal);
	}
	internal static class Rules
	{
		internal const long Unit = 1000L;

		internal static readonly Catch[] Catalog = new Catch[24]
		{
			new Catch("Fish1", 22),
			new Catch("Fish2", 16),
			new Catch("Fish3", 10, "defeated_eikthyr"),
			new Catch("Fish5", 10, "defeated_eikthyr"),
			new Catch("Fish6", 7, "defeated_gdking"),
			new Catch("Fish4_cave", 3, "defeated_bonemass"),
			new Catch("Fish7", 6, "defeated_bonemass"),
			new Catch("Fish8", 4, "defeated_dragon"),
			new Catch("Fish9", 3, "defeated_goblinking"),
			new Catch("Fish12", 2, "defeated_goblinking"),
			new Catch("Fish11", 2, "defeated_queen"),
			new Catch("Fish10", 1, "defeated_fader"),
			new Catch("Item_Shrimp_RtD", 10),
			new Catch("Meat_Crab_RtD", 8),
			new Catch("Meat_Cod_RtD", 8, "defeated_eikthyr"),
			new Catch("Meat_Tuna_RtD", 7, "defeated_eikthyr"),
			new Catch("Meat_Squid_RtD", 5, "defeated_gdking"),
			new Catch("Meat_Manta_RtD", 3, "defeated_gdking"),
			new Catch("Meat_Shark_RtD", 2, "defeated_bonemass"),
			new Catch("Meat_Marlin_RtD", 3, "defeated_bonemass"),
			new Catch("Meat_Turtle_RtD", 1, "defeated_goblinking"),
			new Catch("Meat_Whale_RtD", 1, "defeated_dragon"),
			new Catch("SharkMeatRaw_RtD", 1, "defeated_dragon"),
			new Catch("SeaMonsterTailRaw_RtD", 1, "defeated_queen")
		};

		internal static bool Valid(double v)
		{
			if (!double.IsNaN(v))
			{
				return !double.IsInfinity(v);
			}
			return false;
		}

		internal static double Advance(double progress, double elapsed, double interval, bool active)
		{
			if (!Valid(interval) || interval < 30.0)
			{
				throw new ArgumentOutOfRangeException("interval");
			}
			if (!Valid(progress) || progress < 0.0 || progress >= interval)
			{
				progress = 0.0;
			}
			if (!active || !Valid(elapsed) || elapsed < 0.0 || elapsed > 20.0)
			{
				return progress;
			}
			return Math.Min(interval, progress + elapsed);
		}

		internal static string Choose(IEnumerable<Catch> options, double sample)
		{
			if (!Valid(sample) || sample < 0.0 || sample >= 1.0)
			{
				throw new ArgumentOutOfRangeException("sample");
			}
			Catch[] array = options.ToArray();
			if (array.Any((Catch x) => x == null || x.Weight <= 0) || array.Select((Catch x) => x.Prefab).Distinct().Count() != array.Length)
			{
				throw new ArgumentException("Invalid catch table.");
			}
			long num = array.Aggregate(0L, (long n, Catch x) => checked(n + x.Weight));
			if (num == 0L)
			{
				return null;
			}
			double num2 = sample * (double)num;
			Catch[] array2 = array;
			foreach (Catch obj in array2)
			{
				num2 -= (double)obj.Weight;
				if (num2 < 0.0)
				{
					return obj.Prefab;
				}
			}
			return array.Last().Prefab;
		}

		internal static HashSet<string> FoodChain(IEnumerable<string> seeds, IEnumerable<string> food, IEnumerable<Conversion> stations)
		{
			HashSet<string> hashSet = new HashSet<string>(seeds.Concat(food), StringComparer.Ordinal);
			Conversion[] array = stations.ToArray();
			bool flag;
			do
			{
				flag = false;
				Conversion[] array2 = array;
				foreach (Conversion conversion in array2)
				{
					if (!hashSet.Contains(conversion.Output))
					{
						continue;
					}
					foreach (string key in conversion.Inputs.Keys)
					{
						flag |= hashSet.Add(key);
					}
				}
			}
			while (flag);
			hashSet.RemoveWhere((string x) => x == "Feathers" || x.StartsWith("FishingBait", StringComparison.Ordinal));
			return hashSet;
		}

		internal static Dictionary<string, long> FoodWeights(IEnumerable<string> seeds, IEnumerable<Conversion> conversions)
		{
			HashSet<string> hashSet = new HashSet<string>(seeds, StringComparer.Ordinal);
			Dictionary<string, long> dictionary = hashSet.ToDictionary<string, string, long>((string x) => x, (string x) => 1000L, StringComparer.Ordinal);
			Conversion[] array = conversions.ToArray();
			for (int num = 0; num < 64; num++)
			{
				bool flag = false;
				Conversion[] array2 = array;
				foreach (Conversion conversion in array2)
				{
					if (conversion == null || string.IsNullOrEmpty(conversion.Output) || conversion.Amount <= 0 || conversion.Inputs.Any((KeyValuePair<string, int> x) => x.Value < 0))
					{
						throw new ArgumentException("Invalid food conversion.");
					}
					if (hashSet.Contains(conversion.Output))
					{
						continue;
					}
					long num3 = 0L;
					checked
					{
						foreach (KeyValuePair<string, int> input in conversion.Inputs)
						{
							if (dictionary.TryGetValue(input.Key, out var value))
							{
								long num4 = value * input.Value;
								num3 = (conversion.Any ? Math.Max(num3, num4) : (num3 + num4));
							}
						}
					}
					if (num3 != 0L)
					{
						long num5 = checked(num3 + conversion.Amount - 1) / conversion.Amount;
						if (!dictionary.TryGetValue(conversion.Output, out var value2) || num5 > value2)
						{
							dictionary[conversion.Output] = num5;
							flag = true;
						}
					}
				}
				if (!flag)
				{
					return dictionary;
				}
			}
			throw new InvalidOperationException("Fish recipe cycle did not converge; production paused.");
		}

		internal static long Stock(IEnumerable<KeyValuePair<string, int>> items, IReadOnlyDictionary<string, long> weights)
		{
			long num = 0L;
			foreach (KeyValuePair<string, int> item in items)
			{
				if (item.Value < 0)
				{
					throw new ArgumentException("Negative stack.");
				}
				if (weights.TryGetValue(item.Key, out var value))
				{
					num = checked(num + value * item.Value);
				}
			}
			return num;
		}

		internal static bool Fits(long current, long limit, long incoming)
		{
			if (current >= 0 && limit > 0 && incoming > 0 && current <= limit)
			{
				return incoming <= limit - current;
			}
			return false;
		}
	}
}