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 v1.1.0
ChestButler.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ChestButler.Core; using HarmonyLib; 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("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("ChestButler")] [assembly: AssemblyTitle("ChestButler")] [assembly: AssemblyVersion("1.1.0.0")] namespace ChestButler { [BepInPlugin("eksolutions.chestbutler", "ChestButler", "1.1.0")] [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 = "1.1.0"; 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<int> OrganizeMovesPerTick; internal static ConfigEntry<float> StationRange; private Harmony _harmony; 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_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_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; SorterRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Sorting", "Radius", 32f, new ConfigDescription("Radius (m) around a sorter chest in which target chests are searched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), 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.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 10f), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); StacksPerTick = ((BaseUnityPlugin)this).Config.Bind<int>("Sorting", "StacksPerTick", 2, new ConfigDescription("How many item stacks a sorter moves per tick.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); 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 } })); OrganizeMovesPerTick = ((BaseUnityPlugin)this).Config.Bind<int>("Organize", "MovesPerTick", 4, new ConfigDescription("How many item moves the Organize sweep performs per frame (higher = faster, more hitch).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); StationRange = ((BaseUnityPlugin)this).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.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); Groups.Init(((BaseUnityPlugin)this).Config); Stations.Init(((BaseUnityPlugin)this).Config); _harmony = new Harmony("eksolutions.chestbutler"); _harmony.PatchAll(); Log.LogInfo((object)"ChestButler 1.1.0 loaded"); } } } namespace ChestButler.Patches { [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 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 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; EnsureBar(__instance); Refresh(); } [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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Expected O, but got Unknown //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Expected O, but got Unknown //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_028c: 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; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); if (!((Object)(object)val == (Object)null)) { GameObject val2 = new GameObject("psort_bar", new Type[1] { typeof(RectTransform) }); _bar = (RectTransform)val2.transform; ((Transform)_bar).SetParent(((Transform)component).parent, false); Rect rect = val.rect; Rect rect2 = component.rect; float width = ((Rect)(ref rect2)).width; rect2 = component.rect; float height = ((Rect)(ref rect2)).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); _bar.anchorMin = new Vector2(0f, 0f); _bar.anchorMax = new Vector2(0f, 0f); _bar.pivot = new Vector2(0f, 0f); _bar.anchoredPosition = new Vector2(num5, num6); HorizontalLayoutGroup obj = val2.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 = val2.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 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 { Msg("Chest is empty. Add sample items first"); } } else { bool flag = !Filters.GetManual(_current); Filters.SetManual(_current, flag); Msg(flag ? "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); 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)) { OrganizePlan pendingPlan = _pendingPlan; ClearPending(); Organizer.Execute(pendingPlan); } return; } OrganizePlan organizePlan = Organizer.BuildPlan(_current, Plugin.SorterRadius.Value); if (organizePlan.IsEmpty) { ClearPending(); Msg("Nothing to organize"); return; } _pendingPlan = organizePlan; _pendingChest = _current; _pendingAt = Time.time; OrganizeSummary summary = organizePlan.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")); Msg("Organize: move " + summary.TotalItems + " item" + ((summary.TotalItems == 1) ? "" : "s") + " across " + summary.TargetChests + " chest" + ((summary.TargetChests == 1) ? "" : "s") + " - 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; _pinLabel.text = ((count == 0) ? "Pin" : (Filters.GetManual(_current) ? ("Manual (" + count + ")") : ("Auto (" + count + ")"))); flag4 = count > 0; if (flag4) { _clearLabel.text = "Clear"; } flag5 = Filters.GetSpec(_current).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); } 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); } } } [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); } } } namespace ChestButler.Core { internal static class ContainerTracker { private static readonly HashSet<Container> All = new HashSet<Container>(); internal static void Register(Container c) { if ((Object)(object)c != (Object)null) { All.Add(c); } } internal static void Unregister(Container c) { if ((Object)(object)c != (Object)null) { All.Remove(c); } } internal static List<Container> Candidates(Container sorter, float radius, bool excludeSorters = true) { //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_0087: 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_00bc: Unknown result type (might be due to invalid IL or missing references) All.RemoveWhere((Container c) => (Object)(object)c == (Object)null); Vector3 position = ((Component)sorter).transform.position; List<float> list = new List<float>(); List<Container> list2 = new List<Container>(); foreach (Container item in All) { if (!((Object)(object)item == (Object)(object)sorter) && SorterZdo.HasValidNView(item) && item.GetInventory() != null && !((Object)(object)((Component)item).GetComponentInParent<Piece>() == (Object)null)) { float num = Vector3.Distance(position, ((Component)item).transform.position); if (!(num > radius) && (!excludeSorters || !SorterZdo.IsSorter(item)) && SorterZdo.PlayerCanAccess(item) && PrivateArea.CheckAccess(((Component)item).transform.position, 0f, false, true)) { list.Add(num); list2.Add(item); } } } float[] keys = list.ToArray(); Container[] array = list2.ToArray(); Array.Sort(keys, array); return new List<Container>(array); } 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 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 = 3f; private static readonly int ItemsHash = StringExtensionMethods.GetStableHashCode("psort_items"); private static readonly int ManualHash = StringExtensionMethods.GetStableHashCode("psort_manual"); 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); } } 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 FilterSpec GetSpec(Container c) { if (Cache.TryGetValue(c, out var value) && Time.time - value.Key < 3f) { return value.Value; } FilterSpec filterSpec = new FilterSpec(); foreach (string item in GetPinned(c)) { filterSpec.Items.Add(item); } filterSpec.ManualOnly = GetManual(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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006f: 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) { if (Vector3.Distance(((Component)sign).transform.position, position) > 2.5f || !IsNearestContainerTo(((Component)sign).transform.position, c)) { continue; } string text = sign.GetText(); if (string.IsNullOrEmpty(text)) { continue; } string[] array = text.Split('\n'); for (int num = 0; num < array.Length; num++) { string text2 = array[num].Trim(); if (!text2.ToLowerInvariant().StartsWith("sort:")) { continue; } string[] array2 = text2.Substring(5).Split(','); for (int num2 = 0; num2 < array2.Length; num2++) { string text3 = array2[num2].Trim().ToLowerInvariant(); if (text3.Length == 0) { continue; } switch (text3) { case "off": case "ignore": case "none": spec.Ignore = true; continue; } if (text3.Length >= 2 && text3[0] == 'p' && int.TryParse(text3.Substring(1), out var result)) { spec.Priority = result; } else if (Groups.IsGroup(text3)) { spec.GroupNames.Add(text3); } else { spec.Items.Add(text3); } } } } } 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 Groups { private 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" } }; 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>>(); internal static void Init(ConfigFile config) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown foreach (KeyValuePair<string, string> @default in Defaults) { Entries[@default.Key] = config.Bind<string>("ItemGroups", @default.Key, @default.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(); foreach (KeyValuePair<string, ConfigEntry<string>> entry in Entries) { List<string> list = new List<string>(); 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; } } internal static bool IsGroup(string name) { return Parsed.ContainsKey(name); } 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 Names { internal static string Normalize(string sharedName) { if (string.IsNullOrEmpty(sharedName)) { return string.Empty; } string text = sharedName; if (text.StartsWith("$item_")) { text = text.Substring(6); } else if (text.StartsWith("$")) { text = text.Substring(1); } return text.Replace("_", "").Trim().ToLowerInvariant(); } internal static bool Matches(string token, string normName) { if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(normName)) { return false; } bool flag = token[0] == '*'; bool flag2 = token[token.Length - 1] == '*'; string text = token.Trim('*'); if (text.Length == 0) { return false; } if (flag && flag2) { return normName.Contains(text); } if (flag2) { return normName.StartsWith(text); } if (flag) { return normName.EndsWith(text); } return normName == text; } } internal struct StackView { public string Norm; public int Count; public bool Stackable; } internal sealed class ChestView { public int Id; public bool ExcludedAsTarget; public List<StackView> Stacks; public Func<string, bool> PinsItem; public Func<string, bool> PinsGroup; public Func<string, bool> StationAttracts; public int Priority; public int EmptySlots; public Func<string, int> PartialSpaceFor; public Func<string, int> MaxStackOf; 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 OrganizeSummary { public int TotalItems; public int TargetChests; public int SourceChests; } internal static class OrganizePlanner { private struct Holder { public int ChestId; public int StackIndex; public int Count; } private enum Tier { PinItem, PinGroup, Station, Holds } internal static List<OrganizeMove> Plan(IReadOnlyList<ChestView> chests, out OrganizeSummary summary) { List<OrganizeMove> list = new List<OrganizeMove>(); summary = default(OrganizeSummary); if (chests == null || chests.Count == 0) { return list; } Dictionary<string, List<Holder>> dictionary = new Dictionary<string, List<Holder>>(); List<string> list2 = new List<string>(); for (int i = 0; i < chests.Count; i++) { ChestView chestView = chests[i]; if (chestView == null || 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 && (stackView.Stackable || AnyTargetPins(chests, stackView.Norm))) { if (!dictionary.TryGetValue(stackView.Norm, out var value)) { value = new List<Holder>(); dictionary[stackView.Norm] = value; list2.Add(stackView.Norm); } value.Add(new Holder { ChestId = i, StackIndex = j, Count = stackView.Count }); } } } Dictionary<int, int> dictionary2 = new Dictionary<int, int>(); HashSet<int> hashSet = new HashSet<int>(); HashSet<int> hashSet2 = new HashSet<int>(); foreach (string item in list2) { int num = ChooseTarget(chests, item); if (num < 0) { continue; } ChestView chestView2 = chests[num]; if (!dictionary2.ContainsKey(num)) { dictionary2[num] = Math.Max(0, chestView2.EmptySlots); } int num2 = Math.Max(1, (chestView2.MaxStackOf == null) ? 1 : chestView2.MaxStackOf(item)); int num3 = Math.Max(0, (chestView2.PartialSpaceFor != null) ? chestView2.PartialSpaceFor(item) : 0); int num4 = 0; foreach (Holder item2 in dictionary[item]) { if (item2.ChestId != num) { int val = num3 + num4 + dictionary2[num] * num2; int num5 = Math.Min(item2.Count, val); if (num5 <= 0) { break; } int num6 = num5; int num7 = Math.Min(num6, num3); num3 -= num7; num6 -= num7; int num8 = Math.Min(num6, num4); num4 -= num8; num6 -= num8; while (num6 > 0) { dictionary2[num]--; num4 = num2; int num9 = Math.Min(num6, num4); num4 -= num9; num6 -= num9; } list.Add(new OrganizeMove { SrcId = item2.ChestId, SrcStackIndex = item2.StackIndex, TgtId = num, Norm = item, Amount = num5 }); summary.TotalItems += num5; hashSet.Add(item2.ChestId); hashSet2.Add(num); } } } summary.SourceChests = hashSet.Count; summary.TargetChests = hashSet2.Count; return list; } private static bool AnyTargetPins(IReadOnlyList<ChestView> chests, string norm) { for (int i = 0; i < chests.Count; i++) { ChestView chestView = chests[i]; if (chestView != null && !chestView.ExcludedAsTarget) { if (chestView.PinsItem != null && chestView.PinsItem(norm)) { return true; } if (chestView.PinsGroup != null && chestView.PinsGroup(norm)) { return true; } } } return false; } private static int ChooseTarget(IReadOnlyList<ChestView> chests, string norm) { int num = PickBest(chests, norm, Tier.PinItem); if (num >= 0) { return num; } num = PickBest(chests, norm, Tier.PinGroup); if (num >= 0) { return num; } num = PickBest(chests, norm, Tier.Station); if (num >= 0) { return num; } return PickBest(chests, norm, Tier.Holds); } private static int PickBest(IReadOnlyList<ChestView> chests, string norm, Tier tier) { int result = -1; int num = int.MinValue; int num2 = -1; for (int i = 0; i < chests.Count; i++) { ChestView chestView = chests[i]; if (chestView != null && !chestView.ExcludedAsTarget && tier switch { Tier.PinItem => chestView.PinsItem != null && chestView.PinsItem(norm), Tier.PinGroup => chestView.PinsGroup != null && chestView.PinsGroup(norm), Tier.Station => chestView.StationAttracts != null && chestView.StationAttracts(norm), _ => chestView.HeldOf(norm) > 0, }) { int num3 = chestView.HeldOf(norm); if (chestView.Priority > num || (chestView.Priority == num && num3 > num2)) { num = chestView.Priority; num2 = num3; result = i; } } } return result; } } internal sealed class UnityMove { public Container Source; public ItemData Item; public Container Target; public int Amount; public string Norm; } internal sealed class OrganizePlan { public readonly List<UnityMove> Moves = new List<UnityMove>(); public OrganizeSummary Summary; public bool IsEmpty => Moves.Count == 0; } internal static class Organizer { internal static OrganizePlan BuildPlan(Container origin, float radius) { //IL_012d: Unknown result type (might be due to invalid IL or missing references) OrganizePlan organizePlan = new OrganizePlan(); if ((Object)(object)origin == (Object)null) { return organizePlan; } List<Container> list = new List<Container> { origin }; foreach (Container item in ContainerTracker.Candidates(origin, radius, excludeSorters: false)) { if ((Object)(object)item != (Object)(object)origin) { list.Add(item); } } int count = list.Count; List<ItemData>[] array = new List<ItemData>[count]; List<ChestView> list2 = new List<ChestView>(count); Dictionary<string, ItemData> sampleByNorm = new Dictionary<string, ItemData>(); for (int i = 0; i < count; i++) { Container val = list[i]; Inventory inventory = val.GetInventory(); FilterSpec spec = Filters.GetSpec(val); List<string> list3 = Stations.GroupsForChest(val, Plugin.StationRange.Value); List<ItemData> list4 = new List<ItemData>(); List<StackView> list5 = new List<StackView>(); InventoryBlock val2 = ((inventory != null) ? InventoryBlock.Get(inventory) : null); if (inventory != null) { foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem == null || allItem.m_shared == null || (val2 != null && val2.IsSlotBlocked(allItem.m_gridPos))) { continue; } string text = Names.Normalize(allItem.m_shared.m_name); if (text.Length != 0) { list4.Add(allItem); list5.Add(new StackView { Norm = text, Count = allItem.m_stack, Stackable = (allItem.m_shared.m_maxStackSize > 1) }); if (!sampleByNorm.ContainsKey(text)) { sampleByNorm[text] = allItem; } } } } array[i] = list4; bool excludedAsTarget = SorterZdo.IsSorter(val) || spec.Ignore || spec.ManualOnly; FilterSpec specLocal = spec; Inventory invLocal = inventory; List<string> groupsLocal = list3; ItemData value; list2.Add(new ChestView { Id = i, ExcludedAsTarget = excludedAsTarget, Stacks = list5, PinsItem = (string norm) => specLocal.MatchesItem(norm), PinsGroup = (string norm) => specLocal.MatchesGroup(norm), StationAttracts = (string norm) => StationAttracts(groupsLocal, norm), Priority = spec.Priority, EmptySlots = ((inventory != null) ? inventory.GetEmptySlots() : 0), PartialSpaceFor = delegate(string norm) { if (invLocal == null) { return 0; } return sampleByNorm.TryGetValue(norm, out value) ? invLocal.FindFreeStackSpace(value.m_shared.m_name, (float)value.m_worldLevel) : 0; }, MaxStackOf = (string norm) => (!sampleByNorm.TryGetValue(norm, out value)) ? 1 : value.m_shared.m_maxStackSize }); } OrganizeSummary summary; List<OrganizeMove> list6 = OrganizePlanner.Plan(list2, out summary); organizePlan.Summary = summary; foreach (OrganizeMove item2 in list6) { organizePlan.Moves.Add(new UnityMove { Source = list[item2.SrcId], Item = array[item2.SrcId][item2.SrcStackIndex], Target = list[item2.TgtId], Amount = item2.Amount, Norm = item2.Norm }); } return organizePlan; } private static bool StationAttracts(List<string> groups, string norm) { if (groups == null) { return false; } for (int i = 0; i < groups.Count; i++) { if (Groups.GroupContains(groups[i], norm)) { return true; } } return false; } internal static void Execute(OrganizePlan plan) { if (plan != null && !plan.IsEmpty) { if ((Object)(object)Plugin.Instance == (Object)null) { Plugin.Log.LogWarning((object)"[organize] no Plugin.Instance; cannot start execution"); return; } Plugin.Log.LogInfo((object)("[organize] executing " + plan.Moves.Count + " move(s)")); ((MonoBehaviour)Plugin.Instance).StartCoroutine(Run(plan)); } } private static IEnumerator Run(OrganizePlan plan) { int perTick = Mathf.Max(1, Plugin.OrganizeMovesPerTick.Value); int budget = perTick; int movedItems = 0; HashSet<Container> targetsHit = new HashSet<Container>(); foreach (UnityMove move in plan.Moves) { Container source = move.Source; Container target = move.Target; ItemData item = move.Item; if ((Object)(object)source == (Object)null || (Object)(object)target == (Object)null || item == null || item.m_shared == null) { continue; } Inventory inventory = source.GetInventory(); Inventory inventory2 = target.GetInventory(); if (inventory == null || inventory2 == null || !inventory.GetAllItems().Contains(item)) { continue; } InventoryBlock val = InventoryBlock.Get(inventory); if (val != null && val.IsSlotBlocked(item.m_gridPos)) { continue; } int num = Router.Room(inventory2, item); if (num <= 0) { continue; } int num2 = Math.Min(move.Amount, Math.Min(item.m_stack, num)); if (num2 <= 0) { continue; } ZNetView val2 = SorterZdo.NView(target); if (!((Object)(object)val2 == (Object)null) && val2.IsValid() && !target.IsInUse()) { if (!val2.IsOwner()) { val2.ClaimOwnership(); } ContainerHandler.RemoveItemFromChest(source, item, inventory2, new Vector2i(-1, -1), val2.GetZDO().m_uid, num2, (ItemData)null); movedItems += num2; targetsHit.Add(target); int num3 = budget - 1; budget = num3; if (num3 <= 0) { budget = perTick; yield return null; } } } if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, "Organized " + movedItems + " item" + ((movedItems == 1) ? "" : "s") + " into " + targetsHit.Count + " chest" + ((targetsHit.Count == 1) ? "" : "s"), 0, (Sprite)null); } Plugin.Log.LogInfo((object)("[organize] moved " + movedItems + " items into " + targetsHit.Count + " chests")); } } internal static class Puller { internal static void PullInto(Container dest, out int movedTotal, out int typesMoved) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) movedTotal = 0; typesMoved = 0; FilterSpec spec = Filters.GetSpec(dest); if (!spec.HasExplicit) { return; } Inventory inventory = dest.GetInventory(); ZNetView val = SorterZdo.NView(dest); if (inventory == null || (Object)(object)val == (Object)null || !val.IsValid()) { return; } Dictionary<string, int> dictionary = new Dictionary<string, int>(); HashSet<string> hashSet = new HashSet<string>(); List<Container> list = ContainerTracker.Candidates(dest, Plugin.SorterRadius.Value, excludeSorters: false); List<KeyValuePair<int, KeyValuePair<Container, ItemData>>> list2 = new List<KeyValuePair<int, KeyValuePair<Container, ItemData>>>(); foreach (Container item in list) { Inventory inventory2 = item.GetInventory(); InventoryBlock val2 = InventoryBlock.Get(inventory2); foreach (ItemData allItem in inventory2.GetAllItems()) { if (allItem != null && allItem.m_shared != null && (val2 == null || !val2.IsSlotBlocked(allItem.m_gridPos))) { string normName = Names.Normalize(allItem.m_shared.m_name); if (spec.MatchesItem(normName) || spec.MatchesGroup(normName)) { int key = inventory2.CountItems(allItem.m_shared.m_name, -1, true); list2.Add(new KeyValuePair<int, KeyValuePair<Container, ItemData>>(key, new KeyValuePair<Container, ItemData>(item, allItem))); } } } } list2.Sort((KeyValuePair<int, KeyValuePair<Container, ItemData>> a, KeyValuePair<int, KeyValuePair<Container, ItemData>> b) => b.Key.CompareTo(a.Key)); foreach (KeyValuePair<int, KeyValuePair<Container, ItemData>> item2 in list2) { Container key2 = item2.Value.Key; ItemData value = item2.Value.Value; string text = Names.Normalize(value.m_shared.m_name); int maxStackSize = value.m_shared.m_maxStackSize; if (!dictionary.TryGetValue(text, out var value2)) { value2 = ((maxStackSize <= 1) ? 1 : maxStackSize); } if (value2 <= 0) { continue; } int num = Router.Room(inventory, value); if (num <= 0) { continue; } int num2 = Math.Min(Math.Min(value2, value.m_stack), num); if (num2 > 0) { ContainerHandler.RemoveItemFromChest(key2, value, inventory, new Vector2i(-1, -1), val.GetZDO().m_uid, num2, (ItemData)null); dictionary[text] = value2 - num2; movedTotal += num2; if (hashSet.Add(text)) { typesMoved++; } } } } } internal static class Router { internal static Container FindTarget(Container sorter, ItemData item, float radius, out int amount) { amount = 0; string normName = Names.Normalize(item.m_shared.m_name); bool flag = item.m_shared.m_maxStackSize > 1; Container result = null; int num = 0; int num2 = int.MinValue; int num3 = -1; foreach (Container item2 in ContainerTracker.Candidates(sorter, radius)) { FilterSpec spec = Filters.GetSpec(item2); if (spec.Ignore || spec.ManualOnly) { continue; } Inventory inventory = item2.GetInventory(); int num4 = 0; if (spec.MatchesItem(normName)) { num4 = 3; } else if (spec.MatchesGroup(normName)) { num4 = 2; } else if (flag && Plugin.ContainsFallback.Value && inventory.HaveItem(item.m_shared.m_name, true)) { num4 = 1; } if (num4 == 0) { continue; } int num5 = Room(inventory, item); if (num5 > 0) { int num6 = inventory.CountItems(item.m_shared.m_name, -1, true); if (num4 > num || (num4 == num && (spec.Priority > num2 || (spec.Priority == num2 && num6 > num3)))) { result = item2; num = num4; num2 = spec.Priority; num3 = num6; amount = Math.Min(num5, item.m_stack); } } } return result; } internal static int Room(Inventory inv, ItemData item) { int maxStackSize = item.m_shared.m_maxStackSize; if (maxStackSize <= 1) { return (inv.GetEmptySlots() > 0) ? 1 : 0; } return inv.FindFreeStackSpace(item.m_shared.m_name, (float)item.m_worldLevel) + inv.GetEmptySlots() * maxStackSize; } } internal class SorterBehaviour : MonoBehaviour { private Container _container; private float _nextTick; private void Awake() { _container = ((Component)this).GetComponent<Container>(); _nextTick = Time.time + (float)(((Object)this).GetInstanceID() & 0xF) * 0.05f; } private void FixedUpdate() { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) if (Time.time < _nextTick) { return; } _nextTick = Time.time + Plugin.TransferInterval.Value; if ((Object)(object)_container == (Object)null) { return; } ZNetView val = SorterZdo.NView(_container); if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner() || !SorterZdo.IsSorter(_container) || _container.IsInUse()) { return; } Inventory inventory = _container.GetInventory(); if (inventory == null) { return; } InventoryBlock val2 = InventoryBlock.Get(inventory); int num = Plugin.StacksPerTick.Value; foreach (ItemData item in new List<ItemData>(inventory.GetAllItems())) { if (num <= 0) { break; } if (item != null && item.m_shared != null && (val2 == null || !val2.IsSlotBlocked(item.m_gridPos))) { int amount; Container val3 = Router.FindTarget(_container, item, Plugin.SorterRadius.Value, out amount); if (!((Object)(object)val3 == (Object)null) && amount > 0) { ContainerHandler.AddItemToChest(val3, item, inventory, new Vector2i(-1, -1), val.GetZDO().m_uid, amount); num--; Plugin.Log.LogDebug((object)$"[sorter] routed {item.m_shared.m_name} x{amount}"); } } } } } internal static class SorterZdo { private static readonly int SorterHash = StringExtensionMethods.GetStableHashCode("psort_sorter"); private static readonly FieldRef<Container, ZNetView> NViewRef = AccessTools.FieldRefAccess<Container, ZNetView>("m_nview"); private static readonly Func<Container, long, bool> CheckAccessFunc = AccessTools.MethodDelegate<Func<Container, long, bool>>(AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null), (object)null, true); internal static ZNetView NView(Container c) { if (!Object.op_Implicit((Object)(object)c)) { return null; } return NViewRef.Invoke(c); } internal static bool HasValidNView(Container c) { ZNetView val = NView(c); if ((Object)(object)val != (Object)null) { return val.IsValid(); } return false; } internal static bool IsSorter(Container c) { ZNetView val = NView(c); if ((Object)(object)val != (Object)null && val.IsValid()) { return val.GetZDO().GetBool(SorterHash, false); } return false; } internal static void SetSorter(Container c, bool on) { ZNetView val = NView(c); if (!((Object)(object)val == (Object)null) && val.IsValid()) { if (!val.IsOwner()) { val.ClaimOwnership(); } val.GetZDO().Set(SorterHash, on); } } internal static bool PlayerCanAccess(Container c) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { return CheckAccessFunc(c, localPlayer.GetPlayerID()); } return false; } } internal static class Stations { private static readonly Dictionary<string, string> Defaults = new Dictionary<string, string> { { "$piece_forge", "metals, ores" }, { "$piece_workbench", "wood, hides" }, { "$piece_stonecutter", "stone" }, { "$piece_cauldron", "cooking, meat, seeds" }, { "$piece_fermenter", "meads" }, { "$piece_blackforge", "metals, valuables" }, { "$piece_magetable", "valuables, meads" }, { "$piece_smelter", "ores, fuel" }, { "$piece_blastfurnace", "ores, fuel" } }; 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> Empty = new List<string>(); private static ConfigEntry<string> Extra; private static readonly Dictionary<Component, string> Processors = new Dictionary<Component, string>(); private static readonly List<Component> Dead = new List<Component>(); private static readonly FieldInfo AllStationsField = AccessTools.Field(typeof(CraftingStation), "m_allStations"); private static bool IsReal(Component c) { ZNetView val = (((Object)(object)c != (Object)null) ? c.GetComponentInParent<ZNetView>() : null); if ((Object)(object)val != (Object)null) { return val.IsValid(); } return false; } internal static void RegisterProcessor(Component c, string mName) { if ((Object)(object)c == (Object)null || string.IsNullOrEmpty(mName) || !IsReal(c)) { return; } foreach (KeyValuePair<Component, string> processor in Processors) { if ((Object)(object)processor.Key == (Object)null) { Dead.Add(processor.Key); } } if (Dead.Count > 0) { foreach (Component item in Dead) { Processors.Remove(item); } Dead.Clear(); } Processors[c] = mName; } internal static void UnregisterProcessor(Component c) { if ((Object)(object)c != (Object)null) { Processors.Remove(c); } } internal static void Init(ConfigFile config) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown foreach (KeyValuePair<string, string> @default in Defaults) { Entries[@default.Key] = config.Bind<string>("Stations", @default.Key, @default.Value, new ConfigDescription("Comma-separated group names (see [ItemGroups]) this station attracts during Organize.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } Extra = config.Bind<string>("Stations", "CustomStations", "", new ConfigDescription("Extra mappings for modded/unlisted stations. Format: 'token=group1,group2; token2=group3'. Copy a station's token from the log (Info) after pressing Organize, e.g. '$piece_blacksmith=metals,ores'.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); config.SettingChanged += delegate { Rebuild(); }; Rebuild(); } private static void Rebuild() { Parsed.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; } if (Extra == null || string.IsNullOrEmpty(Extra.Value)) { return; } array = Extra.Value.Split(';'); foreach (string text2 in array) { int num = text2.IndexOf('='); if (num <= 0) { continue; } string text3 = text2.Substring(0, num).Trim(); if (text3.Length == 0) { continue; } List<string> list2 = new List<string>(); string[] array2 = text2.Substring(num + 1).Split(','); for (int j = 0; j < array2.Length; j++) { string text4 = array2[j].Trim().ToLowerInvariant(); if (text4.Length > 0) { list2.Add(text4); } } if (list2.Count > 0) { Parsed[text3] = list2; } } } internal static List<string> GroupsForStationName(string mName) { if (!string.IsNullOrEmpty(mName) && Parsed.TryGetValue(mName, out var value)) { return value; } return Empty; } internal static List<string> GroupsForChest(Container c, float range) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null) { return Empty; } Vector3 position = ((Component)c).transform.position; string bestMapped = null; string bestUnmapped = null; float bestMappedD = range; float bestUnmappedD = range; List<CraftingStation> list = ((AllStationsField != null) ? (AllStationsField.GetValue(null) as List<CraftingStation>) : null); if (list != null) { foreach (CraftingStation item in list) { if (!((Object)(object)item == (Object)null) && IsReal((Component)(object)item)) { Consider(item.m_name, Vector3.Distance(((Component)item).transform.position, position), ref bestMapped, ref bestMappedD, ref bestUnmapped, ref bestUnmappedD); } } } foreach (KeyValuePair<Component, string> processor in Processors) { if ((Object)(object)processor.Key == (Object)null) { Dead.Add(processor.Key); } else if (IsReal(processor.Key)) { Consider(processor.Value, Vector3.Distance(processor.Key.transform.position, position), ref bestMapped, ref bestMappedD, ref bestUnmapped, ref bestUnmappedD); } } if (Dead.Count > 0) { foreach (Component item2 in Dead) { Processors.Remove(item2); } Dead.Clear(); } if (bestMapped != null) { List<string> list2 = GroupsForStationName(bestMapped); Plugin.Log.LogInfo((object)("[organize] chest near '" + bestMapped + "' (" + bestMappedD.ToString("0.0") + "m) -> " + string.Join(",", list2))); return list2; } if (bestUnmapped != null) { Plugin.Log.LogInfo((object)("[organize] chest near station '" + bestUnmapped + "' (" + bestUnmappedD.ToString("0.0") + "m) has NO [Stations] mapping - add '" + bestUnmapped + " = <groups>' to the config to route its materials")); } return Empty; } private static void Consider(string mName, float d, ref string bestMapped, ref float bestMappedD, ref string bestUnmapped, ref float bestUnmappedD) { if (string.IsNullOrEmpty(mName)) { return; } if (Parsed.TryGetValue(mName, out var value) && value.Count > 0) { if (d < bestMappedD) { bestMappedD = d; bestMapped = mName; } } else if (d < bestUnmappedD) { bestUnmappedD = d; bestUnmapped = mName; } } } }