Decompiled source of EverlastingSeedBed v0.1.2

EverlastingSeedBed.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("EverlastingSeedBed")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2")]
[assembly: AssemblyProduct("EverlastingSeedBed")]
[assembly: AssemblyTitle("EverlastingSeedBed")]
[assembly: AssemblyVersion("0.1.2.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 EverlastingSeedBed
{
	internal static class Access
	{
		private static readonly FieldInfo Areas = AccessTools.Field(typeof(PrivateArea), "m_allAreas");

		private static readonly MethodInfo Enabled = AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null);

		private static readonly MethodInfo Permitted = AccessTools.Method(typeof(PrivateArea), "IsPermitted", (Type[])null, (Type[])null);

		internal static Player Resolve(long sender, long playerId)
		{
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if (Object.op_Implicit((Object)(object)allPlayer) && ((Character)allPlayer).GetOwner() == sender && allPlayer.GetPlayerID() == playerId && !((Character)allPlayer).IsDead())
				{
					return allPlayer;
				}
			}
			return null;
		}

		internal static bool CanUse(Player player, Vector3 position)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: 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_0021: 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)player) && !((Character)player).IsDead())
			{
				Vector3 val = ((Component)player).transform.position - position;
				if (((Vector3)(ref val)).sqrMagnitude <= 64f)
				{
					return Ward(player.GetPlayerID(), position);
				}
			}
			return false;
		}

		internal static bool Ward(long user, Vector3 position)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			if (user == 0L || Areas == null || Enabled == null || Permitted == null)
			{
				return false;
			}
			try
			{
				foreach (PrivateArea item in (List<PrivateArea>)Areas.GetValue(null))
				{
					if (!Object.op_Implicit((Object)(object)item) || !(bool)Enabled.Invoke(item, null))
					{
						continue;
					}
					Vector3 val = ((Component)item).transform.position - position;
					val.y = 0f;
					if (!(((Vector3)(ref val)).sqrMagnitude >= item.m_radius * item.m_radius))
					{
						Piece component = ((Component)item).GetComponent<Piece>();
						if ((!Object.op_Implicit((Object)(object)component) || component.GetCreator() != user) && !(bool)Permitted.Invoke(item, new object[1] { user }))
						{
							return false;
						}
					}
				}
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	public sealed class BedBehaviour : MonoBehaviour, Hoverable, Interactable
	{
		internal sealed class Receipt
		{
			public long Sequence;

			public string Token;

			public string Binding;

			public bool Success;

			public bool Acknowledged;

			public bool Prepared;

			public double Expires;
		}

		internal sealed class State
		{
			public int Version = 1;

			public int Level = 1;

			public int Seeds;

			public int Pending;

			public int Revision;

			public string Seed = "";

			public string Output = "";

			public int Yield;

			public double Start;

			public double Duration;

			public double Window;

			public long Planter;

			public bool Destroyed;

			public bool InfiniteCycle = true;

			public bool Consumed;

			public List<Cost> Ledger = new List<Cost>();

			public Dictionary<long, Receipt> Receipts = new Dictionary<long, Receipt>();
		}

		internal const string StateKey = "esb_state_v1";

		public ZNetView View { get; private set; }

		public bool Valid
		{
			get
			{
				if (Object.op_Implicit((Object)(object)View) && View.IsValid())
				{
					return Read() != null;
				}
				return false;
			}
		}

		internal static double Now
		{
			get
			{
				if (!Object.op_Implicit((Object)(object)ZNet.instance))
				{
					return 0.0;
				}
				return ZNet.instance.GetTimeSeconds();
			}
		}

		public bool HasUnacknowledged => Read()?.Receipts.Values.Any((Receipt x) => !x.Acknowledged) ?? true;

		public int Level => Read()?.Level ?? 1;

		public int Seeds => Read()?.Seeds ?? 0;

		public int Pending => Read()?.Pending ?? 0;

		public string Seed => Read()?.Seed ?? "";

		public Crop Crop
		{
			get
			{
				State state = Read();
				if (state == null || string.IsNullOrEmpty(state.Seed))
				{
					return null;
				}
				return new Crop(state.Seed, state.Output, state.Yield);
			}
		}

		public double Remaining
		{
			get
			{
				State state = Read();
				if (state != null && state.Seeds != 0)
				{
					return GrowthRules.Remaining(state.Start, Now, state.Duration);
				}
				return 0.0;
			}
		}

		public double AddRemaining
		{
			get
			{
				State state = Read();
				if (state != null && state.Seeds != 0)
				{
					return Math.Max(0.0, state.Start + state.Window - Now);
				}
				return 0.0;
			}
		}

		public bool CanAdd
		{
			get
			{
				State state = Read();
				if (state != null)
				{
					return GrowthRules.CanAdd(state.Seeds, Settings.Capacity(state.Level), state.Pending, state.Start, Now, (state.Seeds == 0) ? ((double)Settings.Duration.Value) : state.Duration, state.Window);
				}
				return false;
			}
		}

		internal State Read()
		{
			if (!Object.op_Implicit((Object)(object)View) || !View.IsValid())
			{
				return null;
			}
			try
			{
				string text = View.GetZDO().GetString("esb_state_v1", "");
				State state = ((text.Length == 0) ? new State() : JsonConvert.DeserializeObject<State>(text));
				return (state != null && state.Version == 1 && state.Level >= 1 && state.Level <= 5 && state.Seeds >= 0 && state.Seeds <= 200 && state.Pending >= 0 && state.Pending <= 200000 && !double.IsNaN(state.Start) && !double.IsInfinity(state.Start) && !double.IsNaN(state.Duration) && !double.IsInfinity(state.Duration) && !double.IsNaN(state.Window) && !double.IsInfinity(state.Window) && state.Window >= 0.0 && state.Start >= 0.0 && (state.Seeds == 0 || (state.Duration > 0.0 && state.Yield > 0 && state.Yield <= 1000 && !string.IsNullOrEmpty(state.Seed) && !string.IsNullOrEmpty(state.Output))) && state.Ledger != null && state.Ledger.All((Cost x) => x != null && x.Amount > 0 && !string.IsNullOrEmpty(x.Item)) && state.Receipts != null && state.Receipts.Values.All((Receipt x) => x != null)) ? state : null;
			}
			catch
			{
				return null;
			}
		}

		internal void Save(State s)
		{
			s.Revision++;
			View.GetZDO().Set("esb_state_v1", JsonConvert.SerializeObject((object)s));
		}

		internal void ExpirePreparations(State s)
		{
			bool flag = false;
			foreach (Receipt value in s.Receipts.Values)
			{
				if (value.Prepared && Now >= value.Expires)
				{
					value.Prepared = false;
					value.Success = false;
					flag = true;
				}
			}
			if (flag)
			{
				Save(s);
			}
		}

		private void Awake()
		{
			View = ((Component)this).GetComponent<ZNetView>();
			if (Object.op_Implicit((Object)(object)View) && View.IsValid())
			{
				Transactions.Register(this);
				WearNTear component = ((Component)this).GetComponent<WearNTear>();
				if (Object.op_Implicit((Object)(object)component))
				{
					component.m_onDestroyed = (Action)Delegate.Combine(component.m_onDestroyed, new Action(OnDestroyed));
				}
				((MonoBehaviour)this).InvokeRepeating("Tick", 1f, 1.5f);
			}
		}

		private void OnDestroy()
		{
			((MonoBehaviour)this).CancelInvoke();
		}

		private void Tick()
		{
			if (!Plugin.Ready || !Valid || !View.IsOwner())
			{
				return;
			}
			State state = Read();
			ExpirePreparations(state);
			if (state.Destroyed || state.Receipts.Values.Any((Receipt x) => x.Prepared))
			{
				return;
			}
			Crop crop = ((state.Seeds > 0) ? new Crop(state.Seed, state.Output, state.Yield) : null);
			if (state.Seeds > 0 && state.Pending == 0 && crop != null && Now >= state.Start + state.Duration)
			{
				state.Pending = GrowthRules.MatureYield(state.Seeds, crop.Yield, state.Pending, state.Start, Now, state.Duration);
				state.Consumed = !state.InfiniteCycle;
				Save(state);
			}
			if (state.Pending > 0 && crop != null && Settings.AutoCollect.Value)
			{
				int num = ChestStorage.Deposit(this, state.Planter, crop.Output, state.Pending);
				if (num > 0)
				{
					state.Pending -= num;
					if (state.Pending == 0)
					{
						Finish(state);
					}
					Save(state);
				}
			}
			if (!Settings.AutoPlant.Value || string.IsNullOrEmpty(state.Seed) || state.Pending != 0 || ((state.Seeds > 0) ? state.InfiniteCycle : Settings.InfiniteSeeds.Value) || Catalog.Find(state.Seed) == null || !GrowthRules.CanAdd(state.Seeds, Settings.Capacity(state.Level), state.Pending, state.Start, Now, (state.Seeds == 0) ? ((double)Settings.Duration.Value) : state.Duration, state.Window))
			{
				return;
			}
			int num2 = ChestStorage.Withdraw(this, state.Planter, state.Seed, Settings.Capacity(state.Level) - state.Seeds);
			if (num2 > 0)
			{
				if (state.Seeds == 0)
				{
					Begin(state);
				}
				state.Seeds += num2;
				Save(state);
			}
		}

		internal static void Finish(State s)
		{
			if (s.Consumed)
			{
				s.Seeds = 0;
			}
			s.Consumed = false;
			if (s.Seeds > 0)
			{
				Begin(s);
			}
			else
			{
				s.Start = 0.0;
			}
		}

		internal static void Begin(State s)
		{
			double num = Settings.Duration.Value;
			double num2 = Settings.Window.Value;
			if (double.IsNaN(num) || double.IsInfinity(num))
			{
				num = 1419.105;
			}
			if (double.IsNaN(num2) || double.IsInfinity(num2))
			{
				num2 = 300.0;
			}
			s.Start = Now;
			s.Duration = Math.Max(1.0, Math.Min(86400.0, num));
			s.Window = GrowthRules.AddWindow(s.Duration, Math.Max(0.0, num2));
			s.InfiniteCycle = Settings.InfiniteSeeds.Value;
			s.Consumed = false;
			Crop crop = Catalog.Find(s.Seed);
			if (crop != null)
			{
				s.Output = crop.Output;
				s.Yield = crop.Yield;
			}
		}

		internal bool Apply(State s, Transactions.Request r, Player player)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if (s.Destroyed || r.Revision != s.Revision || !Access.CanUse(player, ((Component)this).transform.position))
			{
				return false;
			}
			Crop crop = Catalog.Find(r.Seed);
			if (r.Kind == "add")
			{
				if (crop == null || r.Amount <= 0 || r.Amount > Settings.Capacity(s.Level) - s.Seeds || !GrowthRules.CanAdd(s.Seeds, Settings.Capacity(s.Level), s.Pending, s.Start, Now, (s.Seeds == 0) ? ((double)Settings.Duration.Value) : s.Duration, s.Window) || (!string.IsNullOrEmpty(s.Seed) && s.Seed != r.Seed) || !Transactions.Same(r.Input, new List<Cost>
				{
					new Cost
					{
						Item = r.Seed,
						Amount = r.Amount
					}
				}) || r.Output.Count != 0)
				{
					return false;
				}
				if (s.Seeds == 0)
				{
					s.Seed = r.Seed;
					s.Output = crop.Output;
					s.Yield = crop.Yield;
					s.Planter = r.User;
					Begin(s);
				}
				s.Seeds += r.Amount;
				return true;
			}
			if (r.Kind == "upgrade")
			{
				if (s.Level >= 5 || !Transactions.Same(r.Input, Settings.UpgradeCost(s.Level + 1)) || r.Output.Count != 0)
				{
					return false;
				}
				s.Ledger.AddRange(r.Input.Select((Cost x) => new Cost
				{
					Item = x.Item,
					Amount = x.Amount
				}));
				s.Level++;
				return true;
			}
			if (r.Input.Count != 0)
			{
				return false;
			}
			crop = ((s.Seeds > 0) ? new Crop(s.Seed, s.Output, s.Yield) : null);
			if (r.Kind == "harvest")
			{
				if (crop == null || r.Amount <= 0 || r.Amount > s.Pending || !Transactions.Same(r.Output, new List<Cost>
				{
					new Cost
					{
						Item = crop.Output,
						Amount = r.Amount
					}
				}))
				{
					return false;
				}
				s.Pending -= r.Amount;
				if (s.Pending == 0)
				{
					Finish(s);
				}
				return true;
			}
			if (r.Kind == "clear")
			{
				if (string.IsNullOrEmpty(s.Seed) || !Transactions.Same(r.Output, Refund(s)))
				{
					return false;
				}
				s.Seed = "";
				s.Output = "";
				s.Yield = 0;
				s.Seeds = 0;
				s.Pending = 0;
				s.Start = 0.0;
				s.Planter = 0L;
				s.Consumed = false;
				return true;
			}
			return false;
		}

		internal static List<Cost> Refund(State s)
		{
			List<Cost> list = new List<Cost>();
			if (s.Seeds > 0 && !s.Consumed)
			{
				list.Add(new Cost
				{
					Item = s.Seed,
					Amount = s.Seeds
				});
			}
			Crop crop = ((s.Seeds > 0) ? new Crop(s.Seed, s.Output, s.Yield) : null);
			if (s.Pending > 0 && crop != null)
			{
				list.Add(new Cost
				{
					Item = crop.Output,
					Amount = s.Pending
				});
			}
			return Transactions.Merge(list);
		}

		private void OnDestroyed()
		{
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			if (!Valid || !View.IsOwner())
			{
				return;
			}
			State state = Read();
			if (state.Destroyed)
			{
				return;
			}
			List<Cost> list = Refund(state);
			list.AddRange(state.Ledger);
			state.Destroyed = true;
			state.Seeds = (state.Pending = 0);
			state.Ledger.Clear();
			Save(state);
			foreach (Cost item in Transactions.Merge(list))
			{
				ItemDrop val = Catalog.Item(item.Item);
				if (Object.op_Implicit((Object)(object)val))
				{
					int num = item.Amount;
					while (num > 0)
					{
						ItemData val2 = val.m_itemData.Clone();
						val2.m_dropPrefab = ((Component)val).gameObject;
						val2.m_stack = Math.Min(num, val2.m_shared.m_maxStackSize);
						ItemDrop.DropItem(val2, val2.m_stack, ((Component)this).transform.position + Vector3.up, Quaternion.identity);
						num -= val2.m_stack;
					}
				}
			}
		}

		public void RequestAdd(Crop crop, int amount)
		{
			Transactions.Start(this, "add", crop, amount);
		}

		public void RequestUpgrade()
		{
			Transactions.Start(this, "upgrade", null, 0);
		}

		public void RequestHarvest()
		{
			Transactions.Start(this, "harvest", Crop, Pending);
		}

		public void RequestClear()
		{
			Transactions.Start(this, "clear", Crop, 0);
		}

		public string GetHoverText()
		{
			return HoverUI.Text(this);
		}

		public string GetHoverName()
		{
			return "Everlasting SeedBed";
		}

		public bool Interact(Humanoid user, bool hold, bool alt)
		{
			return HoverUI.Interact(this, user, hold, alt);
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			if ((Object)(object)user != (Object)(object)Player.m_localPlayer || (Object)(object)item?.m_dropPrefab == (Object)null)
			{
				return false;
			}
			Crop crop = Catalog.Find(((Object)item.m_dropPrefab).name);
			if (crop == null)
			{
				return false;
			}
			string text = null;
			if (!Plugin.Ready || !Valid || Transactions.Busy)
			{
				text = "Please wait…";
			}
			else if (!string.IsNullOrEmpty(Seed) && Seed != crop.Seed)
			{
				text = "Clear the bed before changing crops.";
			}
			else if (Seeds >= Settings.Capacity(Level))
			{
				text = "The bed is full.";
			}
			else if (!CanAdd)
			{
				text = "The planting window has closed.";
			}
			if (text != null)
			{
				((Character)user).Message((MessageType)2, text, 0, (Sprite)null, false);
				return true;
			}
			try
			{
				RequestAdd(crop, 1);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Planting rejected: " + ex.Message));
				((Character)user).Message((MessageType)2, "Unable to plant. Check the recipe settings.", 0, (Sprite)null, false);
			}
			return true;
		}

		public float GetHoverOffset()
		{
			return 1f;
		}
	}
	public sealed class Crop
	{
		public string Seed;

		public string Output;

		public int Yield;

		public Crop(string seed, string output, int yield)
		{
			Seed = seed;
			Output = output;
			Yield = yield;
		}
	}
	internal static class Catalog
	{
		public const string Defaults = "Barley:Barley:3;CarrotSeeds:Carrot:3;Dandelion:Dandelion:3;Fiddleheadfern:Fiddleheadfern:3;Flax:Flax:3;Mushroom:Mushroom:3;MushroomMagecap:MushroomMagecap:3;MushroomYellow:MushroomYellow:3;MushroomJotunPuffs:MushroomJotunPuffs:3;OnionSeeds:Onion:3;Carrot:CarrotSeeds:3;Onion:OnionSeeds:3;Turnip:TurnipSeeds:3;MushroomSmokePuff:MushroomSmokePuff:3;Thistle:Thistle:3;TurnipSeeds:Turnip:3;Blueberries:Blueberries:3;Cloudberry:Cloudberry:3;Raspberry:Raspberry:3;Vineberry:Vineberry:3";

		private static string loaded;

		private static ObjectDB database;

		private static int itemCount;

		private static readonly List<Crop> crops = new List<Crop>();

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

		internal static string ConsumingDefaults => "Barley:Barley:3;CarrotSeeds:Carrot:3;Dandelion:Dandelion:3;Fiddleheadfern:Fiddleheadfern:3;Flax:Flax:3;Mushroom:Mushroom:3;MushroomMagecap:MushroomMagecap:3;MushroomYellow:MushroomYellow:3;MushroomJotunPuffs:MushroomJotunPuffs:3;OnionSeeds:Onion:3;Carrot:CarrotSeeds:3;Onion:OnionSeeds:3;Turnip:TurnipSeeds:3;MushroomSmokePuff:MushroomSmokePuff:3;Thistle:Thistle:3;TurnipSeeds:Turnip:3;Blueberries:Blueberries:3;Cloudberry:Cloudberry:3;Raspberry:Raspberry:3;Vineberry:Vineberry:3".Replace("CarrotSeeds:Carrot:3", "CarrotSeeds:Carrot:1").Replace("TurnipSeeds:Turnip:3", "TurnipSeeds:Turnip:1").Replace("OnionSeeds:Onion:3", "OnionSeeds:Onion:1");

		internal static IEnumerable<Crop> All
		{
			get
			{
				Refresh();
				return crops;
			}
		}

		internal static ItemDrop Item(string name)
		{
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || string.IsNullOrEmpty(name))
			{
				return null;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name);
			if (!Object.op_Implicit((Object)(object)itemPrefab))
			{
				return null;
			}
			return itemPrefab.GetComponent<ItemDrop>();
		}

		internal static string Name(string name)
		{
			ItemDrop val = Item(name);
			if (!Object.op_Implicit((Object)(object)val) || Localization.instance == null)
			{
				return name;
			}
			string name2 = val.m_itemData.m_shared.m_name;
			if (EnglishNames.TryGetValue(name2, out var value))
			{
				return value;
			}
			value = (name2.StartsWith("$") ? Localization.instance.TranslateSingleId(name2, "English") : name2);
			if (string.IsNullOrWhiteSpace(value) || value.StartsWith("$") || value.StartsWith("["))
			{
				value = name;
			}
			EnglishNames[name2] = value;
			return value;
		}

		internal static Crop Find(string seed)
		{
			return All.FirstOrDefault((Crop c) => c.Seed == seed);
		}

		internal static List<Crop> Available(Player player)
		{
			if (Object.op_Implicit((Object)(object)player))
			{
				return All.Where((Crop c) => ((Humanoid)player).GetInventory().CountItems(Item(c.Seed).m_itemData.m_shared.m_name, -1, true) > 0).ToList();
			}
			return new List<Crop>();
		}

		internal static void Refresh()
		{
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || ObjectDB.instance.m_items.Count == 0)
			{
				return;
			}
			string text = (Settings.InfiniteSeeds.Value ? Settings.Recipes.Value : Settings.SustainableRecipes.Value);
			if (loaded == text && (Object)(object)database == (Object)(object)ObjectDB.instance && itemCount == ObjectDB.instance.m_items.Count)
			{
				return;
			}
			loaded = text;
			database = ObjectDB.instance;
			itemCount = database.m_items.Count;
			crops.Clear();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			string[] array = text.Split(';');
			foreach (string text2 in array)
			{
				string[] array2 = text2.Trim().Split(':');
				if (array2.Length != 3 || !int.TryParse(array2[2], out var result) || result < 1 || result > 1000 || !hashSet.Add(array2[0]))
				{
					Plugin.Log.LogWarning((object)("Ignored invalid/duplicate crop recipe: " + text2));
				}
				else if (!Object.op_Implicit((Object)(object)Item(array2[0])) || !Object.op_Implicit((Object)(object)Item(array2[1])))
				{
					Plugin.Log.LogWarning((object)("Crop item unavailable, skipped: " + text2));
				}
				else
				{
					crops.Add(new Crop(array2[0], array2[1], result));
				}
			}
			Plugin.Log.LogInfo((object)("Crop recipes available: " + crops.Count));
		}
	}
	internal static class ChestStorage
	{
		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 int Deposit(BedBehaviour bed, long planter, string prefab, int amount)
		{
			return Transfer(bed, planter, prefab, amount, withdraw: false);
		}

		internal static int Withdraw(BedBehaviour bed, long planter, string prefab, int amount)
		{
			return Transfer(bed, planter, prefab, amount, withdraw: true);
		}

		private static int Transfer(BedBehaviour bed, long planter, string prefab, int amount, bool withdraw)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Invalid comparison between Unknown and I4
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			if (amount <= 0 || planter == 0L || !bed.View.IsOwner() || ViewField == null || LoadMethod == null || SaveMethod == null || !Access.Ward(planter, ((Component)bed).transform.position))
			{
				return 0;
			}
			float radius = Mathf.Clamp(Settings.Radius.Value, 0f, 100f);
			int num = amount;
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0).Where(delegate(Container c)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				if (Object.op_Implicit((Object)(object)c))
				{
					Vector3 val5 = ((Component)c).transform.position - ((Component)bed).transform.position;
					return ((Vector3)(ref val5)).sqrMagnitude <= radius * radius;
				}
				return false;
			}).OrderBy(delegate(Container c)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: 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)
				Vector3 val5 = ((Component)c).transform.position - ((Component)bed).transform.position;
				return ((Vector3)(ref val5)).sqrMagnitude;
			})
				.ToArray();
			foreach (Container val in array)
			{
				if (num == 0 || !bed.View.IsOwner())
				{
					break;
				}
				object? value = ViewField.GetValue(val);
				ZNetView val2 = (ZNetView)((value is ZNetView) ? value : null);
				if (!Object.op_Implicit((Object)(object)val2) || !val2.IsValid() || !val2.IsOwner() || val.IsInUse() || val2.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0 || (Object.op_Implicit((Object)(object)val.m_wagon) && val.m_wagon.InUse()) || !Access.Ward(planter, ((Component)val).transform.position) || (int)val.m_privacy == 1)
				{
					continue;
				}
				Piece component = ((Component)val).GetComponent<Piece>();
				if ((int)val.m_privacy == 0 && (!Object.op_Implicit((Object)(object)component) || component.GetCreator() != planter))
				{
					continue;
				}
				Inventory val3 = null;
				int num3 = 0;
				try
				{
					LoadMethod.Invoke(val, null);
					val3 = val.GetInventory();
					if (val3 == null)
					{
						continue;
					}
					num3 = Transactions.Count(val3, prefab);
					if (withdraw)
					{
						int num4 = num;
						ItemData[] array2 = (from i in val3.GetAllItems()
							where Object.op_Implicit((Object)(object)i.m_dropPrefab) && ((Object)i.m_dropPrefab).name == prefab
							select i).ToArray();
						foreach (ItemData val4 in array2)
						{
							int num6 = Math.Min(num4, val4.m_stack);
							val3.RemoveItem(val4, num6);
							num4 -= num6;
							if (num4 == 0)
							{
								break;
							}
						}
					}
					else
					{
						Transactions.Add(val3, prefab, num);
					}
					SaveMethod.Invoke(val, null);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Seed bed chest transfer deferred: " + ex.Message));
					if (val3 != null)
					{
						try
						{
							SaveMethod.Invoke(val, null);
						}
						catch
						{
						}
					}
					break;
				}
				finally
				{
					if (val3 != null)
					{
						int num7 = Transactions.Count(val3, prefab) - num3;
						num -= Math.Min(num, Math.Max(0, withdraw ? (-num7) : num7));
					}
				}
			}
			return amount - num;
		}
	}
	public static class GrowthRules
	{
		public static int Capacity(int level)
		{
			if (level <= 1)
			{
				return 10;
			}
			return level switch
			{
				2 => 15, 
				3 => 20, 
				4 => 30, 
				_ => 50, 
			};
		}

		public static double AddWindow(double duration, double configuredWindow)
		{
			if (!PositiveFinite(duration) || !PositiveFinite(configuredWindow))
			{
				return 0.0;
			}
			return Math.Min(configuredWindow, duration / 4.0);
		}

		public static bool CanAdd(int seeds, int capacity, int pending, double start, double now, double duration, double window)
		{
			if (seeds < 0 || capacity <= 0 || seeds >= capacity || pending != 0 || !PositiveFinite(duration) || !Finite(now))
			{
				return false;
			}
			if (seeds == 0)
			{
				return true;
			}
			if (!Finite(start))
			{
				return false;
			}
			return Math.Max(0.0, now - start) < AddWindow(duration, window);
		}

		public static int Yield(int seeds, int perSeed)
		{
			if (seeds <= 0 || perSeed <= 0)
			{
				return 0;
			}
			long num;
			checked
			{
				num = unchecked((long)seeds) * unchecked((long)perSeed);
			}
			if (num <= int.MaxValue)
			{
				return (int)num;
			}
			return 0;
		}

		public static double Remaining(double start, double now, double duration)
		{
			if (!PositiveFinite(duration) || !Finite(start) || !Finite(now))
			{
				return double.PositiveInfinity;
			}
			return Math.Max(0.0, duration - Math.Max(0.0, now - start));
		}

		public static int MatureYield(int seeds, int perSeed, int pending, double start, double now, double duration)
		{
			if (pending > 0)
			{
				return pending;
			}
			if (pending < 0 || Remaining(start, now, duration) != 0.0)
			{
				return 0;
			}
			return Yield(seeds, perSeed);
		}

		private static bool Finite(double value)
		{
			if (!double.IsNaN(value))
			{
				return !double.IsInfinity(value);
			}
			return false;
		}

		private static bool PositiveFinite(double value)
		{
			if (Finite(value))
			{
				return value > 0.0;
			}
			return false;
		}
	}
	internal static class HoverUI
	{
		[HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })]
		private static class ReservedInput
		{
			private static void Postfix(string name, ref bool __result)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: Invalid comparison between Unknown and I4
				if (__result && !(name != "GP") && (int)Settings.HarvestKey.Value == 102)
				{
					Player localPlayer = Player.m_localPlayer;
					BedBehaviour bedBehaviour = (InputAllowed(localPlayer) ? Hovered(localPlayer) : null);
					if ((reservedFrame == Time.frameCount || (Object.op_Implicit((Object)(object)bedBehaviour) && bedBehaviour.Pending > 0)) && ZInput.GetKey((KeyCode)102, true))
					{
						__result = false;
					}
				}
			}
		}

		private static readonly MethodInfo TakeInput = AccessTools.Method(typeof(Player), "TakeInput", (Type[])null, (Type[])null);

		private static BedBehaviour focused;

		private static float clearStart = -1f;

		private static float nextHold;

		private static int reservedFrame = -1;

		private static void Action(Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Bed interaction rejected: " + ex.Message));
				Player localPlayer = Player.m_localPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, "Unable to perform this action. Check the recipe settings.", 0, (Sprite)null, false);
				}
			}
		}

		internal static BedBehaviour Hovered(Player player)
		{
			GameObject val = (Object.op_Implicit((Object)(object)player) ? ((Humanoid)player).GetHoverObject() : null);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return null;
			}
			return val.GetComponentInParent<BedBehaviour>();
		}

		internal static bool InputAllowed(Player p)
		{
			if (Object.op_Implicit((Object)(object)p) && Plugin.Ready && TakeInput != null)
			{
				return (bool)TakeInput.Invoke(p, null);
			}
			return false;
		}

		private unsafe static string Key(KeyCode code)
		{
			return "<color=#EAC675>[" + ((object)(*(KeyCode*)(&code))/*cast due to .constrained prefix*/).ToString() + "]</color>";
		}

		private static string Clock(double seconds)
		{
			if (double.IsNaN(seconds) || double.IsInfinity(seconds))
			{
				return "—";
			}
			int num = (int)Math.Min(2147483647.0, Math.Max(0.0, Math.Ceiling(seconds)));
			return num / 60 + ":" + (num % 60).ToString("00");
		}

		internal static string Text(BedBehaviour bed)
		{
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			if (!bed.Valid)
			{
				return "Everlasting SeedBed";
			}
			if (!Plugin.Ready)
			{
				return "Everlasting SeedBed\nSynchronizing…";
			}
			Player localPlayer = Player.m_localPlayer;
			Crop crop = bed.Crop;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append((crop != null) ? Catalog.Name(crop.Output) : "Everlasting SeedBed").Append(" · Lv. ").Append(bed.Level);
			stringBuilder.Append("\nSeeds: ").Append(bed.Seeds).Append('/')
				.Append(Settings.Capacity(bed.Level));
			if (bed.Seeds > 0)
			{
				if (bed.Pending > 0)
				{
					stringBuilder.Append("\nReady: ").Append(bed.Pending).Append(" items · ")
						.Append(Key(Settings.HarvestKey.Value))
						.Append(" Harvest");
				}
				else
				{
					stringBuilder.Append(" · Harvest in ").Append(Clock(bed.Remaining));
				}
				if (bed.CanAdd)
				{
					stringBuilder.Append("\nPlanting open for ").Append(Clock(bed.AddRemaining));
				}
			}
			else if (!string.IsNullOrEmpty(bed.Seed))
			{
				stringBuilder.Append("\nWaiting for ").Append(Catalog.Name(bed.Seed));
			}
			else
			{
				stringBuilder.Append("\n<color=#EAC675>[1–8]</color> Use seeds to plant");
			}
			if (crop != null && bed.CanAdd && Object.op_Implicit((Object)(object)localPlayer) && Object.op_Implicit((Object)(object)Catalog.Item(crop.Seed)) && ((Humanoid)localPlayer).GetInventory().CountItems(Catalog.Item(crop.Seed).m_itemData.m_shared.m_name, -1, true) > 0)
			{
				stringBuilder.Append("\n<color=#EAC675>[$KEY_Use]</color> +1 · <color=#EAC675>[Hold $KEY_Use]</color> Fill");
			}
			if (bed.Level < 5)
			{
				stringBuilder.Append("\n<color=#EAC675>[Shift + ").Append(Settings.UpgradeKey.Value).Append("]</color> Upgrade");
				try
				{
					stringBuilder.Append(" · ").Append(string.Join(", ", from c in Settings.UpgradeCost(bed.Level + 1)
						select c.Amount + " " + Catalog.Name(c.Item)));
				}
				catch
				{
					stringBuilder.Append(" · invalid recipe");
				}
			}
			if (!string.IsNullOrEmpty(bed.Seed))
			{
				stringBuilder.Append("\n<color=#EAC675>[Hold ").Append(Settings.ClearKey.Value).Append("]</color> Clear");
			}
			if ((Object)(object)focused == (Object)(object)bed && clearStart >= 0f)
			{
				stringBuilder.Append("\nClearing… ").Append(Math.Max(0f, 2f - (Time.unscaledTime - clearStart)).ToString("0.0"));
			}
			if (Transactions.Busy)
			{
				stringBuilder.Append('\n').Append(string.IsNullOrEmpty(Transactions.Status) ? "Awaiting confirmation…" : Transactions.Status);
			}
			return Localization.instance.Localize(stringBuilder.ToString());
		}

		internal static bool Interact(BedBehaviour bed, Humanoid user, bool hold, bool alt)
		{
			Player val = (Player)(object)((user is Player) ? user : null);
			if ((Object)(object)val != (Object)(object)Player.m_localPlayer || !InputAllowed(val) || Transactions.Busy || !bed.CanAdd)
			{
				return false;
			}
			if (hold && Time.unscaledTime < nextHold)
			{
				return false;
			}
			Crop crop = bed.Crop;
			if (crop == null)
			{
				return false;
			}
			int amount = ((!hold) ? 1 : (Settings.Capacity(bed.Level) - bed.Seeds));
			amount = Math.Min(amount, ((Humanoid)val).GetInventory().CountItems(Catalog.Item(crop.Seed).m_itemData.m_shared.m_name, -1, true));
			if (amount < 1)
			{
				return false;
			}
			nextHold = Time.unscaledTime + 0.5f;
			Action(delegate
			{
				bed.RequestAdd(crop, amount);
			});
			return true;
		}

		internal static void UpdatePlayer(Player player)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			BedBehaviour bedBehaviour = (InputAllowed(player) ? Hovered(player) : null);
			if ((Object)(object)bedBehaviour != (Object)(object)focused)
			{
				focused = bedBehaviour;
				clearStart = -1f;
			}
			if (!Object.op_Implicit((Object)(object)bedBehaviour) || !bedBehaviour.Valid)
			{
				clearStart = -1f;
				return;
			}
			if (Transactions.Busy)
			{
				clearStart = -1f;
				return;
			}
			if (ZInput.GetKeyDown(Settings.UpgradeKey.Value, true) && (ZInput.GetKey((KeyCode)304, true) || ZInput.GetKey((KeyCode)303, true)) && bedBehaviour.Level < 5)
			{
				Action(bedBehaviour.RequestUpgrade);
			}
			if (ZInput.GetKeyDown(Settings.HarvestKey.Value, true) && bedBehaviour.Pending > 0)
			{
				reservedFrame = Time.frameCount;
				Action(bedBehaviour.RequestHarvest);
			}
			if (!string.IsNullOrEmpty(bedBehaviour.Seed) && ZInput.GetKey(Settings.ClearKey.Value, true))
			{
				if (clearStart < 0f)
				{
					clearStart = Time.unscaledTime;
				}
				if (Time.unscaledTime - clearStart >= 2f)
				{
					clearStart = -1f;
					Action(bedBehaviour.RequestClear);
				}
			}
			else
			{
				clearStart = -1f;
			}
		}
	}
	[BepInPlugin("etern.valheim.everlastingseedbed", "Everlasting SeedBed", "0.1.2")]
	[BepInDependency("blacks7ar.SeedBed", "1.2.9")]
	[BepInDependency("com.jotunn.jotunn", "2.30.0")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(ZNet), "Awake")]
		private static class Session
		{
			private static void Prefix()
			{
				synced = false;
			}
		}

		[HarmonyPatch(typeof(Player), "Update")]
		private static class PlayerInput
		{
			private static void Prefix(Player __instance)
			{
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					HoverUI.UpdatePlayer(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(WearNTear), "Destroy")]
		private static class PendingRemoval
		{
			private static bool Prefix(WearNTear __instance)
			{
				return !Transactions.IsRemovalBlocked(((Component)__instance).gameObject);
			}
		}

		public const string Guid = "etern.valheim.everlastingseedbed";

		public const string Name = "Everlasting SeedBed";

		public const string Version = "0.1.2";

		internal static ManualLogSource Log;

		private static bool synced;

		private readonly Harmony harmony = new Harmony("etern.valheim.everlastingseedbed");

		private string lastStatus;

		internal static bool Ready
		{
			get
			{
				if (PrefabFactory.Registered && Object.op_Implicit((Object)(object)ZNet.instance))
				{
					if (!ZNet.instance.IsServer())
					{
						return synced;
					}
					return true;
				}
				return false;
			}
		}

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Settings.Init(((BaseUnityPlugin)this).Config);
			SynchronizationManager.OnConfigurationSynchronized += Synchronized;
			PrefabManager.OnVanillaPrefabsAvailable += PrefabFactory.Register;
			PrefabManager.OnPrefabsRegistered += PrefabFactory.SceneReady;
			harmony.PatchAll(typeof(Plugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Everlasting SeedBed 0.1.2 loaded. SeedBed assets remain in the original dependency.");
		}

		private void Start()
		{
			PrefabFactory.Register();
		}

		private void Update()
		{
			Transactions.Update();
			string status = Transactions.Status;
			if (status != lastStatus)
			{
				lastStatus = status;
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && !string.IsNullOrEmpty(status) && status != "Awaiting confirmation…")
				{
					((Character)Player.m_localPlayer).Message((MessageType)2, status, 0, (Sprite)null, false);
				}
			}
		}

		private static void Synchronized(object sender, ConfigurationSynchronizationEventArgs args)
		{
			if (args.UpdatedPluginGUIDs.Contains("etern.valheim.everlastingseedbed"))
			{
				synced = true;
			}
			PrefabFactory.RefreshRecipe();
		}

		private void OnDestroy()
		{
			SynchronizationManager.OnConfigurationSynchronized -= Synchronized;
			PrefabManager.OnVanillaPrefabsAvailable -= PrefabFactory.Register;
			PrefabManager.OnPrefabsRegistered -= PrefabFactory.SceneReady;
			harmony.UnpatchSelf();
		}
	}
	internal static class PrefabFactory
	{
		internal const string PrefabName = "ESB_EverlastingSeedBed";

		internal static bool Registered;

		internal static GameObject Prefab;

		private static bool failed;

		internal static void Register()
		{
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: 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_01a2: Expected O, but got Unknown
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Expected O, but got Unknown
			if (Registered || failed || !Chainloader.PluginInfos.TryGetValue("blacks7ar.SeedBed", out var value))
			{
				return;
			}
			try
			{
				object? value2 = AccessTools.Field(((object)value.Instance).GetType().Assembly.GetType("SeedBed.Functions.SetupPrefabs", throwOnError: true), "_seedBundle").GetValue(null);
				AssetBundle val = (AssetBundle)((value2 is AssetBundle) ? value2 : null);
				if (!Object.op_Implicit((Object)(object)val))
				{
					return;
				}
				GameObject val2 = val.LoadAsset<GameObject>("BSB_SeedBed");
				if (!Object.op_Implicit((Object)(object)val2))
				{
					throw new InvalidOperationException("SeedBed 1.2.9 model BSB_SeedBed unavailable");
				}
				Prefab = PrefabManager.Instance.CreateClonedPrefab("ESB_EverlastingSeedBed", val2);
				if (!Object.op_Implicit((Object)(object)Prefab))
				{
					throw new InvalidOperationException("Cannot clone SeedBed model");
				}
				MonoBehaviour[] componentsInChildren = Prefab.GetComponentsInChildren<MonoBehaviour>(true);
				foreach (MonoBehaviour val3 in componentsInChildren)
				{
					if (Object.op_Implicit((Object)(object)val3) && (((object)val3).GetType().FullName == "SeedBed.Functions.Germination" || val3 is Switch))
					{
						Object.DestroyImmediate((Object)(object)val3);
					}
				}
				Prefab.AddComponent<BedBehaviour>();
				Prefab.AddComponent<Visuals>();
				List<Cost> source = Settings.ParseCost(Settings.BuildCost.Value);
				CustomPiece val4 = new CustomPiece(Prefab, false, new PieceConfig
				{
					Name = "Everlasting SeedBed",
					Description = "Grow crops and replant from nearby chests.",
					PieceTable = "Hammer",
					Category = "CollectorSeries",
					CraftingStation = "piece_workbench",
					Requirements = ((IEnumerable<Cost>)source).Select((Func<Cost, RequirementConfig>)((Cost c) => new RequirementConfig
					{
						Item = c.Item,
						Amount = c.Amount,
						Recover = true
					})).ToArray()
				});
				if (!PieceManager.Instance.AddPiece(val4))
				{
					throw new InvalidOperationException("Jotunn rejected new seed bed");
				}
				Registered = true;
				Settings.BuildCost.SettingChanged += delegate
				{
					RefreshRecipe();
				};
				Plugin.Log.LogInfo((object)"Registered ESB_EverlastingSeedBed; original SeedBed prefab unchanged.");
			}
			catch (Exception ex)
			{
				failed = true;
				Plugin.Log.LogError((object)("SeedBed addon registration failed: " + ex));
			}
		}

		internal static void SceneReady()
		{
			Register();
			RefreshRecipe();
			Catalog.Refresh();
		}

		internal static void RefreshRecipe()
		{
			if (!Registered || !Object.op_Implicit((Object)(object)Prefab) || !Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				return;
			}
			try
			{
				List<Cost> source = Settings.ParseCost(Settings.BuildCost.Value);
				if (source.Any((Cost c) => !Object.op_Implicit((Object)(object)Catalog.Item(c.Item))))
				{
					throw new InvalidOperationException("Unknown build material");
				}
				Prefab.GetComponent<Piece>().m_resources = ((IEnumerable<Cost>)source).Select((Func<Cost, Requirement>)((Cost c) => new Requirement
				{
					m_resItem = Catalog.Item(c.Item),
					m_amount = c.Amount,
					m_recover = true
				})).ToArray();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Build recipe not changed: " + ex.Message));
			}
		}
	}
	public sealed class Cost
	{
		public string Item;

		public int Amount;

		public Cost()
		{
		}

		public Cost(string item, int amount)
		{
			Item = item;
			Amount = amount;
		}
	}
	internal static class Settings
	{
		internal static ConfigEntry<float> Duration;

		internal static ConfigEntry<float> Window;

		internal static ConfigEntry<float> Radius;

		internal static ConfigEntry<float> UpgradeRadius;

		internal static ConfigEntry<bool> AutoCollect;

		internal static ConfigEntry<bool> UpgradeFromChests;

		internal static ConfigEntry<bool> InfiniteSeeds;

		internal static ConfigEntry<bool> AutoPlant;

		internal static ConfigEntry<string> Recipes;

		internal static ConfigEntry<string> SustainableRecipes;

		internal static ConfigEntry<string> BuildCost;

		internal static ConfigEntry<KeyCode> UpgradeKey;

		internal static ConfigEntry<KeyCode> ClearKey;

		internal static ConfigEntry<KeyCode> HarvestKey;

		private static readonly ConfigEntry<string>[] UpgradeCosts = new ConfigEntry<string>[4];

		private static readonly ConfigEntry<int>[] Capacities = new ConfigEntry<int>[5];

		internal static void Init(ConfigFile config)
		{
			Duration = Synced(config, "01. Growth", "Cycle seconds", 1419.105f, "Growth duration in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 86400f));
			InfiniteSeeds = Synced(config, "01. Growth", "Infinite seeds", value: false, "Keep planted resources after harvest. Disabled by default: every completed crop consumes its planting resources. Changes apply to the next cycle.");
			AutoPlant = Synced(config, "02. Storage", "Auto plant", value: true, "In consuming mode, refill the selected crop from nearby accessible chests during the planting window. An empty assigned bed waits for resources. Uses Chest radius.");
			Window = Synced(config, "01. Growth", "Planting window seconds", 300f, "Additional planting window at the start of a cycle, limited to one quarter of the cycle.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3600f));
			AutoCollect = Synced(config, "02. Storage", "Auto collect", value: true, "Transfer harvest to nearby chests.");
			Radius = Synced(config, "02. Storage", "Chest radius", 10f, "Chest search radius in metres for automatic harvest and replanting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f));
			int[] array = new int[5] { 10, 15, 20, 30, 50 };
			UpgradeFromChests = Synced(config, "02. Storage", "Upgrade from chests", value: true, "Use nearby chest materials for upgrades. ValheimPlus CraftFromChest settings take priority when installed.");
			UpgradeRadius = Synced(config, "02. Storage", "Upgrade chest radius", 10f, "Upgrade material search radius without ValheimPlus. Only accessible, closed, locally owned containers are used.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f));
			for (int i = 0; i < 5; i++)
			{
				Capacities[i] = Synced(config, "03. Levels", "Level " + (i + 1) + " capacity", array[i], "Seed capacity.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 200));
			}
			string[] array2 = new string[4] { "Wood:20,Bronze:4", "FineWood:20,Bronze:8", "FineWood:30,Iron:10", "YggdrasilWood:40,BlackMetal:15" };
			for (int j = 0; j < 4; j++)
			{
				UpgradeCosts[j] = Synced(config, "03. Levels", "Upgrade to " + (j + 2), array2[j], "Upgrade cost: comma-separated Prefab:Amount entries.");
			}
			BuildCost = Synced(config, "03. Levels", "Build cost", "Wood:18,BronzeNails:8", "Construction cost for new beds.");
			Recipes = Synced(config, "04. Crops", "Recipes", "Barley:Barley:3;CarrotSeeds:Carrot:3;Dandelion:Dandelion:3;Fiddleheadfern:Fiddleheadfern:3;Flax:Flax:3;Mushroom:Mushroom:3;MushroomMagecap:MushroomMagecap:3;MushroomYellow:MushroomYellow:3;MushroomJotunPuffs:MushroomJotunPuffs:3;OnionSeeds:Onion:3;Carrot:CarrotSeeds:3;Onion:OnionSeeds:3;Turnip:TurnipSeeds:3;MushroomSmokePuff:MushroomSmokePuff:3;Thistle:Thistle:3;TurnipSeeds:Turnip:3;Blueberries:Blueberries:3;Cloudberry:Cloudberry:3;Raspberry:Raspberry:3;Vineberry:Vineberry:3", "Supported crops: Seed:Output:YieldPerSeed entries separated by semicolons. Missing mod items are skipped.");
			SustainableRecipes = Synced(config, "04. Crops", "Consuming mode recipes", Catalog.ConsumingDefaults, "Recipes when Infinite seeds is disabled. Each planted input is consumed at maturity. Carrot, turnip and onion seeds yield one vegetable; a vegetable yields three seeds. Format: Input:Output:Yield separated by semicolons.");
			UpgradeKey = config.Bind<KeyCode>("05. Local controls", "Upgrade", (KeyCode)117, "Upgrade the hovered bed with Shift + this key.");
			ClearKey = config.Bind<KeyCode>("05. Local controls", "Clear", (KeyCode)103, "Hold two seconds to clear the bed.");
			HarvestKey = config.Bind<KeyCode>("05. Local controls", "Harvest", (KeyCode)102, "Collect the harvest.");
		}

		internal static ConfigEntry<T> Synced<T>(ConfigFile config, string section, string key, T value, string description, AcceptableValueBase range = null)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			return config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
		}

		internal static int Capacity(int level)
		{
			int num = 1;
			for (int i = 0; i < Math.Min(5, Math.Max(1, level)); i++)
			{
				num = Math.Max(num, Capacities[i].Value);
			}
			return Math.Min(200, num);
		}

		internal static List<Cost> UpgradeCost(int nextLevel)
		{
			if (nextLevel >= 2 && nextLevel <= 5)
			{
				return ParseCost(UpgradeCosts[nextLevel - 2].Value);
			}
			return new List<Cost>();
		}

		internal static List<Cost> ParseCost(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				throw new FormatException("A cost must contain at least one item.");
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			string[] array = value.Split(',');
			foreach (string text in array)
			{
				string[] array2 = text.Trim().Split(':');
				if (array2.Length != 2 || string.IsNullOrWhiteSpace(array2[0]) || !int.TryParse(array2[1], out var result) || result < 1 || result > 10000)
				{
					throw new FormatException("Invalid cost: " + text);
				}
				string key = array2[0].Trim();
				dictionary[key] = checked((dictionary.TryGetValue(key, out var value2) ? value2 : 0) + result);
			}
			return dictionary.Select((KeyValuePair<string, int> p) => new Cost(p.Key, p.Value)).ToList();
		}
	}
	internal static class Transactions
	{
		internal sealed class Request
		{
			public int Version = 1;

			public int Revision;

			public int Amount;

			public long World;

			public long User;

			public long ZdoUser;

			public long Sequence;

			public long Owner;

			public uint ZdoId;

			public string Token;

			public string Kind;

			public string Seed;

			public string Phase = "request";

			public List<Cost> Input = new List<Cost>();

			public List<Cost> Output = new List<Cost>();

			public List<Cost> Delivery;

			public List<Cost> Debited = new List<Cost>();
		}

		private const string PendingKey = "EverlastingSeedBed.Transaction.v1";

		private const string SequenceKey = "EverlastingSeedBed.Sequence.v1";

		private const string AcksKey = "EverlastingSeedBed.Acknowledgements.v1";

		private static ZRoutedRpc registered;

		private static float retryAt;

		public static string Status = "";

		public static bool Busy
		{
			get
			{
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					return Player.m_localPlayer.m_customData.ContainsKey("EverlastingSeedBed.Transaction.v1");
				}
				return false;
			}
		}

		internal static void Register(BedBehaviour bed)
		{
			bed.View.Register<ZPackage>("ESB_Request_v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
			{
				Receive(bed, sender, pkg);
			});
			bed.View.Register<ZPackage>("ESB_Ack_v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
			{
				Ack(bed, sender, pkg);
			});
		}

		internal static List<Cost> Merge(IEnumerable<Cost> costs)
		{
			return (from g in costs.GroupBy<Cost, string>((Cost x) => x.Item, StringComparer.Ordinal)
				select new Cost
				{
					Item = g.Key,
					Amount = g.Sum((Cost x) => x.Amount)
				} into x
				where x.Amount > 0
				select x).OrderBy<Cost, string>((Cost x) => x.Item, StringComparer.Ordinal).ToList();
		}

		internal static bool Same(List<Cost> a, List<Cost> b)
		{
			if (a == null || b == null || a.Any((Cost x) => x == null || x.Amount <= 0 || string.IsNullOrEmpty(x.Item)))
			{
				return false;
			}
			List<Cost> list = Merge(a);
			List<Cost> list2 = Merge(b);
			if (list.Count == list2.Count)
			{
				return list.Zip(list2, (Cost x, Cost y) => x.Item == y.Item && x.Amount == y.Amount).All((bool x) => x);
			}
			return false;
		}

		internal static int Count(Inventory inv, string prefab)
		{
			return (from x in inv.GetAllItems()
				where Object.op_Implicit((Object)(object)x.m_dropPrefab) && ((Object)x.m_dropPrefab).name == prefab
				select x).Sum((ItemData x) => x.m_stack);
		}

		internal static int Add(Inventory inv, string prefab, int amount)
		{
			ItemDrop val = Catalog.Item(prefab);
			if (!Object.op_Implicit((Object)(object)val) || amount <= 0)
			{
				return 0;
			}
			int num = inv.GetAllItems().Sum((ItemData x) => x.m_stack);
			int num2 = amount;
			while (num2 > 0)
			{
				ItemData val2 = val.m_itemData.Clone();
				val2.m_dropPrefab = ((Component)val).gameObject;
				val2.m_worldLevel = (byte)Game.m_worldLevel;
				val2.m_stack = Math.Min(num2, val2.m_shared.m_maxStackSize);
				int num3 = 0;
				int num4 = val2.m_stack;
				while (num3 < num4)
				{
					int num5 = (num3 + num4 + 1) / 2;
					if (inv.CanAddItem(val2, num5))
					{
						num3 = num5;
					}
					else
					{
						num4 = num5 - 1;
					}
				}
				if (num3 == 0)
				{
					break;
				}
				val2.m_stack = num3;
				int stack = val2.m_stack;
				bool num6 = inv.AddItem(val2);
				num2 -= stack;
				if (!num6)
				{
					break;
				}
			}
			return Math.Max(0, inv.GetAllItems().Sum((ItemData x) => x.m_stack) - num);
		}

		internal static Inventory Copy(Inventory source)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0036: Expected O, but got Unknown
			Inventory val = new Inventory("ESB capacity check", (Sprite)null, source.GetWidth(), source.GetHeight());
			ZPackage val2 = new ZPackage();
			source.Save(val2);
			val.Load(new ZPackage(val2.GetArray()));
			return val;
		}

		private static bool Fits(Inventory inv, List<Cost> costs)
		{
			Inventory copy = Copy(inv);
			return costs.All((Cost x) => Add(copy, x.Item, x.Amount) == x.Amount);
		}

		internal static void Start(BedBehaviour bed, string kind, Crop crop, int amount)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (Busy || !Plugin.Ready || !bed.Valid || !Access.CanUse(localPlayer, ((Component)bed).transform.position))
			{
				return;
			}
			BedBehaviour.State state = bed.Read();
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			Request request = new Request
			{
				Kind = kind,
				Seed = (crop?.Seed ?? ""),
				Amount = amount,
				Revision = state.Revision,
				Token = Guid.NewGuid().ToString("N"),
				World = ZNet.instance.GetWorldUID(),
				User = localPlayer.GetPlayerID(),
				ZdoUser = ((ZDOID)(ref bed.View.GetZDO().m_uid)).UserID,
				ZdoId = ((ZDOID)(ref bed.View.GetZDO().m_uid)).ID
			};
			switch (kind)
			{
			default:
				return;
			case "add":
				if (crop == null || !bed.CanAdd)
				{
					return;
				}
				request.Amount = Math.Min(Math.Min(amount, Settings.Capacity(state.Level) - state.Seeds), Count(inventory, crop.Seed));
				if (request.Amount <= 0)
				{
					return;
				}
				request.Input.Add(new Cost
				{
					Item = crop.Seed,
					Amount = request.Amount
				});
				break;
			case "upgrade":
				if (state.Level >= 5)
				{
					return;
				}
				request.Input = Merge(Settings.UpgradeCost(state.Level + 1));
				break;
			case "harvest":
				if (crop == null || state.Pending <= 0)
				{
					return;
				}
				request.Amount = Add(Copy(inventory), crop.Output, state.Pending);
				if (request.Amount <= 0)
				{
					Status = "No room for the harvest.";
					return;
				}
				request.Output.Add(new Cost
				{
					Item = crop.Output,
					Amount = request.Amount
				});
				break;
			case "clear":
				if (string.IsNullOrEmpty(state.Seed))
				{
					return;
				}
				request.Output = BedBehaviour.Refund(state);
				if (!Fits(inventory, request.Output))
				{
					Status = "Make room for seeds and harvest.";
					return;
				}
				break;
			}
			if (!UpgradeStorage.CanPay(UpgradeStorage.Sources(localPlayer, kind == "upgrade"), request.Input))
			{
				Status = "Not enough materials.";
				return;
			}
			long result = 0L;
			localPlayer.m_customData.TryGetValue("EverlastingSeedBed.Sequence.v1", out var value);
			long.TryParse(value, out result);
			request.Sequence = checked(result + 1);
			localPlayer.m_customData["EverlastingSeedBed.Sequence.v1"] = request.Sequence.ToString();
			Store(localPlayer, request);
			Status = "Awaiting confirmation…";
			retryAt = 0f;
		}

		private static void Pay(Player player, Request r)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			List<Cost> list = new List<Cost>();
			try
			{
				List<UpgradeStorage.Source> sources = UpgradeStorage.Sources(player, r.Kind == "upgrade");
				if (!UpgradeStorage.CanPay(sources, r.Input) || (r.Kind == "clear" && !Fits(inventory, r.Output)))
				{
					throw new InvalidOperationException("Inventory changed while preparing");
				}
				UpgradeStorage.Debit(sources, r.Input, list);
				if (!Same(Merge(list), r.Input))
				{
					throw new InvalidOperationException("Inventory debit was incomplete");
				}
				r.Debited = Merge(list);
				r.Phase = "commit";
			}
			catch (Exception ex)
			{
				r.Phase = "abort";
				r.Debited = Merge(list);
				Status = "Payment interrupted; returning materials.";
				Plugin.Log.LogWarning((object)("Seed bed debit rolled back: " + ex.Message));
			}
			Store(player, r);
		}

		private static List<Request> Acks(Player player)
		{
			if (!player.m_customData.TryGetValue("EverlastingSeedBed.Acknowledgements.v1", out var value))
			{
				return new List<Request>();
			}
			try
			{
				return JsonConvert.DeserializeObject<List<Request>>(value) ?? new List<Request>();
			}
			catch
			{
				return new List<Request>();
			}
		}

		private static void StoreAcks(Player player, List<Request> acks)
		{
			player.m_customData["EverlastingSeedBed.Acknowledgements.v1"] = JsonConvert.SerializeObject((object)acks);
		}

		private static void QueueAck(Player player, Request r)
		{
			r.Phase = "ack";
			List<Request> list = Acks(player);
			if (!list.Any((Request x) => x.Token == r.Token))
			{
				list.Add(r);
			}
			StoreAcks(player, list);
			player.m_customData.Remove("EverlastingSeedBed.Transaction.v1");
		}

		private static BedBehaviour Find(Request r)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.FindInstance(new ZDOID(r.ZdoUser, r.ZdoId)) : null);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return null;
			}
			return val.GetComponent<BedBehaviour>();
		}

		private static void SendAcks(Player player)
		{
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Expected O, but got Unknown
			Request[] array = Acks(player).ToArray();
			foreach (Request r in array)
			{
				if (r.World != ZNet.instance.GetWorldUID() || r.User != player.GetPlayerID())
				{
					continue;
				}
				BedBehaviour bedBehaviour = Find(r);
				if (Object.op_Implicit((Object)(object)bedBehaviour) && bedBehaviour.Valid)
				{
					r.Owner = bedBehaviour.View.GetZDO().GetOwner();
					List<Request> list = Acks(player);
					Request request = list.FirstOrDefault((Request x) => x.Token == r.Token);
					if (request != null)
					{
						request.Owner = r.Owner;
						StoreAcks(player, list);
						ZPackage val = new ZPackage();
						val.Write(JsonConvert.SerializeObject((object)r));
						bedBehaviour.View.InvokeRPC("ESB_Ack_v1", new object[1] { val });
					}
				}
			}
		}

		private static void Store(Player player, Request r)
		{
			player.m_customData["EverlastingSeedBed.Transaction.v1"] = JsonConvert.SerializeObject((object)r);
		}

		private static Request Load(Player player)
		{
			try
			{
				return JsonConvert.DeserializeObject<Request>(player.m_customData["EverlastingSeedBed.Transaction.v1"]);
			}
			catch
			{
				Status = "Invalid bed transaction receipt; retain your character save for diagnostics.";
				return null;
			}
		}

		public static bool IsRemovalBlocked(GameObject obj)
		{
			BedBehaviour bedBehaviour = (Object.op_Implicit((Object)(object)obj) ? obj.GetComponent<BedBehaviour>() : null);
			if (Object.op_Implicit((Object)(object)bedBehaviour))
			{
				return bedBehaviour.HasUnacknowledged;
			}
			return false;
		}

		public static void Update()
		{
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null && registered != ZRoutedRpc.instance)
			{
				registered = ZRoutedRpc.instance;
				registered.Register<ZPackage>("ESB_Result_v1", (Action<long, ZPackage>)Result);
			}
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Plugin.Ready || !Object.op_Implicit((Object)(object)ZNet.instance) || Time.time < retryAt)
			{
				return;
			}
			retryAt = Time.time + 0.5f;
			Player localPlayer = Player.m_localPlayer;
			SendAcks(localPlayer);
			if (!Busy)
			{
				return;
			}
			Request request = Load(localPlayer);
			if (request == null || request.Version != 1)
			{
				return;
			}
			if (request.World != ZNet.instance.GetWorldUID() || request.User != localPlayer.GetPlayerID())
			{
				Status = "An unfinished transaction belongs to another world.";
				return;
			}
			if (request.Phase == "pay")
			{
				Pay(localPlayer, request);
			}
			if (request.Phase == "delivery")
			{
				foreach (Cost item in request.Delivery)
				{
					Inventory inventory = ((Humanoid)localPlayer).GetInventory();
					int num = inventory.GetAllItems().Sum((ItemData x) => x.m_stack);
					try
					{
						Add(inventory, item.Item, item.Amount);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Seed bed delivery callback failed: " + ex.Message));
					}
					finally
					{
						item.Amount -= Math.Min(item.Amount, Math.Max(0, inventory.GetAllItems().Sum((ItemData x) => x.m_stack) - num));
						Store(localPlayer, request);
					}
				}
				if (request.Delivery.Any((Cost x) => x.Amount > 0))
				{
					Status = "Make inventory space: your items are held in the transaction receipt.";
					return;
				}
				QueueAck(localPlayer, request);
				SendAcks(localPlayer);
				Status = "";
				return;
			}
			BedBehaviour bedBehaviour = Find(request);
			if (!Object.op_Implicit((Object)(object)bedBehaviour) || !bedBehaviour.Valid)
			{
				if (request.Phase == "request")
				{
					QueueAck(localPlayer, request);
					Status = "The bed is unavailable. No materials were consumed.";
				}
				else
				{
					Status = "Return to the bed to finish the transaction. The receipt is saved.";
				}
			}
			else
			{
				request.Owner = bedBehaviour.View.GetZDO().GetOwner();
				Store(localPlayer, request);
				ZPackage val = new ZPackage();
				val.Write(JsonConvert.SerializeObject((object)request));
				bedBehaviour.View.InvokeRPC("ESB_Request_v1", new object[1] { val });
			}
		}

		private static void Receive(BedBehaviour bed, long sender, ZPackage pkg)
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Ready || !bed.Valid || !bed.View.IsOwner())
			{
				return;
			}
			try
			{
				string text = pkg.ReadString();
				if (text.Length > 16384)
				{
					return;
				}
				Request request = JsonConvert.DeserializeObject<Request>(text);
				if (request == null || request.Version != 1 || request.World != ZNet.instance.GetWorldUID() || request.Sequence <= 0 || request.Input == null || request.Output == null || !Guid.TryParseExact(request.Token, "N", out var _))
				{
					return;
				}
				Player val = Access.Resolve(sender, request.User);
				if (!Object.op_Implicit((Object)(object)val))
				{
					return;
				}
				ZDOID uid = bed.View.GetZDO().m_uid;
				if (request.ZdoUser != ((ZDOID)(ref uid)).UserID || request.ZdoId != ((ZDOID)(ref uid)).ID)
				{
					return;
				}
				BedBehaviour.State state = bed.Read();
				bed.ExpirePreparations(state);
				if (state.Receipts.TryGetValue(request.User, out var value) && value.Sequence >= request.Sequence)
				{
					if (value.Sequence == request.Sequence && !(value.Token != request.Token) && !(value.Binding != Bind(request)))
					{
						if (value.Prepared && (request.Phase == "commit" || request.Phase == "abort"))
						{
							request.Revision = state.Revision;
							value.Success = request.Phase == "commit" && bed.Apply(state, request, val);
							value.Prepared = false;
							bed.Save(state);
						}
						Respond(sender, request.Token, value.Prepared ? 3 : (value.Success ? 1 : 0));
					}
				}
				else if ((value == null || value.Acknowledged) && !(request.Phase != "request"))
				{
					BedBehaviour.State s = JsonConvert.DeserializeObject<BedBehaviour.State>(JsonConvert.SerializeObject((object)state));
					bool flag = !state.Receipts.Values.Any((BedBehaviour.Receipt x) => x.Prepared) && bed.Apply(s, request, val);
					state.Receipts[request.User] = new BedBehaviour.Receipt
					{
						Sequence = request.Sequence,
						Token = request.Token,
						Binding = Bind(request),
						Prepared = flag,
						Expires = BedBehaviour.Now + 30.0
					};
					bed.Save(state);
					Respond(sender, request.Token, flag ? 3 : 0);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Seed bed transaction rejected: " + ex.Message));
			}
		}

		private static string Bind(Request r)
		{
			return JsonConvert.SerializeObject((object)new
			{
				r.Version, r.World, r.User, r.ZdoUser, r.ZdoId, r.Sequence, r.Token, r.Kind, r.Seed, r.Amount,
				r.Revision, r.Input, r.Output
			});
		}

		private static void Ack(BedBehaviour bed, long sender, ZPackage pkg)
		{
			if (!bed.Valid || !bed.View.IsOwner())
			{
				return;
			}
			try
			{
				Request request = JsonConvert.DeserializeObject<Request>(pkg.ReadString());
				if (request == null || request.World != ZNet.instance.GetWorldUID() || !Object.op_Implicit((Object)(object)Access.Resolve(sender, request.User)))
				{
					return;
				}
				BedBehaviour.State state = bed.Read();
				if (state.Receipts.TryGetValue(request.User, out var value) && value.Sequence == request.Sequence && !(value.Token != request.Token))
				{
					if (!value.Acknowledged)
					{
						value.Prepared = false;
						value.Acknowledged = true;
						bed.Save(state);
					}
					Respond(sender, request.Token, 2);
				}
			}
			catch
			{
			}
		}

		private static void Respond(long sender, string token, int result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(token);
			val.Write(result);
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "ESB_Result_v1", new object[1] { val });
		}

		private static void Result(long sender, ZPackage pkg)
		{
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)ZNet.instance))
			{
				return;
			}
			try
			{
				Player player = Player.m_localPlayer;
				string token = pkg.ReadString();
				int num = pkg.ReadInt();
				if (num == 2)
				{
					List<Request> list = Acks(player);
					list.RemoveAll((Request x) => x.Token == token && x.Owner == sender && x.World == ZNet.instance.GetWorldUID() && x.User == player.GetPlayerID());
					StoreAcks(player, list);
				}
				else
				{
					if (!Busy)
					{
						return;
					}
					Request request = Load(player);
					if (request != null && !(request.Token != token) && request.World == ZNet.instance.GetWorldUID() && request.User == player.GetPlayerID() && sender == request.Owner)
					{
						if (num == 3 && request.Phase == "request")
						{
							request.Phase = "pay";
							Store(player, request);
							retryAt = 0f;
						}
						else if ((!(request.Phase != "request") || !(request.Phase != "commit") || !(request.Phase != "abort")) && (num == 0 || num == 1) && (num != 1 || !(request.Phase != "commit")))
						{
							request.Delivery = Merge((num == 1) ? request.Output : request.Debited);
							request.Phase = "delivery";
							Store(player, request);
							Status = ((num == 1) ? "Done." : "The bed state changed; returning payment.");
							retryAt = 0f;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Seed bed receipt deferred: " + ex.Message));
			}
		}
	}
	internal static class UpgradeStorage
	{
		internal sealed class Source
		{
			internal Inventory Inventory;

			internal Action Save;
		}

		private static readonly FieldInfo View = AccessTools.Field(typeof(Container), "m_nview");

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

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

		private static object Member(object obj, string name)
		{
			object obj2;
			if (obj != null)
			{
				obj2 = AccessTools.Property(obj.GetType(), name)?.GetValue(obj);
				if (obj2 == null)
				{
					return AccessTools.Field(obj.GetType(), name)?.GetValue(obj);
				}
			}
			else
			{
				obj2 = null;
			}
			return obj2;
		}

		internal static List<Source> Sources(Player player, bool upgrade)
		{
			//IL_0443: Unknown result type (might be due to invalid IL or missing references)
			//IL_047f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0485: Invalid comparison between Unknown and I4
			//IL_0491: Unknown result type (might be due to invalid IL or missing references)
			List<Source> list = new List<Source>
			{
				new Source
				{
					Inventory = ((Humanoid)player).GetInventory()
				}
			};
			if (!upgrade || !Settings.UpgradeFromChests.Value)
			{
				return list;
			}
			Type type = AccessTools.TypeByName("ValheimPlus.Configurations.Configuration");
			if (type != null)
			{
				object obj = Member(AccessTools.Property(type, "Current")?.GetValue(null) ?? AccessTools.Field(type, "Current")?.GetValue(null), "CraftFromChest");
				object obj2 = Member(obj, "IsEnabled");
				if (!(obj2 is bool) || !(bool)obj2)
				{
					return list;
				}
				Type type2 = type.Assembly.GetType("ValheimPlus.InventoryAssistant");
				MethodInfo methodInfo = AccessTools.Method(type2, "GetNearbyChests", new Type[4]
				{
					typeof(GameObject),
					typeof(float),
					typeof(bool),
					typeof(bool)
				}, (Type[])null);
				MethodInfo persist = AccessTools.Method(type2, "ConveyContainerToNetwork", new Type[1] { typeof(Container) }, (Type[])null);
				if (methodInfo == null || persist == null)
				{
					throw new MissingMethodException("Unsupported ValheimPlus storage API");
				}
				GameObject gameObject = ((Component)player).gameObject;
				obj2 = Member(obj, "checkFromWorkbench");
				bool flag = default(bool);
				int num;
				if (obj2 is bool)
				{
					flag = (bool)obj2;
					num = 1;
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0 && Object.op_Implicit((Object)(object)player.GetCurrentCraftingStation()))
				{
					gameObject = ((Component)player.GetCurrentCraftingStation()).gameObject;
				}
				float num2 = Mathf.Clamp(Convert.ToSingle(Member(obj, "range")), 1f, 50f);
				obj2 = Member(obj, "ignorePrivateAreaCheck");
				bool flag2 = default(bool);
				int num3;
				if (obj2 is bool)
				{
					flag2 = (bool)obj2;
					num3 = 1;
				}
				else
				{
					num3 = 0;
				}
				bool flag3 = ((uint)num3 & (flag2 ? 1u : 0u)) == 0;
				{
					foreach (Container chest in ((IEnumerable<Container>)methodInfo.Invoke(null, new object[4] { gameObject, num2, flag3, true })).Where((Container c) => Object.op_Implicit((Object)(object)c) && !c.IsInUse()).Distinct())
					{
						Inventory inventory = chest.GetInventory();
						if (inventory != null && !list.Any((Source s) => s.Inventory == inventory))
						{
							list.Add(new Source
							{
								Inventory = inventory,
								Save = delegate
								{
									persist.Invoke(null, new object[1] { chest });
								}
							});
						}
					}
					return list;
				}
			}
			float range = Settings.UpgradeRadius.Value;
			foreach (Container chest2 in Object.FindObjectsByType<Container>((FindObjectsSortMode)0).Where(delegate(Container c)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				if (Object.op_Implicit((Object)(object)c))
				{
					Vector3 val2 = ((Component)c).transform.position - ((Component)player).transform.position;
					return ((Vector3)(ref val2)).sqrMagnitude <= range * range;
				}
				return false;
			}).OrderBy(delegate(Container c)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: 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)
				Vector3 val2 = ((Component)c).transform.position - ((Component)player).transform.position;
				return ((Vector3)(ref val2)).sqrMagnitude;
			}))
			{
				object? value = View.GetValue(chest2);
				ZNetView val = (ZNetView)((value is ZNetView) ? value : null);
				if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || !val.IsOwner() || chest2.IsInUse() || val.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0 || (Object.op_Implicit((Object)(object)chest2.m_wagon) && chest2.m_wagon.InUse()) || !Access.Ward(player.GetPlayerID(), ((Component)chest2).transform.position))
				{
					continue;
				}
				Piece component = ((Component)chest2).GetComponent<Piece>();
				if (!Object.op_Implicit((Object)(object)component) || component.GetCreator() == 0L || (int)chest2.m_privacy == 1 || ((int)chest2.m_privacy == 0 && component.GetCreator() != player.GetPlayerID()))
				{
					continue;
				}
				Load.Invoke(chest2, null);
				Inventory inventory2 = chest2.GetInventory();
				if (inventory2 != null && !list.Any((Source s) => s.Inventory == inventory2))
				{
					list.Add(new Source
					{
						Inventory = inventory2,
						Save = delegate
						{
							Save.Invoke(chest2, null);
						}
					});
				}
			}
			return list;
		}

		internal static bool CanPay(List<Source> sources, List<Cost> costs)
		{
			return costs.All((Cost c) => Object.op_Implicit((Object)(object)Catalog.Item(c.Item)) && ((IEnumerable<Source>)sources).Sum((Func<Source, long>)((Source s) => Transactions.Count(s.Inventory, c.Item))) >= c.Amount);
		}

		internal static void Debit(List<Source> sources, List<Cost> costs, List<Cost> removed)
		{
			foreach (Cost cost in costs)
			{
				int num = cost.Amount;
				foreach (Source source in sources)
				{
					if (num <= 0)
					{
						break;
					}
					Inventory inventory = source.Inventory;
					int num2 = Transactions.Count(inventory, cost.Item);
					try
					{
						ItemData[] array = (from i in inventory.GetAllItems()
							where Object.op_Implicit((Object)(object)i.m_dropPrefab) && ((Object)i.m_dropPrefab).name == cost.Item
							select i).ToArray();
						foreach (ItemData val in array)
						{
							int num4 = Math.Min(num, val.m_stack);
							inventory.RemoveItem(val, num4);
							num -= num4;
							if (num == 0)
							{
								break;
							}
						}
					}
					finally
					{
						int num5 = Math.Max(0, num2 - Transactions.Count(inventory, cost.Item));
						removed.Add(new Cost(cost.Item, num5));
						if (num5 > 0)
						{
							source.Save?.Invoke();
						}
					}
				}
			}
		}
	}
	internal sealed class Visuals : MonoBehaviour
	{
		private BedBehaviour bed;

		private Transform display;

		private string signature;

		private float next;

		private void Awake()
		{
			bed = ((Component)this).GetComponent<BedBehaviour>();
			string[] array = new string[2] { "_sapling", "_grown" };
			foreach (string text in array)
			{
				Transform val = ((Component)this).transform.Find(text);
				if (Object.op_Implicit((Object)(object)val))
				{
					((Component)val).gameObject.SetActive(false);
				}
			}
		}

		private void Update()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Invalid comparison between Unknown and I4
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Expected O, but got Unknown
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)bed) || !bed.Valid || Time.time < next || (int)SystemInfo.graphicsDeviceType == 4)
			{
				return;
			}
			next = Time.time + 1f;
			string text = bed.Seed + ":" + bed.Seeds + ":" + (bed.Pending > 0);
			if (text == signature)
			{
				return;
			}
			if (Object.op_Implicit((Object)(object)display))
			{
				Object.Destroy((Object)(object)((Component)display).gameObject);
			}
			signature = text;
			if (bed.Seeds == 0)
			{
				return;
			}
			GameObject val = Source(bed.Crop);
			if (!Object.op_Implicit((Object)(object)val))
			{
				signature = null;
				return;
			}
			GameObject val2 = new GameObject("ESB_CropVisuals");
			display = val2.transform;
			display.SetParent(((Component)this).transform, false);
			Transform val3 = ((Component)this).transform.Find("_grown/_grown5x5");
			int num = Math.Min(bed.Seeds, Object.op_Implicit((Object)(object)val3) ? val3.childCount : 9);
			for (int i = 0; i < num; i++)
			{
				Vector3 localPosition = (Vector3)(Object.op_Implicit((Object)(object)val3) ? ((Component)this).transform.InverseTransformPoint(val3.GetChild(i).position) : new Vector3((float)(i % 3 - 1) * 0.28f, 0.7f, (float)(i / 3 - 1) * 0.28f));
				Transform transform = new GameObject("PlantVisual").transform;
				transform.SetParent(display, false);
				transform.localPosition = localPosition;
				CopyMeshes(val, transform, (bed.Pending > 0) ? 1f : 0.6f);
			}
		}

		private static GameObject Source(Crop crop)
		{
			if (crop == null)
			{
				return null;
			}
			if (Chainloader.PluginInfos.TryGetValue("blacks7ar.SeedBed", out var value))
			{
				IDictionary dictionary = AccessTools.Field(((object)value.Instance).GetType(), "_ResourceDB")?.GetValue(null) as IDictionary;
				ItemDrop val = Catalog.Item(crop.Seed);
				if (dictionary != null && Object.op_Implicit((Object)(object)val) && dictionary.Contains(val.m_itemData.m_shared.m_name))
				{
					object? obj = dictionary[val.m_itemData.m_shared.m_name];
					return (GameObject)((obj is GameObject) ? obj : null);
				}
			}
			ItemDrop val2 = Catalog.Item(crop.Output);
			if (!Object.op_Implicit((Object)(object)val2))
			{
				return null;
			}
			return ((Component)val2).gameObject;
		}

		private static void CopyMeshes(GameObject source, Transform target, float growth)
		{
			//IL_000f: 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_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: 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_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: 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_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			MeshRenderer[] componentsInChildren = source.GetComponentsInChildren<MeshRenderer>(true);
			if (componentsInChildren.Length == 0)
			{
				return;
			}
			Bounds val = default(Bounds);
			bool flag = true;
			MeshRenderer[] array = componentsInChildren;
			foreach (MeshRenderer val2 in array)
			{
				MeshFilter component = ((Component)val2).GetComponent<MeshFilter>();
				if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.sharedMesh))
				{
					if (flag)
					{
						val = ((Renderer)val2).bounds;
						flag = false;
					}
					else
					{
						((Bounds)(ref val)).Encapsulate(((Renderer)val2).bounds);
					}
				}
			}
			float num = Mathf.Min(1f, 0.55f / Mathf.Max(0.1f, Mathf.Max(((Bounds)(ref val)).size.x, ((Bounds)(ref val)).size.z))) * growth;
			array = componentsInChildren;
			foreach (MeshRenderer val3 in array)
			{
				MeshFilter component2 = ((Component)val3).GetComponent<MeshFilter>();
				if (Object.op_Implicit((Object)(object)component2) && Object.op_Implicit((Object)(object)component2.sharedMesh))
				{
					GameObject val4 = new GameObject("mesh");
					val4.transform.SetParent(target, false);
					val4.transform.localPosition = source.transform.InverseTransformPoint(((Component)val3).transform.position) * num;
					val4.transform.localRotation = Quaternion.Inverse(source.transform.rotation) * ((Component)val3).transform.rotation;
					Vector3 lossyScale = source.transform.lossyScale;
					Vector3 lossyScale2 = ((Component)val3).transform.lossyScale;
					val4.transform.localScale = new Vector3(lossyScale2.x / Mathf.Max(0.001f, Mathf.Abs(lossyScale.x)), lossyScale2.y / Mathf.Max(0.001f, Mathf.Abs(lossyScale.y)), lossyScale2.z / Mathf.Max(0.001f, Mathf.Abs(lossyScale.z))) * num;
					val4.AddComponent<MeshFilter>().sharedMesh = component2.sharedMesh;
					MeshRenderer obj = val4.AddComponent<MeshRenderer>();
					((Renderer)obj).sharedMaterials = ((Renderer)val3).sharedMaterials;
					((Renderer)obj).shadowCastingMode = ((Renderer)val3).shadowCastingMode;
					((Renderer)obj).receiveShadows = ((Renderer)val3).receiveShadows;
				}
			}
		}
	}
}