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 Build From Chest v0.2.0
FrizzQOL.BuildFromChest.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; 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.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("FrizzQOL.BuildFromChest")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+35261d2f7e3d995926cc3286547d09a3bb9d958e")] [assembly: AssemblyProduct("FrizzQOL.BuildFromChest")] [assembly: AssemblyTitle("FrizzQOL.BuildFromChest")] [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 BuildFromChest { public sealed class ItemAmount { public string SharedName; public string PrefabName; public int Amount; } public sealed class PlannedTake { public int ChestId; public string SharedName; public string PrefabName; public int Amount; } public sealed class ChestCandidate { public int Id; public float Distance; public bool CanOpen; public Dictionary<string, int> Counts; } public static class ChestPay { public static int Missing(int cost, int inInventory) { int num = cost - inInventory; if (num <= 0) { return 0; } return num; } public static void AddCost(IList<ItemAmount> costs, string sharedName, string prefabName, int amount) { if (costs == null || amount <= 0 || string.IsNullOrEmpty(sharedName) || string.IsNullOrEmpty(prefabName)) { return; } for (int i = 0; i < costs.Count; i++) { ItemAmount itemAmount = costs[i]; if (itemAmount != null && itemAmount.SharedName == sharedName) { itemAmount.Amount += amount; return; } } costs.Add(new ItemAmount { SharedName = sharedName, PrefabName = prefabName, Amount = amount }); } public static List<ItemAmount> MissingItems(IList<ItemAmount> costs, IDictionary<string, int> have) { List<ItemAmount> list = new List<ItemAmount>(); if (costs == null) { return list; } for (int i = 0; i < costs.Count; i++) { ItemAmount itemAmount = costs[i]; if (itemAmount != null && itemAmount.Amount > 0 && !string.IsNullOrEmpty(itemAmount.SharedName) && !string.IsNullOrEmpty(itemAmount.PrefabName)) { int value = 0; have?.TryGetValue(itemAmount.SharedName, out value); int num = Missing(itemAmount.Amount, value); if (num > 0) { list.Add(new ItemAmount { SharedName = itemAmount.SharedName, PrefabName = itemAmount.PrefabName, Amount = num }); } } } return list; } public static bool NeedsPull(IList<ItemAmount> missing) { if (missing == null) { return false; } for (int i = 0; i < missing.Count; i++) { ItemAmount itemAmount = missing[i]; if (itemAmount != null && itemAmount.Amount > 0 && !string.IsNullOrEmpty(itemAmount.SharedName) && !string.IsNullOrEmpty(itemAmount.PrefabName)) { return true; } } return false; } public static List<PlannedTake> PlanPulls(IList<ChestCandidate> chests, IList<ItemAmount> missing, float radius) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); Dictionary<string, string> dictionary2 = new Dictionary<string, string>(); if (missing != null) { for (int i = 0; i < missing.Count; i++) { ItemAmount itemAmount = missing[i]; if (itemAmount != null && itemAmount.Amount > 0 && !string.IsNullOrEmpty(itemAmount.SharedName) && !string.IsNullOrEmpty(itemAmount.PrefabName)) { if (dictionary.ContainsKey(itemAmount.SharedName)) { dictionary[itemAmount.SharedName] += itemAmount.Amount; continue; } dictionary[itemAmount.SharedName] = itemAmount.Amount; dictionary2[itemAmount.SharedName] = itemAmount.PrefabName; } } } if (dictionary.Count == 0) { return new List<PlannedTake>(); } if (chests == null) { return null; } List<ChestCandidate> list = new List<ChestCandidate>(); for (int j = 0; j < chests.Count; j++) { ChestCandidate chestCandidate = chests[j]; if (chestCandidate != null && chestCandidate.CanOpen && IsInRange(chestCandidate.Distance, radius)) { list.Add(chestCandidate); } } list.Sort(CompareChests); List<PlannedTake> list2 = new List<PlannedTake>(); for (int k = 0; k < list.Count; k++) { ChestCandidate chestCandidate2 = list[k]; foreach (string item in new List<string>(dictionary.Keys)) { int num = dictionary[item]; if (num > 0) { int value = 0; if (chestCandidate2.Counts != null) { chestCandidate2.Counts.TryGetValue(item, out value); } int num2 = ((value < num) ? value : num); if (num2 > 0) { list2.Add(new PlannedTake { ChestId = chestCandidate2.Id, SharedName = item, PrefabName = dictionary2[item], Amount = num2 }); dictionary[item] = num - num2; } } } } foreach (int value2 in dictionary.Values) { if (value2 > 0) { return null; } } return list2; } public static bool IsInRange(float distance, float radius) { if (radius > 0f) { return distance <= radius; } return false; } public static bool IsChestPrefab(string name) { switch (name) { default: return name == "piece_chest_blackmetal"; case "piece_chest_wood": case "piece_chest": case "piece_chest_private": return true; } } public static bool IsHammer(string prefabName) { return prefabName == "Hammer"; } public static string PrefabKey(string name) { if (string.IsNullOrEmpty(name)) { return ""; } if (name.EndsWith("(Clone)")) { return name.Substring(0, name.Length - "(Clone)".Length); } return name; } public static int? PickNearest(IList<ChestCandidate> candidates, float radius) { int? result = null; float num = 0f; if (candidates == null) { return null; } for (int i = 0; i < candidates.Count; i++) { ChestCandidate chestCandidate = candidates[i]; if (chestCandidate != null && chestCandidate.CanOpen && IsInRange(chestCandidate.Distance, radius) && (!result.HasValue || chestCandidate.Distance < num || (chestCandidate.Distance == num && chestCandidate.Id < result.Value))) { result = chestCandidate.Id; num = chestCandidate.Distance; } } return result; } private static int CompareChests(ChestCandidate a, ChestCandidate b) { int num = a.Distance.CompareTo(b.Distance); if (num != 0) { return num; } return a.Id.CompareTo(b.Id); } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class PlacePatch { private sealed class HeldTake { public Container Chest; public readonly List<MovedStack> Items = new List<MovedStack>(); } private sealed class MovedStack { public string SharedName; public string PrefabName; public int TakenFromChest; public int GivenToPlayer; } private static readonly MethodInfo CheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null); private static readonly MethodInfo LoadMethod = AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo SaveMethod = AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null); private static readonly FieldInfo NViewField = AccessTools.Field(typeof(Container), "m_nview"); private static bool _busy; private static bool _loggedAccess; private static bool _loggedSave; private static readonly List<HeldTake> Held = new List<HeldTake>(); private static void Prefix(Player __instance, Piece piece) { if (!_busy && Held.Count <= 0) { Prepare(__instance, piece); } } internal static void Prepare(Player player, Piece piece) { if (_busy || Held.Count > 0 || (Object)(object)player == (Object)null || (Object)(object)piece == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !CanSaveChests()) { return; } ItemData rightItem = ((Humanoid)player).RightItem; if (!ChestPay.IsHammer((rightItem != null && (Object)(object)rightItem.m_dropPrefab != (Object)null) ? ChestPay.PrefabKey(((Object)rightItem.m_dropPrefab).name) : "") || IsFreeBuild(piece)) { return; } List<ItemAmount> costs = new List<ItemAmount>(); SumCosts(piece.m_resources, costs); Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } List<ItemAmount> missing = ChestPay.MissingItems(costs, CountHave(inventory, costs)); if (!ChestPay.NeedsPull(missing)) { return; } float radius = Plugin.CurrentRadius(); List<ChestCandidate> chests = new List<ChestCandidate>(); Dictionary<int, Container> dictionary = new Dictionary<int, Container>(); FindChests(player, radius, missing, chests, dictionary); List<PlannedTake> list = ChestPay.PlanPulls(chests, missing, radius); if (list == null || list.Count == 0) { return; } _busy = true; try { ApplyPlan(inventory, list, dictionary); } catch (Exception ex) { ReturnHeld(player); Plugin.LogWarning("Chest pull failed: " + ex.Message); } finally { _busy = false; dictionary.Clear(); } } internal static void Finish(Player player) { ReturnHeld(player); } private static bool IsFreeBuild(Piece piece) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)piece == (Object)null || (Object)(object)instance == (Object)null) { return false; } return instance.GetGlobalKey(piece.FreeBuildKey()); } private static void Postfix(Player __instance, bool __result) { try { if (__result) { Held.Clear(); } else { ReturnHeld(__instance); } } finally { _busy = false; } } private static void SumCosts(Requirement[] requirements, List<ItemAmount> costs) { if (requirements == null) { return; } foreach (Requirement val in requirements) { if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_resItem.m_itemData != null && val.m_resItem.m_itemData.m_shared != null && val.m_amount > 0 && !((Object)(object)((Component)val.m_resItem).gameObject == (Object)null)) { string name = val.m_resItem.m_itemData.m_shared.m_name; string prefabName = ChestPay.PrefabKey(((Object)((Component)val.m_resItem).gameObject).name); ChestPay.AddCost(costs, name, prefabName, val.m_amount); } } } private static Dictionary<string, int> CountHave(Inventory inventory, List<ItemAmount> costs) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); for (int i = 0; i < costs.Count; i++) { string sharedName = costs[i].SharedName; if (!dictionary.ContainsKey(sharedName)) { dictionary[sharedName] = inventory.CountItems(sharedName, -1, true); } } return dictionary; } private static void FindChests(Player player, float radius, List<ItemAmount> missing, List<ChestCandidate> chests, Dictionary<int, Container> byId) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if (radius <= 0f) { return; } Vector3 position = ((Component)player).transform.position; Collider[] array = Physics.OverlapSphere(position, radius + 2f, -1, (QueryTriggerInteraction)2); for (int i = 0; i < array.Length; i++) { Collider val = array[i]; array[i] = null; if ((Object)(object)val == (Object)null) { continue; } Container componentInParent = ((Component)val).GetComponentInParent<Container>(); if ((Object)(object)componentInParent == (Object)null) { continue; } int instanceID = ((Object)componentInParent).GetInstanceID(); if (byId.ContainsKey(instanceID) || !ChestPay.IsChestPrefab(ChestPay.PrefabKey(((Object)((Component)componentInParent).gameObject).name))) { continue; } bool flag = CanOpen(componentInParent, player); Inventory val2 = (flag ? componentInParent.GetInventory() : null); if (flag && val2 != null) { RefreshChest(componentInParent); val2 = componentInParent.GetInventory(); } Dictionary<string, int> dictionary = new Dictionary<string, int>(); if (val2 != null) { for (int j = 0; j < missing.Count; j++) { string sharedName = missing[j].SharedName; if (!dictionary.ContainsKey(sharedName)) { dictionary[sharedName] = val2.CountItems(sharedName, -1, true); } } } chests.Add(new ChestCandidate { Id = instanceID, Distance = Vector3.Distance(position, ((Component)componentInParent).transform.position), CanOpen = (flag && val2 != null), Counts = dictionary }); byId[instanceID] = componentInParent; } } private static void ApplyPlan(Inventory playerInventory, List<PlannedTake> plan, Dictionary<int, Container> chests) { List<HeldTake> list = new List<HeldTake>(); HeldTake heldTake = null; int num = 0; try { for (int i = 0; i < plan.Count; i++) { PlannedTake plannedTake = plan[i]; if (plannedTake == null || plannedTake.Amount <= 0) { continue; } if (heldTake == null || plannedTake.ChestId != num) { if (heldTake != null) { SaveChest(heldTake.Chest); list.Add(heldTake); } if (!chests.TryGetValue(plannedTake.ChestId, out var value) || (Object)(object)value == (Object)null || value.GetInventory() == null) { UndoMoved(playerInventory, list); return; } heldTake = new HeldTake { Chest = value }; num = plannedTake.ChestId; } int num2 = Take(heldTake.Chest.GetInventory(), plannedTake.SharedName, plannedTake.Amount); int num3 = ((num2 == plannedTake.Amount) ? Give(playerInventory, plannedTake.PrefabName, plannedTake.SharedName, plannedTake.Amount) : 0); heldTake.Items.Add(new MovedStack { SharedName = plannedTake.SharedName, PrefabName = plannedTake.PrefabName, TakenFromChest = num2, GivenToPlayer = num3 }); if (num2 != plannedTake.Amount || num3 != plannedTake.Amount) { SaveChest(heldTake.Chest); list.Add(heldTake); heldTake = null; UndoMoved(playerInventory, list); return; } } if (heldTake != null) { SaveChest(heldTake.Chest); list.Add(heldTake); } Held.AddRange(list); } catch (Exception ex) { if (heldTake != null && !list.Contains(heldTake)) { list.Add(heldTake); } UndoMoved(playerInventory, list); Held.Clear(); Plugin.LogWarning("Chest pull failed, restoring items: " + ex.Message); } } private static void ReturnHeld(Player player) { if (Held.Count == 0) { _busy = false; return; } List<HeldTake> moved = new List<HeldTake>(Held); Held.Clear(); _busy = false; if ((Object)(object)player == (Object)null || ((Humanoid)player).GetInventory() == null) { Plugin.LogWarning("Could not return pulled materials."); } else { UndoMoved(((Humanoid)player).GetInventory(), moved); } } private static void UndoMoved(Inventory playerInventory, List<HeldTake> moved) { for (int i = 0; i < moved.Count; i++) { HeldTake heldTake = moved[i]; if (heldTake == null || (Object)(object)heldTake.Chest == (Object)null || heldTake.Chest.GetInventory() == null || playerInventory == null) { Plugin.LogWarning("Could not return pulled materials."); continue; } Inventory inventory = heldTake.Chest.GetInventory(); for (int j = 0; j < heldTake.Items.Count; j++) { MovedStack movedStack = heldTake.Items[j]; int num = Take(playerInventory, movedStack.SharedName, movedStack.GivenToPlayer); int num2 = movedStack.TakenFromChest - movedStack.GivenToPlayer + num; int num3 = Give(inventory, movedStack.PrefabName, movedStack.SharedName, num2); int num4 = num2 - num3; if (num4 > 0) { Give(playerInventory, movedStack.PrefabName, movedStack.SharedName, num4); } if (num != movedStack.GivenToPlayer || num4 > 0) { Plugin.LogWarning("Pulled materials could not all return to the chest."); } } SaveChest(heldTake.Chest); } } private static bool CanOpen(Container container, Player player) { if (CheckAccessMethod == null) { if (!_loggedAccess) { _loggedAccess = true; Plugin.LogWarning("Container.CheckAccess is missing. Chest pull is off."); } return false; } object obj = CheckAccessMethod.Invoke(container, new object[1] { player.GetPlayerID() }); if (!(obj is bool) || !(bool)obj || container.IsInUse()) { return false; } if (NViewField == null) { return false; } object? value = NViewField.GetValue(container); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if (val == null || (Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner()) { return false; } return true; } private static bool CanSaveChests() { if (SaveMethod != null) { return true; } if (!_loggedSave) { _loggedSave = true; Plugin.LogWarning("Container.Save is missing. Chest pull is off."); } return false; } private static void RefreshChest(Container container) { if ((Object)(object)container != (Object)null && LoadMethod != null) { LoadMethod.Invoke(container, null); } } private static void SaveChest(Container container) { if ((Object)(object)container != (Object)null && SaveMethod != null) { SaveMethod.Invoke(container, null); } } private static int Take(Inventory inventory, string sharedName, int amount) { if (inventory == null || amount <= 0 || string.IsNullOrEmpty(sharedName)) { return 0; } int num = inventory.CountItems(sharedName, -1, true); inventory.RemoveItem(sharedName, amount, -1, true); return num - inventory.CountItems(sharedName, -1, true); } private static int Give(Inventory inventory, string prefabName, string sharedName, int amount) { if (inventory == null || amount <= 0 || string.IsNullOrEmpty(prefabName) || string.IsNullOrEmpty(sharedName)) { return 0; } int num = inventory.CountItems(sharedName, -1, true); inventory.AddItem(prefabName, amount, 1, 0, 0L, "", false, false); return inventory.CountItems(sharedName, -1, true) - num; } } public static class PlacementClick { [HarmonyPatch(typeof(Player), "UpdatePlacement")] private static class UpdatePlacementPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); MethodInfo methodInfo = AccessTools.Method(typeof(PlacementClick), "PullBeforeRequirements", (Type[])null, (Type[])null); for (int i = 3; i < list.Count; i++) { if (IsPieceRequirementCheck(list[i])) { CodeInstruction val = list[i - 2]; if (!(list[i - 3].opcode != OpCodes.Ldarg_0) && IsLoadLocal(val.opcode) && !(list[i - 1].opcode != OpCodes.Ldc_I4_0)) { int num = i - 3; list.Insert(num, new CodeInstruction(OpCodes.Ldarg_0, (object)null)); list.Insert(num + 1, new CodeInstruction(val.opcode, val.operand)); list.Insert(num + 2, new CodeInstruction(OpCodes.Call, (object)methodInfo)); return list; } } } Plugin.LogWarning("UpdatePlacement does not check requirements before place. A short hammer click will not pull."); return list; } private static void Postfix(Player __instance) { PlacePatch.Finish(__instance); } private static Exception Finalizer(Player __instance, Exception __exception) { PlacePatch.Finish(__instance); return __exception; } private static bool IsPieceRequirementCheck(CodeInstruction instruction) { if (instruction.opcode != OpCodes.Call && instruction.opcode != OpCodes.Callvirt) { return false; } MethodInfo methodInfo = instruction.operand as MethodInfo; if (methodInfo == null || methodInfo.Name != "HaveRequirements") { return false; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 2) { return parameters[0].ParameterType == typeof(Piece); } return false; } private static bool IsLoadLocal(OpCode opcode) { if (!(opcode == OpCodes.Ldloc) && !(opcode == OpCodes.Ldloc_S) && !(opcode == OpCodes.Ldloc_0) && !(opcode == OpCodes.Ldloc_1) && !(opcode == OpCodes.Ldloc_2)) { return opcode == OpCodes.Ldloc_3; } return true; } } public static void PullBeforeRequirements(Player player, Piece piece) { PlacePatch.Prepare(player, piece); } } [BepInPlugin("com.frizzqol.buildfromchest", "FrizzQOL Build From Chest", "0.2.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.frizzqol.buildfromchest"; public const string PluginName = "FrizzQOL Build From Chest"; public const string PluginVersion = "0.2.0"; internal static ConfigEntry<float> ChestRadius; private Harmony _harmony; internal static Plugin Instance { get; private set; } private void Awake() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown Instance = this; ChestRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ChestRadius", 10f, "Meters from the player to the nearest chest. 0 or less disables the pull."); _harmony = new Harmony("com.frizzqol.buildfromchest"); try { _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"FrizzQOL Build From Chest 0.2.0 loaded"); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to patch placement: " + ex.Message)); } } internal static float CurrentRadius() { if (ChestRadius == null) { return 10f; } return ChestRadius.Value; } internal static void LogWarning(string message) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)message); } } } }