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