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 ChestButler v2.1.1
ChestButler.dll
Decompiled 16 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ChestButler.Core; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using MultiUserChest; 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 = "")] [assembly: AssemblyCompany("ChestButler")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.1.1.0")] [assembly: AssemblyInformationalVersion("2.1.1")] [assembly: AssemblyProduct("ChestButler")] [assembly: AssemblyTitle("ChestButler")] [assembly: AssemblyVersion("2.1.1.0")] namespace ChestButler { [BepInPlugin("eksolutions.chestbutler", "ChestButler", "2.1.1")] [BepInProcess("valheim.exe")] [BepInProcess("valheim_server.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string ModGuid = "eksolutions.chestbutler"; public const string ModName = "ChestButler"; public const string ModVersion = "2.1.1"; internal static Plugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<float> SorterRadius; internal static ConfigEntry<float> TransferInterval; internal static ConfigEntry<int> StacksPerTick; internal static ConfigEntry<bool> ContainsFallback; internal static ConfigEntry<bool> VehiclesAreStorage; private Harmony _harmony; internal static ConfigEntry<float> StationRange => OrganizeConfig.StationRange; private void Awake() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Expected O, but got Unknown //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; SorterRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Sorting", "Radius", 128f, new ConfigDescription("Radius (m) around a sorter chest in which target chests are searched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 128f), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); TransferInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Sorting", "TransferInterval", 1f, new ConfigDescription("Seconds between transfer ticks per sorter. Client-side: raise it if the mod costs you frames.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>())); StacksPerTick = ((BaseUnityPlugin)this).Config.Bind<int>("Sorting", "StacksPerTick", 2, new ConfigDescription("How many item stacks a sorter moves per tick. Client-side.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); ContainsFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("Sorting", "ContainsFallback", true, new ConfigDescription("Route items to chests that already contain them when no explicit filter matches.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); VehiclesAreStorage = ((BaseUnityPlugin)this).Config.Bind<bool>("Sorting", "VehiclesAreStorage", false, new ConfigDescription("Treat cart and ship inventories as storage. Off (default): the sorter, Organize, Pull and Gather all ignore vehicles entirely - they are transport, and their own Pin/Pull buttons still work for manual loading.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); Groups.Init(((BaseUnityPlugin)this).Config); Stations.Init(((BaseUnityPlugin)this).Config); OrganizeConfig.Init(((BaseUnityPlugin)this).Config); Gather.Init(((BaseUnityPlugin)this).Config); SorterChestPiece.Register(); _harmony = new Harmony("eksolutions.chestbutler"); _harmony.PatchAll(); Log.LogInfo((object)"ChestButler 2.1.1 loaded"); } } } namespace ChestButler.Patches { [HarmonyPatch(typeof(Hud))] internal static class BuildGatherPatch { internal static bool InBuildInfo { get; private set; } [HarmonyPrefix] [HarmonyPatch("SetupPieceInfo")] private static void SetupPieceInfoPrefix() { InBuildInfo = true; } [HarmonyPostfix] [HarmonyPatch("SetupPieceInfo")] private static void SetupPieceInfoPostfix() { InBuildInfo = false; } } [HarmonyPatch(typeof(Container), "Awake")] internal static class Container_Awake_Patch { private static void Postfix(Container __instance) { ContainerTracker.Register(__instance); if ((Object)(object)((Component)__instance).GetComponent<SorterBehaviour>() == (Object)null && (Object)(object)((Component)__instance).GetComponentInParent<Piece>() != (Object)null) { ((Component)__instance).gameObject.AddComponent<SorterBehaviour>(); } } } [HarmonyPatch(typeof(Container), "OnDestroyed")] internal static class Container_OnDestroyed_Patch { private static void Prefix(Container __instance) { ContainerTracker.Unregister(__instance); } } [HarmonyPatch(typeof(InventoryGui))] internal static class GatherPatch { private static Button _gatherBtn; private static TMP_Text _gatherLabel; private static readonly List<GatherNeed> Needs = new List<GatherNeed>(); private static readonly HashSet<string> Seen = new HashSet<string>(); private static bool _onlyOneIngredient; private static List<Container> _sources; private const int MaxNeeds = 16; private static readonly FieldInfo SelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); private static readonly MethodInfo RecipeGetter = ((SelectedRecipeField != null) ? AccessTools.PropertyGetter(SelectedRecipeField.FieldType, "Recipe") : null); private static bool _reflectionWarned; private static Recipe SelectedRecipe(InventoryGui gui) { if ((Object)(object)gui == (Object)null || SelectedRecipeField == null || RecipeGetter == null) { if (!_reflectionWarned) { _reflectionWarned = true; Plugin.Log.LogWarning((object)"[gather] could not reach InventoryGui.m_selectedRecipe; require-only-one-ingredient recipes will be treated as normal ones"); } return null; } try { object value = SelectedRecipeField.GetValue(gui); return (Recipe)((value == null) ? null : /*isinst with value type is only supported in some contexts*/); } catch (Exception ex) { if (!_reflectionWarned) { _reflectionWarned = true; Plugin.Log.LogWarning((object)("[gather] reading m_selectedRecipe failed: " + ex.Message)); } return null; } } [HarmonyPrefix] [HarmonyPatch("SetupRequirementList")] private static void SetupRequirementListPrefix(InventoryGui __instance) { Needs.Clear(); Seen.Clear(); _sources = null; Recipe val = SelectedRecipe(__instance); _onlyOneIngredient = (Object)(object)val != (Object)null && val.m_requireOnlyOneIngredient; } [HarmonyPostfix] [HarmonyPatch("SetupRequirement")] private static void SetupRequirementPostfix(Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier, bool __result) { if (!__result || req?.m_resItem?.m_itemData?.m_shared == null || (Object)(object)player == (Object)null) { return; } string name = req.m_resItem.m_itemData.m_shared.m_name; if (string.IsNullOrEmpty(name)) { return; } int amount = req.GetAmount(quality); if (amount <= 0) { return; } int needed = amount * Mathf.Max(1, craftMultiplier); Inventory inventory = ((Humanoid)player).GetInventory(); int inPlayer = ((inventory != null) ? inventory.CountItems(name, -1, true) : 0); bool inBuildInfo = BuildGatherPatch.InBuildInfo; List<Container> sources; if (inBuildInfo) { sources = BuildGather.SourcesCached(); } else { if (_sources == null) { _sources = Gatherer.Sources(); } sources = _sources; } int num = Gatherer.CountInStorage(sources, name); if (!inBuildInfo && Seen.Add(name) && Needs.Count < 16) { Needs.Add(new GatherNeed { SharedName = name, Display = Names.Normalize(name), Needed = needed, InPlayer = inPlayer, InStorage = num }); } if (Gather.CountsShown && num > 0) { Annotate(elementRoot, num); } } private static void Annotate(Transform elementRoot, int inStorage) { if ((Object)(object)elementRoot == (Object)null) { return; } TMP_Text val = null; Transform val2 = elementRoot.Find("res_amount"); if ((Object)(object)val2 != (Object)null) { val = ((Component)val2).GetComponent<TMP_Text>(); } if ((Object)(object)val == (Object)null) { TMP_Text[] componentsInChildren = ((Component)elementRoot).GetComponentsInChildren<TMP_Text>(true); if (componentsInChildren != null && componentsInChildren.Length != 0) { val = componentsInChildren[^1]; } } if (!((Object)(object)val == (Object)null) && (val.text == null || !val.text.Contains(" <color=#9BE07A>"))) { val.textWrappingMode = (TextWrappingModes)0; val.overflowMode = (TextOverflowModes)0; TMP_Text obj = val; obj.text = obj.text + " <color=#9BE07A><size=85%>(" + inStorage + ")</size></color>"; } } [HarmonyPostfix] [HarmonyPatch("Show")] private static void ShowPostfix(InventoryGui __instance) { EnsureButton(__instance); } [HarmonyPostfix] [HarmonyPatch("UpdateRecipe")] private static void UpdateRecipePostfix(InventoryGui __instance) { EnsureButton(__instance); RefreshButton(); } [HarmonyPostfix] [HarmonyPatch("Hide")] private static void HidePostfix() { if ((Object)(object)_gatherBtn != (Object)null) { ((Component)_gatherBtn).gameObject.SetActive(false); } } private static void EnsureButton(InventoryGui gui) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_gatherBtn != (Object)null || (Object)(object)gui == (Object)null) { return; } Button craftButton = gui.m_craftButton; if ((Object)(object)craftButton == (Object)null) { return; } RectTransform component = ((Component)craftButton).GetComponent<RectTransform>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)((Transform)component).parent == (Object)null)) { Button val = Object.Instantiate<Button>(craftButton, ((Transform)component).parent); ((Object)val).name = "psort_gather"; Localize componentInChildren = ((Component)val).GetComponentInChildren<Localize>(true); if ((Object)(object)componentInChildren != (Object)null) { Object.DestroyImmediate((Object)(object)componentInChildren); } UIGamePad[] componentsInChildren = ((Component)val).GetComponentsInChildren<UIGamePad>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren[i]); } val.onClick = new ButtonClickedEvent(); ((UnityEvent)val.onClick).AddListener(new UnityAction(OnGatherClick)); RectTransform component2 = ((Component)val).GetComponent<RectTransform>(); component2.anchorMin = component.anchorMin; component2.anchorMax = component.anchorMax; component2.pivot = component.pivot; component2.sizeDelta = component.sizeDelta; Vector2 anchoredPosition = component.anchoredPosition; Rect rect = component.rect; component2.anchoredPosition = anchoredPosition - new Vector2(0f, ((Rect)(ref rect)).height + 6f); _gatherLabel = ((Component)val).GetComponentInChildren<TMP_Text>(); if ((Object)(object)_gatherLabel != (Object)null) { float fontSize = _gatherLabel.fontSize; _gatherLabel.enableAutoSizing = true; _gatherLabel.fontSizeMax = fontSize; _gatherLabel.fontSizeMin = fontSize - 4f; _gatherLabel.text = "Gather"; } _gatherBtn = val; } } private static void RefreshButton() { if ((Object)(object)_gatherBtn == (Object)null) { return; } if (!Gather.IsEnabled) { ((Component)_gatherBtn).gameObject.SetActive(false); return; } List<GatherNeed> list = GatherMath.Resolve(Needs, _onlyOneIngredient); int num = 0; foreach (GatherNeed item in list) { num += item.Gatherable; } bool flag = num > 0; ((Component)_gatherBtn).gameObject.SetActive(Needs.Count > 0); ((Selectable)_gatherBtn).interactable = flag; if ((Object)(object)_gatherLabel != (Object)null) { _gatherLabel.text = (flag ? ("Gather (" + num + ")") : "Gather"); } InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.m_craftButton != (Object)null && ((Component)_gatherBtn).gameObject.activeInHierarchy) { GamepadNav.LinkVertical((Selectable)(object)instance.m_craftButton, (Selectable)(object)_gatherBtn, onlyIfVanillaEmpty: false); } } private static void OnGatherClick() { if (Gather.IsEnabled && !((Object)(object)Player.m_localPlayer == (Object)null)) { List<GatherNeed> list = GatherMath.Resolve(Needs, _onlyOneIngredient); if (list.Count == 0) { Msg((Needs.Count == 0) ? "Select a recipe first" : "Nothing to gather - nearby chests have none of what this needs"); return; } Gatherer.Pull(list, out var movedTotal, out var typesMoved); Msg((movedTotal > 0) ? ("Gathered " + movedTotal + " item" + ((movedTotal == 1) ? "" : "s") + " (" + typesMoved + " type" + ((typesMoved == 1) ? "" : "s") + ")") : "Nothing could be gathered - your inventory may be full"); } } private static void Msg(string text) { if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false); } } } [HarmonyPatch(typeof(InventoryGui))] internal static class GuiPatch { private static RectTransform _bar; private static Button _sorterBtn; private static Button _pinBtn; private static Button _clearBtn; private static Button _pullBtn; private static Button _organizeBtn; private static TMP_Text _sorterLabel; private static TMP_Text _pinLabel; private static TMP_Text _clearLabel; private static TMP_Text _pullLabel; private static TMP_Text _organizeLabel; private static Container _current; private static readonly FieldRef<InventoryGui, InventoryGrid> ContainerGridRef = AccessTools.FieldRefAccess<InventoryGui, InventoryGrid>("m_containerGrid"); private static readonly FieldRef<InventoryGrid, RectTransform> GridRootRef = AccessTools.FieldRefAccess<InventoryGrid, RectTransform>("m_gridRoot"); private static float _lastBarY = float.NaN; private static readonly Vector3[] Corners = (Vector3[])(object)new Vector3[4]; private const float ConfirmWindow = 5f; private const float MinConfirmDelay = 0.3f; private static OrganizePlan _pendingPlan; private static Container _pendingChest; private static float _pendingAt; [HarmonyPostfix] [HarmonyPatch("Show")] private static void ShowPostfix(InventoryGui __instance, Container container) { if ((Object)(object)container != (Object)(object)_pendingChest) { ClearPending(); } _current = container; _lastBarY = float.NaN; EnsureBar(__instance); PositionBar(__instance); Refresh(); } [HarmonyPostfix] [HarmonyPatch("UpdateContainer")] private static void UpdateContainerPostfix(InventoryGui __instance) { if (!((Object)(object)_current == (Object)null) && !((Object)(object)_bar == (Object)null) && ((Component)_bar).gameObject.activeSelf) { PositionBar(__instance); } } [HarmonyPostfix] [HarmonyPatch("Hide")] private static void HidePostfix() { _current = null; ClearPending(); if ((Object)(object)_bar != (Object)null) { ((Component)_bar).gameObject.SetActive(false); } } [HarmonyPostfix] [HarmonyPatch("Update")] private static void UpdatePostfix() { if (_pendingPlan != null && Time.time - _pendingAt > 5f) { ClearPending(); } } private static void EnsureBar(InventoryGui gui) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Expected O, but got Unknown //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Expected O, but got Unknown if ((Object)(object)_bar != (Object)null) { return; } Button takeAllButton = gui.m_takeAllButton; if (!((Object)(object)takeAllButton == (Object)null)) { RectTransform component = ((Component)takeAllButton).GetComponent<RectTransform>(); Transform parent = ((Transform)component).parent; if (!((Object)(object)((parent is RectTransform) ? parent : null) == (Object)null)) { GameObject val = new GameObject("psort_bar", new Type[1] { typeof(RectTransform) }); _bar = (RectTransform)val.transform; ((Transform)_bar).SetParent(((Transform)component).parent, false); _bar.anchorMin = new Vector2(0f, 0f); _bar.anchorMax = new Vector2(0f, 0f); _bar.pivot = new Vector2(0f, 1f); HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f; ((LayoutGroup)obj).childAlignment = (TextAnchor)6; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ContentSizeFitter obj2 = val.AddComponent<ContentSizeFitter>(); obj2.horizontalFit = (FitMode)2; obj2.verticalFit = (FitMode)2; _sorterBtn = MakeButton(takeAllButton, "psort_toggle", new UnityAction(OnSorterClick), out _sorterLabel); _pinBtn = MakeButton(takeAllButton, "psort_pin", new UnityAction(OnPinClick), out _pinLabel); _clearBtn = MakeButton(takeAllButton, "psort_clear", new UnityAction(OnClearClick), out _clearLabel); _pullBtn = MakeButton(takeAllButton, "psort_pull", new UnityAction(OnPullClick), out _pullLabel); _organizeBtn = MakeButton(takeAllButton, "psort_organize", new UnityAction(OnOrganizeClick), out _organizeLabel); } } } private unsafe static void PositionBar(InventoryGui gui) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_bar == (Object)null) { return; } Button takeAllButton = gui.m_takeAllButton; if ((Object)(object)takeAllButton == (Object)null) { return; } RectTransform component = ((Component)takeAllButton).GetComponent<RectTransform>(); Transform parent = ((Transform)component).parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); if ((Object)(object)val == (Object)null) { return; } Rect rect = val.rect; Rect val2 = component.rect; float width = ((Rect)(ref val2)).width; val2 = component.rect; float height = ((Rect)(ref val2)).height; float num = ((Rect)(ref rect)).x + ((Rect)(ref rect)).width * component.anchorMin.x; float num2 = ((Rect)(ref rect)).y + ((Rect)(ref rect)).height * component.anchorMin.y; float num3 = num + component.anchoredPosition.x + (0.5f - component.pivot.x) * width; float num4 = num2 + component.anchoredPosition.y + (0.5f - component.pivot.y) * height; float num5 = num3 - width * 0.5f - ((Rect)(ref rect)).xMin; float num6 = ((Rect)(ref rect)).yMax - (num4 + height * 0.5f) + height; if (TryGetUsedGridBottom(gui, val, out var bottomLocalY) && ((Rect)(ref rect)).yMin + num6 > bottomLocalY - 6f) { num6 = bottomLocalY - 6f - ((Rect)(ref rect)).yMin; float num7 = 0f - height; if (num6 < num7) { num6 = num7; } } if (float.IsNaN(_lastBarY) || Mathf.Abs(num6 - _lastBarY) > 0.5f) { _bar.anchoredPosition = new Vector2(num5, num6); _lastBarY = num6; ManualLogSource log = Plugin.Log; string[] obj = new string[8] { "[ui] panel=", null, null, null, null, null, null, null }; val2 = rect; obj[1] = ((object)(*(Rect*)(&val2))/*cast due to .constrained prefix*/).ToString(); obj[2] = " usedGridBottom="; obj[3] = bottomLocalY.ToString(); obj[4] = " barY="; obj[5] = num6.ToString(); obj[6] = " leftMargin="; obj[7] = num5.ToString(); log.LogDebug((object)string.Concat(obj)); } } private static bool TryGetUsedGridBottom(InventoryGui gui, RectTransform space, out float bottomLocalY) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) bottomLocalY = 0f; InventoryGrid val = ContainerGridRef.Invoke(gui); if ((Object)(object)val == (Object)null) { return false; } RectTransform val2 = GridRootRef.Invoke(val); if ((Object)(object)val2 == (Object)null || ((Transform)val2).childCount == 0) { return false; } float num = float.MaxValue; bool flag = false; for (int i = 0; i < ((Transform)val2).childCount; i++) { Transform child = ((Transform)val2).GetChild(i); if ((Object)(object)child == (Object)null || !((Component)child).gameObject.activeSelf) { continue; } RectTransform val3 = (RectTransform)(object)((child is RectTransform) ? child : null); if (!((Object)(object)val3 == (Object)null)) { val3.GetWorldCorners(Corners); float y = ((Transform)space).InverseTransformPoint(Corners[0]).y; if (y < num) { num = y; flag = true; } } } if (!flag) { return false; } bottomLocalY = num; return true; } private static Button MakeButton(Button template, string name, UnityAction onClick, out TMP_Text label) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) Button val = Object.Instantiate<Button>(template, (Transform)(object)_bar); ((Object)val).name = name; Localize componentInChildren = ((Component)val).GetComponentInChildren<Localize>(true); if ((Object)(object)componentInChildren != (Object)null) { Object.DestroyImmediate((Object)(object)componentInChildren); } UIGamePad[] componentsInChildren = ((Component)val).GetComponentsInChildren<UIGamePad>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren[i]); } val.onClick = new ButtonClickedEvent(); ((UnityEvent)val.onClick).AddListener(onClick); RectTransform component = ((Component)template).GetComponent<RectTransform>(); LayoutElement obj = ((Component)val).gameObject.AddComponent<LayoutElement>(); Rect rect = component.rect; obj.preferredWidth = ((Rect)(ref rect)).width; rect = component.rect; obj.preferredHeight = ((Rect)(ref rect)).height; label = ((Component)val).GetComponentInChildren<TMP_Text>(); if ((Object)(object)label != (Object)null) { float fontSize = label.fontSize; label.enableAutoSizing = true; label.fontSizeMax = fontSize; label.fontSizeMin = fontSize - 4f; } return val; } private static void OnSorterClick() { if (!((Object)(object)_current == (Object)null)) { bool flag = !SorterZdo.IsSorter(_current); SorterZdo.SetSorter(_current, flag); ClearPending(); Msg(flag ? "Sorter enabled. Contents distribute when the chest is closed" : "Sorter disabled"); Refresh(); } } private static void OnPinClick() { if ((Object)(object)_current == (Object)null) { return; } if (Filters.GetPinned(_current).Count <= 0) { if (Filters.PinContents(_current) > 0) { Filters.SetManual(_current, manual: false); List<string> pinned = Filters.GetPinned(_current); Msg("Pinned: " + string.Join(", ", pinned) + " (auto-fill on)"); Plugin.Log.LogInfo((object)("[pin] " + string.Join(", ", pinned))); } else { bool flag = !Filters.GetManual(_current); Filters.SetManual(_current, flag); Msg(flag ? "Chest is empty - marked Manual. Organize will not claim or fill it" : "Manual off. Organize may claim this empty chest as a home"); } } else { bool flag2 = !Filters.GetManual(_current); Filters.SetManual(_current, flag2); Msg(flag2 ? "Auto-fill off. This chest only fills when you click Pull" : "Auto-fill on. The sorter routes matching items here"); } Refresh(); } private static void OnClearClick() { if (!((Object)(object)_current == (Object)null)) { Filters.ClearPinned(_current); Filters.SetManual(_current, manual: false); Filters.ClearHome(_current); Msg("Filters cleared"); Refresh(); } } private static void OnPullClick() { if (!((Object)(object)_current == (Object)null)) { Puller.PullInto(_current, out var movedTotal, out var typesMoved); Msg((movedTotal > 0) ? ("Pulled " + movedTotal + " item" + ((movedTotal == 1) ? "" : "s") + " (" + typesMoved + " type" + ((typesMoved == 1) ? "" : "s") + ")") : "Nothing to pull from nearby chests"); } } private static void OnOrganizeClick() { if ((Object)(object)_current == (Object)null) { return; } if (_pendingPlan != null && (Object)(object)_pendingChest == (Object)(object)_current && Time.time - _pendingAt <= 5f) { if (!(Time.time - _pendingAt < 0.3f)) { Container current = _current; ClearPending(); OrganizePlan organizePlan = Organizer.BuildPlan(current, Plugin.SorterRadius.Value); if (organizePlan.IsEmpty) { Msg("Nothing left to organize"); } else { Organizer.Execute(organizePlan); } } return; } OrganizePlan organizePlan2 = Organizer.BuildPlan(_current, Plugin.SorterRadius.Value); if (organizePlan2.IsEmpty) { ClearPending(); Msg("Nothing to organize"); return; } _pendingPlan = organizePlan2; _pendingChest = _current; _pendingAt = Time.time; OrganizeSummary summary = organizePlan2.Summary; if ((Object)(object)_organizeLabel != (Object)null) { _organizeLabel.text = "Confirm?"; } Plugin.Log.LogInfo((object)("[organize] plan ready: " + summary.TotalItems + " items -> " + summary.TargetChests + " chest(s) from " + summary.SourceChests + " source(s); awaiting confirm")); string text = "Organize: move " + summary.TotalItems + " item" + ((summary.TotalItems == 1) ? "" : "s") + " across " + summary.TargetChests + " chest" + ((summary.TargetChests == 1) ? "" : "s"); if (summary.HomelessItems > 0) { text = text + " (" + summary.HomelessItems + " won't fit - add more chests)"; } Msg(text + " - press again to confirm"); } private static void ClearPending() { _pendingPlan = null; _pendingChest = null; _pendingAt = 0f; if ((Object)(object)_organizeLabel != (Object)null) { _organizeLabel.text = "Organize"; } } private static void Refresh() { if ((Object)(object)_bar == (Object)null) { return; } bool flag = (Object)(object)_current != (Object)null && SorterZdo.HasValidNView(_current); ((Component)_bar).gameObject.SetActive(flag); if (!flag) { return; } bool flag2 = SorterZdo.IsSorter(_current); _sorterLabel.text = (flag2 ? "Sorter: ON" : "Sorter: OFF"); bool flag3 = !flag2; bool flag4 = false; bool flag5 = false; if (flag3) { int count = Filters.GetPinned(_current).Count; bool manual = Filters.GetManual(_current); FilterSpec spec = Filters.GetSpec(_current); _pinLabel.text = ((count != 0) ? (manual ? ("Manual (" + count + ")") : ("Auto (" + count + ")")) : (manual ? "Manual" : "Pin")); flag4 = count > 0 || manual || !string.IsNullOrEmpty(spec.Home); if (flag4) { _clearLabel.text = "Clear"; } flag5 = spec.HasExplicit; if (flag5) { _pullLabel.text = "Pull"; } } if (flag2) { _organizeLabel.text = ((_pendingPlan != null && (Object)(object)_pendingChest == (Object)(object)_current) ? "Confirm?" : "Organize"); } ((Component)_pinBtn).gameObject.SetActive(flag3); ((Component)_clearBtn).gameObject.SetActive(flag4); ((Component)_pullBtn).gameObject.SetActive(flag5); ((Component)_organizeBtn).gameObject.SetActive(flag2); LinkGamepadNav(); } private static void LinkGamepadNav() { if (!((Object)(object)_bar == (Object)null)) { List<Selectable> row = new List<Selectable> { (Selectable)(object)_sorterBtn, (Selectable)(object)_pinBtn, (Selectable)(object)_clearBtn, (Selectable)(object)_pullBtn, (Selectable)(object)_organizeBtn }; GamepadNav.LinkRow(row); InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.m_takeAllButton != (Object)null) { GamepadNav.AttachRowToAnchor((Selectable)(object)instance.m_takeAllButton, row); } } } private static void Msg(string text) { if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false); } } } [HarmonyPatch(typeof(InventoryHandler), "RPC_RequestItemRemoveResponse", new Type[] { typeof(Inventory), typeof(RequestChestRemoveResponse) })] internal static class InventoryHandler_RemoveResponse_Patch { private static void Postfix(RequestChestRemoveResponse response) { if (response != null) { MucResults.RecordRemove(response.SourceID, response.Success, response.Amount); } } } [HarmonyPatch] internal static class PlaceGatherPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "UpdatePlacement", new Type[2] { typeof(bool), typeof(float) }, (Type[])null); } [HarmonyPrefix] private static void Prefix(Player __instance, bool takeInput) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (!Gather.IsEnabled || !Gather.IsBuildEnabled || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || !takeInput || !((Character)__instance).InPlaceMode() || Hud.IsPieceSelectionVisible() || __instance.PlacementCostDisabled || (!ZInput.GetButtonDown("Attack") && !ZInput.GetButtonDown("JoyPlace")) || ZInput.GetButton("AltPlace") || ZInput.GetButton("JoyAltKeys")) { return; } Piece selectedPiece = __instance.GetSelectedPiece(); if ((Object)(object)selectedPiece == (Object)null || selectedPiece.m_resources == null || selectedPiece.m_resources.Length == 0 || __instance.HaveRequirements(selectedPiece, (RequirementMode)0) || (int)__instance.GetPlacementStatus() != 0) { return; } BuildGather.Invalidate(); List<GatherNeed> list = GatherMath.Resolve(BuildGather.NeedsFor(selectedPiece), onlyOneIngredient: false); if (list.Count != 0) { Gatherer.Pull(list, out var movedTotal, out var typesMoved); BuildGather.Invalidate(); if (movedTotal > 0) { Plugin.Log.LogInfo((object)("[gather] place-triggered pull: " + movedTotal + " item(s), " + typesMoved + " type(s) for " + ((Object)selectedPiece).name)); } } } } [HarmonyPatch(typeof(Smelter), "Awake")] internal static class Smelter_Awake_Patch { private static void Postfix(Smelter __instance) { Stations.RegisterProcessor((Component)(object)__instance, __instance.m_name); } } [HarmonyPatch(typeof(Smelter), "OnDestroyed")] internal static class Smelter_OnDestroyed_Patch { private static void Prefix(Smelter __instance) { Stations.UnregisterProcessor((Component)(object)__instance); } } [HarmonyPatch(typeof(Fermenter), "Awake")] internal static class Fermenter_Awake_Patch { private static void Postfix(Fermenter __instance) { Stations.RegisterProcessor((Component)(object)__instance, __instance.m_name); } } [HarmonyPatch(typeof(Fermenter), "OnDestroyed")] internal static class Fermenter_OnDestroyed_Patch { private static void Prefix(Fermenter __instance) { Stations.UnregisterProcessor((Component)(object)__instance); } } [HarmonyPatch(typeof(CookingStation), "Awake")] internal static class CookingStation_Awake_Patch { private static void Postfix(CookingStation __instance) { Stations.RegisterProcessor((Component)(object)__instance, __instance.m_name); } } [HarmonyPatch(typeof(CookingStation), "OnDestroyed")] internal static class CookingStation_OnDestroyed_Patch { private static void Prefix(CookingStation __instance) { Stations.UnregisterProcessor((Component)(object)__instance); } } [HarmonyPatch(typeof(Sign), "Awake")] internal static class Sign_Awake_Patch { private static void Postfix(Sign __instance) { Filters.RegisterSign(__instance); } } [HarmonyPatch(typeof(Sign), "SetText")] internal static class Sign_SetText_Patch { private static void Postfix() { Filters.InvalidateAll(); } } } namespace ChestButler.Core { internal static class BucketKeys { internal const string GearPrefix = "gear:"; internal const string Weapons = "gear:weapons"; internal const string Armor = "gear:armor"; internal const string Tools = "gear:tools"; internal const string GearMisc = "gear:misc"; internal const string TypePrefix = "item:"; internal const string Misc = "misc"; internal static string ForType(string norm) { return "item:" + norm; } internal static bool IsGear(string key) { return key?.StartsWith("gear:", StringComparison.Ordinal) ?? false; } internal static bool IsPerType(string key) { return key?.StartsWith("item:", StringComparison.Ordinal) ?? false; } internal static string TypeOf(string key) { if (!IsPerType(key)) { return null; } return key.Substring("item:".Length); } internal static string Label(string key) { if (string.IsNullOrEmpty(key)) { return "?"; } if (IsGear(key)) { return key.Substring("gear:".Length); } if (IsPerType(key)) { return key.Substring("item:".Length); } return key; } } internal static class BuildGather { private const float SourceMaxAge = 1f; private static List<Container> _sources; private static float _sourcesAt = float.NegativeInfinity; internal static List<Container> SourcesCached() { float unscaledTime = Time.unscaledTime; if (_sources == null || unscaledTime - _sourcesAt > 1f) { _sources = Gatherer.Sources(); _sourcesAt = unscaledTime; } return _sources; } internal static void Invalidate() { _sources = null; } internal static List<GatherNeed> NeedsFor(Piece piece) { List<GatherNeed> list = new List<GatherNeed>(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)piece == (Object)null || (Object)(object)localPlayer == (Object)null || piece.m_resources == null) { return list; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); List<Container> sources = SourcesCached(); Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { SharedData val2 = val?.m_resItem?.m_itemData?.m_shared; if (val2 != null && !string.IsNullOrEmpty(val2.m_name) && val.m_amount > 0) { list.Add(new GatherNeed { SharedName = val2.m_name, Display = Names.Normalize(val2.m_name), Needed = val.m_amount, InPlayer = ((inventory != null) ? inventory.CountItems(val2.m_name, -1, true) : 0), InStorage = Gatherer.CountInStorage(sources, val2.m_name) }); } } return list; } } internal static class ContainerTracker { internal struct Candidate { internal Container Chest; internal float Distance; } private static readonly HashSet<Container> All = new HashSet<Container>(); private static readonly HashSet<Container> Vehicles = new HashSet<Container>(); internal static void Register(Container c) { if (!((Object)(object)c == (Object)null) && !((Object)(object)((Component)c).GetComponentInParent<Incinerator>() != (Object)null)) { All.Add(c); if ((Object)(object)((Component)c).GetComponentInParent<Vagon>() != (Object)null || (Object)(object)((Component)c).GetComponentInParent<Ship>() != (Object)null) { Vehicles.Add(c); } } } internal static void Unregister(Container c) { if (!((Object)(object)c == (Object)null)) { All.Remove(c); Vehicles.Remove(c); Filters.Invalidate(c); } } internal static List<Container> Candidates(Container sorter, float radius, bool excludeSorters = true) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return Accessible(((Component)sorter).transform.position, radius, sorter, excludeSorters); } internal static List<Container> AccessibleNear(Vector3 point, float radius, bool excludeSorters = false) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Accessible(point, radius, null, excludeSorters); } private static List<Container> Accessible(Vector3 pos, float radius, Container exclude, bool excludeSorters) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) All.RemoveWhere((Container c) => (Object)(object)c == (Object)null); List<float> dists = new List<float>(); List<Container> found = new List<Container>(); bool flag = Plugin.VehiclesAreStorage != null && Plugin.VehiclesAreStorage.Value; foreach (Container item in All) { if (!((Object)(object)item == (Object)(object)exclude) && (flag || !Vehicles.Contains(item)) && SorterZdo.HasValidNView(item) && item.GetInventory() != null && !((Object)(object)((Component)item).GetComponentInParent<Piece>() == (Object)null)) { float num = Vector3.Distance(pos, ((Component)item).transform.position); if (!(num > radius) && (!excludeSorters || !SorterZdo.IsSorter(item)) && SorterZdo.PlayerCanAccess(item) && PrivateArea.CheckAccess(((Component)item).transform.position, 0f, false, true)) { dists.Add(num); found.Add(item); } } } List<int> list = new List<int>(found.Count); for (int num2 = 0; num2 < found.Count; num2++) { list.Add(num2); } list.Sort(delegate(int a, int b) { int num3 = dists[a].CompareTo(dists[b]); return (num3 != 0) ? num3 : CompareUid(found[a], found[b]); }); List<Container> list2 = new List<Container>(list.Count); foreach (int item2 in list) { list2.Add(found[item2]); } return list2; } internal static List<Candidate> CandidatesWithDistance(Container sorter, float radius, bool excludeSorters = true) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) List<Container> list = Candidates(sorter, radius, excludeSorters); List<Candidate> list2 = new List<Candidate>(list.Count); Vector3 position = ((Component)sorter).transform.position; foreach (Container item in list) { list2.Add(new Candidate { Chest = item, Distance = Vector3.Distance(position, ((Component)item).transform.position) }); } return list2; } internal static int CompareUid(Container a, Container b) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) ZNetView val = SorterZdo.NView(a); ZNetView val2 = SorterZdo.NView(b); if ((Object)(object)val == (Object)null || !val.IsValid()) { if (!((Object)(object)val2 == (Object)null) && val2.IsValid()) { return 1; } return 0; } if ((Object)(object)val2 == (Object)null || !val2.IsValid()) { return -1; } return ((ZDOID)(ref val.GetZDO().m_uid)).CompareTo(val2.GetZDO().m_uid); } internal static Container NearestTo(Vector3 point, float maxRange) { //IL_0022: 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) Container result = null; float num = maxRange; foreach (Container item in All) { if (!((Object)(object)item == (Object)null)) { float num2 = Vector3.Distance(point, ((Component)item).transform.position); if (num2 < num) { num = num2; result = item; } } } return result; } } internal sealed class FilterSpec { internal readonly HashSet<string> Items = new HashSet<string>(); internal readonly HashSet<string> GroupNames = new HashSet<string>(); internal int Priority; internal bool Ignore; internal bool ManualOnly; internal string Home; internal bool HasExplicit { get { if (Items.Count <= 0) { return GroupNames.Count > 0; } return true; } } internal bool MatchesItem(string normName) { foreach (string item in Items) { if (Names.Matches(item, normName)) { return true; } } return false; } internal bool MatchesGroup(string normName) { foreach (string groupName in GroupNames) { if (Groups.GroupContains(groupName, normName)) { return true; } } return false; } } internal static class Filters { private const float SignRange = 2.5f; private const float CacheTtl = 30f; private static readonly int ItemsHash = StringExtensionMethods.GetStableHashCode("psort_items"); private static readonly int ManualHash = StringExtensionMethods.GetStableHashCode("psort_manual"); private static readonly int HomeHash = StringExtensionMethods.GetStableHashCode("psort_home"); private static readonly HashSet<Sign> Signs = new HashSet<Sign>(); private static readonly Dictionary<Container, KeyValuePair<float, FilterSpec>> Cache = new Dictionary<Container, KeyValuePair<float, FilterSpec>>(); internal static void RegisterSign(Sign s) { if ((Object)(object)s != (Object)null) { Signs.Add(s); InvalidateAll(); } } internal static void Invalidate(Container c) { if ((Object)(object)c != (Object)null) { Cache.Remove(c); } } internal static void InvalidateAll() { Cache.Clear(); } internal static List<string> GetPinned(Container c) { List<string> list = new List<string>(); ZNetView val = SorterZdo.NView(c); if ((Object)(object)val == (Object)null || !val.IsValid()) { return list; } string[] array = val.GetZDO().GetString(ItemsHash, "").Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } return list; } internal static void SetPinned(Container c, IEnumerable<string> tokens) { ZNetView val = SorterZdo.NView(c); if (!((Object)(object)val == (Object)null) && val.IsValid()) { if (!val.IsOwner()) { val.ClaimOwnership(); } val.GetZDO().Set(ItemsHash, string.Join(",", tokens)); Cache.Remove(c); } } internal static int PinContents(Container c) { Inventory inventory = c.GetInventory(); if (inventory == null) { return 0; } HashSet<string> hashSet = new HashSet<string>(); foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem?.m_shared != null) { string text = Names.Normalize(allItem.m_shared.m_name); if (text.Length > 0) { hashSet.Add(text); } } } if (hashSet.Count > 0) { SetPinned(c, hashSet); } return hashSet.Count; } internal static void ClearPinned(Container c) { SetPinned(c, new string[0]); } internal static bool GetManual(Container c) { ZNetView val = SorterZdo.NView(c); if ((Object)(object)val != (Object)null && val.IsValid()) { return val.GetZDO().GetBool(ManualHash, false); } return false; } internal static void SetManual(Container c, bool manual) { ZNetView val = SorterZdo.NView(c); if (!((Object)(object)val == (Object)null) && val.IsValid()) { if (!val.IsOwner()) { val.ClaimOwnership(); } val.GetZDO().Set(ManualHash, manual); Cache.Remove(c); } } internal static string GetHome(Container c) { ZNetView val = SorterZdo.NView(c); if ((Object)(object)val == (Object)null || !val.IsValid()) { return null; } string text = val.GetZDO().GetString(HomeHash, ""); if (!string.IsNullOrEmpty(text)) { return text; } return null; } internal static void SetHome(Container c, string bucketKey) { ZNetView val = SorterZdo.NView(c); if (!((Object)(object)val == (Object)null) && val.IsValid()) { if (!val.IsOwner()) { val.ClaimOwnership(); } val.GetZDO().Set(HomeHash, bucketKey ?? ""); Cache.Remove(c); } } internal static void ClearHome(Container c) { SetHome(c, null); } internal static FilterSpec GetSpec(Container c) { if (Cache.TryGetValue(c, out var value) && Time.time - value.Key < 30f) { return value.Value; } FilterSpec filterSpec = new FilterSpec(); foreach (string item in GetPinned(c)) { filterSpec.Items.Add(item); } filterSpec.ManualOnly = GetManual(c); filterSpec.Home = GetHome(c); ParseNearestSign(c, filterSpec); Cache[c] = new KeyValuePair<float, FilterSpec>(Time.time, filterSpec); return filterSpec; } private static void ParseNearestSign(Container c, FilterSpec spec) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_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_00b6: Unknown result type (might be due to invalid IL or missing references) Signs.RemoveWhere((Sign s) => (Object)(object)s == (Object)null); Vector3 position = ((Component)c).transform.position; foreach (Sign sign in Signs) { string text = sign.GetText(); if (string.IsNullOrEmpty(text)) { continue; } SignSpec signSpec = SignGrammar.Parse(text); float num = Vector3.Distance(((Component)sign).transform.position, position); if (signSpec.HasOff && signSpec.AreaOffRadius > 0f && num <= signSpec.AreaOffRadius) { spec.Ignore = true; } if (num > 2.5f || !IsNearestContainerTo(((Component)sign).transform.position, c)) { continue; } if (signSpec.HasOff) { spec.Ignore = true; } foreach (string token in signSpec.Tokens) { if (token.Length >= 2 && token[0] == 'p' && int.TryParse(token.Substring(1), out var result)) { spec.Priority = result; } else if (Groups.IsGroup(token)) { spec.GroupNames.Add(token); } else { spec.Items.Add(token); } } } } private static bool IsNearestContainerTo(Vector3 signPos, Container candidate) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Container val = ContainerTracker.NearestTo(signPos, 2.5f); if (!((Object)(object)val == (Object)null)) { return (Object)(object)val == (Object)(object)candidate; } return true; } } internal static class GamepadNav { private static ConfigEntry<bool> _enabled; private static bool _bound; internal static bool Enabled { get { //IL_003b: 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_0048: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown if (!_bound) { _bound = true; Plugin instance = Plugin.Instance; if ((Object)(object)instance != (Object)null) { _enabled = ((BaseUnityPlugin)instance).Config.Bind<bool>("Gamepad", "Enabled", true, new ConfigDescription("Let a controller reach ChestButler's buttons by linking them into the panel's D-pad navigation. Client-side.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = false } })); } } if (_enabled != null) { return _enabled.Value; } return true; } } internal static void LinkRow(IList<Selectable> row) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || row == null) { return; } List<Selectable> list = new List<Selectable>(); for (int i = 0; i < row.Count; i++) { Selectable val = row[i]; if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy) { list.Add(val); } } if (list.Count != 0) { for (int j = 0; j < list.Count; j++) { Navigation navigation = list[j].navigation; ((Navigation)(ref navigation)).mode = (Mode)4; ((Navigation)(ref navigation)).selectOnLeft = ((j > 0) ? list[j - 1] : null); ((Navigation)(ref navigation)).selectOnRight = ((j < list.Count - 1) ? list[j + 1] : null); list[j].navigation = navigation; } } } internal static void LinkVertical(Selectable above, Selectable below, bool onlyIfVanillaEmpty) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (Enabled && !((Object)(object)above == (Object)null) && !((Object)(object)below == (Object)null)) { Navigation navigation = above.navigation; Navigation navigation2 = below.navigation; bool flag = false; if (!onlyIfVanillaEmpty || (Object)(object)((Navigation)(ref navigation)).selectOnDown == (Object)null) { ((Navigation)(ref navigation)).mode = (Mode)4; ((Navigation)(ref navigation)).selectOnDown = below; above.navigation = navigation; flag = true; } ((Navigation)(ref navigation2)).mode = (Mode)4; ((Navigation)(ref navigation2)).selectOnUp = above; below.navigation = navigation2; if (!flag) { Plugin.Log.LogDebug((object)("[gamepad] '" + ((Object)above).name + "' already navigates down to '" + (((Object)(object)((Navigation)(ref navigation)).selectOnDown != (Object)null) ? ((Object)((Navigation)(ref navigation)).selectOnDown).name : "?") + "'; left it alone. Our button is reachable upward from '" + ((Object)below).name + "' only.")); } } } internal static void AttachRowToAnchor(Selectable anchor, IList<Selectable> row) { if (!Enabled || (Object)(object)anchor == (Object)null || row == null) { return; } for (int i = 0; i < row.Count; i++) { Selectable val = row[i]; if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { LinkVertical(anchor, val, onlyIfVanillaEmpty: true); break; } } } } internal static class Gather { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> BuildEnabled; internal static ConfigEntry<bool> ShowStorageCounts; internal static bool IsEnabled { get { if (Enabled != null) { return Enabled.Value; } return true; } } internal static bool IsBuildEnabled { get { if (BuildEnabled != null) { return BuildEnabled.Value; } return true; } } internal static bool CountsShown { get { if (ShowStorageCounts != null) { return ShowStorageCounts.Value; } return true; } } internal static void Init(ConfigFile config) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown Enabled = config.Bind<bool>("Gather", "Enabled", true, new ConfigDescription("Show the Gather button in the crafting panel. Client-side.", (AcceptableValueBase)null, Array.Empty<object>())); BuildEnabled = config.Bind<bool>("Gather", "BuildEnabled", true, new ConfigDescription("When you place a piece you can't afford, fetch the missing materials from nearby chests. Client-side.", (AcceptableValueBase)null, Array.Empty<object>())); ShowStorageCounts = config.Bind<bool>("Gather", "ShowStorageCounts", true, new ConfigDescription("Show \"(N in storage)\" beside each ingredient in the crafting panel. Client-side.", (AcceptableValueBase)null, Array.Empty<object>())); } } internal static class Gatherer { internal static List<Container> Sources() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) List<Container> list = new List<Container>(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return list; } foreach (Container item in ContainerTracker.AccessibleNear(((Component)localPlayer).transform.position, Plugin.SorterRadius.Value)) { if (!Filters.GetSpec(item).Ignore) { list.Add(item); } } return list; } internal static int CountInStorage(List<Container> sources, string sharedName) { if (sources == null || string.IsNullOrEmpty(sharedName)) { return 0; } int num = 0; for (int i = 0; i < sources.Count; i++) { Inventory inventory = sources[i].GetInventory(); if (inventory != null) { num += inventory.CountItems(sharedName, -1, true); } } return num; } internal static void Pull(List<GatherNeed> needs, out int movedTotal, out int typesMoved) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) movedTotal = 0; typesMoved = 0; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || needs == null || needs.Count == 0) { return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory == null) { return; } ZDOID zDOID = ((Character)localPlayer).GetZDOID(); List<Container> list = Sources(); int num = 0; foreach (GatherNeed need in needs) { int num2 = need.Gatherable; if (num2 <= 0) { continue; } bool flag = false; List<KeyValuePair<int, Container>> list2 = new List<KeyValuePair<int, Container>>(); for (int i = 0; i < list.Count; i++) { Inventory inventory2 = list[i].GetInventory(); if (inventory2 != null) { int num3 = inventory2.CountItems(need.SharedName, -1, true); if (num3 > 0) { list2.Add(new KeyValuePair<int, Container>(num3, list[i])); } } } list2.Sort(delegate(KeyValuePair<int, Container> a, KeyValuePair<int, Container> b) { int num6 = b.Key.CompareTo(a.Key); return (num6 == 0) ? ContainerTracker.CompareUid(a.Value, b.Value) : num6; }); foreach (KeyValuePair<int, Container> item in list2) { if (num2 <= 0) { break; } Container value = item.Value; Inventory inventory3 = value.GetInventory(); if (inventory3 == null) { continue; } ZNetView val = SorterZdo.NView(value); if ((Object)(object)val == (Object)null || !val.IsValid() || !SorterZdo.PlayerCanAccess(value) || !PrivateArea.CheckAccess(((Component)value).transform.position, 0f, false, true)) { continue; } InventoryBlock val2 = InventoryBlock.Get(inventory3); foreach (ItemData item2 in new List<ItemData>(inventory3.GetAllItems())) { if (num2 <= 0) { break; } if (item2?.m_shared != null && !(item2.m_shared.m_name != need.SharedName) && (val2 == null || !val2.IsSlotBlocked(item2.m_gridPos))) { int num4 = Router.Room(inventory, item2) - num; if (num4 <= 0) { num2 = 0; break; } int num5 = Math.Min(Math.Min(num2, item2.m_stack), num4); if (num5 > 0) { ContainerHandler.RemoveItemFromChest(value, item2, inventory, new Vector2i(-1, -1), zDOID, num5, (ItemData)null); num += num5; movedTotal += num5; num2 -= num5; flag = true; } } } } if (flag) { typesMoved++; } } if (movedTotal > 0) { Plugin.Log.LogInfo((object)("[gather] pulled " + movedTotal + " item(s) across " + typesMoved + " type(s)")); } } } internal struct GatherNeed { public string SharedName; public string Display; public int Needed; public int InPlayer; public int InStorage; public int Shortfall => Math.Max(0, Needed - InPlayer); public int Gatherable => Math.Min(Shortfall, InStorage); } internal static class GatherMath { internal static List<GatherNeed> Resolve(IList<GatherNeed> raw, bool onlyOneIngredient) { List<GatherNeed> list = new List<GatherNeed>(); if (raw == null || raw.Count == 0) { return list; } if (!onlyOneIngredient) { for (int i = 0; i < raw.Count; i++) { if (raw[i].Gatherable > 0) { list.Add(raw[i]); } } return list; } int num = PickSingleIngredient(raw); if (num >= 0 && raw[num].Gatherable > 0) { list.Add(raw[num]); } return list; } internal static int PickSingleIngredient(IList<GatherNeed> raw) { for (int i = 0; i < raw.Count; i++) { if (raw[i].Shortfall == 0) { return -1; } } int num = -1; for (int j = 0; j < raw.Count; j++) { if (raw[j].Gatherable <= 0) { continue; } if (num < 0) { num = j; continue; } int num2 = raw[j].Shortfall - raw[j].Gatherable; int num3 = raw[num].Shortfall - raw[num].Gatherable; if (num2 < num3) { num = j; } else if (num2 <= num3) { if (raw[j].Shortfall < raw[num].Shortfall) { num = j; } else if (raw[j].Shortfall <= raw[num].Shortfall && string.CompareOrdinal(raw[j].SharedName ?? "", raw[num].SharedName ?? "") < 0) { num = j; } } } return num; } } internal static class Gear { private static readonly Dictionary<ItemType, string> ByType = new Dictionary<ItemType, string> { { (ItemType)3, "gear:weapons" }, { (ItemType)14, "gear:weapons" }, { (ItemType)22, "gear:weapons" }, { (ItemType)4, "gear:weapons" }, { (ItemType)5, "gear:weapons" }, { (ItemType)15, "gear:weapons" }, { (ItemType)20, "gear:weapons" }, { (ItemType)6, "gear:armor" }, { (ItemType)7, "gear:armor" }, { (ItemType)11, "gear:armor" }, { (ItemType)17, "gear:armor" }, { (ItemType)12, "gear:armor" }, { (ItemType)18, "gear:armor" }, { (ItemType)24, "gear:armor" }, { (ItemType)19, "gear:tools" } }; private static ConfigEntry<string> _weaponTokens; private static ConfigEntry<string> _armorTokens; private static ConfigEntry<string> _toolTokens; private static readonly List<string> Weapon = new List<string>(); private static readonly List<string> Armour = new List<string>(); private static readonly List<string> Tool = new List<string>(); private static readonly HashSet<string> Reported = new HashSet<string>(); internal static void Init(ConfigFile config) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown _toolTokens = config.Bind<string>("Organize", "ToolTokens", "pickaxe*", new ConfigDescription("Item name tokens ('*' wildcards) forced into the TOOLS gear bucket, overriding the item's own type. Default: pickaxes, which the game classifies as weapons. Comma-separated.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); _weaponTokens = config.Bind<string>("Organize", "WeaponTokens", "", new ConfigDescription("Item name tokens forced into the WEAPONS gear bucket. Comma-separated.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); _armorTokens = config.Bind<string>("Organize", "ArmorTokens", "", new ConfigDescription("Item name tokens forced into the ARMOR gear bucket. Comma-separated.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); config.SettingChanged += delegate { Rebuild(); }; Rebuild(); } private static void Rebuild() { Fill(Weapon, _weaponTokens); Fill(Armour, _armorTokens); Fill(Tool, _toolTokens); Reported.Clear(); } private static void Fill(List<string> into, ConfigEntry<string> entry) { into.Clear(); if (entry == null || string.IsNullOrEmpty(entry.Value)) { return; } string[] array = entry.Value.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim().ToLowerInvariant(); if (text.Length > 0) { into.Add(text); } } } internal static string BucketFor(ItemData item, string norm) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (item?.m_shared == null) { return "gear:misc"; } if (MatchesAny(Tool, norm)) { return "gear:tools"; } if (MatchesAny(Weapon, norm)) { return "gear:weapons"; } if (MatchesAny(Armour, norm)) { return "gear:armor"; } if (ByType.TryGetValue(item.m_shared.m_itemType, out var value)) { return value; } if (Reported.Add(norm)) { Plugin.Log.LogInfo((object)("[organize] '" + norm + "' (item type " + ((object)Unsafe.As<ItemType, ItemType>(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString() + ") has no gear bucket - filed under " + BucketKeys.Label("gear:misc") + ". Add it to [Organize] WeaponTokens/ArmorTokens/ToolTokens to place it.")); } return "gear:misc"; } private static bool MatchesAny(List<string> tokens, string norm) { for (int i = 0; i < tokens.Count; i++) { if (Names.Matches(tokens[i], norm)) { return true; } } return false; } } internal static class Groups { private static readonly Dictionary<string, ConfigEntry<string>> Entries = new Dictionary<string, ConfigEntry<string>>(); private static readonly Dictionary<string, List<string>> Parsed = new Dictionary<string, List<string>>(); private static readonly List<string> Ordered = new List<string>(); private static Dictionary<string, string> Defaults => GroupTables.Defaults; private static string[] GroupOrder => GroupTables.GroupOrder; internal static void Init(ConfigFile config) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown string[] groupOrder = GroupOrder; foreach (string text in groupOrder) { if (!Defaults.ContainsKey(text)) { Plugin.Log.LogError((object)("[groups] GroupOrder lists '" + text + "', which is not a defined group")); } } foreach (KeyValuePair<string, string> @default in Defaults) { if (Array.IndexOf(GroupOrder, @default.Key) < 0) { Plugin.Log.LogError((object)("[groups] group '" + @default.Key + "' is missing from GroupOrder; overlap resolution would be non-deterministic for its items")); } } foreach (KeyValuePair<string, string> default2 in Defaults) { Entries[default2.Key] = config.Bind<string>("ItemGroups", default2.Key, default2.Value, new ConfigDescription("Comma-separated item name tokens ('*' wildcards allowed).", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } config.SettingChanged += delegate { Rebuild(); }; Rebuild(); } private static void Rebuild() { Parsed.Clear(); Ordered.Clear(); string[] array; foreach (KeyValuePair<string, ConfigEntry<string>> entry in Entries) { List<string> list = new List<string>(); array = entry.Value.Value.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim().ToLowerInvariant(); if (text.Length > 0) { list.Add(text); } } Parsed[entry.Key] = list; } array = GroupOrder; foreach (string text2 in array) { if (Parsed.ContainsKey(text2)) { Ordered.Add(text2); } } List<string> list2 = new List<string>(); foreach (KeyValuePair<string, List<string>> item in Parsed) { if (Array.IndexOf(GroupOrder, item.Key) < 0) { list2.Add(item.Key); } } list2.Sort(StringComparer.Ordinal); Ordered.AddRange(list2); } internal static bool IsGroup(string name) { return Parsed.ContainsKey(name); } internal static IReadOnlyList<string> GroupsInOrder() { return Ordered; } internal static string FirstGroupFor(string normName) { for (int i = 0; i < Ordered.Count; i++) { if (GroupContains(Ordered[i], normName)) { return Ordered[i]; } } return null; } internal static bool GroupContains(string group, string normName) { if (!Parsed.TryGetValue(group, out var value)) { return false; } foreach (string item in value) { if (Names.Matches(item, normName)) { return true; } } return false; } } internal static class GroupTables { internal static readonly Dictionary<string, string> Defaults = new Dictionary<string, string> { { "stone", "stone, flint, obsidian, blackmarble, grausten*" }, { "wood", "wood, finewood, roundlog, elderbark, yggdrasilwood, blackwood" }, { "ores", "*ore, ironscrap, bronzescrap, copperscrap, flametalore*" }, { "metals", "copper, tin, bronze, iron, silver, blackmetal*, flametal*" }, { "cooking", "carrot, turnip, onion, raspberries, blueberries, cloudberries, mushroom*, jotunpuffs, magecap, honey, barley, bread*, sausages" }, { "meat", "*meat*, necktail, entrails, bloodbag, fish*" }, { "seeds", "*seeds*, acorn, ancientseed, beechnut, carrotseed, turnipseed, onionseed" }, { "trophies", "trophy*" }, { "valuables", "coins, ruby, amber, amberpearl, silvernecklace" }, { "meads", "mead*, barleywine*" }, { "ammo", "arrow*, bolt*, turretbolt*" }, { "hides", "*hide*, *pelt*, leatherscraps, chitin" }, { "fuel", "coal" } }; internal static readonly string[] GroupOrder = new string[13] { "metals", "ores", "stone", "wood", "fuel", "cooking", "meat", "seeds", "meads", "ammo", "hides", "valuables", "trophies" }; } internal static class MucResults { private struct Result { public bool Success; public int Amount; } private static readonly Dictionary<int, Result> Removes = new Dictionary<int, Result>(); internal static void RecordRemove(int requestId, bool success, int amount) { if (Removes.Count > 256) { Removes.Clear(); } Removes[requestId] = new Result { Success = success, Amount = amount }; } internal static bool TryTakeRemove(int requestId, out bool success, out int amount) { if (Removes.TryGetValue(requestId, out var value)) { Removes.Remove(requestId); success = value.Success; amount = value.Amount; return true; } success = false; amount = 0; return false; } internal static void Clear() { Removes.Clear(); } } internal static class Names { private struct Token { public string Core; public bool Leading; public bool Trailing; } private static readonly Dictionary<string, string> NormCache = new Dictionary<string, string>(512); private static readonly Dictionary<string, Token> TokenCache = new Dictionary<string, Token>(256); internal static string Normalize(string sharedName) { if (string.IsNullOrEmpty(sharedName)) { return string.Empty; } if (NormCache.TryGetValue(sharedName, out var value)) { return value; } string text = sharedName; if (text.StartsWith("$item_", StringComparison.Ordinal)) { text = text.Substring(6); } else if (text[0] == '$') { text = text.Substring(1); } string text2 = text.Replace("_", "").Trim().ToLowerInvariant(); NormCache[sharedName] = text2; return text2; } internal static bool Matches(string token, string normName) { if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(normName)) { return false; } if (!TokenCache.TryGetValue(token, out var value)) { value = new Token { Leading = (token[0] == '*'), Trailing = (token[token.Length - 1] == '*'), Core = token.Trim('*') }; TokenCache[token] = value; } if (value.Core.Length == 0) { return false; } if (value.Leading && value.Trailing) { return normName.IndexOf(value.Core, StringComparison.Ordinal) >= 0; } if (value.Trailing) { return normName.StartsWith(value.Core, StringComparison.Ordinal); } if (value.Leading) { return normName.EndsWith(value.Core, StringComparison.Ordinal); } return string.Equals(normName, value.Core, StringComparison.Ordinal); } } internal static class OrganizeConfig { internal static ConfigEntry<float> StationRange; internal static ConfigEntry<int> MovesPerSecond; internal static ConfigEntry<int> MaxMovesPerRun; internal static ConfigEntry<bool> IncludeGear; internal static ConfigEntry<int> MiscPromoteSlots; internal static int MovesPerSecondValue { get { if (MovesPerSecond == null) { return 25; } return MovesPerSecond.Value; } } internal static int MaxMovesPerRunValue { get { if (MaxMovesPerRun == null) { return 500; } return MaxMovesPerRun.Value; } } internal static void Init(ConfigFile config) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_0103: 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) //IL_0110: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown MovesPerSecond = config.Bind<int>("Organize", "MovesPerSecond", 25, new ConfigDescription("How many item transfers per second the Organize sweep issues (a real per-second rate, independent of your framerate). The mod measures its own cost and backs off below this on its own. Client-side: lower it if Organize costs you frames.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 100), Array.Empty<object>())); MaxMovesPerRun = config.Bind<int>("Organize", "MaxMovesPerRun", 500, new ConfigDescription("Safety cap on transfers per Organize press. On a very large base the run stops at this many and tells you to press Organize again to continue. Client-side.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(50, 5000), Array.Empty<object>())); StationRange = config.Bind<float>("Organize", "StationRange", 8f, new ConfigDescription("Max distance (m) from a chest to a crafting station for the chest to inherit that station's item groups during Organize. Nearest mapped station wins. NOTE: this sets the match distance only - station detection scans every loaded station regardless.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); IncludeGear = config.Bind<bool>("Organize", "IncludeGear", true, new ConfigDescription("Sweep weapons, armor and tools into their own chests during Organize. Off leaves gear where it is unless a chest explicitly pins it (the 1.1.x behaviour).", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); MiscPromoteSlots = config.Bind<int>("Organize", "MiscPromoteSlots", 24, new ConfigDescription("An ungrouped item type gets its own chest(s) only when it needs more than this many slots; smaller piles share a 'misc' chest. Default is one vanilla chest. An item a chest explicitly pins always gets its own home regardless.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 120), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); Gear.Init(config); } } internal struct StackView { public string Norm; public int Count; public bool Stackable; public string BucketKey; } internal enum AnchorKind { None, Home, Station, Sign, Pin } internal sealed class ChestView { public int Id; public string UidKey = ""; public float Distance; public int TotalSlots; public int Priority; public List<StackView> Stacks; public bool ExcludedAsTarget; public bool ExcludedAsSource; public Dictionary<string, AnchorKind> Anchors; public string HomeMarker; public bool IsAnchor { get { if (Anchors != null) { return Anchors.Count > 0; } return false; } } public AnchorKind AnchorFor(string bucket) { if (Anchors == null || bucket == null) { return AnchorKind.None; } if (!Anchors.TryGetValue(bucket, out var value)) { return AnchorKind.None; } return value; } public int HeldOf(string norm) { int num = 0; if (Stacks != null) { for (int i = 0; i < Stacks.Count; i++) { if (Stacks[i].Norm == norm) { num += Stacks[i].Count; } } } return num; } } internal struct OrganizeMove { public int SrcId; public int SrcStackIndex; public int TgtId; public string Norm; public int Amount; } internal struct HomeMark { public int ChestId; public string BucketKey; } internal struct OrganizeSummary { public int TotalItems; public int TargetChests; public int SourceChests; public int HomelessItems; public int BucketsPlanned; } internal sealed class PlannerInput { public IReadOnlyList<ChestView> Chests; public Func<string, int> MaxStackOf; public Func<string, int> BucketRank; public Func<int, int, float> DistanceBetween; public int MiscPromoteSlots = 24; } internal sealed class PlannerResult { public readonly List<OrganizeMove> Moves = new List<OrganizeMove>(); public readonly List<HomeMark> HomeMarks = new List<HomeMark>(); public OrganizeSummary Summary; } internal static class OrganizePlanner { private struct Holder { public int ChestId; public int StackIndex; public int Count; } private sealed class Reservation { public int ChestId; public AnchorKind Kind; public int Priority; public float Distance; public string UidKey; public int Slots; public bool Claimed; } private const int HomeFixedPointCap = 8; internal static PlannerResult Plan(PlannerInput input) { if (input?.Chests == null || input.Chests.Count == 0) { return new PlannerResult(); } List<ChestView> list = CloneForOverlay(input.Chests, stripHome: false); PlannerInput input2 = WithChests(input, list); AdoptIncumbentHomes(list, input.MaxStackOf ?? ((Func<string, int>)((string _) => 1)), input.BucketRank ?? ((Func<string, int>)((string _) => 0)), Math.Max(1, input.MiscPromoteSlots)); PlannerResult plannerResult = null; for (int num = 0; num < 8; num++) { plannerResult = PlanOnce(input2); if (plannerResult.HomeMarks.Count == 0) { break; } ApplyMarksToOverlay(list, plannerResult.HomeMarks); plannerResult = null; } if (plannerResult == null) { list = CloneForOverlay(input.Chests, stripHome: true); input2 = WithChests(input, list); plannerResult = PlanOnce(input2); ApplyMarksToOverlay(list, plannerResult.HomeMarks); } PlannerResult plannerResult2 = new PlannerResult(); plannerResult2.Moves.AddRange(plannerResult.Moves); plannerResult2.Summary = plannerResult.Summary; for (int num2 = 0; num2 < input.Chests.Count; num2++) { ChestView chestView = input.Chests[num2]; ChestView chestView2 = list[num2]; if (chestView != null && chestView2 != null) { string a = (string.IsNullOrEmpty(chestView.HomeMarker) ? null : chestView.HomeMarker); string text = (string.IsNullOrEmpty(chestView2.HomeMarker) ? null : chestView2.HomeMarker); if (!string.Equals(a, text, StringComparison.Ordinal)) { plannerResult2.HomeMarks.Add(new HomeMark { ChestId = num2, BucketKey = text }); } } } return plannerResult2; } private static List<ChestView> CloneForOverlay(IReadOnlyList<ChestView> chests, bool stripHome) { List<ChestView> list = new List<ChestView>(chests.Count); for (int i = 0; i < chests.Count; i++) { ChestView chestView = chests[i]; if (chestView == null) { list.Add(null); continue; } Dictionary<string, AnchorKind> dictionary = null; if (chestView.Anchors != null) { foreach (KeyValuePair<string, AnchorKind> anchor in chestView.Anchors) { if (!stripHome || anchor.Value != AnchorKind.Home) { if (dictionary == null) { dictionary = new Dictionary<string, AnchorKind>(StringComparer.Ordinal); } dictionary[anchor.Key] = anchor.Value; } } } list.Add(new ChestView { Id = chestView.Id, UidKey = chestView.UidKey, Distance = chestView.Distance, TotalSlots = chestView.TotalSlots, Priority = chestView.Priority, Stacks = chestView.Stacks, ExcludedAsTarget = chestView.ExcludedAsTarget, ExcludedAsSource = chestView.ExcludedAsSource, Anchors = dictionary, HomeMarker = (stripHome ? null : chestView.HomeMarker) }); } return list; } private static PlannerInput WithChests(PlannerInput input, List<ChestView> chests) { return new PlannerInput { Chests = chests, MaxStackOf = input.MaxStackOf, BucketRank = input.BucketRank, DistanceBetween = input.DistanceBetween, MiscPromoteSlots = input.MiscPromoteSlots }; } private static void ApplyMarksToOverlay(List<ChestView> overlay, List<HomeMark> marks) { for (int i = 0; i < marks.Count; i++) { HomeMark homeMark = marks[i]; ChestView chestView = overlay[homeMark.ChestId]; if (chestView == null) { continue; } chestView.HomeMarker = homeMark.BucketKey; if (chestView.Anchors != null) { List<string> list = new List<string>(); foreach (KeyValuePair<string, AnchorKind> anchor in chestView.Anchors) { if (anchor.Value == AnchorKind.Home && !string.Equals(anchor.Key, homeMark.BucketKey, StringComparison.Ordinal)) { list.Add(anchor.Key); } } foreach (string item in list) { chestView.Anchors.Remove(item); } if (chestView.Anchors.Count == 0) { chestView.Anchors = null; } } if (homeMark.BucketKey != null) { if (chestView.Anchors == null) { chestView.Anchors = new Dictionary<string, AnchorKind>(StringComparer.Ordinal); } if (!chestView.Anchors.TryGetValue(homeMark.BucketKey, out var value) || value < AnchorKind.Home) { chestView.Anchors[homeMark.BucketKey] = AnchorKind.Home; } } } } private static void AdoptIncumbentHomes(List<ChestView> overlay, Func<string, int> maxStackOf, Func<string, int> bucketRank, int promoteSlots) { Dictionary<string, Dictionary<int, int>> dictionary = new Dictionary<string, Dictionary<int, int>>(StringComparer.Ordinal); Dictionary<string, Dictionary<string, int>> dictionary2 = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal); for (int i = 0; i < overlay.Count; i++) { ChestView chestView = overlay[i]; if (chestView?.Stacks == null) { continue; } for (int j = 0; j < chestView.Stacks.Count; j++) { StackView stackView = chestView.Stacks[j]; if (!string.IsNullOrEmpty(stackView.Norm) && stackView.Count > 0 && !chestView.ExcludedAsSource && !string.IsNullOrEmpty(stackView.BucketKey)) { if (!dictionary.TryGetValue(stackView.BucketKey, out var value)) { value = new Dictionary<int, int>(); dictionary[stackView.BucketKey] = value; dictionary2[stackView.BucketKey] = new Dictionary<string, int>(StringComparer.Ordinal); } value[i] = (value.TryGetValue(i, out var value2) ? value2 : 0) + stackView.Count; Dictionary<string, int> dictionary3 = dictionary2[stackView.BucketKey]; dictionary3[stackView.Norm] = (dictionary3.TryGetValue(stackView.Norm, out var value3) ? value3 : 0) + stackView.Count; } } } List<string> list = new List<string>(); foreach (KeyValuePair<string, Dictionary<string, int>> item in dictionary2) { if (!BucketKeys.IsPerType(item.Key) || AnyChestAnchors(overlay, item.Key)) { continue; } int num = 0; foreach (KeyValuePair<string, int> item2 in item.Value) { num += CeilDiv(item2.Value, Math.Max(1, maxStackOf(item2.Key))); } if (num <= promoteSlots) { list.Add(item.Key); } } foreach (string item3 in list) { if (!dictionary.TryGetValue("misc", out var value4)) { value4 = (dictionary["misc"] = new Dictionary<int, int>()); dictionary2["misc"] = new Dictionary<string, int>(StringComparer.Ordinal); } foreach (KeyValuePair<int, int> item4 in dictionary[item3]) { value4[item4.Key] = (value4.TryGetValue(item4.Key, out var value5) ? value5 : 0) + item4.Value; } Dictionary<string, int> dictionary5 = dictionary2["misc"]; foreach (KeyValuePair<string, int> item5 in dictionary2[item3]) { dictionary5[item5.Key] = (dictionary5.TryGetValue(item5.Key, out var value6) ? value6 : 0) + item5.Value; } dictionary.Remove(item3); dictionary2.Remove(item3); } Dictionary<string, int> demandSlots = new Dictionary<string, int>(StringComparer.Ordinal); foreach (KeyValuePair<string, Dictionary<string, int>> item6 in dictionary2) { int num2 = 0; foreach (KeyValuePair<string, int> item7 in item6.Value) { num2 += CeilDiv(item7.Value, Math.Max(1, maxStackOf(item7.Key))); } demandSlots[item6.Key] = num2; } List<string> list2 = new List<string>(dictionary.Keys); list2.Sort(delegate(string x, string y) { int num7 = demandSlots[y].CompareTo(demandSlots[x]); if (num7 != 0) { return num7; } num7 = bucketRank(x).CompareTo(bucketRank(y)); return (num7 == 0) ? string.CompareOrdinal(x, y) : num7; }); HashSet<string> hashSet = new HashSet<string>(list2, StringComparer.Ordinal); foreach (string item8 in list2) { if (AnyChestAnchors(overlay, item8)) { continue; } int num3 = -1; int num4 = 0; float value7 = 0f; string strB = null; Dictionary<int, int> dictionary6 = dictionary[item8]; for (int num5 = 0; num5 < overlay.Count; num5++) { if (!dictionary6.TryGetValue(num5, out var value8) || value8 <= 0) { continue; } ChestView chestView2 = overlay[num5]; if (chestView2 != null && !chestView2.ExcludedAsTarget && (string.IsNullOrEmpty(chestView2.HomeMarker) || !hashSet.Contains(chestView2.HomeMarker)) && !AnchorsAnotherLiveBucket(chestView2, item8, hashSet)) { bool flag = num3 < 0 || value8 > num4; if (!flag && value8 == num4) { int num6 = chestView2.Distance.CompareTo(value7); flag = num6 < 0 || (num6 == 0 && string.CompareOrdinal(chestView2.UidKey ?? "", strB) < 0); } if (flag) { num3 = num5; num4 = value8; value7 = chestView2.Distance; strB = chestView2.UidKey ?? ""; } } } if (num3 >= 0) { ChestView chestView3 = overlay[num3]; chestView3.HomeMarker = item8; if (chestView3.Anchors == null) { chestView3.Anchors = new Dictionary<string, AnchorKind>(StringComparer.Ordinal); } chestView3.Anchors[item8] = AnchorKind.Home; } } } private static PlannerResult PlanOnce(PlannerInput input) { PlannerResult plannerResult = new PlannerResult(); if (input?.Chests == null || input.Chests.Count == 0) { return plannerResult; } IReadOnlyList<ChestView> chests = input.Chests; Func<string, int> func = input.MaxStackOf ?? ((Func<string, int>)((string _) => 1)); Func<string, int> bucketRank = input.BucketRank ?? ((Func<string, int>)((string _) => 0)); Func<int, int, float> distanceBetween = input.DistanceBetween ?? ((Func<int, int, float>)((int _, int __) => 0f)); int promoteSlots = Math.Max(1, input.MiscPromoteSlots); Dictionary<string, Dictionary<string, List<Holder>>> dictionary = new Dictionary<string, Dictionary<string, List<Holder>>>(StringComparer.Ordinal); List<string> list = new List<string>(); int[] array = new int[chests.Count]; for (int num = 0; num < chests.Count; num++) { ChestView chestView = chests[num]; if (chestView?.Stacks == null) { continue; } for (int num2 = 0; num2 < chestView.Stacks.Count; num2++) { StackView stackView = chestView.Stacks[num2]; if (string.IsNullOrEmpty(stackView.Norm) || stackView.Count <= 0) { continue; } if (chestView.ExcludedAsSource || string.IsNullOrEmpty(stackView.BucketKey)) { array[num]++; continue; } if (!dictionary.TryGetValue(stackView.BucketKey, out var value)) { value = new Dictionary<string, List<Holder>>(StringComparer.Ordinal); dictionary[stackView.BucketKey] = value; list.Add(stackView.BucketKey); } if (!value.TryGetValue(stackView.Norm, out var value2)) { value2 = new List<Holder>(); value[stackView.Norm] = value2; } value2.Add(new Holder { ChestId = num, StackIndex = num2, Count = stackView.Count }); } } FoldSmallTypeBuckets(chests, dictionary, list, func, promoteSlots); Dictionary<string, int> demand = new Dictionary<string, int>(StringComparer.Ordinal); foreach (KeyValuePair<string, Dictionary<string, List<Holder>>> item in dictionary) { int num3 = 0; foreach (KeyValuePair<string, List<Holder>> item2 in item.Value) { int num4 = 0; for (int num5 = 0; num5 < item2.Value.Count; num5++) { num4 += item2.Value[num5].Count; } num3 += CeilDiv(num4, Math.Max(1, func(item2.Key))); } demand[item.Key] = num3; } int[] array2 = new int[chests.Count]; for (int num6 = 0; num6 < chests.Count; num6++) { ChestView chestView2 = chests[num6]; array2[num6] = ((chestView2 != null && !chestView2.ExcludedAsTarget) ? Math.Max(0, chestView2.TotalSlots - array[num6]) : 0); } List<string> list2 = new List<string>(demand.Keys); list2.Sort(delegate(string a, string b) { int num10 = demand[b].CompareTo(demand[a]); if (num10 != 0) { return num10; } num10 = bucketRank(a).CompareTo(bucketRank(b)); return (num10 == 0) ? string.CompareOrdinal(a, b) : num10; }); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (string item3 in list2) { if (demand[item3] > 0) { hashSet.Add(item3); } } string[] reservedBy = new string[chests.Count]; Dictionary<string, List<Reservation>> dictionary2 = new Dictionary<string, List<Reservation>>(StringComparer.Ordinal); Dictionary<string, int> dictionary3 = new Dictionary<string, int>(StringComparer.Ordinal); foreach (string item4 in list2) { int needed = demand[item4]; dictionary2[item4] = TakeAnchors(item4, ref needed, chests, array2, reservedBy); dictionary3[item4] = needed; } foreach (string item5 in list2) { ClaimFree(item5, dictionary3[item5], dictionary2[item5], chests, array2, reservedBy, distanceBetween, hashSet); } List<OrganizeMove> moves = plannerResult.Moves; int num7 = 0; foreach (string item6 in list2) { num7 += DistributeAndDiff(item6, dictionary[item6], dictionary2[item6], chests, array2, func, moves); } OrderEvictionsFirst(moves); EmitHomeMarks(chests, dictionary2, plannerResult.HomeMarks); HashSet<int> hashSet2 = new HashSet<int>(); HashSet<int> hashSet3 = new HashSet<int>(); int num8 = 0; for (int num9 = 0; num9 < moves.Count; num9++) { num8 += moves[num9].Amount; hashSet2.Add(moves[num9].SrcId); hashSet3.Add(moves[num9].TgtId); } plannerResult.Summary = new OrganizeSummary { TotalItems = num8, SourceChests = hashSet2.Count, TargetChests = hashSet3.Count, HomelessItems = num7, BucketsPlanned = list2.Count }; return plannerResult; } private static void FoldSmallTypeBuckets(IReadOnlyList<ChestView> chests, Dictionary<string, Dictionary<string, List<Holder>>> holdersByBucket, List<string> bucketOrderSeen, Func<string, int> maxStackOf, int promoteSlots) { List<string> list = new List<string>(); foreach (string item in bucketOrderSeen) { if (!BucketKeys.IsPerType(item) || !holdersByBucket.TryGetValue(item, out var value) || AnyChestAnchors(chests, item)) { continue; } int num = 0; foreach (KeyValuePair<string, List<Holder>> item2 in value) { int num2 = 0; for (int i = 0; i < item2.Value.Count; i++) { num2 += item2.Value[i].Count; } num += CeilDiv(num2, Math.Max(1, maxStackOf(item2.Key))); } if (num <= promoteSlots) { list.Add(item); } } if (list.Count == 0) { return; } if (!holdersByBucket.TryGetValue("misc", out var value2)) { value2 = (holdersByBucket["misc"] = new Dictionary<string, List<Holder>>(StringComparer.Ordinal)); bucketOrderSeen.Add("misc"); } foreach (string item3 in list) { foreach (KeyValuePair<string, List<Holder>> item4 in holdersByBucket[item3]) { if (!value2.TryGetValue(item4.Key, out var value3)) { value3 = new List<Holder>(); value2[item4.Key] = value3; } value3.AddRange(item4.Value); } holdersByBucket.Remove(item3); bucketOrderSeen.Remove(item3); } } private static bool AnyChestAnchors(IReadOnlyList<ChestView> chests, string bucket) { for (int i = 0; i < chests.Count; i++) { if (chests[i] != null && chests[i].AnchorFor(bucket) != AnchorKind.None) { return true; } } return false; } private static List<Reservation> TakeAnchors(string bucket, ref int needed, IReadOnlyList<ChestView> chests, int[] slotsLeft, string[] reservedBy) { List<Reservation> list = new List<Reservation>(); if (needed <= 0) { return list; } List<Reservation> list2 = new List<Reservation>(); for (int i = 0; i < chests.Count; i++) { ChestView chestView = chests[i]; if (chestView != null && !chestView.ExcludedAsTarget) { AnchorKind anchorKind = chestView.AnchorFor(bucket); if (anchorKind != AnchorKind.None && anchorKind >= AnchorKind.Station) { list2.Add(new Reservation { ChestId = i, Kind = anchorKind, Priority = chestView.Priority, Distance = chestView.Distance, UidKey = (chestView.UidKey ?? "") }); } } } list2.Sort(CompareAnchors); foreach (Reservation item in list2) { if (needed <= 0) { break; } int num = Math.Min(slotsLeft[item.ChestId], needed); if (num > 0) { slotsLeft[item.ChestId] -= num; needed -= num; item.Slots = num; list.Add(item); if (reservedBy[item.ChestId] == null) { reservedBy[item.ChestId] = bucket; } } } return list; } private static void ClaimFree(string bucket, int needed, List<Reservation> taken, IReadOnlyList<ChestView> chests, int[] slotsLeft, string[] reservedBy, Func<int, int, float> distanceBetween, HashSet<string> liveBuckets) { while (needed > 0) { int refChest = ((taken.Count > 0) ? taken[0].ChestId : (-1)); int num = PickFreeChest(bucket, chests, slotsLeft, reservedBy, refChest, distanceBetween, liveBuckets); if (num >= 0) { int num2 = Math.Min(slotsLeft[num], needed); slotsLeft[num] -= num2; needed -= num2; bool flag = reservedBy[num] == null || string.Equals(reservedBy[num], bucket, StringComparison.Ordinal); if (reservedBy[num] == null) { reservedBy[num] = bucket; } ChestView chestView = chests[num]; taken.Add(new Reservation { ChestId = num, Kind = AnchorKind.None, Priority = chestView.Priority, Distance = chestView.Distance, UidKey = (chestView.UidKey ?? ""), Slots = num2, Claimed = (flag && !AnchorsAnotherLiveBucket(chestView, bucket, liveBuckets)) }); continue; } break; } } private static int CompareAnchors(Reservation a, Reservation b) { int kind = (int)b.Kind; int num = kind.CompareTo((int)a.Kind); if (num != 0) { return num; } num = b.Priority.CompareTo(a.Priority); if (num != 0) { return num; }