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 ReliableCrafting v1.0.0
BepInEx/plugins/ReliableCrafting/ReliableCrafting.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using ReliableStationSafety; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: AssemblyMetadata("AI_Assisted_Creation", "Coded with the assistance of ChatGPT 6.0; packaged with OpenAI Codex.")] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyMetadata("AI_Model_Vendor", "OpenAI")] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace ReliableCrafting { [BepInPlugin("au.tyvalheim.reliablecrafting", "Reliable Crafting", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { private sealed class PendingCraft { internal InventoryGui Gui; internal Player Player; internal CraftingStation Station; internal Vector3 Position; internal object[] Values; internal int UpgradeQuality; internal List<Container> Sources; internal CraftWait Wait; } private sealed class Transfer { internal Entry Source; internal ItemData Item; internal ItemData Copy; internal int Amount; } public const string Id = "au.tyvalheim.reliablecrafting"; private List<Container> waitingSources; private PendingCraft pendingCraft; private bool resumingCraft; internal bool AutomaticCraftContext; private static readonly FieldInfo CraftTimer = AccessTools.Field(typeof(InventoryGui), "m_craftTimer"); private static readonly MethodInfo FinishCraft = AccessTools.Method(typeof(InventoryGui), "DoCrafting", (Type[])null, (Type[])null); private static readonly string[] CraftFields = new string[6] { "m_craftRecipe", "m_craftUpgradeItem", "m_craftVariant", "m_multiCrafting", "m_multiCraftAmount", "m_selectedVariant" }; private static readonly FieldInfo SelectedRecipe = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); private string diagnosticRecipe = "none"; private float nextCraftSnapshot; private string lastCraftSnapshot; internal static Plugin Instance; internal Storage Storage; internal ConfigEntry<float> Range; internal ConfigEntry<float> Refresh; internal ConfigEntry<float> Budget; internal ConfigEntry<bool> Enabled; internal ConfigEntry<bool> Cooking; internal ConfigEntry<bool> FoodFromChests; internal ConfigEntry<bool> Diagnostics; internal ConfigEntry<bool> Wardrobes; internal ConfigEntry<bool> Carts; internal ConfigEntry<bool> Boats; private Harmony harmony; private Player player; private ZNetScene scene; private bool faulted; private bool staging; internal bool NativeCraft; internal int RecipeListDepth; private float nextMessage; private float nextDiagnostics; private bool rowRefreshFaulted; internal bool HasPendingCraft => pendingCraft != null; private static object[] ReadCraft(InventoryGui gui) { object[] array = new object[CraftFields.Length]; for (int i = 0; i < array.Length; i++) { array[i] = AccessTools.Field(typeof(InventoryGui), CraftFields[i]).GetValue(gui); } return array; } internal void WaitForCraft(InventoryGui gui, Player local) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) if (waitingSources != null && pendingCraft == null) { if (resumingCraft) { Tell("Craft cancelled: required chest access changed. Please try again."); return; } object[] array = ReadCraft(gui); object obj = array[1]; ItemData val = (ItemData)((obj is ItemData) ? obj : null); pendingCraft = new PendingCraft { Gui = gui, Player = local, Station = local.GetCurrentCraftingStation(), Position = ((Component)local).transform.position, Values = array, UpgradeQuality = (val?.m_quality ?? 0), Sources = waitingSources, Wait = new CraftWait(Time.unscaledTime) }; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Craft] Waiting automatically for chest access; recipe=" + ((Object)(Recipe)array[0]).name)); } } internal void CancelPending(string reason) { PendingCraft pendingCraft = this.pendingCraft; if (pendingCraft != null) { this.pendingCraft = null; pendingCraft.Wait.Cancel(); if ((Object)(object)pendingCraft.Gui != (Object)null) { CraftTimer.SetValue(pendingCraft.Gui, -1f); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Craft] Pending craft cancelled: " + reason)); } } private bool SameCraft(PendingCraft pending) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (!Active(pending.Player) || (Object)(object)pending.Player != (Object)(object)Player.m_localPlayer || (Object)(object)pending.Gui == (Object)null || (Object)(object)pending.Gui != (Object)(object)InventoryGui.instance || !InventoryGui.IsVisible()) { return false; } if (!((Object)(object)pending.Player.GetCurrentCraftingStation() != (Object)(object)pending.Station)) { Vector3 val = ((Component)pending.Player).transform.position - pending.Position; if (!(((Vector3)(ref val)).sqrMagnitude > 4f)) { if (!object.ReferenceEquals(pending.Station, null) && (Object)(object)pending.Station == (Object)null) { return false; } if ((Object)(object)pending.Station != (Object)null && (!pending.Station.InUseDistance((Humanoid)(object)pending.Player) || !pending.Station.CheckUsable(pending.Player, false))) { return false; } object value = SelectedRecipe.GetValue(pending.Gui); if (!object.Equals(AccessTools.Property(value.GetType(), "Recipe").GetValue(value, null), pending.Values[0]) || !object.Equals(AccessTools.Property(value.GetType(), "ItemData").GetValue(value, null), pending.Values[1])) { return false; } object[] array = ReadCraft(pending.Gui); for (int i = 0; i < array.Length; i++) { if (!object.Equals(array[i], pending.Values[i])) { return false; } } object obj = array[1]; ItemData val2 = (ItemData)((obj is ItemData) ? obj : null); if (val2 != null) { if (val2.m_quality == pending.UpgradeQuality) { return ((Humanoid)pending.Player).GetInventory().ContainsItem(val2); } return false; } return true; } } return false; } internal void ValidatePending() { if (pendingCraft != null && !SameCraft(pendingCraft)) { CancelPending("recipe, player or workstation changed"); } } internal void KeepPendingProgress(InventoryGui gui) { if (pendingCraft != null && (Object)(object)pendingCraft.Gui == (Object)(object)gui) { CraftTimer.SetValue(gui, 0f); } } private void TickPending() { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) PendingCraft pendingCraft = this.pendingCraft; if (pendingCraft == null) { return; } if (!SameCraft(pendingCraft)) { CancelPending("recipe, player or workstation changed"); } else if (!Storage.CanAwait(pendingCraft.Sources)) { CancelPending("access refused, expired or source unavailable"); Tell("Craft cancelled: chest access is unavailable. No ingredients were pulled for the waiting craft."); } else if (pendingCraft.Wait.Expired(Time.unscaledTime)) { CancelPending("access timeout"); Tell("Craft cancelled: chest access timed out. Please try again."); } else if (pendingCraft.Wait.TryResume(Time.unscaledTime, sameContext: true, Storage.AwaitReady(pendingCraft.Sources))) { this.pendingCraft = null; CraftTimer.SetValue(pendingCraft.Gui, -1f); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Craft] Access ready; resuming original craft once; recipe=" + ((Object)(Recipe)pendingCraft.Values[0]).name)); resumingCraft = true; try { FinishCraft.Invoke(pendingCraft.Gui, new object[1] { pendingCraft.Player }); } finally { resumingCraft = false; } } } internal void CraftLog(string phase, Recipe recipe, Player local, List<Need> needs) { if (Diagnostics == null || !Diagnostics.Value) { return; } try { if ((Object)(object)recipe != (Object)null) { diagnosticRecipe = ((Object)recipe).name; } string text = "[Craft diagnostic] t=" + Time.unscaledTime.ToString("F2") + "; recipe=" + diagnosticRecipe + "; phase=" + phase; if ((Object)(object)local != (Object)null) { CraftingStation currentCraftingStation = local.GetCurrentCraftingStation(); Inventory inventory = ((Humanoid)local).GetInventory(); object obj = text; text = string.Concat(obj, "; station=", ((Object)(object)currentCraftingStation == (Object)null) ? "none" : ((Object)currentCraftingStation).name, "; inventoryEntries=", inventory.GetAllItems().Count, "; grid=", inventory.GetWidth(), "x", inventory.GetHeight()); if (needs != null) { foreach (Need need in needs) { object obj2 = text; text = string.Concat(obj2, "; need=", need.Name, "/q", need.Quality, ":", need.Amount, "/carried=", inventory.CountItems(need.Name, need.Quality, true)); } } } ((BaseUnityPlugin)this).Logger.LogInfo((object)text); } catch { } } internal void CraftUiSnapshot(InventoryGui gui, bool clicked = false) { if (Diagnostics == null || !Diagnostics.Value || (!clicked && Time.unscaledTime < nextCraftSnapshot)) { return; } nextCraftSnapshot = Time.unscaledTime + 2f; try { object value = SelectedRecipe.GetValue(gui); if (value == null) { return; } object? value2 = AccessTools.Property(value.GetType(), "Recipe").GetValue(value, null); Recipe val = (Recipe)((value2 is Recipe) ? value2 : null); if (!((Object)(object)val == (Object)null)) { object? value3 = AccessTools.Field(typeof(InventoryGui), "m_craftButton").GetValue(gui); Button val2 = (Button)((value3 is Button) ? value3 : null); string text = ((Object)val).name + "; buttonInteractable=" + ((Object)(object)val2 != (Object)null && ((Selectable)val2).interactable) + "; pending=" + HasPendingCraft + "; native=" + NativeCraft + "; faulted=" + faulted; if (clicked || !(text == lastCraftSnapshot)) { lastCraftSnapshot = text; CraftLog((clicked ? "Craft click received; " : "UI ") + text, val, Player.m_localPlayer, null); } } } catch { } } private void Awake() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown Instance = this; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable nearby-container crafting assistance."); Range = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RangeMeters", 30f, new ConfigDescription("3D range around the player. Loaded, permitted player-built storage only; measured at the container, including parked vehicle storage.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), new object[0])); Wardrobes = ((BaseUnityPlugin)this).Config.Bind<bool>("Storage", "UseWardrobes", true, "Use nearby player-built wardrobes as ingredient sources. Ward, privacy and busy-container checks still apply."); Carts = ((BaseUnityPlugin)this).Config.Bind<bool>("Storage", "UseCarts", true, "Use nearby player-built cart storage while parked. The cart must already be owned by this client and closed; crafting never requests or forces vehicle ownership. Open and close it normally before retrying if needed."); Boats = ((BaseUnityPlugin)this).Config.Bind<bool>("Storage", "UseBoats", true, "Use nearby player-built Karve, Longship and Drakkar storage while parked (normal vertical bobbing/rocking is allowed). The boat must already be owned by this client and its storage closed; crafting never requests or forces vehicle ownership. Rafts have no storage."); Cooking = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "CookingEnabled", true, "Supply manual cooking/fuel inputs, including Frost Foundry casts and Frigid Kiln ice. Assisted inputs and fuel require a locally owned station. Carried food in remote-owned ovens/racks uses native multiplayer input, paced by observed station progress. Cauldron recipes use normal crafting assistance."); FoodFromChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Cooking", "FoodFromChests", false, "Opt in to pulling food from nearby chests when interacting with cooking stations or ovens. Default false: only carried food can be inserted, so stored meat and ingredients are not selected for cooking. Does not disable chest fuel, Frost Foundry casts, or cauldron/workbench recipe crafting. Requires General.CookingEnabled."); Refresh = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "RefreshIntervalSeconds", 0.5f, new ConfigDescription("Recheck active nearby containers; local inventory changes invalidate their summaries immediately.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), new object[0])); Budget = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "RefreshBudgetMilliseconds", 0.5f, new ConfigDescription("Soft per-frame refresh budget. One container refresh cannot be split; large containers can exceed this.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), new object[0])); Diagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Enabled", false, "Log recipe/button state changes, craft requirements and outcomes, source refusals, chest requests/replies and 30-second counters. Displayed assistance messages are always logged."); Storage = new Storage(this); Range.SettingChanged += delegate { Storage.Invalidate(); }; Wardrobes.SettingChanged += StorageSettingsChanged; Carts.SettingChanged += StorageSettingsChanged; Boats.SettingChanged += StorageSettingsChanged; try { if (Storage.Load == null || Storage.Access == null || Storage.Changed == null) { throw new MissingMemberException("Required game API is missing."); } harmony = new Harmony("au.tyvalheim.reliablecrafting"); harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Reliable Crafting 1.0.0 loaded. Food from chests=" + FoodFromChests.Value + "; wardrobes=" + Wardrobes.Value + "; carts=" + Carts.Value + "; boats=" + Boats.Value + "; assisted inputs, fuel and vehicle storage require local ownership; remote carried food follows native input with pending-input and capacity checks.")); } catch (Exception ex) { Fault(ex); if (harmony != null) { harmony.UnpatchSelf(); } } } private void StorageSettingsChanged(object sender, EventArgs args) { CancelPending("storage source setting changed"); if (Storage != null) { Storage.Invalidate(); } } internal bool Active(Player candidate) { if (!faulted && Enabled.Value && (Object)(object)candidate != (Object)null && (Object)(object)candidate == (Object)(object)Player.m_localPlayer) { return !((Character)candidate).IsDead(); } return false; } internal bool RefuseFaultedInput(Player candidate) { if (!faulted || !Enabled.Value || !Cooking.Value || (Object)(object)candidate == (Object)null || (Object)(object)candidate != (Object)(object)Player.m_localPlayer) { return false; } Tell("Station inputs paused after an error. No input consumed; restart Valheim before using assisted stations."); return true; } private void Update() { if (faulted) { return; } try { if ((Object)(object)scene != (Object)(object)ZNetScene.instance) { CancelPending("world changed"); Storage.Dispose(); Storage = new Storage(this); scene = ZNetScene.instance; if ((Object)(object)scene != (Object)null) { Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); foreach (Container chest in array) { Storage.Add(chest); } } } if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { player = Player.m_localPlayer; Storage.Invalidate(); } TickPending(); if (Active(player)) { if (InventoryGui.IsVisible()) { Storage.Nearby(player); } Storage.Tick(player); if (Diagnostics.Value && Time.unscaledTime >= nextDiagnostics) { nextDiagnostics = Time.unscaledTime + 30f; ((BaseUnityPlugin)this).Logger.LogInfo((object)("refreshes=" + Storage.Refreshes + " inventoryRebuilds=" + Storage.InventoryRebuilds + " accessRequests=" + Storage.Requests)); } } } catch (Exception ex) { Fault(ex); } } internal int Count(Inventory inventory, string name, int quality, bool worldLevel) { int num = inventory.CountItems(name, quality, worldLevel); Player localPlayer = Player.m_localPlayer; if (!Active(localPlayer) || (NativeCraft && RecipeListDepth == 0) || inventory != ((Humanoid)localPlayer).GetInventory()) { return num; } try { return checked(num + Storage.Count(localPlayer, name, quality)); } catch (Exception ex) { Fault(ex); return num; } } internal ItemData Example(Inventory inventory, string name, int quality, bool equipped) { ItemData item = inventory.GetItem(name, quality, equipped); Player localPlayer = Player.m_localPlayer; if (item != null || (NativeCraft && RecipeListDepth == 0) || !Active(localPlayer) || inventory != ((Humanoid)localPlayer).GetInventory()) { return item; } try { return Storage.Example(localPlayer, name, quality); } catch (Exception ex) { Fault(ex); return item; } } internal List<Need> RecipeNeeds(Player local, Recipe recipe, int quality, int multiplier) { List<Need> list = new List<Need>(); CraftingStation currentCraftingStation = local.GetCurrentCraftingStation(); Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if ((Object)(object)val.m_resItem == (Object)null || (((Object)(object)currentCraftingStation != (Object)null) ? (val.m_upgraderResource != currentCraftingStation.m_upgrader) : val.m_upgraderResource)) { continue; } int num = checked(val.GetAmount(quality) * multiplier); if (num <= 0) { continue; } string name = val.m_resItem.m_itemData.m_shared.m_name; if (recipe.m_requireOnlyOneIngredient) { for (int j = 0; j <= val.m_resItem.m_itemData.m_shared.m_maxQuality; j++) { if (Count(((Humanoid)local).GetInventory(), name, j, worldLevel: true) >= num) { list.Add(new Need { Name = name, Amount = num, Quality = j }); return list; } } } else { list.Add(new Need { Name = name, Amount = num }); } } if (recipe.m_requireOnlyOneIngredient) { return null; } return Policy.Combine(list); } internal bool Prepare(Player local, List<Need> needs) { //IL_03c5: Unknown result type (might be due to invalid IL or missing references) //IL_0400: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Unknown result type (might be due to invalid IL or missing references) //IL_04c3: Unknown result type (might be due to invalid IL or missing references) //IL_04c8: Unknown result type (might be due to invalid IL or missing references) waitingSources = null; if (needs == null) { Tell("Required ingredients are unavailable."); return false; } if (staging) { Tell("An inventory transfer is already in progress."); return false; } List<Need> list = new List<Need>(); foreach (Need item2 in Policy.Combine(needs)) { int num = Math.Max(0, item2.Amount - ((Humanoid)local).GetInventory().CountItems(item2.Name, item2.Quality, true)); if (num > 0) { list.Add(new Need { Name = item2.Name, Amount = num, Quality = item2.Quality }); } } if (list.Count == 0) { return true; } List<Transfer> list2 = new List<Transfer>(); Dictionary<ItemData, int> dictionary = new Dictionary<ItemData, int>(); List<Entry> list3 = Storage.Nearby(local); foreach (Entry item3 in list3) { Storage.Refresh(item3, local); } foreach (Need item4 in list) { int num2 = item4.Amount; foreach (Entry item5 in list3) { if (!Storage.Allowed(item5.Chest, local, Range.Value, requireOwner: false)) { continue; } foreach (ItemData allItem in item5.Inventory.GetAllItems()) { if (allItem.m_stack > 0 && !(allItem.m_shared.m_name != item4.Name) && allItem.m_worldLevel >= Game.m_worldLevel && (item4.Quality < 0 || allItem.m_quality == item4.Quality)) { dictionary.TryGetValue(allItem, out var value); int num3 = Math.Min(num2, Math.Max(0, allItem.m_stack - value)); if (num3 > 0) { list2.Add(new Transfer { Source = item5, Item = allItem, Amount = num3 }); dictionary[allItem] = checked(value + num3); } num2 -= num3; if (num2 == 0) { break; } } } if (num2 == 0) { break; } } if (num2 > 0) { Tell("Materials unavailable: chests may be busy, ward-protected or out of range. Nothing pulled."); return false; } } HashSet<Container> hashSet = new HashSet<Container>(); List<Container> list4 = new List<Container>(); foreach (Transfer item6 in list2) { if (hashSet.Add(item6.Source.Chest)) { list4.Add(item6.Source.Chest); } } if (!Policy.AllReady(list4, (Container chest) => Storage.Ready(chest, local, Range.Value))) { if (Storage.CanAwait(list4)) { waitingSources = list4; } return false; } Inventory inventory = ((Humanoid)local).GetInventory(); List<Vector2i> list5 = new List<Vector2i>(); HashSet<Vector2i> hashSet2 = new HashSet<Vector2i>(); foreach (ItemData allItem2 in inventory.GetAllItems()) { hashSet2.Add(allItem2.m_gridPos); } Vector2i item = default(Vector2i); for (int num4 = 0; num4 < inventory.GetHeight(); num4++) { for (int num5 = 0; num5 < inventory.GetWidth(); num5++) { ((Vector2i)(ref item))..ctor(num5, num4); if (!hashSet2.Contains(item)) { list5.Add(item); } } } int num6 = 0; List<Transfer> list6 = new List<Transfer>(); foreach (Transfer item7 in list2) { int num7 = item7.Amount; while (num7 > 0) { if (num6 == list5.Count) { Tell("Make room for crafting ingredients in your inventory. Nothing pulled."); return false; } int num8 = Math.Min(num7, Math.Max(1, item7.Item.m_shared.m_maxStackSize)); ItemData val = item7.Item.Clone(); val.m_stack = num8; val.m_gridPos = list5[num6++]; val.m_equipped = false; list6.Add(new Transfer { Source = item7.Source, Item = item7.Item, Copy = val, Amount = num8 }); num7 -= num8; } } Dictionary<IList<ItemData>, Inventory> inventories = new Dictionary<IList<ItemData>, Inventory>(); inventories[inventory.GetAllItems()] = inventory; foreach (Transfer item8 in list2) { if (!Storage.Allowed(item8.Source.Chest, local, Range.Value, requireOwner: true) || !item8.Source.Inventory.ContainsItem(item8.Item) || item8.Item.m_stack < item8.Amount) { Tell("Chest access or stock changed. Nothing pulled; retry."); return false; } inventories[item8.Source.Inventory.GetAllItems()] = item8.Source.Inventory; } List<Move<ItemData>> list7 = new List<Move<ItemData>>(); foreach (Transfer item9 in list6) { list7.Add(new Move<ItemData> { Source = item9.Source.Inventory.GetAllItems(), Destination = inventory.GetAllItems(), Item = item9.Item, Copy = item9.Copy, Amount = item9.Amount }); } staging = true; try { TransferBatch.Execute(list7, (ItemData i) => i.m_stack, delegate(ItemData i, int n) { i.m_stack = n; }, delegate(IList<ItemData> key) { Storage.Changed.Invoke(inventories[key], new object[2] { false, false }); }); return true; } finally { staging = false; Storage.Invalidate(); HashSet<Entry> hashSet3 = new HashSet<Entry>(); foreach (Transfer item10 in list2) { if (hashSet3.Add(item10.Source)) { Storage.Refresh(item10.Source, local); } } } } public static bool RequestChestAccess(Container chest, Player local, float radius) { Plugin instance = Instance; if ((Object)(object)instance == (Object)null || !instance.Active(local)) { return false; } try { return instance.Storage.Ready(chest, local, radius); } catch (Exception ex) { instance.Fault(ex); return false; } } internal void Tell(string message) { if (!((Object)(object)Player.m_localPlayer == (Object)null) && !(Time.unscaledTime < nextMessage)) { nextMessage = Time.unscaledTime + 1.5f; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Assistance] " + message)); ((Character)Player.m_localPlayer).Message((MessageType)1, "Reliable Crafting: " + message, 0, (Sprite)null, false); } } internal void AccessLog(string action, Container chest, long sender) { if (Diagnostics.Value) { ZNetView val = Storage.View(chest); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Chest access] " + action + "; chest=" + (((Object)(object)chest == (Object)null) ? "destroyed" : ((Object)chest).name) + "; id=" + ((val2 == null) ? "invalid" : ((object)Unsafe.As<ZDOID, ZDOID>(ref val2.m_uid)/*cast due to .constrained prefix*/).ToString()) + "; peer=" + sender + "; owner=" + ((val2 == null) ? 0 : val2.GetOwner()) + "; localOwner=" + ((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()))); } } internal void Fault(Exception ex) { faulted = true; CancelPending("assistance fault"); ((BaseUnityPlugin)this).Logger.LogError((object)("Chest assistance and guarded station inputs paused until restart; ordinary recipe crafting and unrelated inventory actions remain available. " + ex)); } private void OnDestroy() { CancelPending("plugin unloaded"); if (harmony != null) { harmony.UnpatchSelf(); } if (Storage != null) { Storage.Dispose(); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } internal void RefreshRecipeRows(InventoryGui gui, Player local) { if (rowRefreshFaulted || NativeCraft || !Active(local)) { return; } try { RecipeRows.Refresh(gui, local, Storage); } catch (Exception ex) { rowRefreshFaulted = true; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Live recipe-row display updates paused after a UI error. Native recipe selection and crafting remain available. " + ex)); } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] internal static class PendingProgressPatch { private static void Prefix() { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.ValidatePending(); } } private static void Postfix(InventoryGui __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.KeepPendingProgress(__instance); Plugin.Instance.CraftUiSnapshot(__instance); } } } [HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")] internal static class PendingClickPatch { private static bool Prefix(InventoryGui __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.CraftUiSnapshot(__instance, clicked: true); } if (!((Object)(object)Plugin.Instance == (Object)null)) { return !Plugin.Instance.HasPendingCraft; } return true; } } [HarmonyPatch(typeof(InventoryGui), "OnCraftCancelPressed")] internal static class PendingCancelPatch { private static void Prefix() { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.CancelPending("player cancelled"); } } } [HarmonyPatch(typeof(InventoryGui), "Hide")] internal static class PendingHidePatch { private static void Prefix() { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.CancelPending("menu closed"); } } } [HarmonyPatch(typeof(InventoryGui), "SetRecipe")] internal static class PendingSelectionPatch { private static void Postfix() { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.ValidatePending(); } } } internal static class Hooks { internal static int Count(Inventory inv, string name, int quality, bool world) { if (!((Object)(object)Plugin.Instance == (Object)null)) { return Plugin.Instance.Count(inv, name, quality, world); } return inv.CountItems(name, quality, world); } internal static ItemData Get(Inventory inv, string name, int quality, bool equipped) { if (!((Object)(object)Plugin.Instance == (Object)null)) { return Plugin.Instance.Example(inv, name, quality, equipped); } return inv.GetItem(name, quality, equipped); } internal static int UiCount(Inventory inv, string name, int quality, bool world, bool craft) { if (!craft) { return inv.CountItems(name, quality, world); } return Count(inv, name, quality, world); } internal static IEnumerable<CodeInstruction> Replace(IEnumerable<CodeInstruction> instructions, bool ui, bool item) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(); int num = 0; int num2 = 0; MethodInfo methodInfo = AccessTools.Method(typeof(Inventory), "CountItems", new Type[3] { typeof(string), typeof(int), typeof(bool) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Inventory), "GetItem", new Type[3] { typeof(string), typeof(int), typeof(bool) }, (Type[])null); foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, methodInfo)) { if (ui) { CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_3, (object)null); val.labels.AddRange(instruction.labels); instruction.labels.Clear(); val.blocks.AddRange(instruction.blocks); instruction.blocks.Clear(); list.Add(val); } instruction.opcode = OpCodes.Call; instruction.operand = AccessTools.Method(typeof(Hooks), ui ? "UiCount" : "Count", (Type[])null, (Type[])null); num++; } else if (item && CodeInstructionExtensions.Calls(instruction, methodInfo2)) { instruction.opcode = OpCodes.Call; instruction.operand = AccessTools.Method(typeof(Hooks), "Get", (Type[])null, (Type[])null); num2++; } list.Add(instruction); } if (num != 1 || (item && num2 != 1)) { throw new InvalidOperationException("Unsupported resource-check IL layout; assistance will not load."); } return list; } } [HarmonyPatch(typeof(Player), "HaveRequirementItems", new Type[] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) })] internal static class RequirementPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.Replace(instructions, ui: false, item: false); } } [HarmonyPatch(typeof(Player), "GetFirstRequiredItem")] internal static class AlternativeIngredientPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.Replace(instructions, ui: false, item: true); } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement")] internal static class UiPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.Replace(instructions, ui: true, item: false); } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList")] internal static class RecipeListPatch { private static void Prefix(out Plugin __state) { __state = Plugin.Instance; if ((Object)(object)__state != (Object)null) { __state.RecipeListDepth++; } } private static Exception Finalizer(Exception __exception, Plugin __state) { if ((Object)(object)__state != (Object)null) { __state.RecipeListDepth--; } return __exception; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class CraftPatch { [HarmonyPriority(800)] private static bool Prefix(InventoryGui __instance, Player player, Recipe ___m_craftRecipe, ItemData ___m_craftUpgradeItem, bool ___m_multiCrafting, int ___m_multiCraftAmount, out bool __state) { __state = false; Plugin instance = Plugin.Instance; if ((Object)(object)instance != (Object)null) { instance.CraftLog("DoCrafting entered", ___m_craftRecipe, player, null); } if ((Object)(object)instance == (Object)null || !instance.Active(player) || (Object)(object)___m_craftRecipe == (Object)null) { return true; } if (instance.NativeCraft) { return false; } if (instance.HasPendingCraft) { return false; } try { if (!player.NoCostCheat() && !ZoneSystem.instance.GetGlobalKey((GlobalKeys)25)) { int num = ((___m_craftUpgradeItem == null) ? 1 : (___m_craftUpgradeItem.m_quality + 1)); List<Need> needs = instance.RecipeNeeds(player, ___m_craftRecipe, num, (!___m_multiCrafting) ? 1 : ___m_multiCraftAmount); instance.CraftLog("requirements; targetQuality=" + num, ___m_craftRecipe, player, needs); instance.AutomaticCraftContext = true; bool flag; try { flag = instance.Prepare(player, needs); } finally { instance.AutomaticCraftContext = false; } if (!flag) { instance.WaitForCraft(__instance, player); instance.CraftLog("preparation refused; pending=" + instance.HasPendingCraft, ___m_craftRecipe, player, null); return false; } } instance.NativeCraft = true; __state = true; instance.CraftLog("inputs ready; handing to native crafting", ___m_craftRecipe, player, null); return true; } catch (Exception ex) { instance.Fault(ex); return false; } } private static Exception Finalizer(Exception __exception, bool __state) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.CraftLog("DoCrafting exited; handedToNative=" + __state + "; exception=" + ((__exception == null) ? "none" : __exception.ToString()), null, Player.m_localPlayer, null); } if (__state && (Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.NativeCraft = false; } return __exception; } } [HarmonyPatch(typeof(Container), "Awake")] internal static class RegisterPatch { private static void Postfix(Container __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.Storage.Add(__instance); } } } [HarmonyPatch(typeof(Container), "RPC_OpenResponse")] internal static class ResponsePatch { private static bool Prefix(Container __instance, long __0, bool __1) { if (!((Object)(object)Plugin.Instance == (Object)null)) { return Plugin.Instance.Storage.Response(__instance, __0, __1); } return true; } } [HarmonyPatch(typeof(Container), "Interact")] internal static class ManualOpenPatch { private static void Prefix(Container __instance, Humanoid __0, bool __1) { if (!__1 && !((Object)(object)__0 != (Object)(object)Player.m_localPlayer) && !((Object)(object)Plugin.Instance == (Object)null)) { Plugin.Instance.Storage.ManualOpen(__instance); } } } [HarmonyPatch(typeof(CookingStation), "OnInteract")] internal static class CookPatch { private static readonly MethodInfo Find = AccessTools.Method(typeof(CookingStation), "FindCookableItem", (Type[])null, (Type[])null); private static readonly MethodInfo Done = AccessTools.Method(typeof(CookingStation), "HaveDoneItem", (Type[])null, (Type[])null); private static readonly MethodInfo Free = AccessTools.Method(typeof(CookingStation), "GetFreeSlot", (Type[])null, (Type[])null); private static readonly MethodInfo Fire = AccessTools.Method(typeof(CookingStation), "IsFireLit", (Type[])null, (Type[])null); private static bool Prefix(CookingStation __instance, Humanoid __0, ZNetView ___m_nview, ref bool __result, bool __runOriginal) { //IL_0107: Unknown result type (might be due to invalid IL or missing references) if (!Policy.SupportsCookingStation(((Object)__instance).name)) { return true; } if (!__runOriginal) { return false; } Plugin self = Plugin.Instance; Player local = (Player)(object)((__0 is Player) ? __0 : null); if ((Object)(object)self == (Object)null || !self.Active(local) || !self.Cooking.Value) { return true; } if (!Policy.CanPullCookingInput(((Object)__instance).name, self.FoodFromChests.Value)) { return true; } try { if ((bool)Done.Invoke(__instance, null) || Find.Invoke(__instance, new object[1] { ((Humanoid)local).GetInventory() }) != null) { return true; } if (!StationAccess.Ready((Component)(object)__instance, local)) { __result = false; return false; } if ((int)Free.Invoke(__instance, null) < 0 || !PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, true) || (__instance.m_requireFire && !(bool)Fire.Invoke(__instance, null))) { __result = false; return false; } List<ItemConversion> list = __instance.m_conversion.Where((ItemConversion c) => c != null && (Object)(object)c.m_from != (Object)null && self.Storage.Count(local, c.m_from.m_itemData.m_shared.m_name, -1) > 0).ToList(); if (__instance.m_recordCrafter && list.Select((ItemConversion c) => c.m_from.m_itemData.m_shared.m_name).Distinct().Count() > 1) { self.Tell("Several casts are available. Carry the cast you want to use; nothing moved."); __result = false; return false; } foreach (ItemConversion item in list) { string name = item.m_from.m_itemData.m_shared.m_name; if (self.Storage.Count(local, name, -1) <= 0) { continue; } if (self.Prepare(local, new List<Need> { new Need { Name = name, Amount = 1 } })) { if (___m_nview.IsOwner()) { return true; } self.Tell("Input moved into your inventory; station ownership changed. Nothing inserted."); } __result = false; return false; } self.Tell("No available station input; busy and ward-protected chests are excluded."); __result = false; return false; } catch (Exception ex) { self.Fault(ex); __result = false; return false; } } } [HarmonyPatch(typeof(CookingStation), "OnAddFuelSwitch")] internal static class OvenFuelPatch { private static bool Prefix(CookingStation __instance, Humanoid __1, ItemData __2, ZNetView ___m_nview, ref bool __result, bool __runOriginal) { if (!Policy.SupportsCookingStation(((Object)__instance).name)) { return true; } if (!__runOriginal) { return false; } Plugin instance = Plugin.Instance; Player val = (Player)(object)((__1 is Player) ? __1 : null); if ((Object)(object)__instance.m_fuelItem != (Object)null && (Object)(object)instance != (Object)null && instance.RefuseFaultedInput(val)) { __result = false; return false; } if ((Object)(object)instance == (Object)null || !instance.Active(val) || !instance.Cooking.Value || (Object)(object)__instance.m_fuelItem == (Object)null) { return true; } try { return StationTransfer.Fuel((Component)(object)__instance, val, __2, __instance.m_fuelItem, __instance.m_maxFuel, ref __result); } catch (Exception ex) { instance.Fault(ex); __result = false; return false; } } } internal enum StorageKind { Unsupported, Chest, Wardrobe, Cart, Boat } internal sealed class Need { internal string Name; internal int Amount; internal int Quality = -1; } internal static class Policy { internal static StorageKind StorageType(string name) { if (string.IsNullOrEmpty(name)) { return StorageKind.Unsupported; } if (name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - "(Clone)".Length); } switch (name) { case "piece_chest_wood": case "piece_chest": case "piece_chest_blackmetal": case "piece_chest_private": return StorageKind.Chest; case "piece_chest_warderobe": return StorageKind.Wardrobe; case "Cart": return StorageKind.Cart; case "Karve": case "VikingShip": case "VikingShip_Ashlands": return StorageKind.Boat; default: return StorageKind.Unsupported; } } internal static bool Mobile(StorageKind kind) { if (kind != StorageKind.Cart) { return kind == StorageKind.Boat; } return true; } internal static bool Stationary(float speedSquared, float angularSpeedSquared) { if (speedSquared >= 0f && speedSquared <= 0.01f && angularSpeedSquared >= 0f) { return angularSpeedSquared <= 0.01f; } return false; } internal static bool StorageEnabled(StorageKind kind, bool wardrobes, bool carts, bool boats) { if (kind != StorageKind.Chest && (kind != StorageKind.Wardrobe || !wardrobes) && (kind != StorageKind.Cart || !carts)) { if (kind == StorageKind.Boat) { return boats; } return false; } return true; } internal static bool SupportsCookingStation(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - "(Clone)".Length); } switch (name) { default: return name == "piece_FrostFoundry"; case "piece_cookingstation": case "piece_cookingstation_iron": case "piece_oven": return true; } } internal static bool CanPullCookingInput(string name, bool foodFromChests) { if (SupportsCookingStation(name)) { if (!foodFromChests && !(name == "piece_FrostFoundry")) { return name == "piece_FrostFoundry(Clone)"; } return true; } return false; } internal static bool IsFoodStation(string name) { if (SupportsCookingStation(name) && name != "piece_FrostFoundry") { return name != "piece_FrostFoundry(Clone)"; } return false; } internal static bool AllReady<T>(IEnumerable<T> sources, Func<T, bool> ready) { bool result = true; foreach (T source in sources) { if (!ready(source)) { result = false; } } return result; } internal static bool InRange(float squaredDistance, float radius) { if (!float.IsNaN(radius) && radius >= 1f && radius <= 100f && squaredDistance >= 0f) { return squaredDistance <= radius * radius; } return false; } internal static int Cell(float coordinate) { return (int)Math.Floor(coordinate / 16f); } internal static List<Need> Combine(IEnumerable<Need> input) { List<Need> list = new List<Need>(); checked { foreach (Need need in input) { if (need.Amount < 0) { throw new ArgumentOutOfRangeException("input"); } if (need.Amount != 0) { if (string.IsNullOrEmpty(need.Name)) { throw new ArgumentException("Missing material name"); } Need need2 = list.Find((Need n) => n.Name == need.Name && n.Quality == need.Quality); if (need2 == null) { list.Add(new Need { Name = need.Name, Quality = need.Quality, Amount = need.Amount }); } else { need2.Amount += need.Amount; } } } return list; } } } internal sealed class ActivityWindow { private float until = float.NegativeInfinity; internal void Touch(float now) { until = now + 1f; } internal bool IsActive(float now) { return now <= until; } } internal sealed class CraftWait { private readonly float started; private bool finished; internal CraftWait(float now) { started = now; } internal bool Expired(float now) { return now - started >= 5f; } internal void Cancel() { finished = true; } internal bool TryResume(float now, bool sameContext, bool allReady) { if (finished) { return false; } if (!sameContext || Expired(now)) { finished = true; return false; } if (!allReady) { return false; } finished = true; return true; } } internal sealed class ChestAccessRoutes<T> { internal readonly Dictionary<T, OpenRequest> Requests = new Dictionary<T, OpenRequest>(); private readonly HashSet<T> native = new HashSet<T>(); private readonly HashSet<T> assisted = new HashSet<T>(); internal bool WasAssisted(T chest) { return assisted.Contains(chest); } internal void BeginAssisted(T chest) { native.Remove(chest); assisted.Add(chest); } internal void CompleteManual(T chest) { native.Remove(chest); } internal bool IsNative(T chest) { return native.Contains(chest); } internal bool UseNative(T chest) { Requests.Remove(chest); return native.Add(chest); } internal void Remove(T chest) { Requests.Remove(chest); native.Remove(chest); assisted.Remove(chest); } internal void Clear() { Requests.Clear(); native.Clear(); assisted.Clear(); } } internal sealed class OpenRequest { internal long Owner; internal float Started; internal bool Answered; internal bool Granted; internal float AnswerTime; private float ownedSince = float.NaN; internal bool Accept(long sender, bool granted, float now) { if (Answered || sender != Owner) { return false; } Answered = true; Granted = granted; AnswerTime = now; return true; } internal bool Ready(bool owned, float now) { if (!owned) { ownedSince = float.NaN; return false; } if (float.IsNaN(ownedSince)) { ownedSince = now; } if (!TimedOut(now)) { return now - ownedSince >= 0.25f; } return false; } internal bool TimedOut(float now) { return now - Started >= 5f; } } internal static class RecipeRows { private static readonly Type Pair = typeof(InventoryGui).GetNestedType("RecipeDataPair", BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo Rows = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes"); private static readonly FieldInfo Selected = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); private static readonly PropertyInfo Recipe = AccessTools.Property(Pair, "Recipe"); private static readonly PropertyInfo Item = AccessTools.Property(Pair, "ItemData"); private static readonly PropertyInfo Element = AccessTools.Property(Pair, "InterfaceElement"); private static readonly PropertyInfo Available = AccessTools.Property(Pair, "CanCraft"); private static InventoryGui lastGui; private static Storage lastStorage; private static long lastRefreshes; private static float nextCheck; internal static void Refresh(InventoryGui gui, Player player, Storage storage) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gui == (Object)(object)lastGui && storage == lastStorage && storage.Refreshes == lastRefreshes && Time.unscaledTime < nextCheck) { return; } lastGui = gui; lastStorage = storage; lastRefreshes = storage.Refreshes; nextCheck = Time.unscaledTime + 0.5f; IList list = (IList)Rows.GetValue(gui); if (list == null || list.Count == 0) { return; } bool flag = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)25); CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); bool flag2 = (Object)(object)currentCraftingStation != (Object)null && currentCraftingStation.m_upgrader; object value = Selected.GetValue(gui); GameObject val = (GameObject)Element.GetValue(value, null); for (int i = 0; i < list.Count; i++) { object obj = list[i]; Recipe val2 = (Recipe)Recipe.GetValue(obj, null); ItemData val3 = (ItemData)Item.GetValue(obj, null); GameObject val4 = (GameObject)Element.GetValue(obj, null); if ((Object)(object)val2 == (Object)null || (Object)(object)val4 == (Object)null) { continue; } bool flag3 = (val3 == null || val3.m_quality < val3.m_shared.m_maxQuality || flag2 || flag) && (flag || player.HaveRequirements(val2, false, (val3 == null) ? 1 : (val3.m_quality + 1), 1)); if (flag3 != (bool)Available.GetValue(obj, null)) { Available.SetValue(obj, flag3, null); list[i] = obj; if ((Object)(object)val4 == (Object)(object)val) { Selected.SetValue(gui, obj); } Transform val5 = val4.transform.Find("name"); Transform val6 = val4.transform.Find("icon"); TMP_Text val7 = (((Object)(object)val5 != (Object)null) ? ((Component)val5).GetComponent<TMP_Text>() : null); Image val8 = (((Object)(object)val6 != (Object)null) ? ((Component)val6).GetComponent<Image>() : null); if ((Object)(object)val7 != (Object)null) { ((Graphic)val7).color = (Color)(flag3 ? Color.white : new Color(0.66f, 0.66f, 0.66f, 1f)); } if ((Object)(object)val8 != (Object)null) { ((Graphic)val8).color = (Color)(flag3 ? Color.white : new Color(1f, 0f, 1f, 0f)); } } } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] internal static class RecipeRowRefreshPatch { private static void Postfix(InventoryGui __instance, Player __0) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.RefreshRecipeRows(__instance, __0); } } } internal static class StationAccess { internal static bool Ready(Component station, Player player) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) ZNetView component = station.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { Plugin.Instance.Tell("Another peer controls this station, or it is still loading. Input kept in your inventory; retry when this client owns the station."); return false; } if (!PrivateArea.CheckAccess(station.transform.position, 0f, false, true)) { Plugin.Instance.Tell("Station is ward-protected; nothing moved."); return false; } if ((Object)(object)player != (Object)null && !((Character)player).IsDead()) { return !((Character)player).IsTeleporting(); } return false; } } [HarmonyPatch(typeof(CookingStation), "CookItem")] internal static class StationInputGuard { [HarmonyPriority(800)] private static bool Prefix(CookingStation __instance, Humanoid __0, ItemData __1, ref bool __result, bool __runOriginal, out FoodAttempt __state) { __state = null; if (!__runOriginal) { return false; } Plugin instance = Plugin.Instance; Player val = (Player)(object)((__0 is Player) ? __0 : null); if (Policy.SupportsCookingStation(((Object)__instance).name) && (Object)(object)instance != (Object)null && instance.RefuseFaultedInput(val)) { __result = false; return false; } if ((Object)(object)instance == (Object)null || !instance.Active(val) || !instance.Cooking.Value || !Policy.SupportsCookingStation(((Object)__instance).name)) { return true; } try { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if (Policy.IsFoodStation(((Object)__instance).name) && (Object)(object)component != (Object)null && component.IsValid() && component.HasOwner() && !component.IsOwner()) { string message; bool flag = RemoteFoodInput.Begin(__instance, val, __1, out __state, out message); if (message != null) { instance.Tell(message); } if (!flag) { __result = false; } return flag; } return StationTransfer.Input(__instance, val, __1, ref __result); } catch (Exception ex) { instance.Fault(ex); __result = false; return false; } } private static Exception Finalizer(Exception __exception, FoodAttempt __state) { RemoteFoodInput.Finish(__state); return __exception; } } [HarmonyPatch(typeof(Smelter), "OnAddFuel")] internal static class SmelterFuelPatch { private static bool Prefix(Smelter __instance, Humanoid __1, ItemData __2, ref bool __result, bool __runOriginal) { if (!__runOriginal) { return false; } Plugin instance = Plugin.Instance; Player val = (Player)(object)((__1 is Player) ? __1 : null); if ((Object)(object)__instance.m_fuelItem != (Object)null && __instance.m_maxFuel > 0 && (Object)(object)instance != (Object)null && instance.RefuseFaultedInput(val)) { __result = false; return false; } if ((Object)(object)instance == (Object)null || !instance.Active(val) || !instance.Cooking.Value || (Object)(object)__instance.m_fuelItem == (Object)null || __instance.m_maxFuel <= 0) { return true; } try { return StationTransfer.Fuel((Component)(object)__instance, val, __2, __instance.m_fuelItem, __instance.m_maxFuel, ref __result); } catch (Exception ex) { instance.Fault(ex); __result = false; return false; } } } internal static class StationTransfer { internal static void Move(Inventory inventory, ItemData item, Func<bool> valid, Func<bool> committed, Action write) { if (!valid() || !inventory.ContainsItem(item) || item.m_stack <= 0) { throw new InvalidOperationException("Transfer eligibility changed before debit."); } List<ItemData> allItems = inventory.GetAllItems(); int val = allItems.IndexOf(item); int stack = item.m_stack; item.m_stack--; if (item.m_stack == 0) { allItems.Remove(item); } try { if (!valid()) { throw new InvalidOperationException("Ownership/access changed before station commit."); } write(); if (!committed()) { throw new InvalidOperationException("Station did not accept its input."); } } catch { if (!committed()) { item.m_stack = stack; if (!allItems.Contains(item)) { allItems.Insert(Math.Min(val, allItems.Count), item); } } Storage.Changed.Invoke(inventory, new object[2] { false, false }); throw; } Storage.Changed.Invoke(inventory, new object[2] { false, false }); } internal static bool Fuel(Component station, Player player, ItemData supplied, ItemDrop fuel, int capacity, ref bool result) { Plugin instance = Plugin.Instance; result = false; if (!StationAccess.Ready(station, player)) { return false; } string name = fuel.m_itemData.m_shared.m_name; if (supplied != null && supplied.m_shared.m_name != name) { return true; } MethodInfo getter = AccessTools.Method(((object)station).GetType(), "GetFuel", (Type[])null, (Type[])null); float before = (float)getter.Invoke(station, null); if (float.IsNaN(before) || float.IsInfinity(before) || before < 0f || before > (float)(capacity - 1)) { instance.Tell("Station fuel is full or unavailable; nothing consumed."); return false; } Inventory inventory = ((Humanoid)player).GetInventory(); Func<ItemData> func = () => ((IEnumerable<ItemData>)inventory.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => i.m_stack > 0 && i.m_shared.m_name == name && i.m_worldLevel >= Game.m_worldLevel)); ItemData val = supplied ?? func(); if (val == null && instance.Prepare(player, new List<Need> { new Need { Name = name, Amount = 1 } })) { val = func(); } if (val == null) { return false; } MethodInfo add = AccessTools.Method(((object)station).GetType(), "RPC_AddFuel", new Type[1] { typeof(long) }, (Type[])null); Move(inventory, val, () => StationAccess.Ready(station, player), () => (float)getter.Invoke(station, null) != before, delegate { add.Invoke(station, new object[1] { ZDOMan.GetSessionID() }); }); result = true; return false; } internal static bool Input(CookingStation station, Player player, ItemData item, ref bool result) { result = false; if (!StationAccess.Ready((Component)(object)station, player)) { return false; } if (item == null || !Object.op_Implicit((Object)(object)item.m_dropPrefab)) { return false; } if (station.m_incompatibleItems.Any((ItemMessage c) => Object.op_Implicit((Object)(object)c.m_item) && c.m_item.m_itemData.m_shared.m_name == item.m_shared.m_name)) { return true; } MethodInfo methodInfo = AccessTools.Method(typeof(CookingStation), "IsItemAllowed", new Type[1] { typeof(ItemData) }, (Type[])null); if (!(bool)methodInfo.Invoke(station, new object[1] { item })) { return true; } int slot = (int)AccessTools.Method(typeof(CookingStation), "GetFreeSlot", (Type[])null, (Type[])null).Invoke(station, null); if (slot < 0) { return false; } ZDO zdo = ((Component)station).GetComponent<ZNetView>().GetZDO(); Move(((Humanoid)player).GetInventory(), item, () => StationAccess.Ready((Component)(object)station, player), () => zdo.GetString("slot" + slot, "") != "", delegate { AccessTools.Method(typeof(CookingStation), "RPC_AddItem", (Type[])null, (Type[])null).Invoke(station, new object[3] { ZDOMan.GetSessionID(), ((Object)item.m_dropPrefab).name, item.m_cheated }); }); result = true; return false; } } internal sealed class Entry { internal Container Chest; internal ZNetView View; internal Inventory Inventory; internal StorageKind Kind; internal Vector3 Position; internal bool Parked; internal int X; internal int Z; internal bool Dirty = true; internal bool Allowed; internal uint Revision; internal float NextCheck; internal readonly Dictionary<string, int> Counts = new Dictionary<string, int>(); internal readonly Dictionary<string, ItemData> Examples = new Dictionary<string, ItemData>(); internal Action Changed; } public sealed class ChestLifetime : MonoBehaviour { internal Container Chest; private void OnDestroy() { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.Storage.Remove(Chest); } } } internal sealed class Storage : IDisposable { internal static readonly MethodInfo Load = AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null); internal static readonly MethodInfo Access = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null); internal static readonly MethodInfo Changed = AccessTools.Method(typeof(Inventory), "Changed", new Type[2] { typeof(bool), typeof(bool) }, (Type[])null); private readonly Plugin plugin; private readonly Dictionary<Container, Entry> entries = new Dictionary<Container, Entry>(); private readonly Dictionary<long, List<Entry>> cells = new Dictionary<long, List<Entry>>(); private readonly List<Entry> mobile = new List<Entry>(); private readonly List<Entry> nearby = new List<Entry>(); private readonly Dictionary<string, int> totals = new Dictionary<string, int>(); private readonly ChestAccessRoutes<Container> accessRoutes = new ChestAccessRoutes<Container>(); private readonly ActivityWindow activity = new ActivityWindow(); private readonly Stopwatch timer = new Stopwatch(); private float nextNearby; private Vector3 origin; private int cursor; private bool aggregateDirty = true; internal long Refreshes; internal long InventoryRebuilds; internal long Requests; private Dictionary<Container, OpenRequest> requests => accessRoutes.Requests; internal Storage(Plugin owner) { plugin = owner; } private static long CellKey(int x, int z) { return ((long)x << 32) ^ (uint)z; } internal static string Key(string name, int quality) { return name + "\n" + quality; } internal static ZNetView View(Container chest) { if (!((Object)(object)chest == (Object)null)) { if (!Object.op_Implicit((Object)(object)chest.m_rootObjectOverride)) { return ((Component)chest).GetComponent<ZNetView>(); } return chest.m_rootObjectOverride; } return null; } private static StorageKind Kind(Container chest, ZNetView view) { if ((Object)(object)chest == (Object)null || (Object)(object)view == (Object)null) { return StorageKind.Unsupported; } StorageKind storageKind = Policy.StorageType(((Object)view).name); if (Policy.Mobile(storageKind)) { if ((Object)(object)chest.m_rootObjectOverride != (Object)(object)view || !((Component)chest).transform.IsChildOf(((Component)view).transform) || (storageKind == StorageKind.Cart && ((Object)(object)((Component)view).GetComponent<Vagon>() == (Object)null || (Object)(object)chest.m_wagon != (Object)(object)((Component)view).GetComponent<Vagon>())) || (storageKind == StorageKind.Boat && (Object)(object)((Component)view).GetComponent<Ship>() == (Object)null)) { return StorageKind.Unsupported; } } else if ((Object)(object)((Component)chest).gameObject != (Object)(object)((Component)view).gameObject) { return StorageKind.Unsupported; } return storageKind; } private bool SourceEnabled(StorageKind kind) { return Policy.StorageEnabled(kind, plugin.Wardrobes.Value, plugin.Carts.Value, plugin.Boats.Value); } private static bool Parked(ZNetView view, StorageKind kind) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!Policy.Mobile(kind)) { return true; } if ((Object)(object)view == (Object)null) { return false; } Rigidbody component = ((Component)view).GetComponent<Rigidbody>(); if ((Object)(object)component == (Object)null || component.isKinematic) { return false; } Vector3 linearVelocity = component.linearVelocity; Vector3 angularVelocity = component.angularVelocity; if (kind == StorageKind.Boat) { linearVelocity.y = 0f; angularVelocity.x = 0f; angularVelocity.z = 0f; } return Policy.Stationary(((Vector3)(ref linearVelocity)).sqrMagnitude, ((Vector3)(ref angularVelocity)).sqrMagnitude); } internal void Add(Container chest) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)chest == (Object)null || entries.ContainsKey(chest)) { return; } ZNetView val = View(chest); StorageKind storageKind = Kind(chest, val); if (storageKind == StorageKind.Unsupported) { return; } Piece component = ((Component)val).GetComponent<Piece>(); if (!((Object)(object)val == (Object)null) && val.IsValid() && !((Object)(object)component == (Object)null) && chest.GetInventory() != null) { Entry entry = new Entry { Chest = chest, View = val, Inventory = chest.GetInventory(), Kind = storageKind, Position = ((Component)chest).transform.position, Parked = Parked(val, storageKind), X = Policy.Cell(((Component)chest).transform.position.x), Z = Policy.Cell(((Component)chest).transform.position.z) }; entry.Changed = delegate { entry.Dirty = true; aggregateDirty = true; }; Inventory inventory = entry.Inventory; inventory.m_onChanged = (Action)Delegate.Combine(inventory.m_onChanged, entry.Changed); entries.Add(chest, entry); long key = CellKey(entry.X, entry.Z); if (!cells.TryGetValue(key, out var value)) { value = (cells[key] = new List<Entry>()); } value.Add(entry); if (Policy.Mobile(storageKind)) { mobile.Add(entry); } ChestLifetime chestLifetime = ((Component)chest).GetComponent<ChestLifetime>(); if ((Object)(object)chestLifetime == (Object)null) { chestLifetime = ((Component)chest).gameObject.AddComponent<ChestLifetime>(); } chestLifetime.Chest = chest; nextNearby = 0f; } } internal void Remove(Container chest) { if (object.ReferenceEquals(chest, null)) { return; } accessRoutes.Remove(chest); if (!entries.TryGetValue(chest, out var value)) { return; } Inventory inventory = value.Inventory; inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, value.Changed); entries.Remove(chest); long key = CellKey(value.X, value.Z); if (cells.TryGetValue(key, out var value2)) { value2.Remove(value); if (value2.Count == 0) { cells.Remove(key); } } nearby.Remove(value); mobile.Remove(value); nextNearby = 0f; aggregateDirty = true; } internal bool Allowed(Container chest, Player player, float radius, bool requireOwner) { //IL_0018: 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) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Invalid comparison between Unknown and I4 //IL_00fd: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)chest == (Object)null) && !((Object)(object)player == (Object)null)) { Vector3 val = ((Component)chest).transform.position - ((Component)player).transform.position; if (Policy.InRange(((Vector3)(ref val)).sqrMagnitude, radius)) { ZNetView val2 = View(chest); StorageKind kind = Kind(chest, val2); if (!SourceEnabled(kind)) { return false; } Piece component = ((Component)val2).GetComponent<Piece>(); if ((Object)(object)component == (Object)null || !component.IsPlacedByPlayer()) { return false; } if (!val2.IsValid() || ((requireOwner || Policy.Mobile(kind)) && !val2.IsOwner())) { return false; } if (!Parked(val2, kind)) { return false; } if (chest.IsInUse() || val2.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0) { return false; } if (Policy.Mobile(kind) && (int)chest.m_privacy != 2) { return false; } if ((bool)Access.Invoke(chest, new object[1] { player.GetPlayerID() })) { return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, true); } return false; } } return false; } internal List<Entry> Nearby(Player player, bool activate = true) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) if (activate) { activity.Touch(Time.unscaledTime); } UpdateMobilePositions(); if (Time.unscaledTime < nextNearby) { Vector3 val = origin - ((Component)player).transform.position; if (((Vector3)(ref val)).sqrMagnitude < 0.25f) { return nearby; } } origin = ((Component)player).transform.position; nextNearby = Time.unscaledTime + 0.5f; nearby.Clear(); float value = plugin.Range.Value; for (int i = Policy.Cell(origin.x - value); i <= Policy.Cell(origin.x + value); i++) { for (int j = Policy.Cell(origin.z - value); j <= Policy.Cell(origin.z + value); j++) { if (!cells.TryGetValue(CellKey(i, j), out var value2)) { continue; } foreach (Entry item in value2) { if ((Object)(object)item.Chest != (Object)null && item.Parked && SourceEnabled(item.Kind)) { Vector3 val2 = ((Component)item.Chest).transform.position - origin; if (Policy.InRange(((Vector3)(ref val2)).sqrMagnitude, value)) { nearby.Add(item); } } } } } nearby.Sort(delegate(Entry a, Entry b) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) Vector3 val3 = ((Component)a.Chest).transform.position - origin; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; Vector3 val4 = ((Component)b.Chest).transform.position - origin; return sqrMagnitude.CompareTo(((Vector3)(ref val4)).sqrMagnitude); }); aggregateDirty = true; return nearby; } private void UpdateMobilePositions() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) for (int num = mobile.Count - 1; num >= 0; num--) { Entry entry = mobile[num]; if ((Object)(object)entry.Chest == (Object)null) { Remove(entry.Chest); } else { Vector3 position = ((Component)entry.Chest).transform.position; bool flag = Parked(entry.View, entry.Kind); if (!((Vector3)(ref position)).Equals(entry.Position) || flag != entry.Parked) { int num2 = Policy.Cell(position.x); int num3 = Policy.Cell(position.z); if (num2 != entry.X || num3 != entry.Z) { long key = CellKey(entry.X, entry.Z); if (cells.TryGetValue(key, out var value)) { value.Remove(entry); if (value.Count == 0) { cells.Remove(key); } } long key2 = CellKey(num2, num3); if (!cells.TryGetValue(key2, out value)) { value = (cells[key2] = new List<Entry>()); } value.Add(entry); entry.X = num2; entry.Z = num3; } entry.Position = position; entry.Parked = flag; entry.NextCheck = 0f; nextNearby = 0f; aggregateDirty = true; } } } } internal void Tick(Player player) { if ((Object)(object)player == (Object)null || !activity.IsActive(Time.unscaledTime)) { return; } Nearby(player, activate: false); timer.Restart(); int num = 0; while (nearby.Count > 0 && num++ < nearby.Count && timer.Elapsed.TotalMilliseconds < (double)plugin.Budget.Value) { if (cursor >= nearby.Count) { cursor = 0; } Entry entry = nearby[cursor++]; if ((Object)(object)entry.Chest == (Object)null) { Remove(entry.Chest); } else if ((entry.Dirty && entry.Allowed) || Time.unscaledTime >= entry.NextCheck) { Refresh(entry, player); } } } internal void Refresh(Entry entry, Player player) { Refreshes++; entry.NextCheck = Time.unscaledTime + plugin.Refresh.Value; bool flag = Allowed(entry.Chest, player, plugin.Range.Value, requireOwner: false); if (entry.Allowed != flag) { aggregateDirty = true; } entry.Allowed = flag; if (!flag) { return; } uint dataRevision = entry.View.GetZDO().DataRevision; if (!entry.Dirty && dataRevision == entry.Revision) { return; } Load.Invoke(entry.Chest, null); entry.Counts.Clear(); entry.Examples.Clear(); foreach (ItemData allItem in entry.Inventory.GetAllItems()) { if (allItem.m_stack > 0 && allItem.m_worldLevel >= Game.m_worldLevel) { AddCount(entry.Counts, Key(allItem.m_shared.m_name, allItem.m_quality), allItem.m_stack); AddCount(entry.Counts, Key(allItem.m_shared.m_name, -1), allItem.m_stack); entry.Examples[Key(allItem.m_shared.m_name, allItem.m_quality)] = allItem; entry.Examples[Key(allItem.m_shared.m_name, -1)] = allItem; } } entry.Revision = entry.View.GetZDO().DataRevision; entry.Dirty = false; aggregateDirty = true; InventoryRebuilds++; } private static void AddCount(Dictionary<string, int> counts, string key, int value) { counts.TryGetValue(key, out var value2); counts[key] = checked(value2 + value); } internal int Count(Player player, string name, int quality) { Nearby(player); if (aggregateDirty) { totals.Clear(); foreach (Entry item in nearby) { if (!item.Allowed || !((Object)(object)item.Chest != (Object)null)) { continue; } foreach (KeyValuePair<string, int> count in item.Counts) { AddCount(totals, count.Key, count.Value); } } aggregateDirty = false; } totals.TryGetValue(Key(name, quality), out var value); return value; } internal ItemData Example(Player player, string name, int quality) { foreach (Entry item in Nearby(player)) { if (item.Allowed && item.Examples.TryGetValue(Key(name, quality), out var value)) { return value.Clone(); } } return null; } internal bool Ready(Container chest, Player player, float radius) { if (!Allowed(chest, player, radius, requireOwner: false)) { plugin.Tell("Storage disabled, busy, protected, out of range or vehicle moving/not locally owned; nothing pulled."); return false; } ZNetView val = View(chest); if (requests.TryGetValue(chest, out var value)) { if (value.Ready(val.IsOwner(), Time.unscaledTime)) { plugin.AccessLog("approval consumed on action retry", chest, value.Owner); requests.Remove(chest); return true; } if (!value.TimedOut(Time.unscaledTime)) { return false; } requests.Remove(chest); } if (val.IsOwner()) { return true; } if (!val.HasOwner()) { plugin.Tell("Chest has no confirmed owner yet; retry shortly."); return false; } accessRoutes.BeginAssisted(chest); requests[chest] = new OpenRequest { Owner = val.GetZDO().GetOwner(), Started = Time.unscaledTime }; Requests++; plugin.AccessLog("request sent", chest, val.GetZDO().GetOwner()); val.InvokeRPC("RPC_RequestOpen", new object[1] { player.GetPlayerID() }); plugin.Tell(plugin.AutomaticCraftContext ? "Waiting for chest access; crafting will continue automatically." : "Requesting chest access; retry the action shortly."); return false; } internal bool Response(Container chest, long sender, bool granted) { if (!entries.ContainsKey(chest)) { return true; } if (accessRoutes.IsNative(chest)) { accessRoutes.CompleteManual(chest); plugin.AccessLog("manual reply passed through; granted=" + granted, chest, sender); return true; } if (!requests.TryGetValue(chest, out var value)) { return !accessRoutes.WasAssisted(chest); } if (!value.Accept(sender, granted, Time.unscaledTime)) { plugin.AccessLog("ignored mismatched or duplicate reply", chest, sender); return false; } plugin.AccessLog("crafting reply granted=" + granted + "; elapsed=" + (Time.unscaledTime - value.Started), chest, sender); return false; } internal void ManualOpen(Container chest) { plugin.CancelPending("chest opened manually"); if (entries.ContainsKey(chest) && accessRoutes.UseNative(chest)) { plugin.AccessLog("manual interaction takes priority for this interaction", chest, 0L); } } internal void Invalidate() { nextNearby = 0f; aggregateDirty = true; foreach (Entry value in entries.Values) { value.NextCheck = 0f; } } internal bool CanAwait(List<Container> sources) { foreach (Container source in sources) { if ((Object)(object)source == (Object)null || !entries.ContainsKey(source) || !Allowed(source, Player.m_localPlayer, plugin.Range.Value, requireOwner: false)) { return false; } ZNetView val = View(source); if ((Object)(object)val == (Object)null || !val.IsValid()) { return false; } if (requests.TryGetValue(source, out var value)) { if (value.TimedOut(Time.unscaledTime)) { return false; } } else if (!val.IsOwner()) { return false; } } return true; } internal bool AwaitReady(List<Container> sources) { foreach (Container source in sources) { if (!Allowed(source, Player.m_localPlayer, plugin.Range.Value, requireOwner: true)) { return false; } ZNetView val = View(source); if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner()) { return false; } if (requests.TryGetValue(source, out var value) && !value.Ready(owned: true, Time.unscaledTime)) { return false; } } return true; } public void Dispose() { foreach (Entry value in entries.Values) { Inventory inventory = value.Inventory; inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, value.Changed); } entries.Clear(); cells.Clear(); mobile.Clear(); nearby.Clear(); totals.Clear(); accessRoutes.Clear(); } } internal sealed class Move<T> where T : class { internal IList<T> Source; internal IList<T> Destination; internal T Item; internal T Copy; internal int Amount; } internal static class TransferBatch { internal static void Execute<T>(IList<Move<T>> moves, Func<T, int> count, Action<T, int> setCount, Action<IList<T>> publish) where T : class { Dictionary<IList<T>, List<T>> dictionary = new Dictionary<IList<T>, List<T>>(); Dictionary<T, int> dictionary2 = new Dictionary<T, int>(); Dictionary<T, int> dictionary3 = new Dictionary<T, int>(); HashSet<T> hashSet = new HashSet<T>(); foreach (Move<T> move in moves) { if (move.Amount <= 0 || move.Item == null || move.Copy == null || object.ReferenceEquals(move.Item, move.Copy) || object.ReferenceEquals(move.Source, move.Destination) || !move.Source.Contains(move.Item) || move.Destination.Contains(move.Copy) || !hashSet.Add(move.Copy)) { throw new InvalidOperationException("Invalid transfer plan."); } dictionary3.TryGetValue(move.Item, out var value); value = checked(value + move.Amount); if (value > count(move.Item)) { throw new InvalidOperationException("Stock changed before transfer."); } dictionary3[move.Item] = value; dictionary2[move.Item] = count(move.Item); if (!dictionary.ContainsKey(move.Source)) { dictionary[move.Source] = new List<T>(move.Source); } if (!dictionary.ContainsKey(move.Destination)) { dictionary[move.Destination] = new List<T>(move.Destination); } } try { foreach (Move<T> move2 in moves) { setCount(move2.Item, count(move2.Item) - move2.Amount); if (count(move2.Item) == 0) { move2.Source.Remove(move2.Item); } setCount(move2.Copy, move2.Amount); move2.Destination.Add(move2.Copy); } foreach (IList<T> key in dictionary.Keys) { publish(key); } } catch (Exception item) { foreach (KeyValuePair<T, int> item3 in dictionary2) { setCount(item3.Key, item3.Value); } foreach (KeyValuePair<IList<T>, List<T>> item4 in dictionary) { item4.Key.Clear(); foreach (T item5 in item4.Value) { item4.Key.Add(item5); } } List<Exception> list = new List<Exception>(); list.Add(item); List<Exception> list2 = list; foreach (IList<T> key2 in dictionary.Keys) { try { publish(key2); } catch (Exception item2) { list2.Add(item2); } } throw new AggregateException("Transfer failed; original inventory references restored. Inspect inner errors for persistence failures.", list2); } } } } namespace ReliableStationSafety { public sealed class PendingFood : MonoBehaviour { internal bool Waiting; internal string Input; internal string Output; internal long Owner; internal string[] Before; internal float Started; internal int AcceptedSlot = -1; internal void Observe(long sender, int slot, string item) { if (Waiting && sender == Owner && slot >= 0 && slot < Before.Length) { if (Before[slot] == "" && (item == Input || item == Output)) { AcceptedSlot = slot; } if (slot == AcceptedSlot && item == "") { Waiting = false; } } } internal bool Confirmed(ZDO state) { if (!Waiting) { return true; } for (int i = 0; i < Before.Length; i++) { string text = state.GetString("slot" + i, ""); if (Before[i] == "" && (text == Input || text == Output)) { Waiting = false; return true; } } return false; } } internal sealed class FoodAttempt { internal PendingFood Pending; internal Inventory Inventory; internal string Name; internal int Count; } internal static class RemoteFoodInput { private const string CapacityKey = "reliable_workstations_food_capacity"; private const string OwnerKey = "reliable_workstations_food_capacity_owner"; internal static int NativeCapacity(string name) { name = (name ?? "").Replace("(Clone)", "").Trim(); return name switch { "piece_cookingstation" => 2, "piece_cookingstation_iron" => 5, "piece_oven" => 4, _ => 0, }; } internal static void Publish(CookingStation station) { if (NativeCapacity(((Object)station).name) != 0 && station.m_slots != null) { ZNetView component = ((Component)station).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner()) { ZDO zDO = component.GetZDO(); zDO.Set("reliable_workstations_food_capacity", station.m_slots.Length); zDO.Set("reliable_workstations_food_capacity_owner", zDO.GetOwner()); } } } internal static void ObserveSnapshot(CookingStation station) { PendingFood component = ((Component)station).GetComponent<PendingFood>(); if (!((Object)(object)component == (Object)null) && component.Waiting) { ZNetView component2 = ((Component)station).GetComponent<ZNetView>(); if ((Object)(object)component2 != (Object)null && component2.IsValid()) { component.Confirmed(component2.GetZDO()); } } } internal static bool Begin(CookingStation station, Player player, ItemData item, out FoodAttempt attempt, out string message) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) attempt = null; message = null; ZNetView component = ((Component)station).GetComponent<ZNetView>(); if (item == null || (Object)(object)item.m_dropPrefab == (Object)null || !((Humanoid)player).GetInventory().ContainsItem(item) || item.m_stack <= 0 || (Object)(object)component == (Object)null || !component.IsValid() || !component.HasOwner() || ((Character)player).IsDead() || ((Character)player).IsTeleporting() || !PrivateArea.CheckAccess(((Component)station).transform.position, 0f, false, true)) { return false; } ZDO zDO = component.GetZDO(); PendingFood pendingFood = ((Component)station).GetComponent<PendingFood>(); if ((Object)(object)pendingFood == (Object)null) { pendingFood = ((Component)station).gameObject.AddComponent<PendingFood>(); } if (!pendingFood.Confirmed(zDO)) { if (Time.unscaledTime - pendingFood.Started > 3f) { message = "Waiting for the station's previous food input to appear. Remaining food is still in your inventory."; } return false; } int num = NativeCapacity(((Object)station).name); int num2 = zDO.GetInt("reliable_workstations_food_capacity", 0); if (zDO.GetLong("reliable_workstations_food_capacity_owner", 0L) == zDO.GetOwner() && num2 >= num && num2 <= 32) { num = num2; } num = Math.Min(num, station.m_slots.Length); string[] array = new string[num]; bool flag = false; for (int i = 0; i < num; i++) { array[i] = zDO.GetString("slot" + i, ""); if (array[i] == "") { flag = true; } } if (!flag) { message = "The station's confirmed cooking slots are full. Remaining food is still in your inventory."; return false; } string text = null; foreach (ItemConversion item2 in station.m_conversion) { if ((Object)(object)item2.m_from != (Object)null && ((Object)item2.m_from).name == ((Object)item.m_dropPrefab).name) { text = (((Object)(object)item2.m_to != (Object)null) ? ((Object)item2.m_to).name : null); break; } } if (text == null) { return true; } Inventory inventory = ((Humanoid)player).GetInventory(); pendingFood.Input = ((Object)item.m_dropPrefab).name; pendingFood.Output = text; pendingFood.Before = array; pendingFood.Owner = zDO.GetOwner(); pendingFood.Started = Time.unscaledTime; pendingFood.AcceptedSlot = -1; pendingFood.Waiting = true; attempt = new FoodAttempt { Pending = pendingFood, Inventory = inventory, Name = item.m_shared.m_name, Count = inventory.CountItems(item.m_shared.m_name, -1, true) }; return true; } internal static void Finish(FoodAttempt attempt) { if (attempt != null && (Object)(object)attempt.Pending != (Object)null && attempt.Inventory.CountItems(attempt.Name, -1, true) >= attempt.Count) { attempt.Pending.Waiting = false; } } } [HarmonyPatch(typeof(CookingStation), "CookItem")] internal static class FoodInputObservation { [HarmonyPriority(801)] private static void Prefix(CookingStation __instance) { RemoteFoodInput.ObserveSnapshot(__instance); } } [HarmonyPatch(typeof(CookingStation), "RPC_SetSlotVisual")] internal static class FoodVisualObservation { private static void Postfix(CookingStation __instance, long __0, int __1, string __2) { PendingFood component = ((Component)__instance).GetComponent<PendingFood>(); if ((Object)(object)component != (Object)null) { component.Observe(__0, __1, __2); } } } [HarmonyPatch(typeof(CookingStation), "UpdateCooking")] internal static class FoodCapacityAdvertisement { private static void Prefix(CookingStation __instance) { RemoteFoodInput.ObserveSnapshot(__instance); } private static void Postfix(CookingStation __instance) { RemoteFoodInput.Publish(__instance); } } }