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 EasyCraftAndBuildFromOwnChests v1.3.0
BepInEx/plugins/EasyCraftAndBuildFromOwnChests/EasyCraftAndBuildFromOwnChests.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.4.10.0")] namespace EasyCraftAndBuildFromOwnChests; [BepInPlugin("com.modmaking.easycraftfromownchests", "Easy Craft & Build from own chests", "0.4.10")] public sealed class Plugin : BaseUnityPlugin { private sealed class BuildReceipt { internal Player Player; internal Requirement[] Resources; internal int Frame; internal bool Consumed; } private sealed class Request { internal long Owner; internal float Sent; internal bool Granted; internal bool TimedOut; internal bool Completed; } internal static int PlacementScope; internal static int BuildDisplayDepth; private static bool buildingWorldStarted; private static bool buildingFailure; private static CraftTransaction building; private static BuildReceipt receipt; public const string Id = "com.modmaking.easycraftfromownchests"; internal static Plugin Instance; internal static ConfigEntry<bool> Enabled; private static ConfigEntry<string> ExcludedPrefabs; private static readonly HashSet<string> exclusions = new HashSet<string>(StringComparer.Ordinal); internal static readonly ResourceCache Cache = new ResourceCache(); internal static CraftTransaction Current; internal static int SuppressCount; internal static int opening; internal static int panelDepth; private static bool installed; private static bool faulted; private static bool wasRunning; private static Harmony harmony; private static float nextPanel; internal static bool DisplayExcluded; internal static readonly MethodInfo Load = AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null); internal static readonly MethodInfo Save = AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null); internal static readonly MethodInfo Access = AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null); internal static readonly MethodInfo Changed = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null); internal static readonly MethodInfo SetupEquipment = AccessTools.Method(typeof(Humanoid), "SetupEquipment", (Type[])null, (Type[])null); internal static readonly FieldInfo InUse = AccessTools.Field(typeof(Container), "m_inUse"); private static readonly FieldInfo RecipeField = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe"); private static readonly FieldInfo UpgradeField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem"); private static readonly FieldInfo MultiField = AccessTools.Field(typeof(InventoryGui), "m_multiCrafting"); private static readonly FieldInfo AmountField = AccessTools.Field(typeof(InventoryGui), "m_multiCraftAmount"); private static readonly FieldInfo SelectedField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); private static readonly MethodInfo SelectedGetter = ((SelectedField == null) ? null : AccessTools.PropertyGetter(SelectedField.FieldType, "Recipe")); internal static readonly MethodInfo RefreshPanel = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", (Type[])null, (Type[])null); private static readonly Dictionary<Container, Request> requests = new Dictionary<Container, Request>(); private static Container manual; private static long manualPlayer; private static long manualOwner; private static float manualUntil; private static float requestWindow; private static int sentInWindow; internal static bool Running { get { if (installed && !faulted && Enabled != null) { return Enabled.Value; } return false; } } internal static bool Active { get { Player localPlayer = Player.m_localPlayer; if (Running && Object.op_Implicit((Object)(object)localPlayer)) { if (!((Character)localPlayer).InPlaceMode()) { if (Object.op_Implicit((Object)(object)localPlayer.GetCurrentCraftingStation())) { if (opening <= 0 && panelDepth <= 0) { return InventoryGui.IsVisible(); } return true; } return false; } return true; } return false; } } internal static void BeginBuildDisplay() { BuildDisplayDepth++; if (Running && Current == null) { Cache.Flush(); } } internal static void EndBuildDisplay() { BuildDisplayDepth = Math.Max(0, BuildDisplayDepth - 1); } internal static void PrepareHammer() { if (Running && Object.op_Implicit((Object)(object)Player.m_localPlayer) && Current == null) { SharingService.RequestUse(); Cache.Poll(all: true); } } public static int BuildCount(Inventory inventory, string name, int quality, bool world) { int num = inventory.CountItems(name, quality, world); if (!Running || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || inventory != ((Humanoid)Player.m_localPlayer).GetInventory()) { return num; } return checked(num + Cache.Count(name, quality, world)); } public static bool BuildHave(Inventory inventory, string name, bool world) { return BuildCount(inventory, name, -1, world) > 0; } public static void PlaceWithResources(Player player, Piece piece, Vector3 pos, Quaternion rot, bool attack, bool cheated) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) if (PlacementScope == 0 || !Running || (Object)(object)player != (Object)(object)Player.m_localPlayer || player.PlacementCostDisabled || ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())) { player.PlacePiece(piece, pos, rot, attack, cheated); return; } if (Current != null) { throw new InvalidOperationException("Nested resource transaction during building"); } CraftTransaction craftTransaction = null; try { Cache.Poll(all: true); craftTransaction = new CraftTransaction(player, null); CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { if (Object.op_Implicit((Object)(object)val.m_resItem) && val.m_upgraderResource == (Object.op_Implicit((Object)(object)currentCraftingStation) && currentCraftingStation.m_upgrader)) { craftTransaction.Plan(new ResourceKey(val.m_resItem.m_itemData.m_shared.m_name, -1, world: true), val.GetAmount(0)); } } if (craftTransaction.Sources.Count == 0) { player.PlacePiece(piece, pos, rot, attack, cheated); return; } Current = craftTransaction; craftTransaction.Acquire(); KeyValuePair<ResourceKey, int>[] array = craftTransaction.Remaining.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair<ResourceKey, int> keyValuePair = array[i]; craftTransaction.Remove(((Humanoid)player).GetInventory(), keyValuePair.Key.Name, keyValuePair.Value, keyValuePair.Key.Quality, keyValuePair.Key.World); } craftTransaction.PrepareCommit(); building = craftTransaction; buildingWorldStarted = false; bool flag = craftTransaction.Debits.Any((CraftTransaction.Debit d) => d.Item.m_cheated); player.PlacePiece(piece, pos, rot, attack, cheated || (flag && !PlayerProfile.s_bypassCheatChecks)); receipt = new BuildReceipt { Player = player, Resources = piece.m_resources, Frame = Time.frameCount }; craftTransaction.Commit(); } catch (Exception error) { buildingFailure = true; if (craftTransaction != null && buildingWorldStarted) { craftTransaction.Committed = true; craftTransaction.Publish(); Fault("Placement failed after world creation began. Materials remain paid; inspect the placed piece and log before continuing."); } Finish(error, ownsScope: true); throw; } finally { building = null; buildingWorldStarted = false; Finish(null, ownsScope: true); } } public static GameObject InstantiateBuilding(GameObject prefab, Vector3 pos, Quaternion rot) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (building != null) { buildingWorldStarted = true; } return Object.Instantiate<GameObject>(prefab, pos, rot); } public static void ConsumeBuilding(Player player, Requirement[] resources, int quality, int itemQuality, int multiplier) { if (receipt != null) { BuildReceipt buildReceipt = receipt; if (buildReceipt.Consumed || !((Object)(object)buildReceipt.Player == (Object)(object)player) || buildReceipt.Resources != resources || buildReceipt.Frame != Time.frameCount || quality != 0 || itemQuality != -1 || multiplier != 1) { Fault("Building resource receipt did not match; prevented a second charge."); throw new InvalidOperationException("Building payment sequence changed"); } buildReceipt.Consumed = true; } else { player.ConsumeResources(resources, quality, itemQuality, multiplier); } } internal static Exception FinishPlacement(Exception error) { PlacementScope = Math.Max(0, PlacementScope - 1); bool flag = receipt != null; if (PlacementScope == 0) { receipt = null; } bool num = buildingFailure; buildingFailure = false; if (num) { try { TerrainModifier.SetTriggerOnPlaced(false); } catch (Exception e) { Warn("Placement trigger cleanup failed", e); } if (error != null) { Warn("Building transaction interrupted", error); } return null; } if (flag && error != null) { Warn("Paid placement completed; later building feedback failed (not refunded)", error); return null; } return error; } private void Awake() { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown Instance = this; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Use loaded chests built by your character within 200 metres for station crafting and building (including Free Build Camera)."); ExcludedPrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("Compatibility", "InventoryOnlyRecipePrefabs", "Lesly_WaterproofingBrush", "Comma-separated output prefab names whose custom crafting handlers require inventory-only resources."); ReadExclusions(); ExcludedPrefabs.SettingChanged += ExclusionsChanged; Enabled.SettingChanged += EnabledChanged; harmony = new Harmony("com.modmaking.easycraftfromownchests"); try { RequireApi(); Patches.Install(harmony); installed = true; Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); foreach (Container c in array) { Cache.Add(c); } wasRunning = Running; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Easy Craft & Build from own chests 0.4.10: 200m player-centred radius; own/shared chest crafting + building; cached counts and guarded transactions. All hooks verified."); } catch (Exception e) { faulted = true; installed = false; try { harmony.UnpatchSelf(); } catch (Exception e2) { Warn("Unpatch failed", e2); } Warn("Incompatible game/mod layout; feature remains disabled until restart", e); } } private static void RequireApi() { MemberInfo[] array = new MemberInfo[13] { Load, Save, Access, Changed, SetupEquipment, InUse, RecipeField, UpgradeField, MultiField, AmountField, SelectedField, SelectedGetter, RefreshPanel }; for (int i = 0; i < array.Length; i++) { if (array[i] == null) { throw new MissingMemberException("Required crafting/inventory API missing"); } } } private static void ReadExclusions() { exclusions.Clear(); string[] array = ExcludedPrefabs.Value.Split(','); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text)) { exclusions.Add(text.Trim()); } } } private static void ExclusionsChanged(object sender, EventArgs args) { ReadExclusions(); UpdateSelection(InventoryGui.instance); Cache.PanelDirty = true; } private static void EnabledChanged(object sender, EventArgs args) { Cache.InvalidateAll(); ClearManualIntent(); Cache.PanelDirty = true; } private void OnGUI() { OrePicker.Draw(); SharingUi.Draw(); } private void LateUpdate() { OrePicker.Tick(); SharingUi.Tick(); } private void Update() { SharingService.Tick(); if (Current != null) { return; } try { Cache.Prune(); if (wasRunning != Running) { Cache.InvalidateAll(); wasRunning = Running; } if (Running) { Cache.Poll(all: false); if (Cache.PanelDirty && opening == 0 && Active && Object.op_Implicit((Object)(object)InventoryGui.instance) && InventoryGui.IsVisible() && Time.unscaledTime >= nextPanel) { Cache.PanelDirty = false; nextPanel = Time.unscaledTime + 0.15f; RefreshPanel.Invoke(InventoryGui.instance, new object[1] { false }); } } } catch (Exception e) { Fault("Background resource refresh failed; feature disabled for this session."); Warn("Refresh error", e); } } private void OnDestroy() { try { Finish(null, ownsScope: true); } finally { OrePicker.Close(); SharingService.Clear(); installed = false; if (Enabled != null) { Enabled.SettingChanged -= EnabledChanged; } if (ExcludedPrefabs != null) { ExcludedPrefabs.SettingChanged -= ExclusionsChanged; } try { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } finally { Cache.Dispose(); ChestBuilder.Clear(); requests.Clear(); ClearManualIntent(); Instance = null; } } } internal static void Warn(string message, Exception e = null) { if (Object.op_Implicit((Object)(object)Instance)) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)(message + ((e == null) ? "" : (": " + ((e is TargetInvocationException && e.InnerException != null) ? e.InnerException : e))))); } } internal static void Tell(string text) { if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { ((Character)Player.m_localPlayer).Message((MessageType)2, "Easy craft: " + text, 0, (Sprite)null, false); } } internal static void Fault(string text) { faulted = true; Cache.Clear(); Warn(text); Tell(text); } internal static void Opening() { opening++; DisplayExcluded = false; Cache.PanelDirty = true; nextPanel = 0f; if (!Running || Current != null) { return; } try { SharingService.RequestUse(); Cache.Poll(all: true); } catch (Exception e) { Fault("Could not prepare chest resources; normal inventory remains available."); Warn("Opening error", e); } } internal static void Opened() { opening = Math.Max(0, opening - 1); if (opening != 0 || !Running || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)Player.m_localPlayer.GetCurrentCraftingStation())) { return; } int num = 0; foreach (Chest chest in Cache.Chests) { if (chest.Ready) { num++; } } if (Object.op_Implicit((Object)(object)Instance)) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[station] " + ((Object)Player.m_localPlayer.GetCurrentCraftingStation()).name + ": " + num + "/" + Cache.Chests.Count + " loaded chest candidates ready; range=200m; builder-only.")); } } internal static void BeginPanel() { panelDepth++; DisplayExcluded = false; if (Running && Current == null) { Cache.Flush(); } } internal static void EndPanel(InventoryGui gui) { try { UpdateSelection(gui); } finally { panelDepth = Math.Max(0, panelDepth - 1); } } internal static bool RecipeAllowed(Recipe recipe) { if (Object.op_Implicit((Object)(object)recipe) && Object.op_Implicit((Object)(object)recipe.m_item)) { return !exclusions.Contains(((Object)((Component)recipe.m_item).gameObject).name); } return false; } internal static void UpdateSelection(InventoryGui gui) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)gui) || SelectedGetter == null) { DisplayExcluded = false; return; } try { DisplayExcluded = !RecipeAllowed((Recipe)SelectedGetter.Invoke(SelectedField.GetValue(gui), null)); } catch (Exception e) { DisplayExcluded = true; Warn("Custom recipe selection could not be identified; using inventory only", e); } } internal static void Prepare(Chest c) { if (!Running || !SharingService.CanSpend(c)) { return; } if (requests.TryGetValue(c.Container, out var value)) { float num = Time.unscaledTime - value.Sent; float num2 = ((value.Owner != c.View.GetZDO().GetOwner()) ? 0.5f : (value.TimedOut ? 2f : 8f)); if (num < (value.Completed ? 2f : num2)) { return; } } if (Time.unscaledTime >= requestWindow) { requestWindow = Time.unscaledTime + 1f; sentInWindow = 0; } if (sentInWindow < 4) { sentInWindow++; requests[c.Container] = new Request { Owner = c.View.GetZDO().GetOwner(), Sent = Time.unscaledTime }; c.View.InvokeRPC(c.View.GetZDO().GetOwner(), "RPC_RequestOpen", new object[1] { Player.m_localPlayer.GetPlayerID() }); } } internal static bool AccessReady(Chest c) { if (!SharingService.CanSpend(c)) { return false; } if (!requests.TryGetValue(c.Container, out var value)) { return true; } if (value.Completed) { return c.View.IsOwner(); } if (Time.unscaledTime - value.Sent >= 8f) { value.TimedOut = true; } if (c.View.IsOwner() && (value.Granted || value.TimedOut)) { value.Completed = true; return true; } return false; } internal static void ClearManualIntent() { manual = null; manualPlayer = 0L; manualOwner = 0L; manualUntil = 0f; } internal static void ForgetRequest(Container c) { requests.Remove(c); } internal static void ManualOpen(Container c) { manual = c; manualPlayer = (Object.op_Implicit((Object)(object)Player.m_localPlayer) ? Player.m_localPlayer.GetPlayerID() : 0); manualUntil = Time.unscaledTime + 8f; ZNetView val = (Object.op_Implicit((Object)(object)c) ? StorageIdentity.View(c) : null); manualOwner = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? val.GetZDO().GetOwner() : 0); } internal static bool Response(Container c, long sender, bool granted) { if (!requests.TryGetValue(c, out var value)) { return true; } Cache.ByContainer.TryGetValue(c, out var value2); if (sender == value.Owner) { value.Granted = granted; if (!granted) { value.TimedOut = true; value.Completed = false; } } bool flag = (Object)(object)manual == (Object)(object)c && Time.unscaledTime <= manualUntil && Object.op_Implicit((Object)(object)Player.m_localPlayer) && Player.m_localPlayer.GetPlayerID() == manualPlayer && (sender == value.Owner || sender == manualOwner); if (flag && granted) { value.Completed = true; value.TimedOut = false; ClearManualIntent(); if (value2 != null) { Cache.InventoryDirty(value2.Inventory); } return true; } if (value2 != null) { Cache.Dirty(value2); } if (flag && !granted) { Tell("Chest is busy or access was denied."); } return false; } private static bool Combine(Inventory inventory) { if (Running && Object.op_Implicit((Object)(object)Player.m_localPlayer) && inventory == ((Humanoid)Player.m_localPlayer).GetInventory()) { if (BuildDisplayDepth <= 0) { if (SuppressCount == 0) { return Active; } return false; } return true; } return false; } public static int Count(Inventory inventory, string name, int quality, bool world) { int num = inventory.CountItems(name, quality, world); if (!Combine(inventory)) { return num; } checked { if (Current == null) { return num + Cache.Count(name, quality, world); } foreach (Chest source in Current.Sources) { num += source.Inventory.CountItems(name, quality, world); } return num; } } public static bool HaveItem(Inventory inventory, string name, bool world) { return Count(inventory, name, -1, world) > 0; } public static ItemData GetItem(Inventory inventory, string name, int quality, bool prefab) { ItemData item = inventory.GetItem(name, quality, prefab); if (item != null || !Combine(inventory) || prefab) { return item; } if (Current == null) { return Cache.First(name, quality); } foreach (Chest source in Current.Sources) { item = source.Inventory.GetItem(name, quality, prefab); if (item != null && item.m_worldLevel >= Game.m_worldLevel) { return item; } } return null; } public static void Remove(Inventory inventory, string name, int amount, int quality, bool world) { if (Current == null || inventory != ((Humanoid)Current.Player).GetInventory()) { inventory.RemoveItem(name, amount, quality, world); } else { Current.Remove(inventory, name, amount, quality, world); } } public static bool ItemCheated(Inventory inventory, Requirement[] requirements, int quality, bool world) { if (inventory.ItemCheated(requirements, quality, world)) { return true; } if (Current == null) { if (PlacementScope > 0 && Combine(inventory)) { return Cache.AnyCheated(requirements, quality, world); } return false; } foreach (CraftTransaction.Debit debit in Current.Debits) { if (debit.Item.m_cheated) { return true; } } return false; } public static ItemData AddCrafted(Inventory inventory, string name, int amount, int quality, int variant, long creator, string creatorName, Vector2i pos, bool cheated, bool pickedUp, bool dropIfFull) { //IL_005d: 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) if (Current == null) { return inventory.AddItem(name, amount, quality, variant, creator, creatorName, pos, cheated, pickedUp, dropIfFull); } string name2 = Current.Recipe.m_item.m_itemData.m_shared.m_name; int num = inventory.CountItems(name2, quality, false); Current.Mutated = true; ItemData obj = inventory.AddItem(name, amount, quality, variant, creator, creatorName, pos, cheated, pickedUp, false); if (obj == null || inventory.CountItems(name2, quality, false) - num != amount) { throw new InvalidOperationException("Crafted output could not be inserted completely; recovering inventories"); } return obj; } public static bool BeginNative(InventoryGui gui, Player player) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown if (!Active || (Object)(object)player != (Object)(object)Player.m_localPlayer || !Object.op_Implicit((Object)(object)player.GetCurrentCraftingStation())) { return true; } if (Current != null) { Tell("Another resource transaction is active."); return false; } Recipe val = (Recipe)RecipeField.GetValue(gui); if (!RecipeAllowed(val) || player.NoCostCheat() || ZoneSystem.instance.GetGlobalKey((GlobalKeys)25)) { return true; } try { Cache.Poll(all: true); object? value = UpgradeField.GetValue(gui); ItemData val2 = (ItemData)((value is ItemData) ? value : null); int num = ((val2 == null) ? 1 : (val2.m_quality + 1)); int num2 = ((!(bool)MultiField.GetValue(gui)) ? 1 : ((int)AmountField.GetValue(gui))); if (num2 < 1 || !player.HaveRequirements(val, false, num, num2)) { return false; } CraftTransaction craftTransaction = new CraftTransaction(player, val); if (val.m_requireOnlyOneIngredient) { int amount = default(int); int num3 = default(int); ItemData firstRequiredItem = player.GetFirstRequiredItem(((Humanoid)player).GetInventory(), val, num, ref amount, ref num3, num2); if (firstRequiredItem == null) { return false; } craftTransaction.Plan(new ResourceKey(firstRequiredItem.m_shared.m_name, firstRequiredItem.m_quality, world: true), amount); } else { Requirement[] resources = val.m_resources; foreach (Requirement val3 in resources) { if (Object.op_Implicit((Object)(object)val3.m_resItem) && val3.m_upgraderResource == player.GetCurrentCraftingStation().m_upgrader) { craftTransaction.Plan(new ResourceKey(val3.m_resItem.m_itemData.m_shared.m_name, -1, world: true), checked(val3.GetAmount(num) * num2)); } } } if (craftTransaction.Sources.Count == 0) { return true; } Current = craftTransaction; craftTransaction.Acquire(); return true; } catch (Exception ex) { Warn("Craft preflight rejected", ex); Finish(ex, ownsScope: true); Tell("Materials or chest access changed. Please try again."); return false; } } public static void CommitAndRefresh(InventoryGui gui, bool focus) { if (Current != null) { Current.Commit(); } RefreshPanel.Invoke(gui, new object[1] { focus }); } internal static bool InventoryChanging(Inventory inventory) { CraftTransaction current = Current; if (current == null || !current.Manages(inventory) || current.Publishing || current.Committed) { return true; } current.Mutated = true; return false; } internal static bool ContainerChanged(Container c) { if (Current != null && !Current.Committed && !Current.Publishing) { return !Current.Manages(c.GetInventory()); } return true; } internal static Exception Finish(Exception error, bool ownsScope) { if (!ownsScope || Current == null) { return error; } CraftTransaction current = Current; try { if (!current.Committed) { bool flag = current.Rollback(); if (error != null) { Warn(flag ? "Operation failed; inventory state restored" : "Operation failed; recovery uncertain", error); Tell(flag ? "Operation cancelled; materials restored." : "Recovery is uncertain. Check the log before continuing."); } } else if (error != null) { Warn("Paid operation completed; later feedback failed (not rolled back)", error); } } catch (Exception e) { Fault("Unexpected recovery error. Reload before using Easy craft again."); Warn("Recovery error", e); } finally { try { current.Release(); } finally { Current = null; Cache.PanelDirty = true; } } return null; } } internal static class BuildWindowRefresh { private static readonly FieldInfo Buttons = AccessTools.Field(typeof(BuildUi), "m_pieceButtons"); private static readonly FieldInfo Special = AccessTools.Field(typeof(BuildUi), "m_specialPieceButton"); private static object lastWindow; private static int revision = -1; private static float nextRefresh; internal static void Opened(object window) { revision = -1; nextRefresh = 0f; Tick(window); } internal static void Tick(object __instance) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown if (!Plugin.Running || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !((Character)Player.m_localPlayer).InPlaceMode()) { return; } if (lastWindow != __instance) { lastWindow = __instance; revision = -1; } if (revision == Plugin.Cache.Revision || Time.unscaledTime < nextRefresh) { return; } if (Buttons.GetValue(__instance) is IEnumerable enumerable) { foreach (BuildUiPieceButton item in enumerable) { BuildUiPieceButton val = item; if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.Piece)) { val.UpdateRequirements(); } } } object? value = Special.GetValue(__instance); BuildUiPieceButton val2 = (BuildUiPieceButton)((value is BuildUiPieceButton) ? value : null); if (Object.op_Implicit((Object)(object)val2) && Object.op_Implicit((Object)(object)val2.Piece)) { val2.UpdateRequirements(); } revision = Plugin.Cache.Revision; nextRefresh = Time.unscaledTime + 0.1f; } } internal struct ResourceKey : IEquatable<ResourceKey> { internal string Name; internal int Quality; internal bool World; internal ResourceKey(string name, int quality, bool world) { Name = name; Quality = quality; World = world; } public bool Equals(ResourceKey other) { if (Name == other.Name && Quality == other.Quality) { return World == other.World; } return false; } public override bool Equals(object obj) { if (obj is ResourceKey) { return Equals((ResourceKey)obj); } return false; } public override int GetHashCode() { return ((((Name != null) ? Name.GetHashCode() : 0) * 397) ^ Quality) * 31 + (World ? 1 : 0); } } internal sealed class Chest { internal Container Container; internal ZNetView View; internal Piece Piece; internal Inventory Inventory; internal bool Ready; internal bool Broken; internal bool Pending; internal bool Dirty = true; internal float NextScan; internal float NextSummary; internal byte[] Bytes; internal Dictionary<ResourceKey, int> Counts = new Dictionary<ResourceKey, int>(); internal Dictionary<ResourceKey, int> NextCounts = new Dictionary<ResourceKey, int>(); internal Dictionary<ResourceKey, ItemData> First = new Dictionary<ResourceKey, ItemData>(); internal Dictionary<ResourceKey, ItemData> NextFirst = new Dictionary<ResourceKey, ItemData>(); internal readonly HashSet<ResourceKey> Cheated = new HashSet<ResourceKey>(); internal Chest(Container c) { Container = c; View = StorageIdentity.View(c); Piece = StorageIdentity.Piece(c); Inventory = c.GetInventory(); } } internal sealed class ResourceCache { private sealed class Bucket { internal int Count; internal readonly HashSet<Chest> Sources = new HashSet<Chest>(); } internal readonly List<Chest> Chests = new List<Chest>(); internal readonly Dictionary<Container, Chest> ByContainer = new Dictionary<Container, Chest>(); internal readonly Dictionary<Inventory, Chest> ByInventory = new Dictionary<Inventory, Chest>(); private readonly Dictionary<ResourceKey, Bucket> totals = new Dictionary<ResourceKey, Bucket>(); private readonly HashSet<Chest> dirty = new HashSet<Chest>(); private readonly List<Chest> work = new List<Chest>(); private int cursor; private int pruneCursor; private int world = int.MinValue; private long player; private bool flushing; private bool panelDirty; private readonly HashSet<Chest> forceSummaries = new HashSet<Chest>(); internal int Revision { get; private set; } internal bool PanelDirty { get { return panelDirty; } set { panelDirty = value; if (value) { int revision = Revision + 1; Revision = revision; } } } internal void Add(Container c) { if (Object.op_Implicit((Object)(object)c) && !ByContainer.ContainsKey(c)) { Chest chest = new Chest(c); if (chest.Inventory != null && Object.op_Implicit((Object)(object)chest.View) && Object.op_Implicit((Object)(object)chest.Piece)) { Chests.Add(chest); ByContainer[c] = chest; ByInventory[chest.Inventory] = chest; Dirty(chest); } } } internal void Dirty(Chest c) { c.Dirty = true; dirty.Add(c); } internal void Changed(Inventory inventory) { if (ByInventory.TryGetValue(inventory, out var value)) { Dirty(value); } } internal void Loaded(Container container) { if (ByContainer.TryGetValue(container, out var value)) { forceSummaries.Add(value); Dirty(value); } } private void Subtract(Chest c) { if (!c.Ready) { return; } foreach (KeyValuePair<ResourceKey, int> count in c.Counts) { if (totals.TryGetValue(count.Key, out var value)) { value.Count -= count.Value; value.Sources.Remove(c); if (value.Sources.Count == 0) { totals.Remove(count.Key); } } } c.Ready = false; PanelDirty = true; } internal void Clear() { foreach (Chest chest in Chests) { chest.Ready = false; chest.Pending = false; chest.Counts.Clear(); chest.First.Clear(); chest.Dirty = true; } totals.Clear(); dirty.Clear(); forceSummaries.Clear(); PanelDirty = true; } internal void Dispose() { Clear(); Chests.Clear(); ByContainer.Clear(); ByInventory.Clear(); work.Clear(); } internal void Prune() { int num = Math.Min(16, Chests.Count); for (int i = 0; i < num; i++) { if (Chests.Count <= 0) { break; } if (pruneCursor >= Chests.Count) { pruneCursor = 0; } Chest chest = Chests[pruneCursor]; if (Object.op_Implicit((Object)(object)chest.Container)) { pruneCursor++; continue; } Subtract(chest); dirty.Remove(chest); forceSummaries.Remove(chest); ByContainer.Remove(chest.Container); ByInventory.Remove(chest.Inventory); Plugin.ForgetRequest(chest.Container); Chests.RemoveAt(pruneCursor); } } internal void InvalidateAll() { Clear(); foreach (Chest chest in Chests) { Dirty(chest); } } internal bool Usable(Chest c, bool reserved = false) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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) Player localPlayer = Player.m_localPlayer; if (!Plugin.Running || !Object.op_Implicit((Object)(object)localPlayer) || !Object.op_Implicit((Object)(object)c.Container) || c.Broken || !((Component)c.Container).gameObject.activeInHierarchy || !Object.op_Implicit((Object)(object)c.View) || !c.View.IsValid() || !Object.op_Implicit((Object)(object)c.Piece)) { return false; } if (!SharingService.CanUse(c) || (reserved && !SharingService.CanSpend(c))) { return false; } Vector3 val = ((Component)c.Container).transform.position - ((Component)localPlayer).transform.position; if (((Vector3)(ref val)).sqrMagnitude > 40000f) { return false; } _ = c.Container; if (!Object.op_Implicit((Object)null)) { _ = c.Container; if (!Object.op_Implicit((Object)null) && !Object.op_Implicit((Object)(object)((Component)c.Container).GetComponent<TombStone>())) { if (!(bool)Plugin.Access.Invoke(c.Container, new object[1] { localPlayer.GetPlayerID() })) { return false; } if (c.Container.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)c.Container).transform.position, 0f, false, false)) { return false; } if (!reserved && (c.Container.IsInUse() || c.View.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0)) { return false; } return true; } } return false; } internal void Poll(bool all) { long num = (Object.op_Implicit((Object)(object)Player.m_localPlayer) ? Player.m_localPlayer.GetPlayerID() : 0); if (num != player || world != Game.m_worldLevel) { ChestBuilder.Clear(); player = num; world = Game.m_worldLevel; InvalidateAll(); Plugin.ClearManualIntent(); } if (!Plugin.Running || num == 0L) { if (totals.Count != 0) { Clear(); } return; } int num2 = (all ? Chests.Count : Math.Min(16, Chests.Count)); for (int i = 0; i < num2; i++) { if (Chests.Count <= 0) { break; } if (cursor >= Chests.Count) { cursor = 0; } Chest chest = Chests[cursor]; if (!Object.op_Implicit((Object)(object)chest.Container)) { Subtract(chest); dirty.Remove(chest); ByContainer.Remove(chest.Container); ByInventory.Remove(chest.Inventory); Plugin.ForgetRequest(chest.Container); Chests.RemoveAt(cursor); continue; } cursor++; if (all) { forceSummaries.Add(chest); } if (all || Time.unscaledTime >= chest.NextScan) { Dirty(chest); } } Flush(all ? int.MaxValue : 16); } internal void Flush(int limit = 16) { if (flushing || Plugin.Current != null || !Plugin.Running || dirty.Count == 0) { return; } flushing = true; try { work.Clear(); foreach (Chest item in dirty) { work.Add(item); if (work.Count >= limit) { break; } } foreach (Chest item2 in work) { dirty.Remove(item2); item2.Dirty = false; try { Refresh(item2); } catch (Exception e) { Subtract(item2); item2.Broken = true; Plugin.Warn("Chest skipped after a load/access error", e); } } } finally { work.Clear(); flushing = false; } } internal void Refresh(Chest c) { c.NextScan = Time.unscaledTime + ((Plugin.Active || OrePicker.Visible) ? 0.2f : 0.5f); ChestBuilder.Remember(c.Container); bool flag = (c.Pending = Usable(c)); if (flag && !c.View.IsOwner()) { Plugin.Prepare(c); } if (!flag) { Subtract(c); return; } c.Pending = !c.View.IsOwner() || !Plugin.AccessReady(c); byte[] byteArray = c.View.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null); if (!c.View.IsOwner() && byteArray == null) { Subtract(c); return; } bool flag2 = byteArray != c.Bytes; if (flag2) { Plugin.Load.Invoke(c.Container, null); c.Bytes = byteArray; } if (c.Ready && !c.Dirty && !flag2 && !forceSummaries.Contains(c) && Time.unscaledTime < c.NextSummary) { return; } forceSummaries.Remove(c); c.NextSummary = Time.unscaledTime + (Plugin.Active ? 2f : 5f); c.NextCounts.Clear(); c.NextFirst.Clear(); c.Cheated.Clear(); foreach (ItemData allItem in c.Inventory.GetAllItems()) { if (allItem == null || allItem.m_shared == null || allItem.m_stack <= 0) { throw new InvalidOperationException("Invalid chest item"); } AddItem(c, allItem, allItem.m_quality, world: false); AddItem(c, allItem, -1, world: false); if (allItem.m_worldLevel >= Game.m_worldLevel) { AddItem(c, allItem, allItem.m_quality, world: true); AddItem(c, allItem, -1, world: true); } } bool flag3 = c.Ready && c.Counts.Count == c.NextCounts.Count; if (flag3) { foreach (KeyValuePair<ResourceKey, int> count in c.Counts) { if (!c.NextCounts.TryGetValue(count.Key, out var value) || count.Value != value) { flag3 = false; break; } } } if (!flag3) { Subtract(c); } Dictionary<ResourceKey, int> counts = c.Counts; c.Counts = c.NextCounts; c.NextCounts = counts; Dictionary<ResourceKey, ItemData> first = c.First; c.First = c.NextFirst; c.NextFirst = first; if (flag3) { return; } checked { foreach (KeyValuePair<ResourceKey, int> count2 in c.Counts) { if (!totals.TryGetValue(count2.Key, out var value2)) { value2 = new Bucket(); totals.Add(count2.Key, value2); } value2.Count += count2.Value; value2.Sources.Add(c); } c.Ready = true; PanelDirty = true; } } internal void InventoryDirty(Inventory inventory) { if (ByInventory.TryGetValue(inventory, out var value)) { forceSummaries.Add(value); Dirty(value); } else if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && inventory == ((Humanoid)Player.m_localPlayer).GetInventory()) { PanelDirty = true; } } private static void AddItem(Chest c, ItemData item, int quality, bool world) { ResourceKey resourceKey = new ResourceKey(item.m_shared.m_name, quality, world); c.NextCounts.TryGetValue(resourceKey, out var value); c.NextCounts[resourceKey] = checked(value + item.m_stack); if (!c.NextFirst.ContainsKey(resourceKey)) { c.NextFirst[resourceKey] = item; } if (item.m_cheated) { c.Cheated.Add(resourceKey); } } internal int Count(string name, int quality, bool worldLevel) { if (!totals.TryGetValue(new ResourceKey(name, quality, worldLevel), out var value)) { return 0; } return value.Count; } internal ItemData First(string name, int quality) { ResourceKey key = new ResourceKey(name, quality, world: true); if (totals.TryGetValue(key, out var value)) { foreach (Chest source in value.Sources) { if (source.First.TryGetValue(key, out var value2)) { return value2; } } } return null; } internal bool AnyCheated(Requirement[] resources, int quality, bool worldLevel) { foreach (Requirement val in resources) { if (!Object.op_Implicit((Object)(object)val.m_resItem)) { continue; } ResourceKey resourceKey = new ResourceKey(val.m_resItem.m_itemData.m_shared.m_name, quality, worldLevel); if (!totals.TryGetValue(resourceKey, out var value)) { continue; } foreach (Chest source in value.Sources) { if (source.Cheated.Contains(resourceKey)) { return true; } } } return false; } } internal static class ChestBuilder { internal const string NameKey = "easycraft_builder_name_v1"; internal const string IdKey = "easycraft_builder_id_v1"; private static readonly Dictionary<long, string> names = new Dictionary<long, string>(); private static Container lastChest; private static long lastCreator; private static string lastLine; private static float nextLookup; private static bool warned; internal static void Clear() { names.Clear(); lastChest = null; lastLine = null; nextLookup = 0f; warned = false; } internal static string Clean(string name) { if (string.IsNullOrWhiteSpace(name)) { return null; } name = name.Replace('<', '‹').Replace('>', '›').Replace('\r', ' ') .Replace('\n', ' ') .Replace('\t', ' ') .Trim(); if (name.Length <= 80) { return name; } return name.Substring(0, 80); } internal static string Resolve(Piece piece, ZNetView view) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) long creator = piece.GetCreator(); if (creator == 0L) { return null; } string value = null; Player val = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)val) || val.GetPlayerID() != creator) { val = Player.GetPlayer(creator); } if (Object.op_Implicit((Object)(object)val)) { value = Clean(val.GetPlayerName()); } ZDO val2 = ((Object.op_Implicit((Object)(object)view) && view.IsValid()) ? view.GetZDO() : null); if (value == null && val2 != null && val2.GetLong("easycraft_builder_id_v1", 0L) == creator) { value = Clean(val2.GetString("easycraft_builder_name_v1", "")); } if (value == null) { names.TryGetValue(creator, out value); } if (value == null && val2 != null) { int num = val2.GetInt(ZDOVars.s_creatorIndex, -1); List<CrossNetworkUserInfo> list = ((!Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.GetWorld() == null) ? null : ZNet.instance.GetWorld().m_playerHistory); if (num >= 0 && list != null && num < list.Count) { string text = Clean(list[num].m_displayName); if (text != null) { value = ((text.Length > 68) ? text.Substring(0, 68) : text) + " (account)"; } } } if (value == null) { return null; } if (names.Count >= 4096 && !names.ContainsKey(creator)) { names.Clear(); } names[creator] = value; if (val2 != null && view.IsOwner() && (val2.GetLong("easycraft_builder_id_v1", 0L) != creator || val2.GetString("easycraft_builder_name_v1", "") != value)) { val2.Set("easycraft_builder_name_v1", value); val2.Set("easycraft_builder_id_v1", creator); } return value; } internal static void Remember(Container chest) { try { if (Object.op_Implicit((Object)(object)chest) && !Object.op_Implicit((Object)(object)((Component)chest).GetComponent<TombStone>())) { Piece val = StorageIdentity.Piece(chest); if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)Player.m_localPlayer) && val.GetCreator() == Player.m_localPlayer.GetPlayerID()) { Resolve(val, StorageIdentity.View(chest)); } } } catch (Exception e) { Report(e); } } internal static void Created(Piece piece) { if (Object.op_Implicit((Object)(object)piece)) { Container component = ((Component)piece).GetComponent<Container>(); if (Object.op_Implicit((Object)(object)component)) { Remember(component); } lastChest = null; } } internal static string Hover(Container chest, string original) { if (!Plugin.Running || !Object.op_Implicit((Object)(object)chest) || Object.op_Implicit((Object)(object)((Component)chest).GetComponent<TombStone>())) { return original; } try { Piece val = StorageIdentity.Piece(chest); long num = (Object.op_Implicit((Object)(object)val) ? val.GetCreator() : 0); if ((Object)(object)lastChest != (Object)(object)chest || lastCreator != num || Time.unscaledTime >= nextLookup) { lastChest = chest; lastCreator = num; nextLookup = Time.unscaledTime + 1f; string text = ((Object.op_Implicit((Object)(object)val) && num != 0L) ? Resolve(val, StorageIdentity.View(chest)) : null); string text2 = ((num == 0L) ? "World / no recorded builder" : (text ?? "Unknown player")); bool flag = num != 0L && Object.op_Implicit((Object)(object)Player.m_localPlayer) && num == Player.m_localPlayer.GetPlayerID(); lastLine = "Built by: <color=#FFD479>" + text2 + "</color>" + (flag ? " (you)" : ""); } return original + "\n" + lastLine; } catch (Exception e) { Report(e); return original; } } private static void Report(Exception e) { if (!warned) { warned = true; Plugin.Warn("Chest builder label unavailable; normal chest behaviour preserved", e); } } } internal static class HoeStock { internal static void Show(object hudObject, Piece piece) { Hud val = (Hud)((hudObject is Hud) ? hudObject : null); if (!Plugin.Running || !Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)piece) || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)val.m_pieceDescription)) { return; } HashSet<string> hashSet = new HashSet<string>(); List<string> list = new List<string>(); Requirement[] resources = piece.m_resources; foreach (Requirement val2 in resources) { ItemDrop resItem = val2.m_resItem; if (Object.op_Implicit((Object)(object)resItem) && val2.GetAmount(0) > 0 && hashSet.Add(resItem.m_itemData.m_shared.m_name)) { int num = Plugin.BuildCount(((Humanoid)Player.m_localPlayer).GetInventory(), resItem.m_itemData.m_shared.m_name, -1, world: true); string text = Localization.instance.Localize(resItem.m_itemData.m_shared.m_name); list.Add(text + ": " + num); } } if (list.Count > 0) { TMP_Text pieceDescription = val.m_pieceDescription; pieceDescription.text = pieceDescription.text + "\n<color=#FFD479><b>Available — " + string.Join(" • ", list.ToArray()) + "</b></color>"; } } } internal static class OrePicker { private static Smelter station; private static Switch input; private static List<ItemDrop> allChoices = new List<ItemDrop>(); private static readonly List<ItemDrop> choices = new List<ItemDrop>(); private static readonly List<int> counts = new List<int>(); private static int selected; private static int quantityIndex; private static readonly int[] oreQuantities = new int[3] { 1, 5, 10 }; private static readonly int[] woodQuantities = new int[4] { 1, 5, 10, 25 }; private static int[] quantities = oreQuantities; private static readonly Dictionary<Smelter, QueueCapacity> capacities = new Dictionary<Smelter, QueueCapacity>(); private static bool sending; private static float readyAt; private static float nextRefresh; private static Vector2 scroll; private static CursorLockMode oldLock; private static bool oldVisible; private static string status = "Choose an available material."; private static GUIStyle textStyle; private static GUIStyle smallStyle; private static GUIStyle titleStyle; private static GUIStyle rowStyle; private static GUIStyle buttonStyle; private static GUIStyle emptyStyle; private static GUIStyle countStyle; private static readonly Dictionary<ItemDrop, string> names = new Dictionary<ItemDrop, string>(); private static readonly Dictionary<ItemDrop, Sprite> icons = new Dictionary<ItemDrop, Sprite>(); private const int RowHeight = 66; private static readonly MethodInfo Add = AccessTools.Method(typeof(Smelter), "OnAddOre", (Type[])null, (Type[])null); private static readonly MethodInfo Queue = AccessTools.Method(typeof(Smelter), "GetQueueSize", (Type[])null, (Type[])null); private static bool syncing; private static string stationStock = ""; private static bool opened { get { return PickerInput.Open; } set { PickerInput.Open = value; } } internal static bool Visible => opened; internal static bool Capturing => PickerInput.Capturing; internal static bool BlockInput(ref bool __result) { return PickerInput.BlockPlayerInput(ref __result); } internal static bool Open(Smelter __instance, Switch sw, Humanoid user, ItemData item, ref bool __result) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //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) if (sending || !Plugin.Running || (Object)(object)user != (Object)(object)Player.m_localPlayer || item != null) { return true; } string text = ((Object)((Component)__instance).gameObject).name.Replace("(Clone)", "").Trim(); if (text != "smelter" && text != "blastfurnace" && text != "charcoal_kiln") { return true; } quantities = ((text == "charcoal_kiln") ? woodQuantities : oreQuantities); List<ItemDrop> list = (from x in Processors.Inputs(__instance) group x by x.m_itemData.m_shared.m_name into x select x.First()).ToList(); if (list.Count < 2) { return true; } __result = false; if (Capturing) { return false; } SharingService.RequestUse(); Plugin.Cache.Poll(all: true); opened = true; station = __instance; input = sw; allChoices = list; selected = 0; quantityIndex = 0; scroll = Vector2.zero; oldLock = Cursor.lockState; oldVisible = Cursor.visible; readyAt = Time.unscaledTime + 0.25f; nextRefresh = 0f; status = ""; names.Clear(); icons.Clear(); foreach (ItemDrop allChoice in allChoices) { names[allChoice] = Localization.instance.Localize(allChoice.m_itemData.m_shared.m_name); icons[allChoice] = allChoice.m_itemData.GetIcon(); } RefreshChoices(); ReleaseCursor(); return false; } internal static void ReleaseCursor() { SharingUi.ReleaseCursor(); if (Visible) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = !ZInput.IsGamepadActive(); } } internal static void Close() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (Visible) { Cursor.lockState = oldLock; Cursor.visible = oldVisible; PickerInput.BlockUntil = Time.unscaledTime + 0.2f; } opened = false; station = null; input = null; allChoices.Clear(); choices.Clear(); counts.Clear(); names.Clear(); icons.Clear(); } private static int Available(ItemDrop from) { return Processors.Count(((Humanoid)Player.m_localPlayer).GetInventory(), from.m_itemData.m_shared.m_name, -1, world: true); } private static void RefreshChoices() { int current = (int)Queue.Invoke(station, null); stationStock = "In station: " + current + " / " + station.m_maxOre; if (capacities.TryGetValue(station, out var value)) { value.Free(current, station.m_maxOre, Time.unscaledTime); if (value.Pending == 0 && status == "Waiting for station update.") { status = ""; } if (value.Pending > 0) { stationStock = stationStock + " (+" + value.Pending + " syncing)"; } } string keep = ((selected >= 0 && selected < choices.Count) ? choices[selected].m_itemData.m_shared.m_name : null); Plugin.Cache.Poll(all: false); syncing = Plugin.Cache.Chests.Any((Chest c) => c.Pending); choices.Clear(); counts.Clear(); foreach (ItemDrop allChoice in allChoices) { int num = Available(allChoice); if (num > 0) { choices.Add(allChoice); counts.Add(num); } } int num2 = choices.FindIndex((ItemDrop x) => x.m_itemData.m_shared.m_name == keep); selected = ((num2 >= 0) ? num2 : Math.Max(0, Math.Min(selected, choices.Count - 1))); scroll.y = Mathf.Clamp(scroll.y, 0f, (float)Math.Max(0, (choices.Count - 5) * 66)); nextRefresh = Time.unscaledTime + 0.2f; } internal static void Tick() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!Visible) { return; } if (Object.op_Implicit((Object)(object)station) && Plugin.Running && Object.op_Implicit((Object)(object)Player.m_localPlayer) && !((Character)Player.m_localPlayer).IsDead()) { Vector3 val = ((Component)Player.m_localPlayer).transform.position - ((Component)station).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > 25f)) { ReleaseCursor(); if (Time.unscaledTime >= nextRefresh) { RefreshChoices(); } if (Time.unscaledTime < readyAt) { return; } PickerInput.Reading = true; bool flag; int num; int num2; bool flag2; try { flag = ZInput.GetKeyDown((KeyCode)27, false) || ZInput.GetButtonDown("JoyButtonB"); num = ((ZInput.GetKeyDown((KeyCode)274, false) || ZInput.GetButtonDown("JoyDPadDown")) ? 1 : ((ZInput.GetKeyDown((KeyCode)273, false) || ZInput.GetButtonDown("JoyDPadUp")) ? (-1) : 0)); num2 = ((ZInput.GetKeyDown((KeyCode)275, false) || ZInput.GetButtonDown("JoyDPadRight")) ? 1 : ((ZInput.GetKeyDown((KeyCode)276, false) || ZInput.GetButtonDown("JoyDPadLeft")) ? (-1) : 0)); flag2 = ZInput.GetKeyDown((KeyCode)13, false) || ZInput.GetButtonDown("JoyButtonA"); } finally { PickerInput.Reading = false; } if (flag) { Close(); return; } if (num != 0 && choices.Count > 0) { selected = (selected + num + choices.Count) % choices.Count; if ((float)(selected * 66) < scroll.y) { scroll.y = selected * 66; } if ((float)((selected + 1) * 66) > scroll.y + 330f) { scroll.y = (selected - 4) * 66; } } quantityIndex = (quantityIndex + num2 + quantities.Length) % quantities.Length; if (flag2) { Send(quantities[quantityIndex]); } return; } } Close(); } private static void Send(int requested) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if (sending || !Visible || !Object.op_Implicit((Object)(object)station) || !Plugin.Running || choices.Count == 0 || Time.unscaledTime < readyAt) { return; } Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer)) { Vector3 val = ((Component)localPlayer).transform.position - ((Component)station).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > 25f)) { if (!PrivateArea.CheckAccess(((Component)station).transform.position, 0f, true, false)) { status = "Access denied."; return; } string name = choices[selected].m_itemData.m_shared.m_name; try { Plugin.Cache.Poll(all: true); foreach (Smelter item in capacities.Keys.Where((Smelter x) => !Object.op_Implicit((Object)(object)x)).ToList()) { capacities.Remove(item); } if (!capacities.TryGetValue(station, out var value)) { value = (capacities[station] = new QueueCapacity()); } int current2 = (int)Queue.Invoke(station, null); int num = value.Free(current2, station.m_maxOre, Time.unscaledTime); if (num <= 0) { status = ((value.Pending > 0) ? "Waiting for station update." : "The station is full."); return; } int num2 = Math.Min(Math.Min(requested, num), Processors.Count(((Humanoid)localPlayer).GetInventory(), name, -1, world: true)); if (num2 <= 0) { status = "That material is no longer available."; RefreshChoices(); return; } sending = true; Processors.PickerBatch = true; status = ""; for (int num3 = 0; num3 < num2; num3++) { ItemData val2 = ((Humanoid)localPlayer).GetInventory().GetItem(name, -1, false) ?? Plugin.Cache.First(name, -1); if (val2 == null) { break; } value.Reserve(Time.unscaledTime); if (!(bool)Add.Invoke(station, new object[3] { input, localPlayer, val2 })) { value.Cancel(); status = "Station cannot accept this material."; break; } Plugin.Cache.Flush(); value.Free((int)Queue.Invoke(station, null), station.m_maxOre, Time.unscaledTime); } RefreshChoices(); return; } catch (Exception e) { status = "Could not add material. Check the log."; Plugin.Warn(status, e); return; } finally { sending = false; Processors.PickerBatch = false; } } } Close(); } private static void Solid(Rect rect, Color color) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Color color2 = GUI.color; GUI.color = color; GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture); GUI.color = color2; } private static void Styles() { //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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown if (textStyle == null) { textStyle = new GUIStyle(GUI.skin.label) { fontSize = 26, alignment = (TextAnchor)3 }; textStyle.normal.textColor = Color.white; smallStyle = new GUIStyle(textStyle) { fontSize = 20 }; titleStyle = new GUIStyle(textStyle) { fontSize = 28, fontStyle = (FontStyle)1 }; titleStyle.normal.textColor = new Color(1f, 0.83f, 0.48f); rowStyle = new GUIStyle(textStyle) { fontStyle = (FontStyle)1, wordWrap = true }; countStyle = new GUIStyle(textStyle) { fontSize = 23, alignment = (TextAnchor)5 }; emptyStyle = new GUIStyle(titleStyle) { alignment = (TextAnchor)4 }; emptyStyle.normal.textColor = Color.white; buttonStyle = new GUIStyle(GUI.skin.button) { fontSize = 23 }; } } private static void Icon(ItemDrop from, Rect rect) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_00f2: Unknown result type (might be due to invalid IL or missing references) icons.TryGetValue(from, out var value); if (Object.op_Implicit((Object)(object)value) && Object.op_Implicit((Object)(object)value.texture)) { Rect textureRect = value.textureRect; float num = ((Texture)value.texture).width; float num2 = ((Texture)value.texture).height; float num3 = ((Rect)(ref textureRect)).width / ((Rect)(ref textureRect)).height; if (num3 > 1f) { float num4 = ((Rect)(ref rect)).height / num3; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + (((Rect)(ref rect)).height - num4) / 2f; ((Rect)(ref rect)).height = num4; } else { float num5 = ((Rect)(ref rect)).width * num3; ((Rect)(ref rect)).x = ((Rect)(ref rect)).x + (((Rect)(ref rect)).width - num5) / 2f; ((Rect)(ref rect)).width = num5; } GUI.DrawTextureWithTexCoords(rect, (Texture)(object)value.texture, new Rect(((Rect)(ref textureRect)).x / num, ((Rect)(ref textureRect)).y / num2, ((Rect)(ref textureRect)).width / num, ((Rect)(ref textureRect)).height / num2), true); } } internal static void Draw() { //IL_070b: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_015f: 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_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_0321: 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_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_0387: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Unknown result type (might be due to invalid IL or missing references) //IL_061a: Unknown result type (might be due to invalid IL or missing references) //IL_05eb: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_06eb: Unknown result type (might be due to invalid IL or missing references) //IL_0653: Unknown result type (might be due to invalid IL or missing references) //IL_06f7: Unknown result type (might be due to invalid IL or missing references) //IL_06fd: Invalid comparison between Unknown and I4 //IL_06a2: Unknown result type (might be due to invalid IL or missing references) //IL_0511: Unknown result type (might be due to invalid IL or missing references) //IL_058e: Unknown result type (might be due to invalid IL or missing references) //IL_05a2: Unknown result type (might be due to invalid IL or missing references) //IL_04d0: Unknown result type (might be due to invalid IL or missing references) //IL_04e4: Unknown result type (might be due to invalid IL or missing references) if (!Visible || !Object.op_Implicit((Object)(object)station) || !Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return; } Styles(); bool flag = choices.Count == 0; float num = Mathf.Clamp((float)Screen.height / 1080f, 0.85f, 2.2f); float num2 = Math.Min(5, choices.Count) * 66; bool flag2 = !flag && !string.IsNullOrEmpty(status); float num3 = (flag ? 460 : ((quantities.Length == 4) ? 720 : 640)); float num4 = (flag ? 160f : (164f + num2 + (float)(flag2 ? 32 : 0))); Matrix4x4 matrix = GUI.matrix; try { GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(num, num, 1f)); Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width / num - num3) / 2f, ((float)Screen.height / num - num4) / 2f, num3, num4); Solid(val, new Color(0.055f, 0.065f, 0.075f, 1f)); Solid(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y, ((Rect)(ref val)).width, 3f), new Color(0.8f, 0.6f, 0.25f, 1f)); GUI.BeginGroup(val); try { if (flag) { GUI.Label(new Rect(14f, 8f, num3 - 28f, 36f), stationStock, textStyle); GUI.Label(new Rect(12f, 54f, num3 - 24f, 48f), syncing ? "Syncing chests…" : (((Object)((Component)station).gameObject).name.StartsWith("charcoal_kiln") ? "No wood available" : "No ores available"), emptyStyle); if (GUI.Button(new Rect((num3 - 172f) / 2f, 112f, 172f, 36f), ZInput.IsGamepadActive() ? "Close [B]" : "Close [Esc]", buttonStyle)) { Close(); } } else { GUI.Label(new Rect(14f, 8f, 266f, 36f), Localization.instance.Localize(station.m_name), titleStyle); GUI.Label(new Rect(288f, 8f, 70f, 36f), "Total", countStyle); GUI.Label(new Rect(14f, 48f, num3 - 28f, 36f), stationStock, textStyle); int num5 = -1; int requested = 1; scroll = GUI.BeginScrollView(new Rect(10f, 90f, num3 - 20f, num2), scroll, new Rect(0f, 0f, num3 - 40f, (float)(choices.Count * 66))); try { Rect rect = default(Rect); for (int i = 0; i < choices.Count; i++) { ((Rect)(ref rect))..ctor(0f, (float)(i * 66), num3 - 40f, 62f); Solid(rect, (i == selected) ? new Color(0.3f, 0.24f, 0.12f, 1f) : new Color(0.105f, 0.12f, 0.135f, 1f)); if (GUI.Button(new Rect(0f, ((Rect)(ref rect)).y, 350f, ((Rect)(ref rect)).height), GUIContent.none, GUIStyle.none)) { selected = i; } Icon(choices[i], new Rect(8f, ((Rect)(ref rect)).y + 5f, 52f, 52f)); GUI.Label(new Rect(70f, ((Rect)(ref rect)).y, 206f, ((Rect)(ref rect)).height), names[choices[i]], rowStyle); GUI.Label(new Rect(278f, ((Rect)(ref rect)).y, 70f, ((Rect)(ref rect)).height), counts[i].ToString(), countStyle); bool enabled = GUI.enabled; GUI.enabled = enabled && Time.unscaledTime >= readyAt; for (int j = 0; j < quantities.Length; j++) { if (i == selected && j == quantityIndex) { Solid(new Rect((float)(353 + j * 80), ((Rect)(ref rect)).y + 7f, 78f, 48f), new Color(1f, 0.8f, 0.35f)); } if (GUI.Button(new Rect((float)(355 + j * 80), ((Rect)(ref rect)).y + 9f, 74f, 44f), "+" + quantities[j], buttonStyle)) { num5 = i; requested = quantities[j]; quantityIndex = j; } } GUI.enabled = enabled; if (i == selected) { Solid(new Rect(0f, ((Rect)(ref rect)).y, 4f, ((Rect)(ref rect)).height), new Color(1f, 0.8f, 0.35f)); } } } finally { GUI.EndScrollView(); } float num6 = 94f + num2; if (flag2) { GUI.Label(new Rect(14f, num6, num3 - 28f, 28f), status, smallStyle); num6 += 32f; } GUI.Label(new Rect(14f, num6, 460f, 28f), ZInput.IsGamepadActive() ? "D-pad ↑↓: material ←→: amount" : "Arrows ↑↓: material ←→: amount", smallStyle); GUI.Label(new Rect(14f, num6 + 27f, 440f, 28f), (ZInput.IsGamepadActive() ? "A: add " : "Enter: add ") + quantities[quantityIndex], smallStyle); bool num7 = GUI.Button(new Rect(num3 - 152f, num6, 140f, 36f), ZInput.IsGamepadActive() ? "Close [B]" : "Close [Esc]", buttonStyle); if (num5 >= 0) { selected = num5; Send(requested); } if (num7) { Close(); } } } finally { GUI.EndGroup(); } if ((int)Event.current.type == 0 || (int)Event.current.type == 4) { Event.current.Use(); } } finally { GUI.matrix = matrix; } } } internal static class Patches { private class BuilderHover { private static void Postfix(Container __instance, ref string __result) { __result = SharingUi.Hover(__instance, ChestBuilder.Hover(__instance, __result)); } } private class BuilderCreated { private static void Postfix(Piece __instance) { ChestBuilder.Created(__instance); } } private class Register { private static void Postfix(Container __instance) { Plugin.Cache.Add(__instance); } } private class Loaded { private static void Postfix(Container __instance, bool __result) { if (__result) { Plugin.Cache.InventoryDirty(__instance.GetInventory()); } } } private class Busy { private static void Postfix(Container __instance) { Plugin.Cache.Loaded(__instance); } } private class SaveGate { private static bool Prefix(Container __instance) { return Plugin.ContainerChanged(__instance); } } private class Manual { private static bool Prefix(Container __instance, Humanoid character, bool hold, bool alt, ref bool __result) { if (!SharingUi.Interact(__instance, character, hold, alt, ref __result)) { return false; } Plugin.ManualOpen(__instance); return true; } } private class Response { private static bool Prefix(Container __instance, long uid, bool granted) { return Plugin.Response(__instance, uid, granted); } } private class ChangeGate { private static bool Prefix(Inventory __instance) { return Plugin.InventoryChanging(__instance); } private static void Postfix(Inventory __instance) { Plugin.Cache.InventoryDirty(__instance); } } private class Opening { private static void Prefix() { Plugin.Opening(); } private static Exception Finalizer(Exception __exception) { Plugin.Opened(); return __exception; } } private class Panel { private static void Prefix() { Plugin.BeginPanel(); } private static Exception Finalizer(Exception __exception, InventoryGui __instance) { Plugin.EndPanel(__instance); return __exception; } } private class Requirements { private static void Prefix(InventoryGui __instance) { Plugin.UpdateSelection(__instance); } } private class Hidden { private static void Postfix() { Plugin.ClearManualIntent(); } } private class Selection { private static void Postfix(InventoryGui __instance) { Plugin.UpdateSelection(__instance); } } private class Display { private static void Prefix(bool craft, out bool __state) { __state = ((!craft) ? (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !((Character)Player.m_localPlayer).InPlaceMode()) : (Plugin.DisplayExcluded && (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !((Character)Player.m_localPlayer).InPlaceMode()))); if (__state) { Plugin.SuppressCount++; } } private static Exception Finalizer(Exception __exception, bool __state) { if (__state) { Plugin.SuppressCount--; } return __exception; } } private class RecipeScope { private static void Prefix(Recipe __0, out bool __state) { __state = !Plugin.RecipeAllowed(__0); if (__state) { Plugin.SuppressCount++; } } private static Exception Finalizer(Exception __exception, bool __state) { if (__state) { Plugin.SuppressCount--; } return __exception; } } private class FirstScope { private static void Prefix(Recipe __1, out bool __state) { __state = !Plugin.RecipeAllowed(__1); if (__state) { Plugin.SuppressCount++; } } private static Exception Finalizer(Exception __exception, bool __state) { if (__state) { Plugin.SuppressCount--; } return __exception; } } private class Craft { [HarmonyPriority(0)] private static bool Prefix(out bool __state) { __state = Plugin.Current == null; return __state; } private static Exception Finalizer(Exception __exception, bool __state) { return Plugin.Finish(__exception, __state); } } private class HammerMenu { private static void Prefix() { Plugin.PrepareHammer(); } private static void Postfix(object __instance) { BuildWindowRefresh.Opened(__instance); } } private class ToolEquipped { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && ((Character)__instance).InPlaceMode()) { Plugin.PrepareHammer(); } } } private class BuildDisplay { private static void Prefix() { Plugin.BeginBuildDisplay(); } private static void Postfix(object __instance, Piece piece) { HoeStock.Show(__instance, piece); } private static Exception Finalizer(Exception __exception) { Plugin.EndBuildDisplay(); return __exception; } } private class BuildScope { private static bool Prefix(Player __instance, out bool __state) { __state = (Object)(object)__instance == (Object)(object)Player.m_localPlayer && Plugin.Current == null; if (__state) { Plugin.PlacementScope++; if (Plugin.Running && ((Character)__instance).InPlaceMode()) { Plugin.Cache.Poll(all: false); } } if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { return Plugin.Current == null; } return true; } private static Exception Finalizer(Exception __exception, bool __state) { if (!__state) { return __exception; } return Plugin.FinishPlacement(__exception); } } private static void Hook(Harmony h, MethodBase method, Type callbacks, string pre = null, string post = null, string trans = null, string final = null) { if (method == null) { throw new MissingMethodException("Required patch target missing"); } Func<string, HarmonyMethod> func = (string name) => (name != null) ? new HarmonyMethod(AccessTools.Method(callbacks, name, (Type[])null, (Type[])null)) : ((HarmonyMethod)null); PatchProcessor val = h.CreateProcessor(method); if (pre != null) { val.AddPrefix(func(pre)); } if (post != null) { val.AddPostfix(func(post)); } if (trans != null) { val.AddTranspiler(func(trans)); } if (final != null) { val.AddFinalizer(func(final)); } val.Patch(); Patches patchInfo = Harmony.GetPatchInfo(method); if (patchInfo == null) { throw new InvalidOperationException("No Harmony registration for " + method); } Action<string, IEnumerable<Patch>> obj = delegate(string name, IEnumerable<Patch> list) { if (name != null && !list.Any((Patch p) => p.owner == "com.modmaking.easycraftfromownchests" && p.PatchMethod == AccessTools.Method(callbacks, name, (Type[])null, (Type[])null))) { throw new InvalidOperationException("Hook registration missing: " + method?.ToString() + " / " + name); } }; obj(pre, patchInfo.Prefixes); obj(post, patchInfo.Postfixes); obj(trans, patchInfo.Transpilers); obj(final, patchInfo.Finalizers); } internal static void Install(Harmony h) { Hook(h, AccessTools.Method(typeof(Container), "Awake", (Type[])null, (Type[])null), typeof(Register), null, "Postfix"); Hook(h, AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null), typeof(Loaded), null, "Postfix"); Hook(h, AccessTools.Method(typeof(Container), "SetInUse", (Type[])null, (Type[])null), typeof(Busy), null, "Postfix"); Hook(h, AccessTools.Method(typeof(Container), "OnContainerChanged", (Type[])null, (Type[])null), typeof(SaveGate), "Prefix"); Hook(h, AccessTools.Method(typeof(Container), "Interact", (Type[])null, (Type[])null), typeof(Manual), "Prefix"); Hook(h, AccessTools.Method(typeof(Container), "RPC_OpenResponse", (Type[])null, (Type[])null), typeof(Response), "Prefix"); Hook(h, AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null), typeof(ChangeGate), "Prefix", "Postfix"); Hook(h, AccessTools.Method(typeof(InventoryGui), "Show", (Type[])null, (Type[])null), typeof(Opening), "Prefix", null, null, "Finalizer"); Hook(h, AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", (Type[])null, (Type[])null), typeof(Panel), "Prefix", null, null, "Finalizer"); Hook(h, AccessTools.Method(typeof(InventoryGui), "SetupRequirementList", (Type[])null, (Type[])null), typeof(Requirements), "Prefix"); Hook(h, AccessTools.Method(typeof(InventoryGui), "Hide", (Type[])null, (Type[])null), typeof(Hidden), null, "Postfix"); Hook(h, AccessTools.Method(typeof(InventoryGui), "SetRecipe", (Type[])null, (Type[])null), typeof(Selection), null, "Postfix"); Hook(h, AccessTools.Method(typeof(InventoryGui), "SetupRequirement", (Type[])null, (Type[])null), typeof(Display), "Prefix", null, null, "Finalizer"); Hook(h, AccessTools.Method(typeof(Player), "HaveRequirementItems", (Type[])null, (Type[])null), typeof(RecipeScope), "Prefix", null, null, "Finalizer"); Hook(h, AccessTools.Method(typeof(Player), "GetFirstRequiredItem", (Type[])null, (Type[])null), typeof(FirstScope), "Prefix", null, null, "Finalizer"); Hook(h, AccessTools.Method(typeof(InventoryGui), "DoCrafting", (Type[])null, (Type[])null), typeof(Craft), "Prefix", null, null, "Finalizer"); Hook(h, AccessTools.Method(AccessTools.TypeByName("BuildUi"), "OpenBuildMenu", (Type[])null, (Type[])null), typeof(HammerMenu), "Prefix", "Postfix"); Hook(h, AccessTools.Method(AccessTools.TypeByName("BuildUi"), "Update", (Type[])null, (Type[])null), typeof(BuildWindowRefresh), null, "Tick"); Hook(h, AccessTools.Method(AccessTools.TypeByName("Hud"), "SetupPieceInfo", (Type[])null, (Type[])null), typeof(BuildDisplay), "Prefix", "Postfix", null, "Finalizer"); Hook(h, AccessTools.Method(typeof(Player), "SetPlaceMode", (Type[])null, (Type[])null), typeof(ToolEquipped), null, "Postfix"); Hook(h, AccessTools.Method(typeof(Player), "UpdatePlacement", (Type[])null, (Type[])null), typeof(BuildScope), "Prefix", null, null, "Finalizer"); Hook(h, AccessTools.Method(typeof(Container), "GetHoverText", (Type[])null, (Type[])null), typeof(BuilderHover), null, "Postfix"); Hook(h, AccessTools.Method(typeof(Piece), "SetCreator", (Type[])null, (Type[])null), typeof(BuilderCreated), null, "Postfix"); string[] array = new string[5] { "Smelter:OnHoverAddOre,OnHoverAddFuel", "CookingStation:GetHoverText,OnHoverFuelSwitch", "Fermenter:GetHoverText", "Fireplace:GetHoverText", "ShieldGenerator:OnHoverAddFuel" }; for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(':'); string[] array3 = array2[1].Split(','); foreach (string text in array3) { Hook(h, AccessTools.Method(AccessTools.TypeByName(array2[0]), text, (Type[])null, (Type[])null), typeof(StationStock), null, "Hover"); } } foreach (MethodBase item in Processors.Targets()) { Hook(h, item, typeof(Processors), null, null, "Transpiler", "Finalizer"); } array = new string[3] { "Smelter", "CookingStation", "Fermenter" }; foreach (string text2 in array) { Hook(h, AccessTools.Method(AccessTools.TypeByName(text2), "FindCookableItem", (Type[])null, (Type[])null), typeof(Processors), null, "Find"); } Hook(h, AccessTools.Method(typeof(Smelter), "OnAddOre", (Type[])null, (Type[])null), typeof(OrePicker), "Open"); Hook(h, AccessTools.Method(typeof(GameCamera), "UpdateMouseCapture", (Type[])null, (Type[])null), typeof(OrePicker), null, "ReleaseCursor"); Hook(h, AccessTools.Method(typeof(Player), "TakeInput", (Type[])null, (Type[])null), typeof(OrePicker), "BlockInput"); array = new string[6] { "GetButton", "GetButtonDown", "GetButtonUp", "GetMouseButton", "GetMouseButtonDown", "GetMouseButtonUp" }; foreach (string text3 in array) { Hook(h, AccessTools.Method(typeof(ZInput), text3, (Type[])null, (Type[])null), typeof(PickerInput), "BlockGameButton"); } array = new string[7] { "GetJoyLeftStickX", "GetJoyLeftStickY", "GetJoyRightStickX", "GetJoyRightStickY", "GetJoyLTrigger", "GetJoyRTrigger", "GetMouseScrollWheel" }; foreach (string text4 in array) { Hook(h, AccessTools.Method(typeof(ZInput), text4, (Type[])null, (Type[])null), typeof(PickerInput), "BlockGameAxis"); } array = new string[3] { "GetJoyLeftStick", "GetJoyRightStick", "GetMouseDelta" }; foreach (string text5 in array) { Hook(h, AccessTools.Method(typeof(ZInput), text5, (Type[])null, (Type[])null), typeof(PickerInput), "BlockGameVector"); } Hook(h, AccessTools.Method(AccessTools.TypeByName("Menu"), "Update", (Type[])null, (Type[])null), typeof(PickerInput), "BlockMenus"); foreach (MethodBase item2 in Targets()) { Hook(h, item2, typeof(Patches), null, null, "Transpiler"); } } internal static IEnumerable<MethodBase> Targets() { yield return AccessTools.Method(typeof(Player), "HaveRequirementItems", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Player), "GetFirstRequiredItem", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(InventoryGui), "SetupRequirement", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Player), "ConsumeResources", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(InventoryGui), "DoCrafting", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Player), "HaveRequirements", new Type[2] { typeof(Piece), typeof(RequirementMode) }, (Type[])null); yield return AccessTools.Method(typeof(Player), "TryPlacePiece", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Player), "PlacePiece", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Player), "UpdatePlacement", (Type[])null, (Type[])null); } internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod, ILGenerator generator) { List<CodeInstruction> list = instructions.ToList(); int replacements = 0; string target = __originalMethod.Name; if (target == "DoCrafting") { Label begin = generator.DefineLabel(); list[0].labels.Add(begin); yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null); yield return new CodeInstruction(OpCodes.Ldarg_1, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Plugin), "BeginNative", (Type[])null, (Type[])null)); yield return new CodeInstruction(OpCodes.Brtrue, (object)begin); yield return new CodeInstruction(OpCodes.Ret, (object)null); } foreach (CodeInstruction item in list) { MethodInfo methodInfo = item.operand as MethodInfo; string text = null; if (methodInfo != null && methodInfo.DeclaringType == typeof(Inventory)) { if (methodInfo.Name == "CountItems" && methodInfo.GetParameters().Length == 3) { text = ((target == "HaveRequirements") ? "BuildCount" : "Count"); } if (methodInfo.Name == "HaveItem" && target == "HaveRequirements" && methodInfo.GetParameters().Length == 2) { text = "BuildHave"; } if (methodInfo.Name == "GetItem" && target == "GetFirstRequiredItem" && methodInfo.GetParameters().Length == 3) { text = "GetItem"; } if (methodInfo.Name == "RemoveItem" && methodInfo.GetParameters().Length == 4 && methodInfo.GetParameters()[0].ParameterType == typeof(string)) { text = "Remove"; } if (methodInfo.Name == "ItemCheated" && methodInfo.GetParameters().Length == 3 && methodInfo.GetParameters()[0].ParameterType == typeof(Requirement[])) { text = "ItemCheated"; } if (methodInfo.Name == "AddItem" && target == "DoCrafting" && methodInfo.GetParameters().Length == 10) { text = "AddCrafted"; } } if (methodInfo != null && target == "DoCrafting" && methodInfo.DeclaringType == typeof(InventoryGui) && methodInfo.Name == "UpdateCraftingPanel") { text = "CommitAndRefresh"; } if (methodInfo != null && target == "TryPlacePiece" && methodInfo.DeclaringType == typeof(Player) && methodInfo.Name == "PlacePiece") { text = "PlaceWithResources"; } if (methodInfo != null && target == "UpdatePlacement" && methodInfo.DeclaringType == typeof(Player) && methodInfo.Name == "ConsumeResources") { text = "ConsumeBuilding"; } if (methodInfo != null && target == "PlacePiece" && methodInfo.DeclaringType == typeof(Object) && methodInfo.Name == "Instantiate" && methodInfo.IsGenericMethod && methodInfo.GetGenericArguments()[0] == typeof(GameObject) && methodInfo.GetParameters().Length == 3) { text = "InstantiateBuilding"; } if (text != null) { item.opcode = OpCodes.Call; item.operand = AccessTools.Method(typeof(Plugin), text, (Type[])null, (Type[])null); int num = replacements + 1; replacements = num; } yield return item; } int num2; switch (target) { default: num2 = 1; break; case "GetFirstRequiredItem": case "HaveRequirements": case "TryPlacePiece": num2 = 2; break; case "DoCrafting": num2 = 7; break; } int num3 = num2; if (replacements != num3) { throw new InvalidOperationException("Unsupported " + target + " layout: expected " + num3 + ", found " + replacements); } } } internal static class PickerInput { internal static bool Open; internal static bool SharingOpen; internal static bool Reading; internal static bool ExternalText; internal static float BlockUntil; internal static bool Capturing { get { if (!Open && !SharingOpen) { return Time.unscaledTime < BlockUntil; } return true; } } internal static bool BlockPlayerInput(ref bool __result) { if (!Capturing) { return true; } __result = false; return false; } internal static bool BlockGameButton(ref bool __result) { if (!Capturing || Reading || ExternalText) { return true; } __result = false; return false; } internal static bool BlockGameAxis(ref float __result) { if (!Capturing || Reading || ExternalText) { return true; } __result = 0f; return false; } internal static bool BlockGameVector(ref Vector2 __result) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (!Capturing || Reading || ExternalText) { return true; } __result = Vector2.zero; return false; } internal static bool BlockMenus() { return !Capturing; } } internal static class Processors { internal static bool PickerBatch; internal static bool Local(Inventory inv) { if (Plugin.Running && Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return inv == ((Humanoid)Player.m_localPlayer).GetInventory(); } return false; } internal static IEnumerable<ItemDrop> Inputs(object station) { FieldInfo fieldInfo = AccessTools.Field(station.GetType(), "m_conversion"); IEnumerable enumerable = ((fieldInfo == null) ? null : (fieldInfo.GetValue(station) as IEnumerable)); if (enumerable == null) { yield break; } foreach (object item in enumerable) { object? value = AccessTools.Field(item.GetType(), "m_from").GetValue(item); ItemDrop val = (ItemDrop)((value is ItemDrop) ? value : null); if (Object.op_Implicit((Object)(object)val)) { yield return val; } } } internal static void Find(object __instance, Inventory inventory, ref ItemData __result) { if (__result != null || !Local(inventory)) { return; } foreach (ItemDrop item in Inputs(__instance)) { ItemData val = Plugin.Cache.First(item.m_itemData.m_shared.m_name, -1); if (val != null) { __result = val; break; } } } public static int Count(Inventory inv, string name, int quality, bool world) { return checked(inv.CountItems(name, quality, world) + (Local(inv) ? Plugin.Cache.Count(name, quality, world) : 0)); } public static bool Have(Inventory inv, string name, bool world) { return Count(inv, name, -1, world) > 0; } public static int CountNames(Inventory inv, string[] names, int quality, bool world, bool stacks) { int num = inv.CountItemsByName(names, quality, world, stacks); if (!Local(inv) || stacks || names == null) { return num; } foreach (string item in names.Distinct()) { num = checked(num + Plugin.Cache.Count(item, quality, world)); } return num; } public static bool RemoveOne(Inventory inv, ItemData item) { return RemoveExact(inv, item, 1); } public static bool RemoveExact(Inventory inv, ItemData item, int amount) { if (!Local(inv) || inv.GetAllItems().Contains(item)) { return inv.RemoveItem(item, amount); } if (item == null || amount <= 0) { throw new InvalidOperationException("Invalid processor debit"); } Debit(inv, item.m_shared.m_name, amount, item.m_quality, world: true, item); return true; } public static void RemoveNamed(Inventory inv, string name, int amount, int quality, bool world) { if (!Local(inv) || inv.CountItems(name, quality, world) >= amount) { inv.RemoveItem(name, amount, quality, world); } else { Debit(inv, name, amount, quality, world, null); } } internal static void Debit(Inventory inv, string name, int amount, int quality, bool world, ItemData exact) { if (Plugin.Current != null) { throw new InvalidOperationException("Another inventory transaction is active"); } if (!PickerBatch) { Plugin.Cache.Poll(all: true); } else { Plugin.Cache.Flush(); } ResourceKey resourceKey = new ResourceKey(name, quality, world); CraftTransaction craftTransaction = new CraftTransaction(Player.m_localPlayer, null); try { if (exact == null) { craftTransaction.Plan(resourceKey, amount); } else { Chest chest = Plugin.Cache.Chests.FirstOrDefault((Chest x) => x.Ready && x.Inventory.GetAllItems().Contains(exact)); if (chest == null || !Plugin.Cache.Usable(chest) || !chest.View.IsOwner() || !Plugin.AccessReady(chest) || exact.m_stack < amount || (world && exact.m_worldLevel < Game.m_worldLevel)) { throw new InvalidOperationException("Selected processor material is no longer available"); } craftTransaction.Sources.Add(chest); craftTransaction.Remaining[resourceKey] = amount; craftTransaction.Debits.Add(new CraftTransaction.Debit { Inventory = chest.Inventory, Item = exact, Amount = amount, Cost = resourceKey }); } Plugin.Current = craftTransaction; craftTransaction.Acquire(); craftTransaction.Remove(inv, name, amount, quality, world); craftTransaction.Commit(); } catch (Exception ex) { Plugin.Finish(ex, ownsScope: true); throw new InvalidOperationException("Processor material debit failed; no station request was sent", ex); } finally { if (Plugin.Current == craftTransaction) { Plugin.Finish(null, ownsScope: true); } } } internal static IEnumerable<MethodBase> Targets() { string[] array = new string[5] { "Smelter:OnAddOre,OnAddFuel,CanUseItems", "CookingStation:CookItem,OnAddFuelSwitch,CanUseItems", "Fermenter:AddItem", "Fireplace:Interact,UseItem,CanUseItems", "ShieldGenerator:OnAddFuel" }; for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(':'); Type type = AccessTools.TypeByName(array2[0]); if (type == null) { throw new MissingMemberException(array2[0]); } string[] array3 = array2[1].Split(','); foreach (string text in array3) { yield return AccessTools.Method(type, text, (Type[])null, (Type[])null); } } } internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { int replaced = 0; foreach (CodeInstruction instruction in instructions) { MethodInfo methodInfo = instruction.operand as MethodInfo; string text = null; if (methodInfo != null && methodInfo.DeclaringType == typeof(Inventory)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Name == "HaveItem" && parameters.Length == 2 && parameters[0].ParameterType == typeof(string)) { text = "Have"; } if (methodInfo.Name == "CountItems" && parameters.Length == 3) { text = "Count"; } if (methodInfo.Name == "CountItemsByName" && parameters.Length == 4) { text = "CountNames"; } if (methodInfo.Name == "RemoveOneItem" && parameters.Length == 1) { text = "RemoveOne"; } if (methodInfo.Name == "RemoveItem" && parameters.Length == 2 && parameters[0].ParameterType == typeof(ItemData)) { text = "RemoveExact"; } if (methodInfo.Name == "RemoveItem" && parameters.Length == 4 && parameters[0].ParameterType == typeof(string)) { text = "RemoveNamed"; } } if (text != null) { instruction.opcode = OpCodes.Call; instruction.operand = AccessTools.Method(typeof(Processors), text, (Type[])null, (Type[])null); int num = replaced + 1; replaced = num; } yield return instruction; } string text2 = __originalMethod.DeclaringType.Name + "." + __originalMethod.Name; int num2; switch (text2) { default: num2 = 1; break; case "Smelter.OnAddFuel": case "Smelter.CanUseItems": case "CookingStation.OnAddFuelSwitch": case "Fireplace.Interact": case "ShieldGenerator.OnAddFuel": num2 = 2; break; case "Fireplace.UseItem": num2 = 3; break; } int num3 = num2; if (replaced != num3) { throw new InvalidOperationException("Unsupported processor layout: " + text2 + "; expected " + num3 + ", got " + replaced); } } internal static Exception Finalizer(Exception __exception) { if (__exception != null) { Plugin.Warn("Station interaction failed. Check material and station state before retrying; no automatic post-dispatch refund.", __exception); } return __exception; } } internal sealed class QueueCapacity { internal const float ReservationSeconds = 3f; private readonly List<float> sent = new List<float>(); private int observed; internal int Pending => sent.Count; internal int Free(int current, int maximum, float now = 0f) { int num = Math.Min(sent.Count, Math.Max(0, current - observed)); if (num > 0) { sent.RemoveRange(0, num); } observed = current; sent.RemoveAll((float time) => now - time >= 3f || now < time); return Math.Max(0, maximum - current - Pending); } internal void Reserve(float now = 0f) { sent.Add(now); } internal void Cancel() { if (sent.Count > 0) { sent.RemoveAt(sent.Count - 1); } } } internal static class SharingBatch { private sealed class Target { internal Chest chest; internal int revision; } internal static readonly int[] Radii = new int[5] { 1, 5, 10, 20, 50 }; private static readonly List<Target> targets = new List<Target>(); private static string[] clipboard; private static string[] members; private static long clipboardPlayer; private static Chest anchor; private static Vector3 center; private static int radius; private static int index; private static int saved; private static int skipped; private static float deadline; internal static string Status = ""; internal static bool Running { get; private set; } internal static int Count => targets.Count; internal static string[] RecipientKeys { get { if (members != null) { return members.ToArray(); } return new string[0]; } } internal static int MemberCount { get { if (members != null) { return members.Length; } return 0; } } internal static int CopiedCount { get { if (!HasCopy) { return 0; } return clipboard.Length; } } internal static bool HasCopy { get { if (clipboard != null && Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return clipboardPlayer == Player.m_localPlayer.GetPlayerID(); } return false; } } internal static void Clear() { Cancel(); clipboard = null; clipboardPlayer = 0L; Status = ""; } internal static void Cancel() { Running = false; targets.Clear(); anchor = null; members = null; } internal static void Copy(string id) { if (Running) { throw new InvalidOperationException("Wait for the current operation to finish."); } Chest c = SharingService.Find(id); SharingService.Validate(c); clipboard = SharingService.Current(c).members.ToArray(); clipboardPlayer = Player.m_localPlayer.GetPlayerID(); Status = "Copied " + clipboard.Length + " shared player(s). Open another chest and choose Paste."; } internal static void Preview(string id, int distance) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (Running) { throw new InvalidOperationException("Wait for the current operation to finish."); } Cancel(); anchor = SharingService.Find(id); SharingService.Validate(anchor); if (distance != 0 && !Radii.Contains(distance)) { throw new InvalidOperationException("Unsupported radius."); } if (distance == 0 && !HasCopy) { throw new InvalidOperationException("Copy a sharing list first."); } radius = distance; center = ((Component)anchor.Container).transform.position; members = ((distance == 0) ? clipboard : SharingService.Current(anchor).members).ToArray(); foreach (Chest chest in Plugin.Cache.Chests) { if (Eligible(chest)) { try { targets.Add(new Target { chest = chest, revision = SharingService.Current(chest).revision }); } catch { } } } Status = "Replace sharing on " + Count + " chest(s) with " + MemberCount + " shared player(s)?"; } private static bool Eligible(Chest c) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (c == null || !Plugin.Cache.Usable(c) || c.Piece.GetCreator() != Player.m_localPlayer.GetPlayerID()) { return false; } if (radius != 0) { if (c != anchor) { Vector3 val = ((Component)c.Container).transform.position - center; return ((Vector3)(ref val)).sqrMagnitude <= (float)(radius * radius); } return false; } return c == anchor; } internal static void Start() { if (!Running && targets.Count != 0) { SharingService.Validate(anchor); index = (saved = (skipped = 0)); deadline = Time.unscaledTime + 9f; Running = true; Status = "Applying sharing…"; } }