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 GorilaChestMod v2.5.0
GorilaChestMod.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("GorilaChestMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.5.0.0")] [assembly: AssemblyInformationalVersion("2.5.0+4d8f3b3c924041283d69da11acaa21df6c81c0ab")] [assembly: AssemblyProduct("GorilaChestMod")] [assembly: AssemblyTitle("GorilaChestMod")] [assembly: AssemblyVersion("2.5.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GorilaChestMod { internal static class BuildContext { private static int _depth; internal static bool Active => _depth > 0; internal static void Enter() { _depth++; } internal static void Exit() { if (_depth > 0) { _depth--; } } } internal sealed class CallSwap { internal readonly MethodInfo From; internal readonly MethodInfo To; internal CallSwap(MethodInfo from, MethodInfo to) { From = from; To = to; } } internal static class CallRedirector { internal static IEnumerable<CodeInstruction> Apply(IEnumerable<CodeInstruction> instructions, string patchedMethod, params CallSwap[] swaps) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); int[] array = new int[swaps.Length]; foreach (CodeInstruction item in list) { MethodInfo methodInfo = item.operand as MethodInfo; if (methodInfo == null) { continue; } for (int i = 0; i < swaps.Length; i++) { if (SameMember(methodInfo, swaps[i].From)) { item.opcode = OpCodes.Call; item.operand = swaps[i].To; array[i]++; break; } } } for (int j = 0; j < swaps.Length; j++) { if (array[j] == 0) { GorilaChestModPlugin.Log.LogError((object)(patchedMethod + ": no call to " + swaps[j].From.DeclaringType?.Name + "." + swaps[j].From.Name + " was found. The game code changed, part of the mod is inactive.")); } else if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"{patchedMethod}: redirected {array[j]} call(s) to {swaps[j].From.Name}."); } } return list; } internal static IEnumerable<CodeInstruction> ReplaceFieldReads(IEnumerable<CodeInstruction> instructions, string patchedMethod, FieldInfo field, MethodInfo replacement, Func<IEnumerable<CodeInstruction>> extraArgs, out int replaced) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); replaced = 0; for (int i = 0; i < list.Count; i++) { if (!(list[i].opcode != OpCodes.Ldfld) && list[i].operand is FieldInfo a && SameMember(a, field)) { List<CodeInstruction> list2 = new List<CodeInstruction>(extraArgs()); if (list2.Count != 0) { list2[0].labels.AddRange(list[i].labels); list[i].labels.Clear(); list2[0].blocks.AddRange(list[i].blocks); list[i].blocks.Clear(); list[i].opcode = OpCodes.Call; list[i].operand = replacement; list.InsertRange(i, list2); i += list2.Count; replaced++; } } } if (replaced > 0 && ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"{patchedMethod}: redirected {replaced} read(s) of {field.DeclaringType?.Name}.{field.Name}."); } return list; } internal static bool SameMember(MemberInfo a, MemberInfo b) { if ((object)a == b) { return true; } if (a != null && b != null && a.Module == b.Module) { return a.MetadataToken == b.MetadataToken; } return false; } } internal static class ChestStacks { internal static int LoadFloor; private static readonly MethodInfo ChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", new Type[2] { typeof(bool), typeof(bool) }, (Type[])null); internal static int MaxStackFor(SharedData shared, Inventory inventory) { if (shared == null) { return 1; } int maxStackSize = shared.m_maxStackSize; if (maxStackSize <= 1) { return maxStackSize; } if (!StackSettings.Enabled) { return maxStackSize; } if (!ContainerTracker.IsContainerInventory(inventory)) { return maxStackSize; } return Mathf.Max(maxStackSize, Mathf.Max(StackSettings.StackSize, LoadFloor)); } internal static int MaxStackForItem(SharedData shared, ItemData item) { return MaxStackFor(shared, InventoryOf(item)); } internal static Inventory InventoryOf(ItemData item) { if (item == null) { return null; } InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.ContainerGrid != (Object)null) { Inventory inventory = instance.ContainerGrid.GetInventory(); if (inventory != null && inventory.ContainsItem(item)) { return inventory; } } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { Inventory inventory2 = ((Humanoid)localPlayer).GetInventory(); if (inventory2 != null && inventory2.ContainsItem(item)) { return inventory2; } } return null; } internal static void NotifyChanged(Inventory inventory) { if (inventory != null) { if (ChangedMethod == null) { GorilaChestModPlugin.Log.LogWarning((object)"Inventory.Changed is gone, a chest may not save right away."); return; } ChangedMethod.Invoke(inventory, new object[2] { false, false }); } } internal static bool AddInChunks(Inventory destination, ItemData item, int chunkSize) { while (item.m_stack > 0) { int num = Mathf.Min(chunkSize, item.m_stack); int num2 = MoveChunk(destination, item, num); item.m_stack -= num2; if (num2 < num) { return false; } } return true; } internal static int MoveChunk(Inventory destination, ItemData item, int chunk) { if (chunk <= 0) { return 0; } ItemData val = item.Clone(); val.m_stack = chunk; if (destination.AddItem(val)) { return chunk; } return Mathf.Clamp(chunk - val.m_stack, 0, chunk); } } internal static class ConfigSync { private const string RequestRpc = "GorilaChestMod_ConfigRequest"; private const string ConfigRpc = "GorilaChestMod_Config"; private static bool _registered; internal static bool ServerEnforced { get; private set; } internal static bool ServerStacksEnabled { get; private set; } internal static int ServerStackSize { get; private set; } internal static int? ServerReturnPercent { get; private set; } internal static void Register() { if (!_registered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("GorilaChestMod_ConfigRequest", (Action<long>)OnConfigRequested); ZRoutedRpc.instance.Register<ZPackage>("GorilaChestMod_Config", (Action<long, ZPackage>)OnConfigReceived); _registered = true; } } internal static void Reset() { _registered = false; ServerEnforced = false; ServerStacksEnabled = false; ServerStackSize = 0; ServerReturnPercent = null; } internal static void RequestFromServer() { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC("GorilaChestMod_ConfigRequest", Array.Empty<object>()); if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)"Asked the server for its chest stack settings."); } } } private static void OnConfigRequested(long sender) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZPackage val = new ZPackage(); val.Write(ModConfig.ChestStacksEnabled.Value); val.Write(ModConfig.ChestStackSize.Value); val.Write(ModConfig.ReturnPercent.Value); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "GorilaChestMod_Config", new object[1] { val }); GorilaChestModPlugin.Log.LogInfo((object)($"Sent settings to peer {sender}: chest stacks enabled {ModConfig.ChestStacksEnabled.Value}, " + $"size {ModConfig.ChestStackSize.Value}, deconstruct returns {ModConfig.ReturnPercent.Value}%.")); } } private static void OnConfigReceived(long sender, ZPackage package) { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && package != null) { ServerStacksEnabled = package.ReadBool(); ServerStackSize = package.ReadInt(); ServerEnforced = true; ServerReturnPercent = ((package.GetPos() < package.Size()) ? new int?(package.ReadInt()) : ((int?)null)); GorilaChestModPlugin.Log.LogInfo((object)($"Server set chest stacks to enabled {ServerStacksEnabled}, size {ServerStackSize}, " + "deconstruct returns " + (ServerReturnPercent.HasValue ? (ServerReturnPercent + "%") : "not sent, keeping yours") + ". Your local values for those are ignored while connected, and your config file is left untouched.")); } } } internal static class StackSettings { internal static bool Enabled { get { if (!ConfigSync.ServerEnforced) { if (ModConfig.ChestStacksEnabled != null) { return ModConfig.ChestStacksEnabled.Value; } return false; } return ConfigSync.ServerStacksEnabled; } } internal static int StackSize { get { if (!ConfigSync.ServerEnforced) { if (ModConfig.ChestStackSize == null) { return 1; } return ModConfig.ChestStackSize.Value; } return ConfigSync.ServerStackSize; } } internal static string DescribeStacks() { string text = (ConfigSync.ServerEnforced ? " (from the server)" : ""); if (!Enabled) { return "off" + text; } return StackSize + text; } } internal static class DeconstructSettings { internal const int MinPercent = 50; internal const int MaxPercent = 100; internal static int Percent { get { int num = ConfigSync.ServerReturnPercent ?? ((ModConfig.ReturnPercent != null) ? ModConfig.ReturnPercent.Value : 100); if (num >= 50) { if (num <= 100) { return num; } return 100; } return 50; } } internal static string Describe() { if (!ModConfig.DeconstructEnabled.Value) { return "off"; } return Percent + "%" + (ConfigSync.ServerReturnPercent.HasValue ? " (from the server)" : ""); } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakePatch { private static void Postfix() { ConfigSync.Reset(); ConfigSync.Register(); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class ZNetPeerInfoPatch { private static void Postfix() { ConfigSync.Register(); ConfigSync.RequestFromServer(); } } [HarmonyPatch(typeof(ZNet), "Shutdown")] internal static class ZNetShutdownPatch { private static void Postfix() { ConfigSync.Reset(); ContainerTracker.Clear(); } } internal static class ContainerTracker { private const float NearbyCacheSeconds = 0.25f; private const float PruneSeconds = 5f; private static readonly HashSet<Container> Known = new HashSet<Container>(); private static readonly HashSet<Inventory> ContainerInventories = new HashSet<Inventory>(); private static readonly List<Container> Nearby = new List<Container>(); private static float _nextRefresh; private static float _nextPrune; private static Vector3 _origin; internal static void Register(Container container) { if (!((Object)(object)container == (Object)null)) { Known.Add(container); Inventory inventory = container.GetInventory(); if (inventory != null) { ContainerInventories.Add(inventory); } } } internal static void Clear() { Known.Clear(); ContainerInventories.Clear(); Nearby.Clear(); _nextRefresh = 0f; _nextPrune = 0f; } internal static bool IsContainerInventory(Inventory inventory) { if (inventory == null) { return false; } Prune(); return ContainerInventories.Contains(inventory); } internal static List<Container> GetNearby() { if (Time.time < _nextRefresh) { return Nearby; } _nextRefresh = Time.time + 0.25f; Refresh(); return Nearby; } private static void Prune() { if (Time.time < _nextPrune) { return; } _nextPrune = Time.time + 5f; int count = Known.Count; Known.RemoveWhere((Container container) => (Object)(object)container == (Object)null); if (Known.Count == count) { return; } ContainerInventories.Clear(); foreach (Container item in Known) { Inventory inventory = item.GetInventory(); if (inventory != null) { ContainerInventories.Add(inventory); } } } private static void Refresh() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) Nearby.Clear(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)Game.instance == (Object)null) { return; } Prune(); _origin = ((Component)localPlayer).transform.position; long playerID = Game.instance.GetPlayerProfile().GetPlayerID(); float value = ModConfig.Range.Value; float num = value * value; foreach (Container item in Known) { if (!((Object)(object)item == (Object)null)) { Vector3 val = ((Component)item).transform.position - _origin; if (!(((Vector3)(ref val)).sqrMagnitude > num) && IsUsable(item, playerID)) { Nearby.Add(item); } } } Nearby.Sort(CompareByDistance); } private static int CompareByDistance(Container a, Container b) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)a).transform.position - _origin; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; val = ((Component)b).transform.position - _origin; float sqrMagnitude2 = ((Vector3)(ref val)).sqrMagnitude; return sqrMagnitude.CompareTo(sqrMagnitude2); } private static bool IsUsable(Container container, long playerId) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (container.GetInventory() == null) { return false; } if (!ModConfig.IncludeVehicleContainers.Value && (Object)(object)container.m_rootObjectOverride != (Object)null) { return false; } ZNetView netView = GetNetView(container); if ((Object)(object)netView == (Object)null || !netView.IsValid()) { return false; } if (!HasPrivacyAccess(container, playerId)) { return false; } if (container.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false)) { return false; } if (!netView.IsOwner() && netView.GetZDO() != null && netView.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1) { return false; } return true; } private static bool HasPrivacyAccess(Container container, long playerId) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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_000c: Invalid comparison between Unknown and I4 PrivacySetting privacy = container.m_privacy; if ((int)privacy != 0) { if ((int)privacy == 2) { return true; } return false; } Piece component = ((Component)container).GetComponent<Piece>(); if ((Object)(object)component != (Object)null) { return component.GetCreator() == playerId; } return false; } internal static ZNetView GetNetView(Container container) { if (!((Object)(object)container.m_rootObjectOverride != (Object)null)) { return ((Component)container).GetComponent<ZNetView>(); } return ((Component)container.m_rootObjectOverride).GetComponent<ZNetView>(); } internal static bool TryTakeOwnership(Container container) { ZNetView netView = GetNetView(container); if ((Object)(object)netView == (Object)null || !netView.IsValid()) { return false; } if (!netView.IsOwner()) { netView.ClaimOwnership(); } return netView.IsOwner(); } } internal static class Swaps { private static readonly MethodInfo InventoryCountItems = AccessTools.Method(typeof(Inventory), "CountItems", new Type[3] { typeof(string), typeof(int), typeof(bool) }, (Type[])null); private static readonly MethodInfo InventoryHaveItem = AccessTools.Method(typeof(Inventory), "HaveItem", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); private static readonly MethodInfo InventoryGetItem = AccessTools.Method(typeof(Inventory), "GetItem", new Type[3] { typeof(string), typeof(int), typeof(bool) }, (Type[])null); private static readonly MethodInfo InventoryRemoveItem = AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[4] { typeof(string), typeof(int), typeof(int), typeof(bool) }, (Type[])null); internal static readonly CallSwap CountItems = new CallSwap(InventoryCountItems, AccessTools.Method(typeof(InventoryBridge), "CountItems", (Type[])null, (Type[])null)); internal static readonly CallSwap HaveItem = new CallSwap(InventoryHaveItem, AccessTools.Method(typeof(InventoryBridge), "HaveItem", (Type[])null, (Type[])null)); internal static readonly CallSwap GetItem = new CallSwap(InventoryGetItem, AccessTools.Method(typeof(InventoryBridge), "GetItem", (Type[])null, (Type[])null)); internal static readonly CallSwap RemoveItem = new CallSwap(InventoryRemoveItem, AccessTools.Method(typeof(InventoryBridge), "RemoveItem", (Type[])null, (Type[])null)); } [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakePatch { private static void Postfix(Container __instance) { ContainerTracker.Register(__instance); } } [HarmonyPatch(typeof(Player), "HaveRequirementItems", new Type[] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) })] internal static class PlayerHaveRequirementItemsPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CallRedirector.Apply(instructions, "Player.HaveRequirementItems", Swaps.CountItems); } } [HarmonyPatch] internal static class PlayerGetFirstRequiredItemPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "GetFirstRequiredItem", (Type[])null, (Type[])null); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CallRedirector.Apply(instructions, "Player.GetFirstRequiredItem", Swaps.CountItems, Swaps.GetItem); } } [HarmonyPatch(typeof(Player), "ConsumeResources")] internal static class PlayerConsumeResourcesPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CallRedirector.Apply(instructions, "Player.ConsumeResources", Swaps.RemoveItem); } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting", new Type[] { typeof(Player) })] internal static class InventoryGuiDoCraftingPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CallRedirector.Apply(instructions, "InventoryGui.DoCrafting", Swaps.RemoveItem); } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement")] internal static class InventoryGuiSetupRequirementPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CallRedirector.Apply(instructions, "InventoryGui.SetupRequirement", Swaps.CountItems); } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] internal static class PlayerHaveRequirementsPiecePatch { private static void Prefix() { BuildContext.Enter(); } private static void Finalizer() { BuildContext.Exit(); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CallRedirector.Apply(instructions, "Player.HaveRequirements(Piece)", Swaps.HaveItem, Swaps.CountItems); } } [HarmonyPatch(typeof(Player), "UpdatePlacement", new Type[] { typeof(bool), typeof(float) })] internal static class PlayerUpdatePlacementPatch { private static void Prefix() { BuildContext.Enter(); } private static void Finalizer() { BuildContext.Exit(); } } [HarmonyPatch(typeof(Hud), "SetupPieceInfo", new Type[] { typeof(Piece) })] internal static class HudSetupPieceInfoPatch { private static void Prefix() { BuildContext.Enter(); } private static void Finalizer() { BuildContext.Exit(); } } internal static class DeconstructPanel { private struct Entry { internal Recipe Recipe; internal ItemData Item; internal bool CanDeconstruct; internal string Name; } internal const string ButtonLabel = "Deconstruct"; private static ItemData _pendingItem; private static Recipe _pendingRecipe; internal static void BuildList(InventoryGui gui) { Player localPlayer = Player.m_localPlayer; IList list = GuiAccess.AvailableRecipes(gui); foreach (object item in list) { Object.Destroy((Object)(object)GuiAccess.ElementOf(item)); } list.Clear(); if ((Object)(object)localPlayer == (Object)null) { return; } List<Entry> list2 = new List<Entry>(); foreach (ItemData allItem in ((Humanoid)localPlayer).GetInventory().GetAllItems()) { Recipe val = Salvage.FindRecipe(localPlayer, allItem); if (!((Object)(object)val == (Object)null)) { list2.Add(new Entry { Recipe = val, Item = allItem, CanDeconstruct = (Salvage.Refusal(localPlayer, allItem, val) == null), Name = Localization.instance.Localize(allItem.m_shared.m_name) }); } } list2.Sort(delegate(Entry a, Entry b) { int num2 = b.CanDeconstruct.CompareTo(a.CanDeconstruct); if (num2 == 0) { num2 = string.Compare(a.Name, b.Name, StringComparison.CurrentCulture); } if (num2 == 0) { num2 = b.Item.m_quality.CompareTo(a.Item.m_quality); } if (num2 == 0) { num2 = a.Item.m_gridPos.y.CompareTo(b.Item.m_gridPos.y); } if (num2 == 0) { num2 = a.Item.m_gridPos.x.CompareTo(b.Item.m_gridPos.x); } return num2; }); foreach (Entry item2 in list2) { GuiAccess.AddRecipeToList(gui, localPlayer, item2.Recipe, item2.Item, item2.CanDeconstruct); } float num = Mathf.Max(GuiAccess.RecipeListBaseSize(gui), (float)list.Count * gui.m_recipeListSpace); gui.m_recipeListRoot.SetSizeWithCurrentAnchors((Axis)1, num); } internal static void UpdateDetails(InventoryGui gui, Player player) { GuiAccess.Selected(gui, out var recipe, out var item); if (!((Object)(object)recipe == (Object)null) && item != null) { string text = Salvage.Refusal(player, item, recipe); int percent = DeconstructSettings.Percent; string text2 = Localization.instance.Localize(item.m_shared.m_name); gui.m_recipeName.text = ((recipe.m_amount > 1) ? $"{text2} x{recipe.m_amount}" : text2); gui.m_recipeDecription.text = Localization.instance.Localize(ItemData.GetTooltip(item, item.m_quality, false, (float)Game.m_worldLevel, -1, false)); ((Component)gui.m_itemCraftType).gameObject.SetActive(true); gui.m_itemCraftType.text = text ?? $"Returns {percent}% of the materials"; ((Component)gui.m_variantButton).gameObject.SetActive(false); ((Component)gui.m_minStationLevelIcon).gameObject.SetActive(false); ShowMaterials(gui, Salvage.Returns(recipe, item.m_quality, percent)); ((Selectable)gui.m_craftButton).interactable = text == null; TMP_Text componentInChildren = ((Component)gui.m_craftButton).GetComponentInChildren<TMP_Text>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = "Deconstruct"; } UITooltip component = ((Component)gui.m_craftButton).GetComponent<UITooltip>(); if ((Object)(object)component != (Object)null) { component.m_text = text ?? ""; } } } internal static void Begin(InventoryGui gui) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; GuiAccess.Selected(gui, out var recipe, out var item); if ((Object)(object)localPlayer == (Object)null || (Object)(object)recipe == (Object)null || item == null) { return; } string text = Salvage.Refusal(localPlayer, item, recipe); if (text != null) { ((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false); return; } GuiAccess.FocusCraftingPanel(gui); _pendingItem = item; _pendingRecipe = recipe; GuiAccess.StartTimer(gui, recipe); if ((Object)(object)gui.CraftingVibration != (Object)null) { Object.Instantiate<GameObject>(gui.CraftingVibration); } CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { currentCraftingStation.m_craftItemEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } else { gui.m_craftItemEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } } internal static void ClearPending() { _pendingItem = null; _pendingRecipe = null; } internal static bool Finish(InventoryGui gui, Player player) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (_pendingItem == null) { return false; } ItemData pendingItem = _pendingItem; Recipe pendingRecipe = _pendingRecipe; ClearPending(); if (Salvage.Deconstruct(player, pendingItem, pendingRecipe)) { CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { currentCraftingStation.m_craftItemDoneEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } else { gui.m_craftItemDoneEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } } GuiAccess.UpdateCraftingPanel(gui); return true; } private static void ShowMaterials(InventoryGui gui, List<Refund> materials) { //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) GameObject[] recipeRequirementList = gui.m_recipeRequirementList; if (recipeRequirementList == null || recipeRequirementList.Length == 0) { return; } int num = 0; if (materials.Count > recipeRequirementList.Length) { int num2 = Mathf.CeilToInt((float)materials.Count / (float)recipeRequirementList.Length); num = (int)Time.fixedTime % num2 * recipeRequirementList.Length; } for (int i = 0; i < recipeRequirementList.Length; i++) { Transform transform = recipeRequirementList[i].transform; int num3 = num + i; if (num3 >= materials.Count) { InventoryGui.HideRequirement(transform); continue; } Refund refund = materials[num3]; ItemData itemData = refund.Item.m_itemData; string text = Localization.instance.Localize(itemData.m_shared.m_name); Image component = ((Component)transform.Find("res_icon")).GetComponent<Image>(); TMP_Text component2 = ((Component)transform.Find("res_name")).GetComponent<TMP_Text>(); TMP_Text component3 = ((Component)transform.Find("res_amount")).GetComponent<TMP_Text>(); ((Component)component).gameObject.SetActive(true); ((Component)component2).gameObject.SetActive(true); ((Component)component3).gameObject.SetActive(true); component.sprite = itemData.GetIcon(); ((Graphic)component).color = Color.white; component2.text = text; component3.text = refund.Amount.ToString(); ((Graphic)component3).color = Color.white; UITooltip component4 = ((Component)transform).GetComponent<UITooltip>(); if ((Object)(object)component4 != (Object)null) { component4.m_text = text; } } } } [HarmonyPatch(typeof(InventoryGui), "Awake")] internal static class InventoryGuiAwakePatch { private static void Postfix(InventoryGui __instance) { DeconstructTab.Create(__instance); } } [HarmonyPatch(typeof(InventoryGui), "UpdateCraftingPanel", new Type[] { typeof(bool) })] internal static class InventoryGuiUpdateCraftingPanelPatch { private static void Prefix(InventoryGui __instance) { DeconstructTab.BeforePanelUpdate(__instance, Player.m_localPlayer); } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })] internal static class InventoryGuiUpdateRecipeListPatch { private static bool Prefix(InventoryGui __instance) { if (!DeconstructTab.Active) { return true; } DeconstructPanel.BuildList(__instance); return false; } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe", new Type[] { typeof(Player), typeof(float) })] internal static class InventoryGuiUpdateRecipePatch { private static void Postfix(InventoryGui __instance, Player player) { if (DeconstructTab.Active) { DeconstructPanel.UpdateDetails(__instance, player); } } } [HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")] internal static class InventoryGuiOnCraftPressedPatch { private static bool Prefix(InventoryGui __instance) { if (!DeconstructTab.Active) { DeconstructPanel.ClearPending(); return true; } DeconstructPanel.Begin(__instance); return false; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting", new Type[] { typeof(Player) })] internal static class InventoryGuiDoCraftingDeconstructPatch { private static bool Prefix(InventoryGui __instance, Player player) { return !DeconstructPanel.Finish(__instance, player); } } [HarmonyPatch(typeof(InventoryGui), "OnTabCraftPressed")] internal static class InventoryGuiOnTabCraftPressedPatch { private static void Prefix() { DeconstructTab.Deselect(); } } [HarmonyPatch(typeof(InventoryGui), "OnTabUpgradePressed")] internal static class InventoryGuiOnTabUpgradePressedPatch { private static void Prefix() { DeconstructTab.Deselect(); } } internal static class DeconstructTab { internal const string Label = "Deconstruct"; private static Button _button; internal static bool Active { get; private set; } internal static void Create(InventoryGui gui) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected O, but got Unknown //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) if (!GuiAccess.Ready || !ModConfig.DeconstructEnabled.Value) { return; } Button tabUpgrade = gui.m_tabUpgrade; Button tabCraft = gui.m_tabCraft; if ((Object)(object)tabUpgrade == (Object)null || (Object)(object)tabCraft == (Object)null) { GorilaChestModPlugin.Log.LogError((object)"InventoryGui has no Craft or Upgrade tab to copy, the Deconstruct tab stays off."); return; } GameObject val = Object.Instantiate<GameObject>(((Component)tabUpgrade).gameObject, ((Component)tabUpgrade).transform.parent, false); ((Object)val).name = "TabDeconstruct"; UIGamePad[] componentsInChildren = val.GetComponentsInChildren<UIGamePad>(true); foreach (UIGamePad val2 in componentsInChildren) { if ((Object)(object)val2.m_hint != (Object)null) { Object.DestroyImmediate((Object)(object)val2.m_hint); } Object.DestroyImmediate((Object)(object)val2); } Localize[] componentsInChildren2 = val.GetComponentsInChildren<Localize>(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren2[i]); } UITooltip[] componentsInChildren3 = val.GetComponentsInChildren<UITooltip>(true); for (int i = 0; i < componentsInChildren3.Length; i++) { componentsInChildren3[i].m_text = ""; } RectTransform val3 = (RectTransform)((Component)tabUpgrade).transform; RectTransform val4 = (RectTransform)((Component)tabCraft).transform; RectTransform val5 = (RectTransform)val.transform; Vector2 val6 = val3.anchoredPosition - val4.anchoredPosition; Rect rect; if (((Vector2)(ref val6)).sqrMagnitude < 1f) { rect = val3.rect; ((Vector2)(ref val6))..ctor(((Rect)(ref rect)).width, 0f); } val5.anchoredPosition = val3.anchoredPosition + val6; val.transform.SetSiblingIndex(((Component)tabUpgrade).transform.GetSiblingIndex() + 1); _button = val.GetComponent<Button>(); _button.onClick = new ButtonClickedEvent(); ((UnityEvent)_button.onClick).AddListener((UnityAction)delegate { Select(gui); }); SetCaption(); Active = false; ((Selectable)_button).interactable = true; val.SetActive(false); if (ModConfig.Verbose.Value) { ManualLogSource log = GorilaChestModPlugin.Log; object arg = val4.anchoredPosition; rect = val4.rect; string text = $"Tabs: craft at {arg} size {((Rect)(ref rect)).size}, "; object arg2 = val3.anchoredPosition; rect = val3.rect; string text2 = $"upgrade at {arg2} size {((Rect)(ref rect)).size}, "; object arg3 = val5.anchoredPosition; Transform parent = ((Component)tabUpgrade).transform.parent; log.LogInfo((object)(text + text2 + $"deconstruct at {arg3}, parent {((parent != null) ? ((Object)parent).name : null)}.")); } } internal static void Destroy() { if ((Object)(object)_button != (Object)null) { Object.Destroy((Object)(object)((Component)_button).gameObject); } _button = null; Active = false; } internal static bool Available(Player player) { if (!ModConfig.DeconstructEnabled.Value || (Object)(object)player == (Object)null || (Object)(object)_button == (Object)null) { return false; } CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null && currentCraftingStation.m_hasCraftTab) { return !currentCraftingStation.m_upgrader; } return false; } internal static void BeforePanelUpdate(InventoryGui gui, Player player) { if (!((Object)(object)_button == (Object)null)) { bool flag = Available(player); if (!flag && Active) { Active = false; ((Selectable)gui.m_tabCraft).interactable = false; ((Selectable)gui.m_tabUpgrade).interactable = true; } ((Component)_button).gameObject.SetActive(flag); ((Selectable)_button).interactable = !Active; if (Active) { ((Selectable)gui.m_tabCraft).interactable = true; ((Selectable)gui.m_tabUpgrade).interactable = true; } SetCaption(); } } internal static void Deselect() { Active = false; if ((Object)(object)_button != (Object)null) { ((Selectable)_button).interactable = true; } } private static void Select(InventoryGui gui) { if (Available(Player.m_localPlayer)) { GuiAccess.FocusCraftingPanel(gui); Active = true; ((Selectable)gui.m_tabCraft).interactable = true; ((Selectable)gui.m_tabUpgrade).interactable = true; ((Selectable)_button).interactable = false; GuiAccess.UpdateCraftingPanel(gui); } } private static void SetCaption() { TMP_Text[] componentsInChildren = ((Component)_button).GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val in componentsInChildren) { if (val.text != "Deconstruct") { val.text = "Deconstruct"; } } } } internal static class GuiAccess { private static FieldInfo _availableRecipes; private static FieldInfo _selectedRecipe; private static FieldInfo _craftTimer; private static FieldInfo _craftRecipe; private static FieldInfo _multiCrafting; private static FieldInfo _recipeListBaseSize; private static PropertyInfo _pairRecipe; private static PropertyInfo _pairItem; private static PropertyInfo _pairElement; private static MethodInfo _addRecipeToList; private static MethodInfo _updateCraftingPanel; private static MethodInfo _setActiveGroup; internal const int CraftingGroup = 3; internal static bool Ready { get; private set; } internal static bool Resolve() { Type typeFromHandle = typeof(InventoryGui); Type type = AccessTools.Inner(typeFromHandle, "RecipeDataPair"); _availableRecipes = AccessTools.Field(typeFromHandle, "m_availableRecipes"); _selectedRecipe = AccessTools.Field(typeFromHandle, "m_selectedRecipe"); _craftTimer = AccessTools.Field(typeFromHandle, "m_craftTimer"); _craftRecipe = AccessTools.Field(typeFromHandle, "m_craftRecipe"); _multiCrafting = AccessTools.Field(typeFromHandle, "m_multiCrafting"); _recipeListBaseSize = AccessTools.Field(typeFromHandle, "m_recipeListBaseSize"); if (type != null) { _pairRecipe = AccessTools.Property(type, "Recipe"); _pairItem = AccessTools.Property(type, "ItemData"); _pairElement = AccessTools.Property(type, "InterfaceElement"); } _addRecipeToList = AccessTools.Method(typeFromHandle, "AddRecipeToList", new Type[4] { typeof(Player), typeof(Recipe), typeof(ItemData), typeof(bool) }, (Type[])null); _updateCraftingPanel = AccessTools.Method(typeFromHandle, "UpdateCraftingPanel", new Type[1] { typeof(bool) }, (Type[])null); _setActiveGroup = AccessTools.Method(typeFromHandle, "SetActiveGroup", new Type[2] { typeof(int), typeof(bool) }, (Type[])null); (string, object)[] obj = new(string, object)[13] { ("InventoryGui.RecipeDataPair", type), ("InventoryGui.m_availableRecipes", _availableRecipes), ("InventoryGui.m_selectedRecipe", _selectedRecipe), ("InventoryGui.m_craftTimer", _craftTimer), ("InventoryGui.m_craftRecipe", _craftRecipe), ("InventoryGui.m_multiCrafting", _multiCrafting), ("InventoryGui.m_recipeListBaseSize", _recipeListBaseSize), ("RecipeDataPair.Recipe", _pairRecipe), ("RecipeDataPair.ItemData", _pairItem), ("RecipeDataPair.InterfaceElement", _pairElement), ("InventoryGui.AddRecipeToList", _addRecipeToList), ("InventoryGui.UpdateCraftingPanel", _updateCraftingPanel), ("InventoryGui.SetActiveGroup(int, bool)", _setActiveGroup) }; bool flag = true; (string, object)[] array = obj; for (int i = 0; i < array.Length; i++) { (string, object) tuple = array[i]; var (text, _) = tuple; if (tuple.Item2 == null) { GorilaChestModPlugin.Log.LogError((object)("Missing game member " + text + ".")); flag = false; } } Ready = flag; return flag; } internal static IList AvailableRecipes(InventoryGui gui) { return (IList)_availableRecipes.GetValue(gui); } internal static GameObject ElementOf(object pair) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown return (GameObject)_pairElement.GetValue(pair); } internal static void Selected(InventoryGui gui, out Recipe recipe, out ItemData item) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown object value = _selectedRecipe.GetValue(gui); recipe = (Recipe)_pairRecipe.GetValue(value); item = (ItemData)_pairItem.GetValue(value); } internal static float CraftTimer(InventoryGui gui) { return (float)_craftTimer.GetValue(gui); } internal static float RecipeListBaseSize(InventoryGui gui) { return (float)_recipeListBaseSize.GetValue(gui); } internal static void StartTimer(InventoryGui gui, Recipe recipe) { _craftRecipe.SetValue(gui, recipe); _multiCrafting.SetValue(gui, false); _craftTimer.SetValue(gui, 0f); } internal static void AddRecipeToList(InventoryGui gui, Player player, Recipe recipe, ItemData item, bool canCraft) { _addRecipeToList.Invoke(gui, new object[4] { player, recipe, item, canCraft }); } internal static void UpdateCraftingPanel(InventoryGui gui) { _updateCraftingPanel.Invoke(gui, new object[1] { false }); } internal static void FocusCraftingPanel(InventoryGui gui) { if (gui.m_uiGroups != null && gui.m_uiGroups.Length > 3) { _setActiveGroup.Invoke(gui, new object[2] { 3, true }); } } } internal struct Refund { internal ItemDrop Item; internal int Amount; } internal static class Salvage { internal static Recipe FindRecipe(Player player, ItemData item) { if ((Object)(object)player == (Object)null || item?.m_shared == null || item.m_shared.m_questItem || (Object)(object)ObjectDB.instance == (Object)null) { return null; } Recipe recipe = ObjectDB.instance.GetRecipe(item); if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || (Object)(object)recipe.m_item == (Object)null || recipe.m_resources == null) { return null; } if (recipe.m_requireOnlyOneIngredient) { return null; } if (!player.RequiredCraftingStation(recipe, 1, false)) { return null; } if (Returns(recipe, item.m_quality, 100).Count <= 0) { return null; } return recipe; } internal static string Refusal(Player player, ItemData item, Recipe recipe) { if ((Object)(object)player == (Object)null || item == null || (Object)(object)recipe == (Object)null || !((Humanoid)player).GetInventory().ContainsItem(item)) { return "The item is no longer in your inventory"; } if (item.m_equipped || ((Humanoid)player).IsItemEquiped(item)) { return "Unequip it first"; } if (ModConfig.DeconstructRespectFavorites.Value && Favorites.IsFavorite(item)) { return "Marked as favorite, Alt click it to unmark"; } if (item.m_stack < recipe.m_amount) { return $"Needs a full stack of {recipe.m_amount}"; } if (Returns(recipe, item.m_quality, DeconstructSettings.Percent).Count == 0) { return "Too little would come back at this percentage"; } return null; } internal static List<Refund> Returns(Recipe recipe, int quality, int percent) { List<Refund> list = new List<Refund>(); int num = Mathf.Max(1, recipe.m_item.m_itemData.m_shared.m_maxQuality); int num2 = Mathf.Clamp(quality, 1, num); Requirement[] resources = recipe.m_resources; foreach (Requirement req in resources) { if (req == null || (Object)(object)req.m_resItem == (Object)null || req.m_upgraderResource || !req.m_recover) { continue; } int num3 = 0; for (int j = 1; j <= num2; j++) { num3 += req.GetAmount(j); } int num4 = num3 * percent / 100; if (num4 > 0) { int num5 = list.FindIndex((Refund m) => m.Item.m_itemData.m_shared.m_name == req.m_resItem.m_itemData.m_shared.m_name); if (num5 >= 0) { Refund value = list[num5]; value.Amount += num4; list[num5] = value; } else { list.Add(new Refund { Item = req.m_resItem, Amount = num4 }); } } } return list; } internal static bool Deconstruct(Player player, ItemData item, Recipe recipe) { string text = Refusal(player, item, recipe); if (text != null) { if (player != null) { ((Character)player).Message((MessageType)2, text, 0, (Sprite)null, false); } return false; } int percent = DeconstructSettings.Percent; List<Refund> list = Returns(recipe, item.m_quality, percent); string name = item.m_shared.m_name; int quality = item.m_quality; bool cheated = item.m_cheated; if (!((Humanoid)player).GetInventory().RemoveItem(item, recipe.m_amount)) { return false; } int num = 0; foreach (Refund item2 in list) { num += Give(player, item2, cheated); } if (num > 0) { ((Character)player).Message((MessageType)2, "$inventory_full", 0, (Sprite)null, false); } if (ModConfig.Verbose.Value) { List<string> list2 = new List<string>(); foreach (Refund item3 in list) { list2.Add($"{item3.Item.m_itemData.m_shared.m_name} x{item3.Amount}"); } GorilaChestModPlugin.Log.LogInfo((object)(string.Format("Deconstructed {0} x{1} at quality {2}, {3}%: {4}", name, recipe.m_amount, quality, percent, string.Join(", ", list2)) + ((num > 0) ? $", {num} dropped on the ground" : "") + ".")); } return true; } private static int Give(Player player, Refund material, bool cheated) { //IL_010f: 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_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) GameObject val = ObjectDB.instance.GetItemPrefab(((Object)((Component)material.Item).gameObject).name); if ((Object)(object)val == (Object)null) { val = ((Component)material.Item).gameObject; } ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { GorilaChestModPlugin.Log.LogWarning((object)("No item prefab for " + ((Object)((Component)material.Item).gameObject).name + ", skipped.")); return 0; } Inventory inventory = ((Humanoid)player).GetInventory(); int num = Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize); int num2 = material.Amount; int num3 = 0; while (num2 > 0) { int num4 = Mathf.Min(num2, num); num2 -= num4; ItemData val2 = NewItem(component, val, num4, cheated); int num5 = Mathf.Min(inventory.FindFreeStackSpace(val2.m_shared.m_name, (float)val2.m_worldLevel) + inventory.GetEmptySlots() * num, num4); if (num5 > 0) { inventory.AddItem(NewItem(component, val, num5, cheated)); } if (num4 > num5) { Transform transform = ((Component)player).transform; ItemDrop.DropItem(val2, num4 - num5, transform.position + transform.forward + Vector3.up, transform.rotation); num3 += num4 - num5; } } ((Character)player).ShowPickupMessage(component.m_itemData, material.Amount); return num3; } private static ItemData NewItem(ItemDrop template, GameObject prefab, int stack, bool cheated) { ItemData obj = template.m_itemData.Clone(); obj.m_dropPrefab = prefab; obj.m_stack = stack; obj.m_durability = obj.GetMaxDurability(); obj.m_worldLevel = (byte)Game.m_worldLevel; obj.m_cheated = cheated; return obj; } } [HarmonyPatch(typeof(InventoryGrid), "DropItem")] internal static class InventoryGridDropItemPatch { private static bool Prefix(InventoryGrid __instance, Inventory fromInventory, ItemData item, int amount, Vector2i pos, ref bool __result) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) Inventory inventory = __instance.GetInventory(); if (inventory == null || fromInventory == null || item == null || item.m_shared == null) { return true; } if (inventory == fromInventory) { return true; } int num = ChestStacks.MaxStackFor(item.m_shared, inventory); if (num <= 1) { return true; } int num2 = Mathf.Min(amount, item.m_stack); if (num2 <= num) { return true; } ItemData itemAt = inventory.GetItemAt(pos.x, pos.y); if (itemAt != null && !itemAt.IsSameType(item)) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).Message((MessageType)2, $"Stack too large to swap, move {num} or fewer at a time", 0, (Sprite)null, false); } __result = false; return false; } __result = MoveOversized(fromInventory, inventory, item, num2, pos, num); return false; } private static bool MoveOversized(Inventory from, Inventory to, ItemData item, int wanted, Vector2i pos, int max) { //IL_0012: 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) int stack = item.m_stack; to.MoveItemToThis(from, item, Mathf.Min(wanted, max), pos.x, pos.y); int num = Mathf.Min(wanted - (stack - item.m_stack), item.m_stack); while (num > 0) { int chunk = Mathf.Min(max, num); int num2 = ChestStacks.MoveChunk(to, item, chunk); if (num2 <= 0) { break; } item.m_stack -= num2; num -= num2; if (item.m_stack <= 0) { from.RemoveItem(item); break; } } ChestStacks.NotifyChanged(from); ChestStacks.NotifyChanged(to); int num3 = stack - item.m_stack; if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"Moved {num3} of {wanted}x {item.m_shared.m_name} out in stacks of {max}, {item.m_stack} left behind."); } if (num3 < wanted && num3 > 0) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).Message((MessageType)2, "$inventory_full", 0, (Sprite)null, false); } } return num3 >= wanted; } } [HarmonyPatch(typeof(InventoryGui), "CanDropDragOntoItem")] internal static class InventoryGuiCanDropDragOntoItemPatch { private static readonly FieldInfo DragItem = AccessTools.Field(typeof(InventoryGui), "m_dragItem"); private static bool Prefix(InventoryGui __instance, ItemData item, ref bool __result) { if (DragItem == null || item == null || item.m_shared == null) { return true; } object? value = DragItem.GetValue(__instance); ItemData val = (ItemData)((value is ItemData) ? value : null); if (val == null || !item.IsSameType(val)) { return true; } Inventory val2 = ChestStacks.InventoryOf(item); if (val2 == null) { return true; } __result = ChestStacks.MaxStackFor(item.m_shared, val2) - item.m_stack > 0; return false; } } internal static class FavoriteMarker { private const string ChildName = "GorilaChestMod_Favorite"; private const int TextureSize = 64; private static Sprite _star; internal static Sprite Star { get { if ((Object)(object)_star == (Object)null) { _star = Paint(); } return _star; } } internal static void Apply(InventoryElement element, bool favorite) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)element == (Object)null || (Object)(object)element.m_icon == (Object)null) { return; } Transform parent = ((Component)element.m_icon).transform.parent; if ((Object)(object)parent == (Object)null) { return; } Transform val = parent.Find("GorilaChestMod_Favorite"); if (!favorite || !ModConfig.FavoritesShowMarker.Value) { if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(false); } return; } if ((Object)(object)val == (Object)null) { GameObject val2 = new GameObject("GorilaChestMod_Favorite", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component = val2.GetComponent<RectTransform>(); ((Transform)component).SetParent(parent, false); component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(0f, 1f); component.pivot = new Vector2(0f, 1f); component.anchoredPosition = new Vector2(2f, -2f); component.sizeDelta = new Vector2(22f, 22f); Image component2 = val2.GetComponent<Image>(); component2.sprite = Star; ((Graphic)component2).raycastTarget = false; val = (Transform)(object)component; } val.SetAsLastSibling(); ((Component)val).gameObject.SetActive(true); } private static Sprite Paint() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false) { filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(32f, 32f); Vector2[] array = (Vector2[])(object)new Vector2[10]; for (int i = 0; i < array.Length; i++) { float num = ((i % 2 == 0) ? 29.44f : 12.16f); float num2 = -(float)Math.PI / 2f + (float)i * (float)Math.PI / 5f; array[i] = val2 + new Vector2(Mathf.Cos(num2), Mathf.Sin(num2)) * num; } Color val3 = default(Color); ((Color)(ref val3))..ctor(1f, 0.78f, 0.22f, 1f); Color val4 = default(Color); ((Color)(ref val4))..ctor(0.16f, 0.11f, 0.02f, 1f); Color val5 = default(Color); ((Color)(ref val5))..ctor(0f, 0f, 0f, 0f); Vector2 val6 = default(Vector2); for (int j = 0; j < 64; j++) { for (int k = 0; k < 64; k++) { ((Vector2)(ref val6))..ctor((float)k + 0.5f, (float)j + 0.5f); if (!Inside(array, val6)) { val.SetPixel(k, j, val5); continue; } bool flag = !Inside(array, val6 + Vector2.right * 2f) || !Inside(array, val6 + Vector2.left * 2f) || !Inside(array, val6 + Vector2.up * 2f) || !Inside(array, val6 + Vector2.down * 2f); val.SetPixel(k, j, flag ? val4 : val3); } } val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f)); } private static bool Inside(Vector2[] polygon, Vector2 point) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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) bool flag = false; int num = 0; int num2 = polygon.Length - 1; while (num < polygon.Length) { if (polygon[num].y > point.y != polygon[num2].y > point.y && point.x < (polygon[num2].x - polygon[num].x) * (point.y - polygon[num].y) / (polygon[num2].y - polygon[num].y) + polygon[num].x) { flag = !flag; } num2 = num++; } return flag; } } [HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")] internal static class InventoryGuiSelectFavoritePatch { private static readonly FieldInfo DragGo = AccessTools.Field(typeof(InventoryGui), "m_dragGo"); private static bool Prefix(InventoryGui __instance, ItemData item, Modifier mod) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.FavoritesEnabled.Value || item == null || (int)mod != 0) { return true; } if (!Favorites.ModifierHeld()) { return true; } if (DragGo != null) { object? value = DragGo.GetValue(__instance); GameObject val = (GameObject)((value is GameObject) ? value : null); if (val != null && (Object)(object)val != (Object)null) { return true; } } bool flag = Favorites.Toggle(item); Favorites.Announce(item, flag); if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)(item.m_shared.m_name + " is " + (flag ? "now a favorite" : "no longer a favorite") + ".")); } return false; } } [HarmonyPatch(typeof(InventoryGrid), "UpdateGui")] internal static class InventoryGridFavoriteMarkerPatch { private static readonly FieldInfo Elements = AccessTools.Field(typeof(InventoryGrid), "m_elements"); private static void Postfix(InventoryGrid __instance) { if (!ModConfig.FavoritesEnabled.Value || Elements == null || !(Elements.GetValue(__instance) is List<InventoryElement> { Count: not 0 } list)) { return; } Inventory inventory = __instance.GetInventory(); if (inventory == null) { return; } foreach (InventoryElement item in list) { FavoriteMarker.Apply(item, favorite: false); } int width = inventory.GetWidth(); foreach (ItemData allItem in inventory.GetAllItems()) { if (Favorites.IsFavorite(allItem)) { int num = allItem.m_gridPos.y * width + allItem.m_gridPos.x; if (num >= 0 && num < list.Count) { FavoriteMarker.Apply(list[num], favorite: true); } } } } } [HarmonyPatch(typeof(Inventory), "StackAll")] internal static class InventoryStackAllFavoritePatch { internal static bool IsProtected(Humanoid character, ItemData item) { if (!((Object)(object)character != (Object)null) || !character.IsItemEquiped(item)) { return Favorites.IsFavorite(item); } return true; } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CallRedirector.Apply(instructions, "Inventory.StackAll", new CallSwap(AccessTools.Method(typeof(Humanoid), "IsItemEquiped", (Type[])null, (Type[])null), AccessTools.Method(typeof(InventoryStackAllFavoritePatch), "IsProtected", (Type[])null, (Type[])null))); } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(int), typeof(int), typeof(int), typeof(bool) })] internal static class InventoryAddItemFavoriteMergePatch { private static void Prefix(Inventory __instance, int x, int y, out ItemData __state) { __state = __instance.GetItemAt(x, y); } private static void Postfix(Inventory __instance, ItemData item, int x, int y, ItemData __state) { if (ModConfig.FavoritesEnabled.Value) { ItemData itemAt = __instance.GetItemAt(x, y); if (itemAt != null && itemAt != item && __state != null && __state == itemAt) { Favorites.CarryOverOnMerge(itemAt, item); } } } } internal static class Favorites { private const string Key = "GorilaChestMod.favorite"; private const string Yes = "1"; internal static bool IsFavorite(ItemData item) { if (ModConfig.FavoritesEnabled.Value && item?.m_customData != null && item.m_customData.TryGetValue("GorilaChestMod.favorite", out var value)) { return value == "1"; } return false; } internal static bool Toggle(ItemData item) { return Set(item, !IsFavorite(item)); } internal static bool Set(ItemData item, bool favorite) { if (item == null) { return false; } if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } if (favorite) { item.m_customData["GorilaChestMod.favorite"] = "1"; } else { item.m_customData.Remove("GorilaChestMod.favorite"); } return favorite; } internal static void CarryOverOnMerge(ItemData destination, ItemData source) { if (destination != null && source != null && IsFavorite(source)) { Set(destination, favorite: true); } } internal static void Announce(ItemData item, bool favorite) { if (ModConfig.FavoritesAnnounce.Value && !((Object)(object)Player.m_localPlayer == (Object)null) && item?.m_shared != null) { string text = Localization.instance.Localize(item.m_shared.m_name); ((Character)Player.m_localPlayer).Message((MessageType)1, favorite ? (text + ": favorite") : (text + ": favorite removed"), 0, (Sprite)null, false); } } internal static bool ModifierHeld() { //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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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) //IL_0019: Unknown result type (might be due to invalid IL or missing references) KeyCode value = ModConfig.FavoriteModifier.Value; if ((int)value == 0) { return false; } if (!Input.GetKey(value)) { return Input.GetKey(PairedSide(value)); } return true; } private static KeyCode PairedSide(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected I4, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) return (KeyCode)((key - 303) switch { 5 => 307, 4 => 308, 3 => 305, 2 => 306, 1 => 303, 0 => 304, _ => key, }); } } [BepInPlugin("dev.trentini.gorilachestmod", "GorilaChestMod", "2.5.0")] public class GorilaChestModPlugin : BaseUnityPlugin { public const string PluginGuid = "dev.trentini.gorilachestmod"; public const string PluginName = "GorilaChestMod"; public const string PluginVersion = "2.5.0"; internal static ManualLogSource Log; private Harmony _harmony; internal static bool IsDedicatedServer { get { if (!((Object)(object)ZNet.instance != (Object)null)) { return Application.isBatchMode; } return ZNet.instance.IsDedicated(); } } private void Awake() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); if (!GuiAccess.Resolve()) { Log.LogError((object)"The crafting panel changed in this game version, the Deconstruct tab stays off."); } _harmony = new Harmony("dev.trentini.gorilachestmod"); _harmony.PatchAll(typeof(GorilaChestModPlugin).Assembly); Log.LogInfo((object)("GorilaChestMod 2.5.0 loaded. " + $"Craft range {ModConfig.Range.Value}m, building {ModConfig.UseForBuilding.Value}, " + "chest stacks " + StackSettings.DescribeStacks() + ", " + $"quick stack {ModConfig.QuickStackEnabled.Value}, " + "deconstruct " + DeconstructSettings.Describe() + ".")); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } ContainerTracker.Clear(); } } internal static class InventoryBridge { internal static bool ShouldExtend(Inventory inventory) { if (inventory == null || ModConfig.Enabled == null || !ModConfig.Enabled.Value) { return false; } if (BuildContext.Active && !ModConfig.UseForBuilding.Value) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { return inventory == ((Humanoid)localPlayer).GetInventory(); } return false; } public static int CountItems(Inventory inventory, string name, int quality, bool matchWorldLevel) { int num = inventory.CountItems(name, quality, matchWorldLevel); if (!ShouldExtend(inventory)) { return num; } List<Container> nearby = ContainerTracker.GetNearby(); for (int i = 0; i < nearby.Count; i++) { Inventory inventory2 = nearby[i].GetInventory(); if (inventory2 != null) { num += inventory2.CountItems(name, quality, matchWorldLevel); } } return num; } public static bool HaveItem(Inventory inventory, string name, bool matchWorldLevel) { if (inventory.HaveItem(name, matchWorldLevel)) { return true; } if (!ShouldExtend(inventory)) { return false; } List<Container> nearby = ContainerTracker.GetNearby(); for (int i = 0; i < nearby.Count; i++) { Inventory inventory2 = nearby[i].GetInventory(); if (inventory2 != null && inventory2.HaveItem(name, matchWorldLevel)) { return true; } } return false; } public static ItemData GetItem(Inventory inventory, string name, int quality, bool isPrefabName) { ItemData item = inventory.GetItem(name, quality, isPrefabName); if (item != null || !ShouldExtend(inventory)) { return item; } List<Container> nearby = ContainerTracker.GetNearby(); for (int i = 0; i < nearby.Count; i++) { Inventory inventory2 = nearby[i].GetInventory(); if (inventory2 != null) { item = inventory2.GetItem(name, quality, isPrefabName); if (item != null) { return item; } } } return null; } public static void RemoveItem(Inventory inventory, string name, int amount, int itemQuality, bool worldLevelBased) { if (!ShouldExtend(inventory)) { inventory.RemoveItem(name, amount, itemQuality, worldLevelBased); return; } int num = amount; int num2 = Mathf.Min(num, inventory.CountItems(name, itemQuality, worldLevelBased)); if (num2 > 0) { inventory.RemoveItem(name, num2, itemQuality, worldLevelBased); num -= num2; } if (num <= 0) { return; } List<Container> nearby = ContainerTracker.GetNearby(); for (int i = 0; i < nearby.Count; i++) { if (num <= 0) { break; } Container val = nearby[i]; Inventory inventory2 = val.GetInventory(); if (inventory2 == null) { continue; } int num3 = inventory2.CountItems(name, itemQuality, worldLevelBased); if (num3 <= 0) { continue; } if (!ContainerTracker.TryTakeOwnership(val)) { GorilaChestModPlugin.Log.LogWarning((object)("Could not take ownership of '" + val.m_name + "', skipping it for " + name + ".")); } else { int num4 = Mathf.Min(num3, num); inventory2.RemoveItem(name, num4, itemQuality, worldLevelBased); num -= num4; if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"Took {num4}x {name} from '{val.m_name}'."); } } } if (num > 0) { GorilaChestModPlugin.Log.LogWarning((object)$"Still missing {num}x {name} after checking the player and {nearby.Count} container(s)."); } } } internal static class ModConfig { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> Range; internal static ConfigEntry<bool> UseForBuilding; internal static ConfigEntry<bool> IncludeVehicleContainers; internal static ConfigEntry<bool> ChestStacksEnabled; internal static ConfigEntry<int> ChestStackSize; internal static ConfigEntry<bool> QuickStackEnabled; internal static ConfigEntry<bool> QuickStackSkipHotbar; internal static ConfigEntry<KeyboardShortcut> QuickStackHotkey; internal static ConfigEntry<bool> QuickStackHotkeyNeedsInventory; internal static ConfigEntry<bool> FavoritesEnabled; internal static ConfigEntry<KeyCode> FavoriteModifier; internal static ConfigEntry<bool> FavoritesShowMarker; internal static ConfigEntry<bool> FavoritesAnnounce; internal static ConfigEntry<bool> ShrinkStackText; internal static ConfigEntry<bool> DeconstructEnabled; internal static ConfigEntry<int> ReturnPercent; internal static ConfigEntry<bool> DeconstructRespectFavorites; internal static ConfigEntry<bool> Verbose; internal static void Bind(ConfigFile config) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Expected O, but got Unknown Enabled = config.Bind<bool>("1 - Craft from chests", "Enabled", true, "Count the contents of nearby chests when crafting, upgrading and building."); Range = config.Bind<float>("1 - Craft from chests", "Range", 20f, new ConfigDescription("Radius in meters around the player that chests are pulled from. The quick stack button uses the same radius.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>())); UseForBuilding = config.Bind<bool>("1 - Craft from chests", "UseForBuilding", true, "Also pay hammer, hoe and cultivator building costs from nearby chests."); IncludeVehicleContainers = config.Bind<bool>("1 - Craft from chests", "IncludeVehicleContainers", true, "Include containers that belong to carts and ships."); ChestStacksEnabled = config.Bind<bool>("2 - Chest stacks", "Enabled", true, "Let items stack far beyond their normal limit while they sit in a chest. The player inventory keeps the vanilla limits."); ChestStackSize = config.Bind<int>("2 - Chest stacks", "ChestStackSize", 1000, new ConfigDescription("Stack size a chest slot may hold, 1000 by default. Items whose vanilla limit is already higher keep theirs, and items that do not stack at all, like weapons and armour, are never affected. On a server this value is handed to every client that connects.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000), Array.Empty<object>())); QuickStackEnabled = config.Bind<bool>("3 - Quick stack", "Enabled", true, "Enable the quick stack hotkey, which pushes matching items from your inventory into nearby chests."); QuickStackSkipHotbar = config.Bind<bool>("3 - Quick stack", "SkipHotbar", true, "Leave the hotbar row alone, so your weapons, tools and food stay where they are."); QuickStackHotkey = config.Bind<KeyboardShortcut>("3 - Quick stack", "Hotkey", new KeyboardShortcut((KeyCode)106, Array.Empty<KeyCode>()), "Hotkey for quick stacking. Works with the inventory closed as well. J is free in Valheim: V is auto pickup and G opens the hotbar radial. Set to None to use only the button."); QuickStackHotkeyNeedsInventory = config.Bind<bool>("3 - Quick stack", "HotkeyNeedsInventory", false, "Require the inventory to be open before the hotkey does anything."); ShrinkStackText = config.Bind<bool>("2 - Chest stacks", "ShrinkStackText", true, "Keep the amount label on a slot readable when the numbers get long: a large limit is written as 100k, and the font shrinks from there. Turn off for the vanilla label."); FavoritesEnabled = config.Bind<bool>("4 - Favorites", "Enabled", true, "Let a stack be marked so that quick stack and the chest stack button leave it where it is."); FavoriteModifier = config.Bind<KeyCode>("4 - Favorites", "Modifier", (KeyCode)308, "Hold this and left click a stack in your inventory to mark or unmark it. Left and right side keys both work. Set to None to turn marking off."); FavoritesShowMarker = config.Bind<bool>("4 - Favorites", "ShowMarker", true, "Draw a small star on a marked slot."); FavoritesAnnounce = config.Bind<bool>("4 - Favorites", "Announce", true, "Print a line in the corner when you mark or unmark a stack."); DeconstructEnabled = config.Bind<bool>("5 - Deconstruct", "Enabled", true, "Add a Deconstruct tab beside Craft and Upgrade at crafting stations, which takes a crafted item apart and gives its materials back."); ReturnPercent = config.Bind<int>("5 - Deconstruct", "ReturnPercent", 100, new ConfigDescription("How much of the materials a deconstruction gives back, from 50 to 100. Each material is rounded down on its own, so at 50% an item that cost 3 wood gives 1 back. On a server this value is handed to every client that connects.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(50, 100), Array.Empty<object>())); DeconstructRespectFavorites = config.Bind<bool>("5 - Deconstruct", "RespectFavorites", true, "Refuse to deconstruct a stack you marked as favorite."); Verbose = config.Bind<bool>("6 - Debug", "Verbose", false, "Log every chest withdrawal, every quick stack move, every deconstruction and every patched call to the BepInEx log."); } } internal static class QuickStack { internal static int Run() { //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !ModConfig.QuickStackEnabled.Value) { return 0; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); List<Container> nearby = ContainerTracker.GetNearby(); if (nearby.Count == 0) { ((Character)localPlayer).Message((MessageType)2, "$msg_stackall_none", 0, (Sprite)null, false); return 0; } int num = inventory.CountItems((string)null, -1, true); List<ItemData> list = new List<ItemData>(inventory.GetAllItems()); List<Container> list2 = new List<Container>(); foreach (ItemData item in list) { if (!CanMove(localPlayer, inventory, item)) { continue; } foreach (Container item2 in nearby) { if (item.m_stack <= 0 || !inventory.ContainsItem(item)) { break; } Inventory inventory2 = item2.GetInventory(); if (inventory2 != null && inventory2.ContainsItemByName(item.m_shared.m_name) && ContainerTracker.TryTakeOwnership(item2)) { inventory2.MoveItemToThis(inventory, item); if (!list2.Contains(item2)) { list2.Add(item2); } if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)("Quick stacked " + item.m_shared.m_name + " into '" + item2.m_name + "'.")); } } } } int num2 = num - inventory.CountItems((string)null, -1, true); if (num2 > 0) { ((Character)localPlayer).Message((MessageType)2, Localization.instance.Localize("$msg_stackall") + " " + num2, 0, (Sprite)null, false); if ((Object)(object)InventoryGui.instance != (Object)null) { InventoryGui.instance.m_moveItemEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } } else { ((Character)localPlayer).Message((MessageType)2, "$msg_stackall_none", 0, (Sprite)null, false); } if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"Quick stack moved {num2} item(s) into {list2.Count} chest(s)."); } return num2; } private static bool CanMove(Player player, Inventory playerInventory, ItemData item) { if (item == null || item.m_shared == null) { return false; } if (item.m_equipped || ((Humanoid)player).IsItemEquiped(item)) { return false; } if (Favorites.IsFavorite(item)) { return false; } if (item.m_shared.m_questItem) { return false; } if (ModConfig.QuickStackSkipHotbar.Value && item.m_gridPos.y == 0) { return false; } return playerInventory.ContainsItem(item); } } [HarmonyPatch(typeof(InventoryGui), "OnDestroy")] internal static class InventoryGuiOnDestroyPatch { private static void Postfix() { StackText.Forget(); DeconstructTab.Destroy(); DeconstructPanel.ClearPending(); } } [HarmonyPatch(typeof(InventoryGui), "Update")] internal static class InventoryGuiUpdatePatch { private static void Postfix() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (ModConfig.QuickStackEnabled.Value && !((Object)(object)Player.m_localPlayer == (Object)null)) { KeyboardShortcut value = ModConfig.QuickStackHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown() && CanTakeHotkey()) { QuickStack.Run(); } } } private static bool CanTakeHotkey() { if (ModConfig.QuickStackHotkeyNeedsInventory.Value && !InventoryGui.IsVisible()) { return false; } if (Menu.IsVisible() || Console.IsVisible() || TextInput.IsVisible()) { return false; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return false; } return !((Character)Player.m_localPlayer).IsDead(); } } internal static class StackFields { internal static readonly FieldInfo MaxStackSize = AccessTools.Field(typeof(SharedData), "m_maxStackSize"); internal static readonly MethodInfo MaxStackFor = AccessTools.Method(typeof(ChestStacks), "MaxStackFor", (Type[])null, (Type[])null); internal static readonly FieldInfo GridInventory = AccessTools.Field(typeof(InventoryGrid), "m_inventory"); internal static IEnumerable<MethodBase> MethodsReadingMaxStack(Type type) { List<Type> list = new List<Type> { type }; list.AddRange(type.GetNestedTypes(AccessTools.all)); foreach (Type item in list) { foreach (MethodBase item2 in AccessTools.GetDeclaredMethods(item).Cast<MethodBase>()) { if (!item2.IsAbstract && !item2.ContainsGenericParameters && ReadsMaxStack(item2)) { yield return item2; } } } } private static bool ReadsMaxStack(MethodBase method) { try { if (method.GetMethodBody() == null) { return false; } return PatchProcessor.GetOriginalInstructions(method, (ILGenerator)null).Any((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo a && CallRedirector.SameMember(a, MaxStackSize)); } catch (Exception ex) { GorilaChestModPlugin.Log.LogWarning((object)("Could not read the body of " + method.DeclaringType?.Name + "." + method.Name + ": " + ex.Message)); return false; } } } [HarmonyPatch] internal static class InventoryStackLimitPatch { private static IEnumerable<MethodBase> TargetMethods() { List<MethodBase> list = StackFields.MethodsReadingMaxStack(typeof(Inventory)).ToList(); if (list.Count == 0) { GorilaChestModPlugin.Log.LogError((object)"No Inventory method reads m_maxStackSize any more. The game code changed, chest stacks are inactive."); } else { GorilaChestModPlugin.Log.LogInfo((object)("Chest stacks hooked into Inventory: " + string.Join(", ", list.Select((MethodBase m) => m.Name).Distinct().ToArray()))); } return list; } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { int replaced; IEnumerable<CodeInstruction> result = CallRedirector.ReplaceFieldReads(instructions, "Inventory." + original.Name, StackFields.MaxStackSize, StackFields.MaxStackFor, () => (IEnumerable<CodeInstruction>)(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Ldarg_0, (object)null) }, out replaced); if (replaced == 0) { GorilaChestModPlugin.Log.LogError((object)("Inventory." + original.Name + " was hooked but no stack limit read was replaced. The game code changed, part of chest stacks is inactive.")); return result; } if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"Chest stacks: replaced {replaced} stack limit read(s) in Inventory.{original.Name}."); } return result; } } [HarmonyPatch(typeof(InventoryGrid), "UpdateGui")] internal static class InventoryGridStackTextPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { int replaced; IEnumerable<CodeInstruction> result = CallRedirector.ReplaceFieldReads(instructions, "InventoryGrid.UpdateGui", StackFields.MaxStackSize, StackFields.MaxStackFor, () => (IEnumerable<CodeInstruction>)(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldfld, (object)StackFields.GridInventory) }, out replaced); if (replaced == 0) { GorilaChestModPlugin.Log.LogWarning((object)"InventoryGrid.UpdateGui no longer reads m_maxStackSize, chest slots will show the vanilla limit."); return result; } if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"Chest stacks: replaced {replaced} stack limit read(s) in InventoryGrid.UpdateGui."); } return result; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] internal static class InventoryAddItemSplitPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (item == null || item.m_shared == null) { return true; } int num = ChestStacks.MaxStackFor(item.m_shared, __instance); if (num <= 1 || item.m_stack <= num) { return true; } __result = ChestStacks.AddInChunks(__instance, item, num); if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"Split an oversized stack of {item.m_shared.m_name} into chunks of {num}, leftover {item.m_stack}."); } return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(int), typeof(int), typeof(int), typeof(bool) })] internal static class InventoryAddItemAtSlotClampPatch { private static void Prefix(Inventory __instance, ItemData item, ref int amount, out bool __state) { __state = false; if (item != null && item.m_shared != null) { int num = ChestStacks.MaxStackFor(item.m_shared, __instance); if (num > 1 && Mathf.Min(amount, item.m_stack) > num) { amount = num; __state = true; } } } private static void Postfix(ref bool __result, bool __state) { if (__state) { __result = false; } } } [HarmonyPatch(typeof(ItemDrop), "DropItem")] internal static class ItemDropSplitPatch { private static bool Prefix(ItemData item, int amount, Vector3 position, Quaternion rotation, ref ItemDrop __result) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (item == null || item.m_shared == null) { return true; } int maxStackSize = item.m_shared.m_maxStackSize; int num = ((amount > 0) ? amount : item.m_stack); if (maxStackSize <= 1 || num <= maxStackSize) { return true; } ItemDrop val = null; int num2 = num; while (num2 > 0) { int num3 = Mathf.Min(maxStackSize, num2); val = ItemDrop.DropItem(item, num3, position + Random.insideUnitSphere * 0.3f, rotation); num2 -= num3; } __result = val; if (ModConfig.Verbose.Value) { GorilaChestModPlugin.Log.LogInfo((object)$"Dropped {num}x {item.m_shared.m_name} as stacks of {maxStackSize}."); } return false; } } [HarmonyPatch] internal static class InventoryLoadFloorPatch { private static MethodBase TargetMethod() { foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(Inventory))) { ParameterInfo[] parameters = declaredMethod.GetParameters(); if (declaredMethod.Name == "AddItem" && parameters.Length > 5 && parameters[0].ParameterType == typeof(int) && parameters[1].ParameterType == typeof(int)) { return declaredMethod; } } GorilaChestModPlugin.Log.LogError((object)"The Inventory.AddItem overload that rebuilds a saved inventory was not found, chests could be trimmed after lowering the stack size."); return null; } private static void Prefix(int stack) { ChestStacks.LoadFloor = stack; } private static void Finalizer() { ChestStacks.LoadFloor = 0; } } internal static class StackReport { private static bool _reported; internal static void Report() { if (_reported || !ModConfig.Verbose.Value || (Object)(object)ObjectDB.instance == (Object)null) { return; } List<GameObject> items = ObjectDB.instance.m_items; if (items == null || items.Count == 0) { return; } Dictionary<int, int> dictionary = new Dictionary<int, int>(); List<string> list = new List<string>(); foreach (GameObject item in items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null); if (!((Object)(object)val == (Object)null) && val.m_itemData?.m_shared != null) { int maxStackSize = val.m_itemData.m_shared.m_maxStackSize; dictionary[maxStackSize] = ((!dictionary.TryGetValue(maxStackSize, out var value)) ? 1 : (value + 1)); if (list.Count < 8 && maxStackSize > 1) { list.Add($"{((Object)item).name} {maxStackSize}"); } } } if (dictionary.Count != 0) { _reported = true; string text = string.Join(", ", (from p in dictionary orderby p.Key select $"{p.Key} -> {p.Value} item(s)").ToArray()); GorilaChestModPlugin.Log.LogInfo((object)("Vanilla stack limits in this build: " + text + ".")); GorilaChestModPlugin.Log.LogInfo((object)("Examples: " + string.Join(", ", list.ToArray()) + ".")); GorilaChestModPlugin.Log.LogInfo((object)$"Inside chests those become {StackSettings.StackSize}, except where the vanilla limit is already higher."); } } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class ObjectDbAwakePatch { private static void Postfix() { StackReport.Report(); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] internal static class ObjectDbCopyPatch { private static void Postfix() { StackReport.Report(); } } internal static class StackText { private const int ComfortableLength = 6; private const float MinScale = 0.35f; private const int CompactFrom = 1000; private static readonly Dictionary<TMP_Text, float> OriginalSizes = new Dictionary<TMP_Text, float>(); private static bool _announced; internal static void Fit(TMP_Text label) { if ((Object)(object)label == (Object)null || !((Behaviour)label).enabled || string.IsNullOrEmpty(label.text)) { return; } string text = Shorten(label.text); if (text != label.text) { label.text = text; } if (!OriginalSizes.TryGetValue(label, out var value)) { value = label.fontSize; OriginalSizes[label] = value; } float num = ((text.Length <= 6) ? 1f : Mathf.Max(0.35f, 6f / (float)text.Length)); float num2 = value * num; if (!Mathf.Approximately(label.fontSize, num2)) { label.enableAutoSizing = false; label.fontSize = num2; if (!_announced) { _announced = true; GorilaChestModPlugin.Log.LogInfo((object)("Slot labels are being fitted, first one was '" + text + "' at " + num2.ToString("0.#", CultureInfo.InvariantCulture) + " instead of " + value.ToString("0.#", CultureInfo.InvariantCulture) + ".")); } } } private static string Shorten(string text) { int num = text.LastIndexOf('/'); if (num <= 0 || num == text.Length - 1) { return text; } if (!int.TryParse(text.Substring(num + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 1000) { return text; } return text.Substring(0, num + 1) + Compact(result); } private static string Compact(int value) { if (value >= 1000000) { return Trim((float)value / 1000000f) + "M"; } return Trim((float)value / 1000f) + "k"; } private static string Trim(float value) { if (!(value >= 10f) && !Mathf.Approximately(value, Mathf.Round(value))) { return value.ToString("0.#", CultureInfo.InvariantCulture); } return Mathf.RoundToInt(value).ToString(CultureInfo.InvariantCulture); } internal static void Forget() { OriginalSizes.Clear(); _announced = false; } } [HarmonyPatch(typeof(InventoryGrid), "UpdateGui")] internal static class InventoryGridStackTextSizePatch { private static readonly FieldInfo Elements = AccessTools.Field(typeof(InventoryGrid), "m_elements"); private static void Postfix(InventoryGrid __instance) { if (!ModConfig.ShrinkStackText.Value) { return; } if (Elements == null) { GorilaChestModPlugin.Log.LogError((object)"InventoryGrid.m_elements is gone, slot labels are left at their vanilla size."); } else { if (!(Elements.GetValue(__instance) is List<InventoryElement> list)) { return; } foreach (InventoryElement item in list) { if ((Object)(object)item != (Object)null) { StackText.Fit(item.m_amount); } } } } } internal static class BuildInfo { internal const string Version = "2.5.0"; } }