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 CraftFromStorage v1.0.0
plugins/CraftFromStorage.dll
Decompiled 2 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Utilities; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("CraftFromStorage")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Craft, build and smelt with materials from nearby chests. Built on Multi User Chest for multiplayer; works with Quick Stack Store Sort Trash Restock and Epic Loot.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+8c6e7c1275a24001b379f08cded2dec3467d65bc")] [assembly: AssemblyProduct("CraftFromStorage")] [assembly: AssemblyTitle("CraftFromStorage")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CraftFromStorage { internal static class ChestAccess { public sealed class Pull { public Container Chest; public string Name; public int Amount; public Vector2i Slot; public int StartCount; public float Deadline; public bool Finished; } public sealed class PullPlan { public readonly List<(Container Chest, ItemData Item, int Amount, Vector2i Slot)> Steps = new List<(Container, ItemData, int, Vector2i)>(); public int Missing; public int SlotsShort; public bool Ok { get { if (Missing == 0) { return SlotsShort == 0; } return false; } } public int Total => Steps.Sum(((Container Chest, ItemData Item, int Amount, Vector2i Slot) s) => s.Amount); } public sealed class SlotReservation { private readonly HashSet<Vector2i> m_reserved = new HashSet<Vector2i>(s_pending.Select((Pull p) => p.Slot)); public bool TryReserve(Inventory inventory, ItemData item, int amount, out Vector2i slot) { //IL_001d: 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_0058: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) foreach (ItemData item2 in inventory.m_inventory) { if (!m_reserved.Contains(item2.m_gridPos) && item2.IsSameType(item) && item2.m_quality == item.m_quality && item2.m_shared.m_maxStackSize - item2.m_stack >= amount) { slot = item2.m_gridPos; m_reserved.Add(slot); return true; } } Vector2i val = default(Vector2i); for (int i = 1; i <= inventory.GetHeight(); i++) { int num = i % inventory.GetHeight(); for (int j = 0; j < inventory.GetWidth(); j++) { ((Vector2i)(ref val))..ctor(j, num); if (!m_reserved.Contains(val) && inventory.GetItemAt(j, num) == null) { slot = val; m_reserved.Add(slot); return true; } } } slot = default(Vector2i); return false; } } private const string BorrowRpc = "s992.CraftFromStorage.Borrow"; private const string BorrowReplyRpc = "s992.CraftFromStorage.BorrowReply"; private const float BorrowTimeout = 5f; private const float BorrowCooldown = 15f; private const float BorrowForInterval = 3f; private const int MaxBorrowsPerCall = 8; private const float PullTimeout = 5f; private static readonly Dictionary<ZDOID, float> s_borrowPending = new Dictionary<ZDOID, float>(); private static readonly Dictionary<ZDOID, float> s_awaitingOwnership = new Dictionary<ZDOID, float>(); private static readonly Dictionary<ZDOID, float> s_borrowCooldown = new Dictionary<ZDOID, float>(); private static readonly Dictionary<string, float> s_nextBorrowFor = new Dictionary<string, float>(); private static readonly Dictionary<ZDOID, float> s_borrowedAt = new Dictionary<ZDOID, float>(); private const float KeepBorrowed = 30f; private const int MaxBorrowsInFlight = 3; private const float MinSecondsBetweenBorrows = 0.3f; private static float s_nextBorrowAllowed; private static readonly List<Pull> s_pending = new List<Pull>(); private static readonly List<ZDOID> s_expired = new List<ZDOID>(); public static int BorrowsSent { get; private set; } public static int BorrowsGranted { get; private set; } public static int HandedAway { get; private set; } public static int PendingPulls => s_pending.Count; public static bool PrepareOwned(Container chest) { ZNetView nview = chest.m_nview; if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid()) { return false; } if (!nview.IsOwner()) { if (nview.GetZDO().GetOwner() != 0L || !StorageNetwork.IsSinglePlayer()) { return false; } nview.ClaimOwnership(); } if (nview.GetZDO().DataRevision != chest.m_lastRevision && !chest.m_inUse) { chest.Load(); } return true; } public static int TakeFromOwned(string name, int amount, int quality, bool worldLevelBased, string reason) { int num = amount; foreach (Container item in StorageNetwork.Usable) { if (num <= 0) { break; } if (!StorageNetwork.IsOwnedForUse(item) || !PrepareOwned(item)) { continue; } Inventory inventory = item.GetInventory(); foreach (ItemData item2 in inventory.m_inventory.ToList()) { if (num <= 0) { break; } if (StorageNetwork.Matches(item2, name, quality, worldLevelBased)) { int num2 = Mathf.Min(item2.m_stack, num); if (inventory.RemoveItem(item2, num2)) { num -= num2; StorageLog.Action($"took {num2} {name} from {Describe(item)} for {reason}"); } } } } return amount - num; } public static void RegisterRpcs() { ZRoutedRpc.instance.Register<ZDOID>("s992.CraftFromStorage.Borrow", (Action<long, ZDOID>)RPC_Borrow); ZRoutedRpc.instance.Register<ZDOID, bool>("s992.CraftFromStorage.BorrowReply", (Action<long, ZDOID, bool>)RPC_BorrowReply); } public static void BorrowFor(string key, Func<ItemData, bool> match) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) if (!Settings.Enabled.Value || !Settings.Borrow.Value || ZRoutedRpc.instance == null || (s_nextBorrowFor.TryGetValue(key, out var value) && Time.time < value)) { return; } s_nextBorrowFor[key] = Time.time + 3f; int num = 0; foreach (Container candidate in StorageNetwork.Candidates) { if (num >= 8 || !BudgetAllows()) { break; } ZNetView nview = candidate.m_nview; if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid() || nview.IsOwner()) { continue; } ZDO zDO = nview.GetZDO(); long owner = zDO.GetOwner(); if (owner != 0L && zDO.GetInt(ZDOVars.s_inUse, 0) != 1) { ZDOID uid = zDO.m_uid; if (!s_borrowPending.ContainsKey(uid) && !s_awaitingOwnership.ContainsKey(uid) && (!s_borrowCooldown.TryGetValue(uid, out var value2) || !(Time.time < value2)) && candidate.GetInventory().m_inventory.Any((ItemData item) => StorageNetwork.IsUsableItem(item) && match(item))) { s_borrowPending[uid] = Time.time + 5f; s_nextBorrowAllowed = Time.time + 0.3f; ZRoutedRpc.instance.InvokeRoutedRPC(owner, "s992.CraftFromStorage.Borrow", new object[1] { uid }); BorrowsSent++; num++; } } } } private static bool BudgetAllows() { if (Time.time >= s_nextBorrowAllowed) { return s_borrowPending.Count + s_awaitingOwnership.Count < 3; } return false; } public static void BorrowFor(string itemName) { BorrowFor(itemName, (ItemData item) => item.m_shared.m_name == itemName); } private static void RPC_Borrow(long sender, ZDOID id) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0111: 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) bool flag = false; ZDO zDO = ZDOMan.instance.GetZDO(id); ZNetView val = ((zDO != null) ? ZNetScene.instance.FindInstance(zDO) : null); Container val2 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponentInChildren<Container>() : null); float value; bool flag2 = s_borrowedAt.TryGetValue(id, out value) && Time.time - value < 30f; if (Object.op_Implicit((Object)(object)val2) && val.IsOwner() && sender != ZDOMan.GetSessionID() && !flag2 && !val2.IsInUse() && (!Object.op_Implicit((Object)(object)val2.m_wagon) || !val2.m_wagon.InUse()) && (!Object.op_Implicit((Object)(object)InventoryGui.instance) || !((Object)(object)InventoryGui.instance.m_currentContainer == (Object)(object)val2))) { ZDOMan.instance.ForceSendZDO(sender, zDO.m_uid); zDO.SetOwner(sender); flag = true; HandedAway++; StorageLog.Action($"handed {Describe(val2)} to player {sender}"); } ZRoutedRpc.instance.InvokeRoutedRPC(sender, "s992.CraftFromStorage.BorrowReply", new object[2] { id, flag }); } private static void RPC_BorrowReply(long sender, ZDOID id, bool granted) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) s_borrowPending.Remove(id); s_borrowCooldown[id] = Time.time + 15f; if (granted) { s_awaitingOwnership[id] = Time.time + 5f; s_borrowedAt[id] = Time.time; BorrowsGranted++; } } public static PullPlan PlanPulls(string name, int quality, int amount, SlotReservation slots) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) PullPlan pullPlan = new PullPlan(); Player localPlayer = Player.m_localPlayer; int num = amount; if (!Object.op_Implicit((Object)(object)localPlayer) || amount <= 0 || !MucHealth.CanUseNonOwnedChests) { pullPlan.Missing = Mathf.Max(0, amount); return pullPlan; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); foreach (Container item in StorageNetwork.Usable) { if (num <= 0) { break; } if (StorageNetwork.IsOwnedForUse(item)) { continue; } foreach (ItemData item2 in item.GetInventory().m_inventory) { if (num <= 0) { break; } if (StorageNetwork.Matches(item2, name, quality, matchWorldLevel: true)) { int num2 = Mathf.Min(new int[3] { item2.m_stack, num, item2.m_shared.m_maxStackSize }); num -= num2; if (slots.TryReserve(inventory, item2, num2, out var slot)) { pullPlan.Steps.Add((item, item2, num2, slot)); } else { pullPlan.SlotsShort++; } } } } pullPlan.Missing = num; return pullPlan; } public static List<Pull> Start(PullPlan plan, string reason) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) List<Pull> list = new List<Pull>(); Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return list; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); foreach (var (val, val2, num, val3) in plan.Steps) { if (!Object.op_Implicit((Object)(object)val) || !val.GetInventory().ContainsItem(val2)) { continue; } string name = val2.m_shared.m_name; Pull pull = new Pull { Chest = val, Name = name, Amount = num, Slot = val3, StartCount = inventory.CountItems(name, -1, false), Deadline = Time.time + 5f }; if (StorageNetwork.IsOwnedForUse(val)) { if (PrepareOwned(val) && val.GetInventory().ContainsItem(val2)) { inventory.MoveItemToThis(val.GetInventory(), val2, num, val3.x, val3.y); pull.Finished = true; StorageLog.Action($"moved {num} {name} from {Describe(val)} for {reason}"); list.Add(pull); } } else { bool flag = inventory.MoveItemToThis(val.GetInventory(), val2, num, val3.x, val3.y); if (MucInternals.Available ? (!MucInternals.IsSlotBlocked(inventory, val3)) : (!flag)) { StorageLog.Warning($"Couldn't fetch {num} {name} from {Describe(val)}: MUC didn't send the request"); continue; } s_pending.Add(pull); list.Add(pull); StorageLog.Action($"fetching {num} {name} from {Describe(val)} for {reason}"); } } return list; } public static void Tick() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) float time = Time.time; ExpireAll(s_borrowPending, time, delegate(ZDOID id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) StorageNetwork.MarkUnresponsive(FindChest(id)); }); ExpireAll(s_borrowCooldown, time, null); ExpireAll(s_borrowedAt, time - 30f, null); if (s_awaitingOwnership.Count > 0) { foreach (ZDOID item in s_awaitingOwnership.Keys.ToList()) { Container val = FindChest(item); if (Object.op_Implicit((Object)(object)val) && val.m_nview.IsOwner()) { PrepareOwned(val); s_awaitingOwnership.Remove(item); StorageNetwork.Invalidate(); StorageLog.Action("borrowed " + Describe(val)); } else if (time > s_awaitingOwnership[item]) { s_awaitingOwnership.Remove(item); } } } Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || s_pending.Count == 0) { return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); foreach (Pull item2 in s_pending) { if (MucInternals.Available ? (!MucInternals.IsSlotBlocked(inventory, item2.Slot)) : (inventory.CountItems(item2.Name, -1, false) >= item2.StartCount + item2.Amount)) { item2.Finished = true; if (inventory.CountItems(item2.Name, -1, false) <= item2.StartCount) { if (Object.op_Implicit((Object)(object)item2.Chest)) { StorageNetwork.MarkUnresponsive(item2.Chest); } StorageLog.Action($"fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} came back empty"); } else { StorageLog.Action($"fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} answered"); } } else if (time > item2.Deadline) { MucInternals.ReleaseSlot(inventory, item2.Slot); item2.Finished = true; if (Object.op_Implicit((Object)(object)item2.Chest)) { StorageNetwork.MarkUnresponsive(item2.Chest); } StorageLog.Warning($"Fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} got no reply in {5f:0} s"); StorageLog.Action($"fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} timed out"); } } if (s_pending.RemoveAll((Pull p) => p.Finished) > 0) { StorageNetwork.Invalidate(); } } private static void ExpireAll(Dictionary<ZDOID, float> deadlines, float now, Action<ZDOID> onExpired) { //IL_0035: 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (deadlines.Count == 0) { return; } s_expired.Clear(); foreach (KeyValuePair<ZDOID, float> deadline in deadlines) { if (now > deadline.Value) { s_expired.Add(deadline.Key); } } foreach (ZDOID item in s_expired) { deadlines.Remove(item); onExpired?.Invoke(item); } } private static Container FindChest(ZDOID id) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(id) : null); ZNetView val2 = ((val != null) ? ZNetScene.instance.FindInstance(val) : null); if (!Object.op_Implicit((Object)(object)val2)) { return null; } return ((Component)val2).GetComponentInChildren<Container>(); } public static string Describe(Container chest) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)chest)) { return "a chest that's gone"; } Vector3 position = ((Component)chest).transform.position; string text = (Object.op_Implicit((Object)(object)chest.m_nview) ? StorageNetwork.PrefabName(((Component)chest.m_nview).gameObject) : ((Object)chest).name); return $"{text} at ({position.x:0}, {position.y:0}, {position.z:0})"; } } [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakePatch { private static void Postfix(Container __instance) { StorageNetwork.Register(__instance); } } [HarmonyPatch(typeof(Inventory), "Changed")] internal static class InventoryChangedPatch { private static void Postfix(Inventory __instance) { StorageNetwork.OnInventoryChanged(__instance); } } [HarmonyPatch(typeof(Game), "Start")] internal static class GameStartPatch { private static void Postfix() { ChestAccess.RegisterRpcs(); } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class TerminalPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleOptionsFetcher <>9__0_1; internal void <Postfix>b__0_0(ConsoleEventArgs args) { if (((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status") == "audit") { Audit(args.Context); return; } Terminal context = args.Context; if (context != null) { context.AddString(Status.Line()); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString($"Borrowing this session: asked {ChestAccess.BorrowsSent}, granted {ChestAccess.BorrowsGranted}, " + $"handed to others {ChestAccess.HandedAway}"); } } internal List<string> <Postfix>b__0_1() { return new List<string> { "status", "audit" }; } } private static void Postfix() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if (((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status") == "audit") { Audit(args.Context); } else { Terminal context = args.Context; if (context != null) { context.AddString(Status.Line()); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString($"Borrowing this session: asked {ChestAccess.BorrowsSent}, granted {ChestAccess.BorrowsGranted}, " + $"handed to others {ChestAccess.HandedAway}"); } } }; <>c.<>9__0_0 = val; obj = (object)val; } object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleOptionsFetcher val2 = () => new List<string> { "status", "audit" }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("storage", "[status|audit] - nearby storage: what's used, and item totals", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); } private static void Audit(Terminal terminal) { Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { if (terminal != null) { terminal.AddString("Storage audit needs a player in the world"); } return; } SortedDictionary<string, int> totals = new SortedDictionary<string, int>(); IReadOnlyList<Container> candidates = StorageNetwork.Candidates; foreach (Container item in candidates) { Add(item.GetInventory()); } Add(((Humanoid)localPlayer).GetInventory()); int num = totals.Values.Sum(); string text = $"Storage audit: {candidates.Count} chests in reach + your inventory: {num} items, {totals.Count} kinds"; if (terminal != null) { terminal.AddString(text + " (full list in BepInEx/CraftFromStorage.log)"); } StorageLog.Write("AUDIT " + text); foreach (KeyValuePair<string, int> item2 in totals) { StorageLog.Write($"AUDIT {Localization.instance.Localize(item2.Key)} ({item2.Key}): {item2.Value}"); } void Add(Inventory inventory) { foreach (ItemData item3 in inventory.m_inventory) { string name = item3.m_shared.m_name; totals[name] = (totals.TryGetValue(name, out var value) ? value : 0) + item3.m_stack; } } } } [HarmonyPatch(typeof(Player), "HaveRequirementItems")] internal static class HaveRequirementItemsPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.ReplaceCalls(instructions, Hooks.CountItems, Hooks.Self("CountForCrafting"), 1, "Player.HaveRequirementItems"); } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] internal static class HaveRequirementsPiecePatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.ReplaceCalls(Hooks.ReplaceCalls(instructions, Hooks.CountItems, Hooks.Self("CountForBuilding"), 1, "Player.HaveRequirements(Piece)"), Hooks.HaveItem, Hooks.Self("HaveForBuilding"), 1, "Player.HaveRequirements(Piece)"); } } [HarmonyPatch(typeof(Player), "GetFirstRequiredItem")] internal static class GetFirstRequiredItemPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.ReplaceCalls(Hooks.ReplaceCalls(instructions, Hooks.CountItems, Hooks.Self("CountForCrafting"), 1, "Player.GetFirstRequiredItem"), Hooks.GetItem, Hooks.Self("GetItemForCrafting"), 1, "Player.GetFirstRequiredItem"); } } [HarmonyPatch(typeof(Player), "ConsumeResources")] internal static class ConsumeResourcesPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.ReplaceCalls(instructions, Hooks.RemoveItemByName, Hooks.Self("RemoveWithStorage"), 1, "Player.ConsumeResources"); } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement")] internal static class SetupRequirementPatch { private static void Postfix(bool __result, Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier) { //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) if (__result && !((Object)(object)req?.m_resItem == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && Hooks.Active(craft ? Settings.Crafting.Value : Settings.Building.Value)) { Transform obj = ((Component)elementRoot).transform.Find("res_amount"); TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null); if (Object.op_Implicit((Object)(object)val)) { string name = req.m_resItem.m_itemData.m_shared.m_name; int num = ((Humanoid)player).GetInventory().CountItems(name, -1, true) + StorageNetwork.Count(name, -1, matchWorldLevel: true, ownedOnly: false); int num2 = req.GetAmount(quality) * craftMultiplier; val.text = $"{num}/{num2}"; bool globalKey = ZoneSystem.instance.GetGlobalKey((GlobalKeys)(craft ? 25 : 24)); ((Graphic)val).color = ((num < num2 && !globalKey && Mathf.Sin(Time.time * 10f) > 0f) ? Color.red : Color.white); } } } } internal static class CraftFlow { private const float MaxWait = 5f; private static List<ChestAccess.Pull> s_pulls; private static float s_deadline; private static bool s_resume; private static List<Need> CurrentNeeds(InventoryGui gui, Player player) { int quality = ((gui.m_craftUpgradeItem == null) ? 1 : (gui.m_craftUpgradeItem.m_quality + 1)); int multiplier = ((!gui.m_multiCrafting) ? 1 : gui.m_multiCraftAmount); return Needs.ForRecipe(player, gui.m_craftRecipe, quality, multiplier); } public static void OnCraftPressed(InventoryGui gui) { s_pulls = null; Player localPlayer = Player.m_localPlayer; if (gui.m_craftTimer < 0f || !Object.op_Implicit((Object)(object)localPlayer) || !Hooks.Active(Settings.Crafting.Value)) { return; } string problem; List<ChestAccess.PullPlan> list = Needs.PlanShortfall(CurrentNeeds(gui, localPlayer), ((Humanoid)localPlayer).GetInventory(), out problem); if (list == null) { ((Character)localPlayer).Message((MessageType)2, problem, 0, (Sprite)null, false); gui.m_craftTimer = -1f; } else if (list.Count != 0) { s_pulls = list.SelectMany((ChestAccess.PullPlan plan) => ChestAccess.Start(plan, "crafting " + ((Object)gui.m_craftRecipe).name)).ToList(); s_deadline = Time.time + 5f; } } public static bool BeforeDoCrafting(InventoryGui gui, Player player) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Crafting.Value) || (Object)(object)gui.m_craftRecipe == (Object)null) { return true; } if (s_pulls != null && s_pulls.Any((ChestAccess.Pull p) => !p.Finished) && Time.time < s_deadline) { s_resume = true; return false; } s_pulls = null; Inventory inventory = ((Humanoid)player).GetInventory(); foreach (Need item in CurrentNeeds(gui, player)) { if (item.PayableNow(inventory) < item.Amount) { ((Character)player).Message((MessageType)2, "Storage: " + Localization.instance.Localize(item.Name) + " didn't arrive from storage, crafting stopped", 0, (Sprite)null, false); return false; } } return true; } public static void AfterUpdateRecipe(InventoryGui gui) { if (s_resume) { s_resume = false; gui.m_craftTimer = 10000f; } } } [HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")] internal static class OnCraftPressedPatch { private static void Postfix(InventoryGui __instance) { CraftFlow.OnCraftPressed(__instance); } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class DoCraftingPatch { [HarmonyPriority(800)] private static bool Prefix(InventoryGui __instance, Player player) { return CraftFlow.BeforeDoCrafting(__instance, player); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return Hooks.ReplaceCalls(instructions, Hooks.RemoveItemByName, Hooks.Self("RemoveWithStorage"), 1, "InventoryGui.DoCrafting"); } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] internal static class UpdateRecipePatch { private static void Postfix(InventoryGui __instance) { CraftFlow.AfterUpdateRecipe(__instance); } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class TryPlacePiecePatch { private static List<ChestAccess.Pull> s_pulls; private static bool Prefix(Player __instance, Piece piece, ref bool __result) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || __instance.m_noPlacementCost || !Hooks.Active(Settings.Building.Value)) { return true; } List<Need> list = Needs.ForPiece(piece); Inventory inventory = ((Humanoid)__instance).GetInventory(); if (list.All((Need n) => n.PayableNow(inventory) >= n.Amount)) { return true; } __result = false; if (s_pulls != null && s_pulls.Any((ChestAccess.Pull p) => !p.Finished)) { ((Character)__instance).Message((MessageType)2, "Storage: still fetching materials…", 0, (Sprite)null, false); return false; } foreach (Need item in list) { ChestAccess.BorrowFor(item.Name); } string problem; List<ChestAccess.PullPlan> list2 = Needs.PlanShortfall(list, inventory, out problem); if (list2 == null) { ((Character)__instance).Message((MessageType)2, problem, 0, (Sprite)null, false); return false; } s_pulls = list2.SelectMany((ChestAccess.PullPlan plan) => ChestAccess.Start(plan, "building " + ((Object)piece).name)).ToList(); ((Character)__instance).Message((MessageType)2, "Storage: fetching materials, place again in a moment", 0, (Sprite)null, false); return false; } } internal static class EpicLootCompat { public const string Guid = "randyknapp.mods.epicloot"; private static Func<ItemData, bool> s_isMagicItem; private static Func<ItemData, bool> s_isMagicCraftingMaterial; public static bool Available { get; private set; } public static void Init(ManualLogSource log) { if (!Chainloader.PluginInfos.ContainsKey("randyknapp.mods.epicloot")) { return; } try { Type api = AccessTools.TypeByName("EpicLoot.API"); s_isMagicItem = Make(api, "IsMagicItem"); s_isMagicCraftingMaterial = Make(api, "IsMagicCraftingMaterial"); Available = s_isMagicItem != null && s_isMagicCraftingMaterial != null; if (!Available) { log.LogWarning((object)"Epic Loot API not found; enchanted items in chests can't be told apart from plain ones"); } } catch (Exception ex) { log.LogWarning((object)("Epic Loot hookup failed; enchanted items in chests can't be told apart: " + ex.Message)); } } private static Func<ItemData, bool> Make(Type api, string name) { MethodInfo methodInfo = ((api == null) ? null : AccessTools.Method(api, name, new Type[1] { typeof(ItemData) }, (Type[])null)); if (!(methodInfo == null)) { return (Func<ItemData, bool>)Delegate.CreateDelegate(typeof(Func<ItemData, bool>), methodInfo); } return null; } public static bool IsEnchanted(ItemData item) { if (Available && s_isMagicItem(item)) { return !s_isMagicCraftingMaterial(item); } return false; } } internal static class Hooks { public static readonly MethodInfo CountItems = AccessTools.Method(typeof(Inventory), "CountItems", new Type[3] { typeof(string), typeof(int), typeof(bool) }, (Type[])null); public static readonly MethodInfo HaveItem = AccessTools.Method(typeof(Inventory), "HaveItem", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); public static readonly MethodInfo GetItem = AccessTools.Method(typeof(Inventory), "GetItem", new Type[3] { typeof(string), typeof(int), typeof(bool) }, (Type[])null); public static readonly MethodInfo RemoveItemByName = AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[4] { typeof(string), typeof(int), typeof(int), typeof(bool) }, (Type[])null); public static bool Active(bool feature) { Player localPlayer = Player.m_localPlayer; if (Settings.Enabled.Value && feature && Object.op_Implicit((Object)(object)localPlayer)) { return !localPlayer.NoCostCheat(); } return false; } private static bool IsLocalPlayers(Inventory inventory) { if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return inventory == ((Humanoid)Player.m_localPlayer).m_inventory; } return false; } private static bool BuildingNow() { if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return ((Character)Player.m_localPlayer).InPlaceMode(); } return false; } public static int CountForCrafting(Inventory inventory, string name, int quality, bool matchWorldLevel) { return inventory.CountItems(name, quality, matchWorldLevel) + ((IsLocalPlayers(inventory) && Active(Settings.Crafting.Value)) ? StorageNetwork.Count(name, quality, matchWorldLevel, ownedOnly: false) : 0); } public static int CountForBuilding(Inventory inventory, string name, int quality, bool matchWorldLevel) { return inventory.CountItems(name, quality, matchWorldLevel) + ((IsLocalPlayers(inventory) && Active(Settings.Building.Value)) ? StorageNetwork.Count(name, quality, matchWorldLevel, ownedOnly: false) : 0); } public static bool HaveForBuilding(Inventory inventory, string name, bool matchWorldLevel) { if (!inventory.HaveItem(name, matchWorldLevel)) { if (IsLocalPlayers(inventory) && Active(Settings.Building.Value)) { return StorageNetwork.Count(name, -1, matchWorldLevel, ownedOnly: false) > 0; } return false; } return true; } public static ItemData GetItemForCrafting(Inventory inventory, string name, int quality, bool isPrefabName) { ItemData item = inventory.GetItem(name, quality, isPrefabName); if (item != null || isPrefabName || !IsLocalPlayers(inventory) || !Active(Settings.Crafting.Value)) { return item; } return StorageNetwork.FindItem(name, quality); } public static void RemoveWithStorage(Inventory inventory, string name, int amount, int itemQuality, bool worldLevelBased) { bool feature = (BuildingNow() ? Settings.Building.Value : Settings.Crafting.Value); if (!IsLocalPlayers(inventory) || !Active(feature)) { inventory.RemoveItem(name, amount, itemQuality, worldLevelBased); return; } int num = inventory.CountItems(name, itemQuality, worldLevelBased); inventory.RemoveItem(name, amount, itemQuality, worldLevelBased); int num2 = amount - (num - inventory.CountItems(name, itemQuality, worldLevelBased)); if (num2 > 0) { num2 -= ChestAccess.TakeFromOwned(name, num2, itemQuality, worldLevelBased, BuildingNow() ? "building" : "crafting"); if (num2 > 0) { StorageLog.Warning($"{num2} {name} couldn't be taken from storage; that part of the cost was free"); } } } public static IEnumerable<CodeInstruction> ReplaceCalls(IEnumerable<CodeInstruction> instructions, MethodInfo target, MethodInfo replacement, int expected, string where) { int replaced = 0; foreach (CodeInstruction instruction in instructions) { if (target != null && CodeInstructionExtensions.Calls(instruction, target)) { replaced++; yield return new CodeInstruction(OpCodes.Call, (object)replacement) { labels = instruction.labels, blocks = instruction.blocks }; } else { yield return instruction; } } if (replaced != expected) { Debug.LogWarning((object)(string.Format("[CraftFromStorage] {0}: expected {1} call(s) to {2}, replaced {3}. ", where, expected, target?.Name ?? "a missing method", replaced) + "Nearby storage may not be counted there.")); } } public static MethodInfo Self(string name) { return AccessTools.Method(typeof(Hooks), name, (Type[])null, (Type[])null); } } internal static class Hotkeys { private static readonly KeyCode[] ModifierKeys; private static readonly MethodInfo s_keyCodeToPath; public static bool Pressed(ConfigEntry<KeyboardShortcut> entry) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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) //IL_0018: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = entry.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, false)) { return false; } List<KeyCode> modifiers = ((KeyboardShortcut)(ref value)).Modifiers.ToList(); if (modifiers.Any((KeyCode key) => !ZInput.GetKey(key, false))) { return false; } return !ModifierKeys.Any((KeyCode key) => !modifiers.Contains(key) && ZInput.GetKey(key, false)); } public static bool IsBoundToUse(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if ((int)key == 0 || ZInput.instance == null || s_keyCodeToPath == null) { return false; } ButtonDef buttonDef = ZInput.instance.GetButtonDef("Use"); if (((buttonDef != null) ? buttonDef.ButtonAction : null) == null) { return false; } string text = s_keyCodeToPath.Invoke(null, new object[2] { key, false }) as string; if (string.IsNullOrEmpty(text)) { return false; } InputControl val = InputSystem.FindControl(text); Enumerator<InputBinding> enumerator = buttonDef.ButtonAction.bindings.GetEnumerator(); try { while (enumerator.MoveNext()) { InputBinding current = enumerator.Current; if (!string.IsNullOrEmpty(((InputBinding)(ref current)).effectivePath)) { if (string.Equals(((InputBinding)(ref current)).effectivePath, text, StringComparison.OrdinalIgnoreCase)) { return true; } if (val != null && InputControlPath.Matches(((InputBinding)(ref current)).effectivePath, val)) { return true; } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return false; } public static bool IsTyping() { if (!Console.IsVisible() && !TextInput.IsVisible() && !Menu.IsVisible() && !Minimap.InTextInput()) { if (Object.op_Implicit((Object)(object)Chat.instance)) { return Chat.instance.HasFocus(); } return false; } return true; } static Hotkeys() { KeyCode[] array = new KeyCode[6]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); ModifierKeys = (KeyCode[])(object)array; s_keyCodeToPath = AccessTools.Method(typeof(ZInput), "KeyCodeToPath", (Type[])null, (Type[])null); } } public enum MucState { Unknown, Healthy, Missing, TooOld, PatchesMissing } public static class MucHealth { public const string Guid = "com.maxsch.valheim.MultiUserChest"; public static readonly Version MinVersion = new Version(0, 6, 2); public static MucState State { get; private set; } = MucState.Unknown; public static string Problem { get; private set; } = ""; public static bool CanUseNonOwnedChests => State == MucState.Healthy; private static IEnumerable<(string Name, MethodBase Method, Func<Patches, IEnumerable<Patch>> Kind)> RequiredPatches() { yield return (Name: "Inventory.AddItem(item, amount, x, y)", Method: AccessTools.Method(typeof(Inventory), "AddItem", new Type[5] { typeof(ItemData), typeof(int), typeof(int), typeof(int), typeof(bool) }, (Type[])null), Kind: (Patches p) => p.Prefixes); yield return (Name: "Inventory.AddItem(item)", Method: AccessTools.Method(typeof(Inventory), "AddItem", new Type[1] { typeof(ItemData) }, (Type[])null), Kind: (Patches p) => p.Prefixes); yield return (Name: "Inventory.RemoveItem(item)", Method: AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[1] { typeof(ItemData) }, (Type[])null), Kind: (Patches p) => p.Prefixes); yield return (Name: "Container.RPC_RequestOpen", Method: AccessTools.Method(typeof(Container), "RPC_RequestOpen", (Type[])null, (Type[])null), Kind: (Patches p) => p.Prefixes); yield return (Name: "InventoryGui.UpdateContainer", Method: AccessTools.Method(typeof(InventoryGui), "UpdateContainer", (Type[])null, (Type[])null), Kind: (Patches p) => p.Transpilers); } public static void Check(ManualLogSource log) { (State, Problem) = Evaluate(); if (State == MucState.Healthy) { log.LogInfo((object)string.Format("MultiUserChest {0} found and healthy: chests other players own can be used", Chainloader.PluginInfos["com.maxsch.valheim.MultiUserChest"].Metadata.Version)); } else { log.LogWarning((object)(Problem + ". Only chests you own will be used (in multiplayer that's usually the ones you opened last).")); } } private static (MucState, string) Evaluate() { if (!Chainloader.PluginInfos.TryGetValue("com.maxsch.valheim.MultiUserChest", out var value)) { return (MucState.Missing, "MultiUserChest isn't installed"); } Version version = value.Metadata.Version; if (version < MinVersion) { return (MucState.TooOld, $"MultiUserChest {version} is older than {MinVersion}"); } List<string> list = new List<string>(); foreach (var (text, methodBase, func) in RequiredPatches()) { if (methodBase == null) { list.Add(text + " (method not found in this game version)"); continue; } Patches patchInfo = Harmony.GetPatchInfo(methodBase); if (patchInfo == null || !func(patchInfo).Any((Patch p) => p.owner == "com.maxsch.valheim.MultiUserChest")) { list.Add(text); } } if (list.Count > 0) { return (MucState.PatchesMissing, string.Format("MultiUserChest {0} is loaded but its patches are missing on {1} ", version, string.Join(", ", list)) + "(another mod may have broken its patching; see MUC issue #19)"); } return (MucState.Healthy, ""); } } internal static class MucInternals { private static MethodInfo s_get; private static MethodInfo s_isSlotBlocked; private static MethodInfo s_releaseSlot; public static bool Available { get; private set; } public static void Init(ManualLogSource log) { if (MucHealth.State == MucState.Healthy) { try { Type type = AccessTools.TypeByName("MultiUserChest.InventoryBlock"); s_get = ((type == null) ? null : AccessTools.Method(type, "Get", new Type[1] { typeof(Inventory) }, (Type[])null)); s_isSlotBlocked = ((type == null) ? null : AccessTools.Method(type, "IsSlotBlocked", new Type[1] { typeof(Vector2i) }, (Type[])null)); s_releaseSlot = ((type == null) ? null : AccessTools.Method(type, "ReleaseSlot", new Type[1] { typeof(Vector2i) }, (Type[])null)); Available = s_get != null && s_isSlotBlocked != null && s_releaseSlot != null; } catch (Exception ex) { log.LogWarning((object)("MUC slot-block lookup failed: " + ex.Message)); } if (!Available) { log.LogWarning((object)"Couldn't find MUC's slot blocking; a fetch that never gets a reply may stop auto-pickup until you relog"); } } } public static bool IsSlotBlocked(Inventory inventory, Vector2i slot) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (!Available) { return false; } object obj = s_get.Invoke(null, new object[1] { inventory }); return (bool)s_isSlotBlocked.Invoke(obj, new object[1] { slot }); } public static void ReleaseSlot(Inventory inventory, Vector2i slot) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (Available) { object obj = s_get.Invoke(null, new object[1] { inventory }); s_releaseSlot.Invoke(obj, new object[1] { slot }); } } } internal readonly struct Need { public readonly string Name; public readonly int Amount; public readonly int Quality; public Need(string name, int amount, int quality) { Name = name; Amount = amount; Quality = quality; } public int PayableNow(Inventory inventory) { return inventory.CountItems(Name, Quality, true) + StorageNetwork.Count(Name, Quality, matchWorldLevel: true, ownedOnly: true); } } internal static class Needs { public static List<Need> ForRecipe(Player player, Recipe recipe, int quality, int multiplier) { List<Need> list = new List<Need>(); if (!Object.op_Implicit((Object)(object)recipe) || ZoneSystem.instance.GetGlobalKey((GlobalKeys)25)) { return list; } if (recipe.m_requireOnlyOneIngredient) { int num = default(int); ItemData val = default(ItemData); recipe.GetAmount(quality, ref num, ref val, multiplier); if (val != null && num > 0) { list.Add(new Need(val.m_shared.m_name, num, val.m_quality)); } return list; } CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); Requirement[] resources = recipe.m_resources; foreach (Requirement val2 in resources) { if ((!((Object)(object)currentCraftingStation != (Object)null) || currentCraftingStation.m_upgrader == val2.m_upgraderResource) && (!((Object)(object)currentCraftingStation == (Object)null) || !val2.m_upgraderResource) && Object.op_Implicit((Object)(object)val2.m_resItem)) { int num2 = val2.GetAmount(quality) * multiplier; if (num2 > 0) { Add(list, val2.m_resItem.m_itemData.m_shared.m_name, num2); } } } return list; } public static List<Need> ForPiece(Piece piece) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) List<Need> list = new List<Need>(); if (!Object.op_Implicit((Object)(object)piece) || ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())) { return list; } Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { if (Object.op_Implicit((Object)(object)val.m_resItem) && val.m_amount > 0) { Add(list, val.m_resItem.m_itemData.m_shared.m_name, val.m_amount); } } return list; } private static void Add(List<Need> needs, string name, int amount) { for (int i = 0; i < needs.Count; i++) { if (!(needs[i].Name != name)) { needs[i] = new Need(name, needs[i].Amount + amount, -1); return; } } needs.Add(new Need(name, amount, -1)); } public static List<ChestAccess.PullPlan> PlanShortfall(List<Need> needs, Inventory inventory, out string problem) { problem = null; List<ChestAccess.PullPlan> list = new List<ChestAccess.PullPlan>(); ChestAccess.SlotReservation slots = new ChestAccess.SlotReservation(); int num = 0; foreach (Need need in needs) { int num2 = need.Amount - need.PayableNow(inventory); if (num2 > 0) { ChestAccess.PullPlan pullPlan = ChestAccess.PlanPulls(need.Name, need.Quality, num2, slots); if (pullPlan.Missing > 0) { problem = "Storage: not enough " + Localization.instance.Localize(need.Name) + " in reach"; return null; } num += pullPlan.SlotsShort; list.Add(pullPlan); } } if (num > 0) { problem = string.Format("Storage: need {0} free inventory slot{1} to fetch materials from chests other players have", num, (num == 1) ? "" : "s"); return null; } return list; } } [BepInPlugin("s992.CraftFromStorage", "CraftFromStorage", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("toxo.craftfromchests")] public class Plugin : BaseUnityPlugin { private Harmony m_harmony; private void Awake() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown Settings.Bind(((BaseUnityPlugin)this).Config); StorageLog.Init(((BaseUnityPlugin)this).Logger); EpicLootCompat.Init(((BaseUnityPlugin)this).Logger); m_harmony = new Harmony("s992.CraftFromStorage"); m_harmony.PatchAll(); ((Component)this).gameObject.AddComponent<Runner>(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"CraftFromStorage 1.0.0 loaded"); } private void Start() { MucHealth.Check(((BaseUnityPlugin)this).Logger); MucInternals.Init(((BaseUnityPlugin)this).Logger); } } internal static class Status { public static string Line() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) string text = ((object)Settings.ToggleKey.Value/*cast due to .constrained prefix*/).ToString(); if (!Settings.Enabled.Value) { return "Storage: off [" + text + "]"; } int count = StorageNetwork.Usable.Count; List<string> list = new List<string>(); list.Add(string.Format("Storage [{0}]: {1} chest{2} within {3:0} m", text, count, (count == 1) ? "" : "s", StorageNetwork.EffectiveRange)); List<string> list2 = list; if (StorageNetwork.RangeCapped) { list2.Add("range capped by loaded area"); } if (StorageNetwork.SkippedNoOwner > 0) { list2.Add($"{StorageNetwork.SkippedNoOwner} skipped (no owner)"); } if (StorageNetwork.InUseByOthers > 0) { list2.Add($"{StorageNetwork.InUseByOthers} in use"); } if (StorageNetwork.Unresponsive > 0) { list2.Add($"{StorageNetwork.Unresponsive} not answering"); } if (ChestAccess.PendingPulls > 0) { list2.Add($"fetching {ChestAccess.PendingPulls}"); } if (!MucHealth.CanUseNonOwnedChests && !StorageNetwork.IsSinglePlayer()) { list2.Add("only your chests (MultiUserChest missing or broken)"); } return string.Join(" · ", list2); } } internal sealed class Runner : MonoBehaviour { private bool m_checkChangeKey = true; private const float BorrowAheadInterval = 1f; private float m_nextBorrowAhead; private GUIStyle m_style; private GUIStyle m_shadow; public static Runner Instance { get; private set; } private void Awake() { Instance = this; Settings.ChangeMaterialKey.SettingChanged += delegate { m_checkChangeKey = true; }; } private static bool RejectIfUseKey() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = Settings.ChangeMaterialKey.Value; KeyCode mainKey = ((KeyboardShortcut)(ref value)).MainKey; if (!Hotkeys.IsBoundToUse(mainKey)) { return false; } Settings.ChangeMaterialKey.Value = KeyboardShortcut.Empty; string text = $"Change material key can't be your Use key ({mainKey}); it's back to Alt Place + Use"; StorageLog.Warning(text); if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { ((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false); } return true; } private void Update() { if (m_checkChangeKey && ZInput.instance != null) { m_checkChangeKey = false; RejectIfUseKey(); } ChestAccess.Tick(); Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !Hotkeys.IsTyping()) { if (Hotkeys.Pressed(Settings.ToggleKey)) { Settings.Enabled.Value = !Settings.Enabled.Value; StorageNetwork.Invalidate(); ((Character)localPlayer).Message((MessageType)1, "Use nearby storage: " + (Settings.Enabled.Value ? "on" : "off"), 0, (Sprite)null, false); } if (!SmeltFlow.UsesAltUse && Hotkeys.Pressed(Settings.ChangeMaterialKey) && Hooks.Active(Settings.Smelting.Value) && !RejectIfUseKey()) { SmeltFlow.CycleOnHover(localPlayer); } if (Hotkeys.Pressed(Settings.BlocklistKey) && Hooks.Active(Settings.Smelting.Value)) { SmeltFlow.ToggleBlocklistOnHover(localPlayer); } if (Time.time >= m_nextBorrowAhead) { m_nextBorrowAhead = Time.time + 1f; BorrowAhead(localPlayer); SmeltFlow.Prune(); SmelterInFlight.Prune(); StorageNetwork.Prune(); } } } private static void BorrowAhead(Player player) { if (!Settings.Enabled.Value) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); List<Need> list = null; if (((Character)player).InPlaceMode()) { if (Settings.Building.Value) { list = Needs.ForPiece(player.GetSelectedPiece()); } } else if (InventoryGui.IsVisible() && Settings.Crafting.Value && Object.op_Implicit((Object)(object)((RecipeDataPair)(ref InventoryGui.instance.m_selectedRecipe)).Recipe)) { InventoryGui instance = InventoryGui.instance; ItemData itemData = ((RecipeDataPair)(ref instance.m_selectedRecipe)).ItemData; list = Needs.ForRecipe(player, ((RecipeDataPair)(ref instance.m_selectedRecipe)).Recipe, (itemData == null) ? 1 : (itemData.m_quality + 1), 1); } if (list == null) { return; } foreach (Need item in list) { if (item.PayableNow(inventory) < item.Amount) { ChestAccess.BorrowFor(item.Name); } } } private static bool StatusContext(Player player) { if (!((Character)player).InPlaceMode() && (!InventoryGui.IsVisible() || !((Object)(object)InventoryGui.instance.m_currentContainer == (Object)null))) { return Object.op_Implicit((Object)(object)SmeltFlow.HoveredOreSwitchSmelter(player)); } return true; } private void OnGUI() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if ((int)Event.current.type != 7 || !Settings.ShowStatus.Value) { return; } Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && StatusContext(localPlayer)) { if (m_style == null) { m_style = new GUIStyle(GUI.skin.label) { fontSize = 15, alignment = (TextAnchor)1 }; m_style.normal.textColor = new Color(1f, 0.9f, 0.7f); m_shadow = new GUIStyle(m_style); m_shadow.normal.textColor = new Color(0f, 0f, 0f, 0.8f); } string text = Status.Line(); Rect val = default(Rect); ((Rect)(ref val))..ctor(0f, 8f, (float)Screen.width, 24f); GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), text, m_shadow); GUI.Label(val, text, m_style); } } } internal static class Settings { public static ConfigEntry<bool> Enabled; public static ConfigEntry<KeyboardShortcut> ToggleKey; public static ConfigEntry<int> Range; public static ConfigEntry<bool> ShowStatus; public static ConfigEntry<bool> Crafting; public static ConfigEntry<bool> Building; public static ConfigEntry<bool> Smelting; public static ConfigEntry<bool> CartsAndShips; public static ConfigEntry<string> ExcludedContainers; public static ConfigEntry<string> SmeltBlocklist; public static ConfigEntry<KeyboardShortcut> BlocklistKey; public static ConfigEntry<KeyboardShortcut> ChangeMaterialKey; public static ConfigEntry<string> SmeltSelections; public static ConfigEntry<bool> Borrow; public static ConfigEntry<bool> LogActions; public static int Version { get; private set; } public static HashSet<string> Excluded { get; private set; } = new HashSet<string>(); public static HashSet<string> Blocklist { get; private set; } = new HashSet<string>(); public static void Bind(ConfigFile config) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) Enabled = config.Bind<bool>("1. General", "Use nearby storage", true, "Crafting, building and smelting use materials from nearby chests. The toggle key flips this."); ToggleKey = config.Bind<KeyboardShortcut>("1. General", "Toggle key", new KeyboardShortcut((KeyCode)278, Array.Empty<KeyCode>()), "Turns \"use nearby storage\" on and off."); Range = config.Bind<int>("1. General", "Range", 30, new ConfigDescription("Chests within this many metres of you are used. It's capped at the distance the game guarantees chests are loaded (128 m on the default simulation distance); the status line says when the cap applies.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 250), Array.Empty<object>())); ShowStatus = config.Bind<bool>("1. General", "Show status line", true, "Shows a line at the top of the screen while crafting, building or hovering a smelter: how many chests are used, how many were skipped and why."); Crafting = config.Bind<bool>("2. Features", "Crafting", true, "Crafting stations (and upgrades) use nearby storage."); Building = config.Bind<bool>("2. Features", "Building", true, "Building pieces use nearby storage."); Smelting = config.Bind<bool>("2. Features", "Smelting", true, "Smelters, furnaces, kilns, windmills, spinning wheels and eitr refineries take materials and fuel from nearby storage."); CartsAndShips = config.Bind<bool>("3. Chests", "Carts and ships", false, "Carts and ships nearby count as storage."); ExcludedContainers = config.Bind<string>("3. Chests", "Excluded containers", "", "Container prefab names that never count as storage, comma separated (e.g. piece_chest_private)."); SmeltBlocklist = config.Bind<string>("4. Smelting", "Blocklist", "FineWood,RoundLog,YggdrasilWood", "Item prefab names that are never put into a smelter automatically, from your inventory or storage, comma separated. Using an item from your hotbar on a smelter still works. The blocklist key toggles the material shown when hovering a smelter."); BlocklistKey = config.Bind<KeyboardShortcut>("4. Smelting", "Blocklist key", new KeyboardShortcut((KeyCode)279, Array.Empty<KeyCode>()), "While hovering a smelter's input, adds or removes its selected material from the blocklist."); ChangeMaterialKey = config.Bind<KeyboardShortcut>("4. Smelting", "Change material key", KeyboardShortcut.Empty, "While hovering a smelter's input, switches which material goes in next. Empty: the game's Alt Place + Use (Shift+E by default; also works on a gamepad). Your Use key is refused (it would also insert)."); SmeltSelections = config.Bind<string>("4. Smelting", "Selected materials", "", "Managed by the mod: which material each kind of smelter takes (changed in game with the change material key)."); Borrow = config.Bind<bool>("5. Diagnostics", "Borrow chests", true, "Ask other players for ownership of nearby chests before they're needed, so crafting and building can take from them instantly. Off: those chests are only reached by fetching items into your inventory."); LogActions = config.Bind<bool>("5. Diagnostics", "Log actions", false, "Writes every item the mod takes from or fetches out of a chest to BepInEx/CraftFromStorage.log. Handy when something looks wrong; the file is never trimmed, so leave it off otherwise. `storage audit` writes there either way."); Parse(); config.SettingChanged += delegate { Parse(); }; } private static void Parse() { Excluded = SplitNames(ExcludedContainers.Value); Blocklist = SplitNames(SmeltBlocklist.Value); Version++; } public static HashSet<string> SplitNames(string value) { return new HashSet<string>(from s in (value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select s.Trim() into s where s.Length > 0 select s, StringComparer.OrdinalIgnoreCase); } public static void ToggleBlocklisted(string prefabName) { HashSet<string> hashSet = SplitNames(SmeltBlocklist.Value); if (!hashSet.Remove(prefabName)) { hashSet.Add(prefabName); } SmeltBlocklist.Value = string.Join(",", hashSet); } public static string GetSelection(string smelterPrefab) { string[] array = (SmeltSelections.Value ?? "").Split(';'); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0 && text.Substring(0, num) == smelterPrefab) { return text.Substring(num + 1); } } return null; } public static void SetSelection(string smelterPrefab, string itemPrefab) { List<string> list = (from p in (SmeltSelections.Value ?? "").Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries) where !p.StartsWith(smelterPrefab + "=", StringComparison.Ordinal) select p).ToList(); list.Add(smelterPrefab + "=" + itemPrefab); SmeltSelections.Value = string.Join(";", list); } } internal static class SmelterInFlight { private sealed class Channel { private int m_pending; private float m_lastSeen = float.NaN; private float m_expires; public float Effective(float observed) { Catch(observed); return observed + (float)m_pending; } public void Sent(float observed) { Catch(observed); m_pending++; m_expires = Time.time + 3f; } private void Catch(float observed) { if (!float.IsNaN(m_lastSeen)) { float num = observed - m_lastSeen; if (num >= 0.5f) { m_pending = Mathf.Max(0, m_pending - Mathf.RoundToInt(num)); } } m_lastSeen = observed; if (m_pending > 0 && Time.time > m_expires) { m_pending = 0; } } } private const float Expiry = 3f; private static readonly Dictionary<Smelter, (Channel Ore, Channel Fuel)> s_channels = new Dictionary<Smelter, (Channel, Channel)>(); private static (Channel Ore, Channel Fuel) For(Smelter smelter) { if (!s_channels.TryGetValue(smelter, out (Channel, Channel) value)) { value = (s_channels[smelter] = (new Channel(), new Channel())); } return value; } public static int Queue(Smelter smelter) { return Mathf.RoundToInt(For(smelter).Ore.Effective(smelter.GetQueueSize())); } public static float Fuel(Smelter smelter) { return For(smelter).Fuel.Effective(smelter.GetFuel()); } public static void OreSent(Smelter smelter) { if (!smelter.m_nview.IsOwner()) { For(smelter).Ore.Sent(smelter.GetQueueSize()); } } public static void FuelSent(Smelter smelter) { if (!smelter.m_nview.IsOwner()) { For(smelter).Fuel.Sent(smelter.GetFuel()); } } public static void Prune() { foreach (Smelter item in s_channels.Keys.Where((Smelter s) => !Object.op_Implicit((Object)(object)s)).ToList()) { s_channels.Remove(item); } } } internal static class SmeltFlow { public sealed class Material { public string Prefab; public string Name; public ItemDrop Drop; } private static readonly Dictionary<Smelter, List<ChestAccess.Pull>> s_pulls = new Dictionary<Smelter, List<ChestAccess.Pull>>(); private static readonly HashSet<string> s_reportedDuplicates = new HashSet<string>(); public static bool UsesAltUse { get { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 KeyboardShortcut value = Settings.ChangeMaterialKey.Value; return (int)((KeyboardShortcut)(ref value)).MainKey == 0; } } public static string SmelterPrefab(Smelter smelter) { return StorageNetwork.PrefabName(((Component)smelter).gameObject); } public static List<Material> Materials(Smelter smelter) { List<IGrouping<string, ItemConversion>> list = (from c in smelter.m_conversion where (Object)(object)c.m_from != (Object)null group c by ((Object)((Component)c.m_from).gameObject).name).ToList(); if (list.Count != smelter.m_conversion.Count((ItemConversion c) => (Object)(object)c.m_from != (Object)null) && s_reportedDuplicates.Add(SmelterPrefab(smelter))) { StorageLog.Info(SmelterPrefab(smelter) + " lists the same input more than once: " + string.Join(", ", from g in list where g.Count() > 1 select $"{g.Key} x{g.Count()}")); } return list.Select((IGrouping<string, ItemConversion> g) => new Material { Prefab = g.Key, Name = g.First().m_from.m_itemData.m_shared.m_name, Drop = g.First().m_from }).ToList(); } private static int PositionOf(List<Material> options, Material material) { if (material != null) { return options.FindIndex((Material m) => m.Prefab == material.Prefab); } return -1; } public static int InInventory(string name) { return ((Humanoid)Player.m_localPlayer).GetInventory().CountItems(name, -1, true); } public static int InStorage(string name) { return StorageNetwork.Count(name, -1, matchWorldLevel: true, ownedOnly: false); } private static int Available(Material m) { return InInventory(m.Name) + InStorage(m.Name); } public static bool Blocklisted(string prefab) { return Settings.Blocklist.Contains(prefab); } public static Material Selected(Smelter smelter, List<Material> materials) { string chosen = Settings.GetSelection(SmelterPrefab(smelter)); Material material = materials.FirstOrDefault((Material m) => m.Prefab == chosen); if (material != null) { return material; } return materials.FirstOrDefault((Material m) => !Blocklisted(m.Prefab) && Available(m) > 0) ?? materials.FirstOrDefault((Material m) => !Blocklisted(m.Prefab)); } public static void Cycle(Smelter smelter, Humanoid user) { List<Material> list = Materials(smelter); Material current = Selected(smelter, list); List<Material> list2 = list.Where((Material m) => Available(m) > 0 || m == current).ToList(); if (list2.Count == 0) { ((Character)user).Message((MessageType)2, "$msg_noprocessableitems", 0, (Sprite)null, false); return; } Material material = list2[(PositionOf(list2, current) + 1) % list2.Count]; Settings.SetSelection(SmelterPrefab(smelter), material.Prefab); ((Character)user).Message((MessageType)1, smelter.m_name + ": " + material.Name + (Blocklisted(material.Prefab) ? " (blocklisted)" : ""), 0, (Sprite)null, false); } public static string HoverLines(Smelter smelter) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) List<Material> list = Materials(smelter); Material selected = Selected(smelter, list); if (selected == null) { return ""; } if (InInventory(selected.Name) + StorageNetwork.Count(selected.Name, -1, matchWorldLevel: true, ownedOnly: true) == 0) { ChestAccess.BorrowFor(selected.Name); } List<Material> list2 = list.Where((Material m) => Available(m) > 0 || m == selected).ToList(); string text = (Blocklisted(selected.Prefab) ? $" <color=orange>blocklisted</color> ({Settings.BlocklistKey.Value} to allow)" : $" — {InInventory(selected.Name)} in inventory, {InStorage(selected.Name)} in storage"); string text2 = "\nAdding: " + selected.Name + text; if (list2.Count > 1) { text2 += $"\n[<color=yellow><b>{ChangeKeyLabel()}</b></color>] change ({PositionOf(list2, selected) + 1}/{list2.Count})"; } return Localization.instance.Localize(text2); } public static bool BeforeAddOre(Smelter smelter, Humanoid user, ref ItemData item, ref bool result) { if ((Object)(object)user == (Object)(object)Player.m_localPlayer && Settings.Smelting.Value && SmelterInFlight.Queue(smelter) >= smelter.m_maxOre) { ((Character)user).Message((MessageType)2, "$msg_itsfull", 0, (Sprite)null, false); result = false; return false; } if (item != null || (Object)(object)user != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Smelting.Value)) { return true; } List<Material> list = Materials(smelter); if (list.Count == 0) { return true; } Material material = Selected(smelter, list); result = false; if (material == null) { ((Character)user).Message((MessageType)2, "Storage: everything this takes is blocklisted", 0, (Sprite)null, false); return false; } if (Blocklisted(material.Prefab)) { ((Character)user).Message((MessageType)2, "Storage: " + Localization.instance.Localize(material.Name) + " is blocklisted", 0, (Sprite)null, false); return false; } ItemData item2 = user.GetInventory().GetItem(material.Name, -1, false); if (item2 != null) { item = item2; return true; } if (ChestAccess.TakeFromOwned(material.Name, 1, -1, worldLevelBased: true, smelter.m_name ?? "") == 1) { item = material.Drop.m_itemData.Clone(); item.m_dropPrefab = ((Component)material.Drop).gameObject; item.m_stack = 2; return true; } return FetchBatch(smelter, user, material.Name, smelter.m_maxOre - SmelterInFlight.Queue(smelter)); } public static bool BeforeAddFuel(Smelter smelter, Humanoid user, ItemData item, ref bool result) { if ((Object)(object)user == (Object)(object)Player.m_localPlayer && Settings.Smelting.Value && Object.op_Implicit((Object)(object)smelter.m_fuelItem) && SmelterInFlight.Fuel(smelter) > (float)(smelter.m_maxFuel - 1)) { ((Character)user).Message((MessageType)2, "$msg_itsfull", 0, (Sprite)null, false); result = false; return false; } if (item != null || (Object)(object)user != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Smelting.Value) || !Object.op_Implicit((Object)(object)smelter.m_fuelItem)) { return true; } string name = smelter.m_fuelItem.m_itemData.m_shared.m_name; if (user.GetInventory().HaveItem(name, true) || Blocklisted(((Object)((Component)smelter.m_fuelItem).gameObject).name) || InStorage(name) == 0) { return true; } result = false; if (ChestAccess.TakeFromOwned(name, 1, -1, worldLevelBased: true, smelter.m_name + " fuel") == 1) { ((Character)user).Message((MessageType)2, "$msg_added " + name, 0, (Sprite)null, false); smelter.m_nview.InvokeRPC("RPC_AddFuel", Array.Empty<object>()); result = true; return false; } return FetchBatch(smelter, user, name, smelter.m_maxFuel - Mathf.CeilToInt(SmelterInFlight.Fuel(smelter))); } private static bool FetchBatch(Smelter smelter, Humanoid user, string name, int wanted) { if (s_pulls.TryGetValue(smelter, out var value) && value.Any((ChestAccess.Pull p) => !p.Finished)) { ((Character)user).Message((MessageType)2, "Storage: still fetching…", 0, (Sprite)null, false); return false; } int num = Mathf.Min(wanted, InStorage(name)); if (num <= 0) { ((Character)user).Message((MessageType)2, "$msg_donthaveany " + name, 0, (Sprite)null, false); return false; } ChestAccess.SlotReservation slots = new ChestAccess.SlotReservation(); ChestAccess.PullPlan pullPlan = ChestAccess.PlanPulls(name, -1, num, slots); if (pullPlan.Steps.Count == 0) { ((Character)user).Message((MessageType)2, (pullPlan.SlotsShort > 0) ? "Storage: need 1 free inventory slot to fetch from chests other players have" : ("$msg_donthaveany " + name), 0, (Sprite)null, false); return false; } s_pulls[smelter] = ChestAccess.Start(pullPlan, smelter.m_name); ((Character)user).Message((MessageType)2, $"Storage: fetching {pullPlan.Total} {Localization.instance.Localize(name)}", 0, (Sprite)null, false); return false; } private static string ChangeKeyLabel() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (!UsesAltUse) { return ((object)Settings.ChangeMaterialKey.Value/*cast due to .constrained prefix*/).ToString(); } return "$KEY_AltPlace + $KEY_Use"; } public static void CycleOnHover(Player player) { Smelter val = HoveredOreSwitchSmelter(player); if (Object.op_Implicit((Object)(object)val)) { Cycle(val, (Humanoid)(object)player); } } public static void ToggleBlocklistOnHover(Player player) { Smelter val = HoveredOreSwitchSmelter(player); if (Object.op_Implicit((Object)(object)val)) { Material material = Selected(val, Materials(val)); if (material != null) { Settings.ToggleBlocklisted(material.Prefab); ((Character)player).Message((MessageType)1, material.Name + ": " + (Blocklisted(material.Prefab) ? "never put into smelters automatically" : "allowed"), 0, (Sprite)null, false); } } } public static Smelter HoveredOreSwitchSmelter(Player player) { GameObject val = (Object.op_Implicit((Object)(object)player) ? ((Humanoid)player).GetHoverObject() : null); Switch val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponentInParent<Switch>() : null); Smelter val3 = (Object.op_Implicit((Object)(object)val2) ? ((Component)val2).GetComponentInParent<Smelter>() : null); if (!Object.op_Implicit((Object)(object)val3) || !((Object)(object)val3.m_addOreSwitch == (Object)(object)val2)) { return null; } return val3; } public static void Prune() { foreach (Smelter item in s_pulls.Keys.Where((Smelter s) => !Object.op_Implicit((Object)(object)s)).ToList()) { s_pulls.Remove(item); } } } [HarmonyPatch(typeof(Smelter), "OnHoverAddOre")] internal static class SmelterHoverOrePatch { private static void Postfix(Smelter __instance, ref string __result) { if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Settings.Smelting.Value) { int queueSize = __instance.GetQueueSize(); int num = SmelterInFlight.Queue(__instance); if (num != queueSize) { __result = ReplaceFirst(__result, $"({queueSize}/{__instance.m_maxOre})", $"({num}/{__instance.m_maxOre})"); } if (Hooks.Active(Settings.Smelting.Value)) { __result += SmeltFlow.HoverLines(__instance); } } } public static string ReplaceFirst(string text, string old, string replacement) { int num = text.IndexOf(old, StringComparison.Ordinal); if (num >= 0) { return text.Substring(0, num) + replacement + text.Substring(num + old.Length); } return text; } } [HarmonyPatch(typeof(Smelter), "OnHoverAddFuel")] internal static class SmelterHoverFuelPatch { private static void Postfix(Smelter __instance, ref string __result) { if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)__instance.m_fuelItem) || !Settings.Smelting.Value) { return; } float num = Mathf.Ceil(__instance.GetFuel()); float num2 = Mathf.Ceil(SmelterInFlight.Fuel(__instance)); if (num2 != num) { __result = SmelterHoverOrePatch.ReplaceFirst(__result, $" {num}/{__instance.m_maxFuel})", $" {num2}/{__instance.m_maxFuel})"); } if (Hooks.Active(Settings.Smelting.Value)) { string name = __instance.m_fuelItem.m_itemData.m_shared.m_name; if (SmeltFlow.InInventory(name) + StorageNetwork.Count(name, -1, matchWorldLevel: true, ownedOnly: true) == 0) { ChestAccess.BorrowFor(name); } __result += Localization.instance.Localize($"\n{SmeltFlow.InStorage(name)} {name} in storage"); } } } [HarmonyPatch(typeof(Smelter), "OnAddOre")] internal static class SmelterAddOrePatch { private static bool Prefix(Smelter __instance, Humanoid user, ref ItemData item, ref bool __result) { return SmeltFlow.BeforeAddOre(__instance, user, ref item, ref __result); } private static void Postfix(Smelter __instance, Humanoid user, bool __result) { if (__result && (Object)(object)user == (Object)(object)Player.m_localPlayer) { SmelterInFlight.OreSent(__instance); } } } [HarmonyPatch(typeof(Smelter), "OnAddFuel")] internal static class SmelterAddFuelPatch { private static bool Prefix(Smelter __instance, Humanoid user, ItemData item, ref bool __result) { return SmeltFlow.BeforeAddFuel(__instance, user, item, ref __result); } private static void Postfix(Smelter __instance, Humanoid user, bool __result) { if (__result && (Object)(object)user == (Object)(object)Player.m_localPlayer) { SmelterInFlight.FuelSent(__instance); } } } [HarmonyPatch(typeof(Switch), "Interact")] internal static class SwitchInteractPatch { private static bool Prefix(Switch __instance, Humanoid character, bool hold, bool alt, ref bool __result) { if (!alt || !SmeltFlow.UsesAltUse || (Object)(object)character != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Smelting.Value)) { return true; } Smelter componentInParent = ((Component)__instance).GetComponentInParent<Smelter>(); if (!Object.op_Implicit((Object)(object)componentInParent) || (Object)(object)componentInParent.m_addOreSwitch != (Object)(object)__instance) { return true; } __result = false; if (!hold) { SmeltFlow.Cycle(componentInParent, character); __result = true; } return false; } } internal static class StorageLog { private static ManualLogSource s_log; private static string s_path; public static void Init(ManualLogSource log) { s_log = log; s_path = Path.Combine(Paths.BepInExRootPath, "CraftFromStorage.log"); } public static void Info(string message) { ManualLogSource obj = s_log; if (obj != null) { obj.LogInfo((object)message); } } public static void Warning(string message) { ManualLogSource obj = s_log; if (obj != null) { obj.LogWarning((object)message); } } public static void Action(string message) { if (Settings.LogActions.Value) { Write(message); } } public static void Write(string message) { if (s_path == null) { return; } try { File.AppendAllText(s_path, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {message}{Environment.NewLine}"); } catch (Exception ex) { ManualLogSource obj = s_log; if (obj != null) { obj.LogWarning((object)("Couldn't write " + s_path + ": " + ex.Message)); } } } } internal static class StorageNetwork { private const float RefreshInterval = 0.5f; private const float UnresponsiveSeconds = 30f; private static readonly List<Container> s_all = new List<Container>(); private static readonly HashSet<Inventory> s_inventories = new HashSet<Inventory>(); private static readonly Dictionary<Container, bool> s_staticOk = new Dictionary<Container, bool>(); private static readonly Dictionary<ZDOID, float> s_unresponsiveUntil = new Dictionary<ZDOID, float>(); private static readonly List<Container> s_candidates = new List<Container>(); private static readonly List<Container> s_usable = new List<Container>(); private static readonly List<(Container Chest, float Distance)> s_sortBuffer = new List<(Container, float)>(); private static readonly Dictionary<(string Name, int Quality, bool MatchWorldLevel, bool OwnedOnly), int> s_counts = new Dictionary<(string, int, bool, bool), int>(); private static int s_contentVersion; private static int s_countsVersion = -1; private static int s_settingsVersion = -1; private static int s_setSignature; private static float s_nextRefresh; private static SimulationDistance s_radiusFor; private static float s_radius; public static int SkippedNoOwner { get; private set; } public static int InUseByOthers { get; private set; } public static int Unresponsive { get; private set; } public static float EffectiveRange { get; private set; } public static bool RangeCapped { get; private set; } public static IReadOnlyList<Container> Candidates { get { Refresh(); return s_candidates; } } public static IReadOnlyList<Container> Usable { get { Refresh(); return s_usable; } } public static void Register(Container container) { if (Object.op_Implicit((Object)(object)container) && container.GetInventory() != null) { s_all.Add(container); s_inventories.Add(container.GetInventory()); } } public static void OnInventoryChanged(Inventory inventory) { if (s_inventories.Contains(inventory)) { s_contentVersion++; } } public static void Invalidate() { s_contentVersion++; s_nextRefresh = 0f; } public static void MarkUnresponsive(Container container) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)container) && Object.op_Implicit((Object)(object)container.m_nview) && container.m_nview.IsValid()) { s_unresponsiveUntil[container.m_nview.GetZDO().m_uid] = Time.time + 30f; Invalidate(); } } public static bool IsSinglePlayer() { if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && !ZNet.instance.IsDedicated()) { return ZNet.instance.GetConnectedPeers().Count == 0; } return false; } public static bool IsOwnedForUse(Container container) { ZNetView nview = container.m_nview; if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid()) { return false; } if (!nview.IsOwner()) { if (nview.GetZDO().GetOwner() == 0L) { return IsSinglePlayer(); } return false; } return true; } public static bool IsUsableItem(ItemData item) { return !EpicLootCompat.IsEnchanted(item); } public static bool Matches(ItemData item, string name, int quality, bool matchWorldLevel) { if (item.m_shared.m_name == name && (quality < 0 || item.m_quality == quality) && (!matchWorldLevel || item.m_worldLevel >= Game.m_worldLevel)) { return IsUsableItem(item); } return false; } public static int Count(string name, int quality, bool matchWorldLevel, bool ownedOnly) { Refresh(); if (s_countsVersion != s_contentVersion || s_settingsVersion != Settings.Version) { s_counts.Clear(); s_countsVersion = s_contentVersion; s_settingsVersion = Settings.Version; } (string, int, bool, bool) key = (name, quality, matchWorldLevel, ownedOnly); if (s_counts.TryGetValue(key, out var value)) { return value; } int num = 0; foreach (Container item in s_usable) { if (!ownedOnly || IsOwnedForUse(item)) { num += CountIn(item.GetInventory(), name, quality, matchWorldLevel); } } s_counts[key] = num; return num; } public static int CountIn(Inventory inventory, string name, int quality, bool matchWorldLevel) { int num = 0; foreach (ItemData item in inventory.m_inventory) { if (Matches(item, name, quality, matchWorldLevel)) { num += item.m_stack; } } return num; } public static ItemData FindItem(string name, int quality) { foreach (Container item in Usable) { foreach (ItemData item2 in item.GetInventory().m_inventory) { if (Matches(item2, name, quality, matchWorldLevel: true)) { return item2; } } } return null; } private static void Refresh() { //IL_006e: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) if (Time.time < s_nextRefresh) { return; } s_nextRefresh = Time.time + 0.5f; s_candidates.Clear(); s_usable.Clear(); SkippedNoOwner = (InUseByOthers = (Unresponsive = 0)); Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || !Object.op_Implicit((Object)(object)ZNet.instance)) { s_setSignature = 0; return; } Prune(); float num3 = GuaranteedLoadedRadius(ZNet.instance.GetSyncedSimulationDistance()); RangeCapped = (float)Settings.Range.Value > num3; EffectiveRange = Mathf.Min((float)Settings.Range.Value, num3); Vector3 position = ((Component)localPlayer).transform.position; long playerID = localPlayer.GetPlayerID(); bool flag = IsSinglePlayer(); s_sortBuffer.Clear(); foreach (Container item2 in s_all) { if (!((Behaviour)item2).isActiveAndEnabled || !IsStaticallyOk(item2) || !IsAllowedKind(item2)) { continue; } ZNetView nview = item2.m_nview; if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid()) { continue; } float num4 = Vector3.Distance(position, ((Component)item2).transform.position); if (num4 > EffectiveRange || !item2.CheckAccess(playerID) || (item2.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)item2).transform.position, 0f, false, false))) { continue; } ZDO zDO = nview.GetZDO(); if (zDO.GetBool("MUC_Ignore", false)) { continue; } if (zDO.GetOwner() == 0L && !flag) { SkippedNoOwner++; continue; } if (s_unresponsiveUntil.TryGetValue(zDO.m_uid, out var value)) { if (Time.time < value) { Unresponsive++; continue; } s_unresponsiveUntil.Remove(zDO.m_uid); } s_sortBuffer.Add((item2, num4)); } s_sortBuffer.Sort(((Container Chest, float Distance) a, (Container Chest, float Distance) b) => a.Distance.CompareTo(b.Distance)); int num5 = 17; foreach (var item3 in s_sortBuffer) { Container item = item3.Chest; s_candidates.Add(item); bool flag2 = IsOwnedForUse(item); if (!flag2 && item.m_nview.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1) { InUseByOthers++; } if (flag2 || MucHealth.CanUseNonOwnedChests) { s_usable.Add(item); num5 = num5 * 31 + ((Object)item).GetInstanceID() * (flag2 ? 3 : 7); } } if (num5 != s_setSignature) { s_setSignature = num5; s_contentVersion++; } } public static float GuaranteedLoadedRadius(SimulationDistance distance) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (((SimulationDistance)(ref distance)).Equals(s_radiusFor) && s_radius > 0f) { return s_radius; } int num = Mathf.Max(0, ((SimulationDistance)(ref distance)).NearSimulationDistance); float num2 = 64f; float num3 = (float)(num + 1) * num2; for (int i = -(num + 1); i <= num + 1; i++) { for (int j = -(num + 1); j <= num + 1; j++) { bool num4 = Mathf.Abs(i) > num || Mathf.Abs(j) > num; int num5; if (!((SimulationDistance)(ref distance)).IsClassic) { Vector2 val = new Vector2((float)i, (float)j); num5 = ((((Vector2)(ref val)).magnitude * num2 >= ((float)num + 0.5f) * num2) ? 1 : 0); } else { num5 = 0; } bool flag = (byte)num5 != 0; if (num4 || flag) { float num6 = (float)Mathf.Max(0, Mathf.Abs(i) - 1) * num2; float num7 = (float)Mathf.Max(0, Mathf.Abs(j) - 1) * num2; num3 = Mathf.Min(num3, Mathf.Sqrt(num6 * num6 + num7 * num7)); } } } s_radiusFor = distance; s_radius = Mathf.Max(num2, num3); return s_radius; } public static void Prune() { if (s_all.RemoveAll((Container c) => !Object.op_Implicit((Object)(object)c)) == 0) { return; } s_inventories.Clear(); foreach (Container item in s_all) { s_inventories.Add(item.GetInventory()); } List<Container> list = new List<Container>(); foreach (Container key in s_staticOk.Keys) { if (!Object.op_Implicit((Object)(object)key)) { list.Add(key); } } foreach (Container item2 in list) { s_staticOk.Remove(item2); } } private static bool IsStaticallyOk(Container chest) { if (s_staticOk.TryGetValue(chest, out var value)) { return value; } Piece componentInParent = ((Component)chest).GetComponentInParent<Piece>(); value = !Object.op_Implicit((Object)(object)((Component)chest).GetComponentInParent<Player>()) && !Object.op_Implicit((Object)(object)((Component)chest).GetComponentInParent<TombStone>()) && Object.op_Implicit((Object)(object)componentInParent) && componentInParent.IsPlacedByPlayer(); s_staticOk[chest] = value; return value; } private static bool IsAllowedKind(Container chest) { if (!Settings.CartsAndShips.Value && (Object.op_Implicit((Object)(object)chest.m_wagon) || Object.op_Implicit((Object)(object)((Component)chest).GetComponentInParent<Ship>()))) { return false; } if (Settings.Excluded.Count != 0) { return !Settings.Excluded.Contains(PrefabName(Object.op_Implicit((Object)(object)chest.m_nview) ? ((Component)chest.m_nview).gameObject : ((Component)chest).gameObject)); } return true; } public static string PrefabName(GameObject go) { return ((Object)go).name.Replace("(Clone)", "").Trim(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "s992.CraftFromStorage"; public const string PLUGIN_NAME = "CraftFromStorage"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }