using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyCompany("ValheimAutoSort")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0")] [assembly: AssemblyProduct("ValheimAutoSort")] [assembly: AssemblyTitle("ValheimAutoSort")] [assembly: AssemblyVersion("2.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ValheimAutoSort { [BepInPlugin("com.yourname.valheim.autosort", "AutoSort", "2.1.0")] [BepInProcess("valheim.exe")] public class AutoSortPlugin : BaseUnityPlugin { public const string PluginGUID = "com.yourname.valheim.autosort"; public const string PluginName = "AutoSort"; public const string PluginVersion = "2.1.0"; internal static ManualLogSource Log; public static ConfigEntry ModEnabled; public static ConfigEntry LogDebug; public static ConfigEntry EnableTagOverride; public static ConfigEntry SortKeybind; public static ConfigEntry ScanRadius; public static ConfigEntry EnableGroundSuck; public static ConfigEntry GroundSuckRadius; public static ConfigEntry GroundSuckInterval; public static ConfigEntry HighlightContainers; public static ConfigEntry PingContainers; public static ConfigEntry PingVFX; public static ConfigEntry Cat_Stone; public static ConfigEntry Cat_Wood; public static ConfigEntry Cat_Ore; public static ConfigEntry Cat_Metal; public static ConfigEntry Cat_Gem; public static ConfigEntry Cat_Leather; public static ConfigEntry Cat_Bone; public static ConfigEntry Cat_Fiber; public static ConfigEntry Cat_Seed; public static ConfigEntry Cat_Food; public static ConfigEntry Cat_Potion; public static ConfigEntry Cat_Fish; public static ConfigEntry Cat_Arrow; public static ConfigEntry Cat_Weapon; public static ConfigEntry Cat_Shield; public static ConfigEntry Cat_Armor; public static ConfigEntry Cat_Tool; public static ConfigEntry Cat_Magic; public static ConfigEntry Cat_Trophy; public static ConfigEntry Cat_Furniture; public static ConfigEntry Cat_Misc; private Harmony _harmony; private void Awake() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_0096: 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) //IL_00b0: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Expected O, but got Unknown //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Expected O, but got Unknown //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_0528: Expected O, but got Unknown //IL_054c: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; ModEnabled = ((BaseUnityPlugin)this).Config.Bind("1 - General", "ModEnabled", true, "Master switch. Set to false to disable all AutoSort behaviour without uninstalling."); LogDebug = ((BaseUnityPlugin)this).Config.Bind("1 - General", "LogDebug", false, "Print verbose debug messages to the BepInEx console. Turn on when troubleshooting. Also prints the prefab name of any item sorted — useful for adding custom items to a category."); EnableTagOverride = ((BaseUnityPlugin)this).Config.Bind("2 - Chest Tagging", "EnableTagOverride", true, new ConfigDescription("Allow chest categories to be set via the in-chest GUI button or the 'autosort_tag ' console command. Tagged chests always override inferred categories and are never repurposed by the sort pass.", (AcceptableValueBase)null, Array.Empty())); SortKeybind = ((BaseUnityPlugin)this).Config.Bind("3 - Sort Pass", "SortKeybind", new KeyboardShortcut((KeyCode)114, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), new ConfigDescription("Keyboard shortcut that triggers a full sort of all chests within ScanRadius of the player. Default: Alt+R. Only fires when no GUI/inventory is open.", (AcceptableValueBase)null, Array.Empty())); ScanRadius = ((BaseUnityPlugin)this).Config.Bind("3 - Sort Pass", "ScanRadius", 30f, new ConfigDescription("Radius in metres around the player within which chests are included in a sort pass. Larger values cover more chests but scan more objects each press. Default 30 covers a typical house interior from end to end.", (AcceptableValueBase)(object)new AcceptableValueRange(4f, 128f), Array.Empty())); EnableGroundSuck = ((BaseUnityPlugin)this).Config.Bind("4 - Ground Items", "EnableGroundSuck", true, "When enabled, items lying on the ground within GroundSuckRadius of the player are periodically pulled into nearby matching chests."); GroundSuckRadius = ((BaseUnityPlugin)this).Config.Bind("4 - Ground Items", "GroundSuckRadius", 8f, new ConfigDescription("How far from the player (metres) to look for ground items to suck up.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 32f), Array.Empty())); GroundSuckInterval = ((BaseUnityPlugin)this).Config.Bind("4 - Ground Items", "GroundSuckInterval", 5f, new ConfigDescription("Seconds between each ground-item scan.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 60f), Array.Empty())); HighlightContainers = ((BaseUnityPlugin)this).Config.Bind("5 - Effects", "HighlightContainers", true, "Briefly flash a chest with a blue emission glow when an item is sorted into it."); PingContainers = ((BaseUnityPlugin)this).Config.Bind("5 - Effects", "PingContainers", true, "Spawn a particle VFX at a chest when an item is sorted into it."); PingVFX = ((BaseUnityPlugin)this).Config.Bind("5 - Effects", "PingVFX", "vfx_Potion_health_medium", "VFX prefab name to spawn on sort. Leave blank to disable. Browse names at: https://valheim-modding.github.io/Jotunn/data/prefabs/prefab-list.html"); Cat_Stone = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Stone", "Stone, Flint, Obsidian, Coal, BlackMarble, Grausten", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Wood = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Wood", "Wood, FineWood, RoundLog, YggdrasilWood, AncientBark, ElderBark, AshWood, WoodBeam", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Ore = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Ore", "CopperOre, TinOre, IronScrap, SilverOre, BlackMetalScrap, FlametalOre, MucusOre, CopperScrap", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Metal = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Metal", "Bronze, Iron, Silver, BlackMetal, Copper, Tin, Flametal, DvergrMetal, BlackCore, MorgenCore, BronzeNails, IronNails", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Gem = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Gem", "Amber, AmberPearl, Ruby, SilverNecklace, BlackGem, Coins", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Leather = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Leather", "LeatherScraps, DeerHide, ScaleHide, WolfPelt, LoxPelt, TrollHide, AskHide", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Bone = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Bone", "BoneFragments, WitheredBone, Chitin, Carapace, HardAntler, DragonEgg", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Fiber = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Fiber", "Flax, FlaxThread, Barley, BarleyFlour, Thistle, Dandelion, Bloodbag, Guck, LinenThread, JuteRed, JuteBlue", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Seed = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Seed", "Acorn, AncientSeed, BeechSeeds, BirchSeeds, CarrotSeeds, TurnipSeeds, OnionSeeds, FlaxSeeds, BarleySeeds, JuteSeedRed, SeedCarrot, SeedTurnip, SeedOnion, SeedBarley, SeedFlax, PineCone, FirCone, Surtling_Immature", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Food = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Food", "Mushroom, MushroomBlue, MushroomYellow, Raspberry, Blueberries, Cloudberry, Carrot, Turnip, Onion, Honey, Egg, Entrails, NeckTail, DeerMeat, WolfMeat, LoxMeat, SerpentMeat, SerpentMeatCooked, ChickenMeat, ChickenEgg, VoltureMeat, AsksvinMeat, CharredMeat, CarrotSoup, TurnipStew, OnionSoup, QueensJam, Bread, FishWraps, MinceMeatSauce, Sausages, BlackSoup, WolfMeatSkewer, WolfJerky, LoxPie, LoxMeatPie, SerpentStew, BloodPudding, EyeScream, BugMeat, MisthareSupreme, HareMeat, YggdrasilPorridge, MarinatedGreens, ScalpSkewer, SeekerAspic, FishNBread, Salad, SpicedMeat, AshenMeat, CookedBugMeat, CookedChickenMeat, CookedDeerMeat, CookedWolfMeat, CookedLoxMeat, CookedHareMeat, CookedVoltureMeat, CookedAsksvinMeat, CookedCharredMeat, BarleyWine, CloudberryWine", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Potion = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Potion", "MeadBase, MeadBaseFrostResist, MeadBaseHealthMinor, MeadBaseHealthMedium, MeadBaseHealthMajor, MeadBaseStaminaMinor, MeadBaseStaminaMedium, MeadBaseStaminaLingering, MeadBasePoisonResist, MeadBaseTasty, MeadBaseEitrMinor, MeadBaseEitrMedium, MeadFrostResist, MeadHealthMinor, MeadHealthMedium, MeadHealthMajor, MeadStaminaMinor, MeadStaminaMedium, MeadStaminaLingering, MeadPoisonResist, MeadTasty, MeadEitrMinor, MeadEitrMedium", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Fish = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Fish", "Fish1, Fish2, Fish3, Fish4_cave, Fish5, Fish6, Fish7, Fish8, Fish9, Fish10, Fish11, Fish12, FishCooked, FishAndBread", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Arrow = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Arrow", "ArrowWood, ArrowFlint, ArrowBronze, ArrowIron, ArrowObsidian, ArrowSilver, ArrowNeedle, ArrowFire, ArrowPoison, ArrowFrost, ArrowCharred, ArrowCarapace, BoltBone, BoltIron, BoltBlackmetal, BoltCarapace, BoltCharred", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Weapon = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Weapon", "", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted.\nLeave blank — weapons are detected automatically by item type."); Cat_Shield = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Shield", "", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted.\nLeave blank — shields are detected automatically by item type."); Cat_Armor = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Armor", "", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted.\nLeave blank — armor is detected automatically by item type."); Cat_Tool = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Tool", "", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted.\nLeave blank — tools are detected automatically by item type."); Cat_Magic = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Magic", "Eitr, Sap, SoftTissue, Wisp, WispTorch, DvergrNeedle, Dundr, GemstoneGreen, GemstoneBlue, GemstoneRed, VolcanicRock, AshflameKindling, CharredRemains, Emberlite", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Trophy = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Trophy", "TrophyBoar, TrophyNeck, TrophyDeer, TrophyGreydwarf, TrophyGreydwarfBrute, TrophyGreydwarfShaman, TrophyForestTroll, TrophySkeletonPoison, TrophySkeleton, TrophyDraugr, TrophyDraugrElite, TrophyBlob, TrophyLeech, TrophyDraugrFem, TrophyWraith, TrophyAbomination, TrophyLox, TrophySGolem, TrophyWolf, TrophyHatchling, TrophyFenring, TrophySerpent, TrophyDeathsquito, TrophyGrowth, TrophySeeker, TrophySeekerBrute, TrophyTick, TrophyHare, TrophyGjall, TrophyCharredMelee, TrophyCharredArcher, TrophyCharredTwitcher, TrophyCharredWarlock, TrophyFader, TrophyEikthyr, TrophyElder, TrophyBonemass, TrophyDragonQueen, TrophyGoblinKing, TrophyQueenBee, TrophyVolture, TrophyAskvin", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted."); Cat_Furniture = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Furniture", "", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted.\nAdd furniture/building piece prefab names here if desired."); Cat_Misc = ((BaseUnityPlugin)this).Config.Bind("6 - Categories", "Misc", "", "\nComma-separated prefab names. Add modded items here. Enable LogDebug to print prefab names as items are sorted.\nAny prefab listed here is forced to Misc regardless of other rules."); ItemCategorizer.LoadFromConfig(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate { ItemCategorizer.LoadFromConfig(); }; ItemCategorizer.InitJsonWatcher(); _harmony = new Harmony("com.yourname.valheim.autosort"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)("AutoSort 2.1.0 loaded. " + $"Sort keybind: {SortKeybind.Value}. Scan radius: {ScanRadius.Value} m.")); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } public static class ChestEffects { [CompilerGenerated] private sealed class d__4 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Container container; private Renderer[] 5__2; private Color[] 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; 5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: { <>1__state = -1; if ((Object)(object)container == (Object)null) { return false; } 5__2 = ((Component)container).GetComponentsInChildren(); if (5__2 == null || 5__2.Length == 0) { return false; } 5__3 = (Color[])(object)new Color[5__2.Length]; for (int j = 0; j < 5__2.Length; j++) { Material material2 = 5__2[j].material; 5__3[j] = (material2.HasProperty("_EmissionColor") ? material2.GetColor("_EmissionColor") : Color.black); if (material2.HasProperty("_EmissionColor")) { material2.EnableKeyword("_EMISSION"); material2.SetColor("_EmissionColor", new Color(0.2f, 0.6f, 1f, 1f) * 1.5f); } } <>2__current = (object)new WaitForSeconds(0.6f); <>1__state = 1; return true; } case 1: <>1__state = -1; if ((Object)(object)container != (Object)null) { for (int i = 0; i < 5__2.Length; i++) { if (!((Object)(object)5__2[i] == (Object)null)) { Material material = 5__2[i].material; if (material.HasProperty("_EmissionColor")) { material.SetColor("_EmissionColor", 5__3[i]); } } } } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly Dictionary _cooldowns = new Dictionary(); private const float CooldownSeconds = 2f; public static void PlayEffect(Container container) { if (!AutoSortPlugin.ModEnabled.Value || (Object)(object)container == (Object)null) { return; } float time = Time.time; if (!_cooldowns.TryGetValue(container, out var value) || !(time - value < 2f)) { _cooldowns[container] = time; if (AutoSortPlugin.PingContainers.Value) { SpawnVFX(container); } if (AutoSortPlugin.HighlightContainers.Value) { ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(HighlightContainer(container)); } } } private static void SpawnVFX(Container container) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) string value = AutoSortPlugin.PingVFX.Value; if (string.IsNullOrWhiteSpace(value)) { return; } ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab(value) : null); if ((Object)(object)val == (Object)null) { if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)("[AutoSort] VFX prefab '" + value + "' not found.")); } } else { Vector3 val2 = ((Component)container).transform.position + Vector3.up * 1.2f; Object.Instantiate(val, val2, Quaternion.identity); } } [IteratorStateMachine(typeof(d__4))] private static IEnumerator HighlightContainer(Container container) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__4(0) { container = container }; } } public class ChestOwnership { public Container Container { get; } public SortCategory Category { get; set; } public bool IsTagged { get; } public int Count { get; } public bool IsFull { get { Inventory inventory = Container.GetInventory(); if (inventory == null) { return false; } return inventory.GetEmptySlots() == 0; } } public int EmptySlots { get { Inventory inventory = Container.GetInventory(); if (inventory == null) { return 0; } return inventory.GetEmptySlots(); } } public int Capacity { get { Inventory inventory = Container.GetInventory(); int? num = ((inventory != null) ? new int?(inventory.GetWidth()) : null); Inventory inventory2 = Container.GetInventory(); return (num * ((inventory2 != null) ? new int?(inventory2.GetHeight()) : null)).GetValueOrDefault(); } } public bool IsFreeChest { get; set; } public ChestOwnership(Container c, SortCategory cat, bool isTagged, int count) { Container = c; Category = cat; IsTagged = isTagged; Count = count; } } public static class ChestOwnershipResolver { private class VSlot { public string Name; public int Stack; public int MaxStack; public VSlot(string name, int stack, int maxStack) { Name = name; Stack = stack; MaxStack = maxStack; } } public static List Resolve(Vector3 origin, float radius) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (Container item in ContainersTracker.GetNearby(origin, radius)) { if (IsAccessible(item)) { ChestOwnership chestOwnership = ResolveContainer(item); if (chestOwnership != null) { list.Add(chestOwnership); } } } list.Sort(delegate(ChestOwnership a, ChestOwnership b) { //IL_005a: 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_0072: Unknown result type (might be due to invalid IL or missing references) if (a.IsTagged != b.IsTagged) { if (!a.IsTagged) { return 1; } return -1; } if (a.IsTagged && b.IsTagged) { int num = a.Category.CompareTo(b.Category); if (num != 0) { return num; } return ((Component)b.Container).transform.position.x.CompareTo(((Component)a.Container).transform.position.x); } return b.Count.CompareTo(a.Count); }); if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)$"[AutoSort] Resolved {list.Count} chest(s):"); foreach (ChestOwnership item2 in list) { AutoSortPlugin.Log.LogDebug((object)($" [{item2.Category}] tagged={item2.IsTagged} count={item2.Count} " + $"full={item2.IsFull} free={item2.IsFreeChest} slots={item2.EmptySlots}/{item2.Capacity}")); } } return list; } public static Dictionary PlanSortFull(List ownerships) { //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) List ownerships2 = ownerships; Dictionary> virtualSlots = new Dictionary>(); foreach (ChestOwnership item4 in ownerships2) { Inventory inventory = item4.Container.GetInventory(); List list = new List(); if (inventory != null) { foreach (ItemData allItem in inventory.GetAllItems()) { list.Add(new VSlot(allItem.m_shared.m_name, allItem.m_stack, allItem.m_shared.m_maxStackSize)); } } virtualSlots[item4.Container] = list; } List first = ownerships2.Where((ChestOwnership o) => o.IsTagged).ToList(); List inferred = ownerships2.Where((ChestOwnership o) => !o.IsTagged).ToList(); inferred = SortInferredByPosition(inferred); ownerships2 = first.Concat(inferred).ToList(); if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)"[AutoSort] Inferred chest spatial planning order:"); for (int i = 0; i < inferred.Count; i++) { Vector3 position = ((Component)inferred[i].Container).transform.position; AutoSortPlugin.Log.LogDebug((object)($" [{i}] [{inferred[i].Category}] " + $"x={position.x:F1} z={position.z:F1} " + $"free={inferred[i].IsFreeChest} slots={inferred[i].EmptySlots}/{inferred[i].Capacity}")); } } Dictionary freeClaimed = new Dictionary(); List<(ItemData, Container, ChestOwnership)> list2 = new List<(ItemData, Container, ChestOwnership)>(); foreach (ChestOwnership item5 in ownerships2) { Inventory inventory2 = item5.Container.GetInventory(); if (inventory2 == null) { continue; } foreach (ItemData allItem2 in inventory2.GetAllItems()) { if (allItem2.m_stack > 0) { list2.Add((allItem2, item5.Container, item5)); } } } list2 = (from x in list2 orderby (x.owner.IsTagged && x.owner.Category == ItemCategorizer.GetCategory(x.item)) ? 1 : 0 descending, ItemCategorizer.GetCategory(x.item).ToString(), ItemCategorizer.GetItemKey(x.item) select x).ToList(); Dictionary dictionary = new Dictionary(ReferenceEqualityComparer.Instance); foreach (var item6 in list2) { ItemData item2 = item6.Item1; Container item3 = item6.Item2; SortCategory category = ItemCategorizer.GetCategory(item2); string name = item2.m_shared.m_name; int maxStackSize = item2.m_shared.m_maxStackSize; Container c2 = (dictionary[item2] = FindDest(item2, category, item3) ?? item3); VirtualRemove(item3, name, item2.m_stack); VirtualAdd(c2, name, item2.m_stack, maxStackSize); } return dictionary; static int Capacity(Container c) { Inventory inventory3 = c.GetInventory(); if (inventory3 == null) { return 0; } return inventory3.GetWidth() * inventory3.GetHeight(); } Container? FindDest(ItemData item, SortCategory cat, Container home) { string sharedName4 = item.m_shared.m_name; int maxStack2 = item.m_shared.m_maxStackSize; ChestOwnership chestOwnership = ownerships2.FirstOrDefault((ChestOwnership o) => o.IsTagged && !o.IsFreeChest && o.Category == cat && VirtualRoom(o.Container, sharedName4, maxStack2) > 0); if (chestOwnership != null) { return chestOwnership.Container; } ChestOwnership chestOwnership2 = ownerships2.FirstOrDefault((ChestOwnership o) => !o.IsTagged && !o.IsFreeChest && o.Category == cat && VirtualRoom(o.Container, sharedName4, maxStack2) > 0 && virtualSlots[o.Container].Any((VSlot s) => s.Name == sharedName4)); if (chestOwnership2 != null) { return chestOwnership2.Container; } ChestOwnership chestOwnership3 = ownerships2.FirstOrDefault((ChestOwnership o) => !o.IsTagged && !o.IsFreeChest && o.Category == cat && VirtualRoom(o.Container, sharedName4, maxStack2) > 0); if (chestOwnership3 != null) { return chestOwnership3.Container; } ChestOwnership chestOwnership4 = ownerships2.FirstOrDefault(delegate(ChestOwnership o) { if (freeClaimed.TryGetValue(o.Container, out var value)) { if (value == cat) { return VirtualRoom(o.Container, sharedName4, maxStack2) > 0; } return false; } return (o.IsFreeChest || o.Category == SortCategory.Unknown) && VirtualRoom(o.Container, sharedName4, maxStack2) > 0; }); if (chestOwnership4 != null) { freeClaimed[chestOwnership4.Container] = cat; chestOwnership4.IsFreeChest = false; chestOwnership4.Category = cat; return chestOwnership4.Container; } return null; } int VirtualAdd(Container c, string sharedName, int stack, int maxStack) { List list3 = virtualSlots[c]; int num = Capacity(c); int num2 = stack; foreach (VSlot item7 in list3) { if (!(item7.Name != sharedName) && item7.Stack < item7.MaxStack) { int num3 = Math.Min(item7.MaxStack - item7.Stack, num2); item7.Stack += num3; num2 -= num3; if (num2 == 0) { return stack; } } } while (num2 > 0 && list3.Count < num) { int num4 = Math.Min(maxStack, num2); list3.Add(new VSlot(sharedName, num4, maxStack)); num2 -= num4; } return stack - num2; } void VirtualRemove(Container c, string sharedName, int stack) { string sharedName2 = sharedName; List list4 = virtualSlots[c]; VSlot vSlot = list4.FirstOrDefault((VSlot s) => s.Name == sharedName2 && s.Stack == stack); if (vSlot == null) { vSlot = (from s in list4 where s.Name == sharedName2 && s.Stack >= stack orderby s.Stack - stack select s).FirstOrDefault(); } if (vSlot == null) { vSlot = list4.FirstOrDefault((VSlot s) => s.Name == sharedName2); } if (vSlot != null) { vSlot.Stack -= stack; if (vSlot.Stack <= 0) { list4.Remove(vSlot); } } } int VirtualRoom(Container c, string sharedName, int maxStack) { string sharedName3 = sharedName; List list5 = virtualSlots[c]; int num5 = Capacity(c); int num6 = Math.Max(0, num5 - list5.Count); int num7 = list5.Where((VSlot s) => s.Name == sharedName3 && s.Stack < s.MaxStack).Sum((VSlot s) => s.MaxStack - s.Stack); return num6 * maxStack + num7; } } public static Container? FindBestDestination(ItemData item, List ownerships, Container? sourceContainer) { Container sourceContainer2 = sourceContainer; ItemData item2 = item; SortCategory itemCat = ItemCategorizer.GetCategory(item2); string itemKey = ItemCategorizer.GetItemKey(item2); List list = ownerships.Where((ChestOwnership o) => !o.IsFreeChest && o.Category == itemCat).ToList(); if (list.Count > 0) { ChestOwnership chestOwnership = list.FirstOrDefault((ChestOwnership o) => o.IsTagged && (Object)(object)o.Container != (Object)(object)sourceContainer2 && HasRoomFor(o.Container, item2)); if (chestOwnership != null) { return chestOwnership.Container; } ChestOwnership chestOwnership2 = list.FirstOrDefault(delegate(ChestOwnership o) { if (!o.IsTagged && (Object)(object)o.Container != (Object)(object)sourceContainer2 && HasRoomFor(o.Container, item2)) { Inventory inventory = o.Container.GetInventory(); if (inventory == null) { return false; } return inventory.GetAllItems().Any((ItemData i) => ItemCategorizer.GetItemKey(i) == itemKey); } return false; }); if (chestOwnership2 != null) { return chestOwnership2.Container; } ChestOwnership chestOwnership3 = list.FirstOrDefault((ChestOwnership o) => (Object)(object)o.Container != (Object)(object)sourceContainer2 && HasRoomFor(o.Container, item2)); if (chestOwnership3 != null) { return chestOwnership3.Container; } } ChestOwnership chestOwnership4 = ownerships.FirstOrDefault((ChestOwnership o) => (o.IsFreeChest || o.Category == SortCategory.Unknown) && o.EmptySlots > 0 && (Object)(object)o.Container != (Object)(object)sourceContainer2); if (chestOwnership4 != null) { chestOwnership4.IsFreeChest = false; chestOwnership4.Category = itemCat; return chestOwnership4.Container; } return null; } public static bool HasRoomFor(Container container, ItemData item) { ItemData item2 = item; Inventory inventory = container.GetInventory(); if (inventory == null) { return false; } if (inventory.GetEmptySlots() > 0) { return true; } return inventory.GetAllItems().Any((ItemData i) => i.m_shared.m_name == item2.m_shared.m_name && i.m_stack < i.m_shared.m_maxStackSize); } public static ChestOwnership? ResolveContainer(Container container) { Inventory inventory = container.GetInventory(); if (inventory == null) { return null; } if (AutoSortPlugin.EnableTagOverride.Value && ItemCategorizer.TryParseChestTag(GetChestTag(container), out var category)) { return new ChestOwnership(container, category, isTagged: true, int.MaxValue); } List allItems = inventory.GetAllItems(); if (allItems == null || allItems.Count == 0) { return new ChestOwnership(container, SortCategory.Unknown, isTagged: false, 0) { IsFreeChest = true }; } Dictionary dictionary = new Dictionary(); foreach (ItemData item in allItems) { SortCategory category2 = ItemCategorizer.GetCategory(item); dictionary.TryGetValue(category2, out var value); dictionary[category2] = value + item.m_stack; } KeyValuePair keyValuePair = dictionary.OrderByDescending((KeyValuePair kv) => kv.Value).First(); return new ChestOwnership(container, keyValuePair.Key, isTagged: false, keyValuePair.Value); } public static bool IsAccessible(Container container) { if ((Object)(object)container == (Object)null) { return false; } ZNetView component = ((Component)container).GetComponent(); if ((Object)(object)component != (Object)null) { return component.IsValid(); } return false; } public static string GetChestTag(Container container) { ZNetView component = ((Component)container).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return string.Empty; } return component.GetZDO().GetString("tag", string.Empty); } public static (SortCategory category, bool isTagged, bool isFree) GetContainerStatus(Container container) { ChestOwnership chestOwnership = ResolveContainer(container); if (chestOwnership == null) { return (SortCategory.Unknown, false, true); } return (chestOwnership.Category, chestOwnership.IsTagged, chestOwnership.IsFreeChest); } private static List SortInferredByPosition(List inferred) { if (inferred.Count <= 1) { return inferred; } List list = (from o in inferred orderby Mathf.Round(((Component)o.Container).transform.position.z / 1.5f) descending, ((Component)o.Container).transform.position.x select o).ToList(); Dictionary catFirstIndex = new Dictionary(); for (int i = 0; i < list.Count; i++) { SortCategory category = list[i].Category; if (!catFirstIndex.ContainsKey(category)) { catFirstIndex[category] = i; } } return (from t in list.Select((ChestOwnership o, int idx) => (o, idx)) orderby catFirstIndex[t.o.Category], t.idx select t.o).ToList(); } public static List<(ItemData item, Container src, Container dst)> PlanSort(List ownerships) { Dictionary dictionary = PlanSortFull(ownerships); List<(ItemData, Container, Container)> list = new List<(ItemData, Container, Container)>(); foreach (ChestOwnership ownership in ownerships) { Inventory inventory = ownership.Container.GetInventory(); if (inventory == null) { continue; } foreach (ItemData allItem in inventory.GetAllItems()) { if (dictionary.TryGetValue(allItem, out var value) && (Object)(object)value != (Object)(object)ownership.Container) { list.Add((allItem, ownership.Container, value)); } } } return list; } } internal sealed class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); public bool Equals(ItemData x, ItemData y) { return x == y; } public int GetHashCode(ItemData obj) { return RuntimeHelpers.GetHashCode(obj); } } public static class ContainersTracker { private static readonly List _containers = new List(); public static bool IsRearrangingItem { get; set; } = false; public static IReadOnlyList All => _containers; public static void Register(Container c) { if ((Object)(object)c != (Object)null && !_containers.Contains(c)) { _containers.Add(c); } } public static void Unregister(Container c) { _containers.Remove(c); } public static void Cleanup() { _containers.RemoveAll(delegate(Container c) { if ((Object)(object)c == (Object)null) { return true; } ZNetView component = ((Component)c).GetComponent(); return (Object)(object)component == (Object)null || !component.IsValid(); }); } public static List GetNearby(Vector3 origin, float radius) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) Cleanup(); List list = new List(); float num = radius * radius; foreach (Container container in _containers) { if ((Object)(object)container != (Object)null) { Vector3 val = ((Component)container).transform.position - origin; if (((Vector3)(ref val)).sqrMagnitude <= num) { list.Add(container); } } } return list; } } [HarmonyPatch(typeof(Container), "Awake")] public static class Patch_Container_Awake { [HarmonyPostfix] public static void Postfix(Container __instance) { if (!AutoSortPlugin.ModEnabled.Value || ((Object)__instance).name.StartsWith("Treasure") || __instance.GetInventory() == null) { return; } ZNetView component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return; } ZDO zDO = component.GetZDO(); long @long = zDO.GetLong(StringExtensionMethods.GetStableHashCode("creator"), 0L); long owner = zDO.GetOwner(); if (@long != 0L || owner != 0L) { ContainersTracker.Register(__instance); if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)("[AutoSort] Registered chest '" + ((Object)__instance).name + "'")); } } } } [HarmonyPatch(typeof(Container), "OnDestroyed")] public static class Patch_Container_OnDestroyed { [HarmonyPostfix] public static void Postfix(Container __instance) { ContainersTracker.Unregister(__instance); } } public static class GroundItemSucker { private static float _timer = 0f; private static readonly HashSet _claimedZdoids = new HashSet(); public static void Tick(float dt) { if (AutoSortPlugin.ModEnabled.Value && AutoSortPlugin.EnableGroundSuck.Value && !((Object)(object)Player.m_localPlayer == (Object)null) && !ContainersTracker.IsRearrangingItem) { _timer += dt; if (!(_timer < AutoSortPlugin.GroundSuckInterval.Value)) { _timer = 0f; SuckNearbyGroundItems(); } } } private static void SuckNearbyGroundItems() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: 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_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } Vector3 position = ((Component)localPlayer).transform.position; float value = AutoSortPlugin.ScanRadius.Value; float value2 = AutoSortPlugin.GroundSuckRadius.Value; List list = ChestOwnershipResolver.Resolve(position, value); if (list.Count == 0) { return; } ItemDrop[] array = Object.FindObjectsOfType(); HashSet hashSet = new HashSet(); ItemDrop[] array2 = array; foreach (ItemDrop obj in array2) { ZNetView val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val != (Object)null && val.IsValid()) { hashSet.Add(val.GetZDO().m_uid); } } _claimedZdoids.IntersectWith(hashSet); List<(ItemDrop, ZDOID)> list2 = new List<(ItemDrop, ZDOID)>(); array2 = array; foreach (ItemDrop val2 in array2) { if ((Object)(object)val2 == (Object)null || !((Component)val2).gameObject.activeInHierarchy) { continue; } ZNetView component = ((Component)val2).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { ZDOID uid = component.GetZDO().m_uid; if (!_claimedZdoids.Contains(uid) && component.GetZDO().GetInt("inUse", 0) == 0 && !(Vector3.Distance(((Component)val2).transform.position, position) > value2) && val2.m_itemData?.m_shared != null) { list2.Add((val2, uid)); } } } if (list2.Count == 0) { return; } ContainersTracker.IsRearrangingItem = true; HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(); int num = 0; try { foreach (var (val3, item) in list2) { if (!((Object)(object)val3 == (Object)null) && ((Component)val3).gameObject.activeInHierarchy) { ItemData itemData = val3.m_itemData; Container val4 = ChestOwnershipResolver.FindBestDestination(itemData, list, null); if ((Object)(object)val4 == (Object)null) { hashSet3.Add(ItemCategorizer.GetCategory(itemData).ToString()); } else if (ChestOwnershipResolver.HasRoomFor(val4, itemData) && val4.GetInventory().AddItem(itemData)) { _claimedZdoids.Add(item); hashSet2.Add(val4); ChestEffects.PlayEffect(val4); ZNetScene.instance.Destroy(((Component)val3).gameObject); num++; } } } foreach (Container item2 in hashSet2) { SortEngine.MarkDirty(item2); } } finally { ContainersTracker.IsRearrangingItem = false; } if (num > 0 && AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)$"[AutoSort] Ground sucker pulled {num} item(s)."); } if (hashSet3.Count > 0) { Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, "AutoSort: no chest for " + string.Join(", ", hashSet3) + " — items left on ground.", 0, (Sprite)null); } } } } public static class InventoryExtensions { public static string SaveInventory(this Inventory inventory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); inventory.Save(val); return val.GetBase64(); } public static void LoadInventory(this Inventory inventory, string data) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown if (!string.IsNullOrEmpty(data)) { ZPackage val = new ZPackage(data); inventory.Load(val); } } } public enum SortCategory { Stone, Wood, Metal, Ore, Gem, Leather, Bone, Fiber, Seed, Food, Potion, Fish, Arrow, Weapon, Shield, Armor, Tool, Magic, Trophy, Furniture, Misc, Unknown } public static class ItemCategorizer { private enum MatchMode { Equals, StartsWith, Contains } private static Dictionary _configMap = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Dictionary _jsonMap = new Dictionary(StringComparer.OrdinalIgnoreCase); private static List _jsonExtraCategories = new List(); private static FileSystemWatcher? _jsonWatcher; private static Timer? _reloadTimer; private const int ReloadDelayMs = 400; private static readonly Dictionary _exactMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Stone", SortCategory.Stone }, { "Flint", SortCategory.Stone }, { "Obsidian", SortCategory.Stone }, { "Coal", SortCategory.Stone }, { "BlackMarble", SortCategory.Stone }, { "Grausten", SortCategory.Stone }, { "Wood", SortCategory.Wood }, { "FineWood", SortCategory.Wood }, { "RoundLog", SortCategory.Wood }, { "YggdrasilWood", SortCategory.Wood }, { "AncientBark", SortCategory.Wood }, { "ElderBark", SortCategory.Wood }, { "AshWood", SortCategory.Wood }, { "WoodBeam", SortCategory.Wood }, { "CopperOre", SortCategory.Ore }, { "TinOre", SortCategory.Ore }, { "IronScrap", SortCategory.Ore }, { "SilverOre", SortCategory.Ore }, { "BlackMetalScrap", SortCategory.Ore }, { "FlametalOre", SortCategory.Ore }, { "MucusOre", SortCategory.Ore }, { "CopperScrap", SortCategory.Ore }, { "Bronze", SortCategory.Metal }, { "Iron", SortCategory.Metal }, { "Silver", SortCategory.Metal }, { "BlackMetal", SortCategory.Metal }, { "Copper", SortCategory.Metal }, { "Tin", SortCategory.Metal }, { "Flametal", SortCategory.Metal }, { "DvergrMetal", SortCategory.Metal }, { "BlackCore", SortCategory.Metal }, { "MorgenCore", SortCategory.Metal }, { "BronzeNails", SortCategory.Metal }, { "IronNails", SortCategory.Metal }, { "Amber", SortCategory.Gem }, { "AmberPearl", SortCategory.Gem }, { "Ruby", SortCategory.Gem }, { "SilverNecklace", SortCategory.Gem }, { "BlackGem", SortCategory.Gem }, { "Coins", SortCategory.Gem }, { "LeatherScraps", SortCategory.Leather }, { "DeerHide", SortCategory.Leather }, { "ScaleHide", SortCategory.Leather }, { "WolfPelt", SortCategory.Leather }, { "LoxPelt", SortCategory.Leather }, { "TrollHide", SortCategory.Leather }, { "AskHide", SortCategory.Leather }, { "BoneFragments", SortCategory.Bone }, { "WitheredBone", SortCategory.Bone }, { "Chitin", SortCategory.Bone }, { "Carapace", SortCategory.Bone }, { "HardAntler", SortCategory.Bone }, { "DragonEgg", SortCategory.Bone }, { "Flax", SortCategory.Fiber }, { "FlaxThread", SortCategory.Fiber }, { "Barley", SortCategory.Fiber }, { "BarleyFlour", SortCategory.Fiber }, { "Thistle", SortCategory.Fiber }, { "Dandelion", SortCategory.Fiber }, { "Bloodbag", SortCategory.Fiber }, { "Guck", SortCategory.Fiber }, { "LinenThread", SortCategory.Fiber }, { "JuteRed", SortCategory.Fiber }, { "JuteBlue", SortCategory.Fiber }, { "Eitr", SortCategory.Magic }, { "Sap", SortCategory.Magic }, { "SoftTissue", SortCategory.Magic }, { "Wisp", SortCategory.Magic }, { "WispTorch", SortCategory.Magic }, { "DvergrNeedle", SortCategory.Magic }, { "Dundr", SortCategory.Magic }, { "GemstoneGreen", SortCategory.Magic }, { "GemstoneBlue", SortCategory.Magic }, { "GemstoneRed", SortCategory.Magic }, { "VolcanicRock", SortCategory.Magic }, { "AshflameKindling", SortCategory.Magic }, { "CharredRemains", SortCategory.Magic }, { "Emberlite", SortCategory.Magic }, { "Acorn", SortCategory.Seed }, { "AncientSeed", SortCategory.Seed }, { "BeechSeeds", SortCategory.Seed }, { "BirchSeeds", SortCategory.Seed }, { "CarrotSeeds", SortCategory.Seed }, { "TurnipSeeds", SortCategory.Seed }, { "OnionSeeds", SortCategory.Seed }, { "FlaxSeeds", SortCategory.Seed }, { "BarleySeeds", SortCategory.Seed }, { "JuteSeedRed", SortCategory.Seed }, { "SeedCarrot", SortCategory.Seed }, { "SeedTurnip", SortCategory.Seed }, { "SeedOnion", SortCategory.Seed }, { "SeedBarley", SortCategory.Seed }, { "SeedFlax", SortCategory.Seed }, { "PineCone", SortCategory.Seed }, { "FirCone", SortCategory.Seed }, { "Surtling_Immature", SortCategory.Seed }, { "Mushroom", SortCategory.Food }, { "MushroomBlue", SortCategory.Food }, { "MushroomYellow", SortCategory.Food }, { "Raspberry", SortCategory.Food }, { "Blueberries", SortCategory.Food }, { "Cloudberry", SortCategory.Food }, { "Carrot", SortCategory.Food }, { "Turnip", SortCategory.Food }, { "Onion", SortCategory.Food }, { "Honey", SortCategory.Food }, { "Egg", SortCategory.Food }, { "Entrails", SortCategory.Food }, { "NeckTail", SortCategory.Food }, { "DeerMeat", SortCategory.Food }, { "WolfMeat", SortCategory.Food }, { "LoxMeat", SortCategory.Food }, { "SerpentMeat", SortCategory.Food }, { "SerpentMeatCooked", SortCategory.Food }, { "ChickenMeat", SortCategory.Food }, { "ChickenEgg", SortCategory.Food }, { "VoltureMeat", SortCategory.Food }, { "AsksvinMeat", SortCategory.Food }, { "CharredMeat", SortCategory.Food }, { "CarrotSoup", SortCategory.Food }, { "TurnipStew", SortCategory.Food }, { "OnionSoup", SortCategory.Food }, { "QueensJam", SortCategory.Food }, { "Bread", SortCategory.Food }, { "FishWraps", SortCategory.Food }, { "MinceMeatSauce", SortCategory.Food }, { "Sausages", SortCategory.Food }, { "BlackSoup", SortCategory.Food }, { "WolfMeatSkewer", SortCategory.Food }, { "WolfJerky", SortCategory.Food }, { "LoxPie", SortCategory.Food }, { "LoxMeatPie", SortCategory.Food }, { "SerpentStew", SortCategory.Food }, { "BloodPudding", SortCategory.Food }, { "EyeScream", SortCategory.Food }, { "BugMeat", SortCategory.Food }, { "MisthareSupreme", SortCategory.Food }, { "HareMeat", SortCategory.Food }, { "YggdrasilPorridge", SortCategory.Food }, { "MarinatedGreens", SortCategory.Food }, { "ScalpSkewer", SortCategory.Food }, { "SeekerAspic", SortCategory.Food }, { "FishNBread", SortCategory.Food }, { "Salad", SortCategory.Food }, { "SpicedMeat", SortCategory.Food }, { "AshenMeat", SortCategory.Food }, { "CookedBugMeat", SortCategory.Food }, { "CookedChickenMeat", SortCategory.Food }, { "CookedDeerMeat", SortCategory.Food }, { "CookedWolfMeat", SortCategory.Food }, { "CookedLoxMeat", SortCategory.Food }, { "CookedHareMeat", SortCategory.Food }, { "CookedVoltureMeat", SortCategory.Food }, { "CookedAsksvinMeat", SortCategory.Food }, { "CookedCharredMeat", SortCategory.Food }, { "BarleyWine", SortCategory.Food }, { "CloudberryWine", SortCategory.Food }, { "Fish1", SortCategory.Fish }, { "Fish2", SortCategory.Fish }, { "Fish3", SortCategory.Fish }, { "Fish4_cave", SortCategory.Fish }, { "Fish5", SortCategory.Fish }, { "Fish6", SortCategory.Fish }, { "Fish7", SortCategory.Fish }, { "Fish8", SortCategory.Fish }, { "Fish9", SortCategory.Fish }, { "Fish10", SortCategory.Fish }, { "Fish11", SortCategory.Fish }, { "Fish12", SortCategory.Fish }, { "FishCooked", SortCategory.Fish }, { "FishAndBread", SortCategory.Fish }, { "MeadBase", SortCategory.Potion }, { "MeadBaseFrostResist", SortCategory.Potion }, { "MeadBaseHealthMinor", SortCategory.Potion }, { "MeadBaseHealthMedium", SortCategory.Potion }, { "MeadBaseHealthMajor", SortCategory.Potion }, { "MeadBaseStaminaMinor", SortCategory.Potion }, { "MeadBaseStaminaMedium", SortCategory.Potion }, { "MeadBaseStaminaLingering", SortCategory.Potion }, { "MeadBasePoisonResist", SortCategory.Potion }, { "MeadBaseTasty", SortCategory.Potion }, { "MeadBaseEitrMinor", SortCategory.Potion }, { "MeadBaseEitrMedium", SortCategory.Potion }, { "MeadFrostResist", SortCategory.Potion }, { "MeadHealthMinor", SortCategory.Potion }, { "MeadHealthMedium", SortCategory.Potion }, { "MeadHealthMajor", SortCategory.Potion }, { "MeadStaminaMinor", SortCategory.Potion }, { "MeadStaminaMedium", SortCategory.Potion }, { "MeadStaminaLingering", SortCategory.Potion }, { "MeadPoisonResist", SortCategory.Potion }, { "MeadTasty", SortCategory.Potion }, { "MeadEitrMinor", SortCategory.Potion }, { "MeadEitrMedium", SortCategory.Potion }, { "ArrowWood", SortCategory.Arrow }, { "ArrowFlint", SortCategory.Arrow }, { "ArrowBronze", SortCategory.Arrow }, { "ArrowIron", SortCategory.Arrow }, { "ArrowObsidian", SortCategory.Arrow }, { "ArrowSilver", SortCategory.Arrow }, { "ArrowNeedle", SortCategory.Arrow }, { "ArrowFire", SortCategory.Arrow }, { "ArrowPoison", SortCategory.Arrow }, { "ArrowFrost", SortCategory.Arrow }, { "ArrowCharred", SortCategory.Arrow }, { "ArrowCarapace", SortCategory.Arrow }, { "BoltBone", SortCategory.Arrow }, { "BoltIron", SortCategory.Arrow }, { "BoltBlackmetal", SortCategory.Arrow }, { "BoltCarapace", SortCategory.Arrow }, { "BoltCharred", SortCategory.Arrow }, { "TrophyBoar", SortCategory.Trophy }, { "TrophyNeck", SortCategory.Trophy }, { "TrophyDeer", SortCategory.Trophy }, { "TrophyGreydwarf", SortCategory.Trophy }, { "TrophyGreydwarfBrute", SortCategory.Trophy }, { "TrophyGreydwarfShaman", SortCategory.Trophy }, { "TrophyForestTroll", SortCategory.Trophy }, { "TrophySkeletonPoison", SortCategory.Trophy }, { "TrophySkeleton", SortCategory.Trophy }, { "TrophyDraugr", SortCategory.Trophy }, { "TrophyDraugrElite", SortCategory.Trophy }, { "TrophyBlob", SortCategory.Trophy }, { "TrophyLeech", SortCategory.Trophy }, { "TrophyDraugrFem", SortCategory.Trophy }, { "TrophyWraith", SortCategory.Trophy }, { "TrophyAbomination", SortCategory.Trophy }, { "TrophyLox", SortCategory.Trophy }, { "TrophySGolem", SortCategory.Trophy }, { "TrophyWolf", SortCategory.Trophy }, { "TrophyHatchling", SortCategory.Trophy }, { "TrophyFenring", SortCategory.Trophy }, { "TrophySerpent", SortCategory.Trophy }, { "TrophyDeathsquito", SortCategory.Trophy }, { "TrophyGrowth", SortCategory.Trophy }, { "TrophySeeker", SortCategory.Trophy }, { "TrophySeekerBrute", SortCategory.Trophy }, { "TrophyTick", SortCategory.Trophy }, { "TrophyHare", SortCategory.Trophy }, { "TrophyGjall", SortCategory.Trophy }, { "TrophyCharredMelee", SortCategory.Trophy }, { "TrophyCharredArcher", SortCategory.Trophy }, { "TrophyCharredTwitcher", SortCategory.Trophy }, { "TrophyCharredWarlock", SortCategory.Trophy }, { "TrophyFader", SortCategory.Trophy }, { "TrophyEikthyr", SortCategory.Trophy }, { "TrophyElder", SortCategory.Trophy }, { "TrophyBonemass", SortCategory.Trophy }, { "TrophyDragonQueen", SortCategory.Trophy }, { "TrophyGoblinKing", SortCategory.Trophy }, { "TrophyQueenBee", SortCategory.Trophy }, { "TrophyVolture", SortCategory.Trophy }, { "TrophyAskvin", SortCategory.Trophy } }; private static readonly (string key, SortCategory cat, MatchMode mode)[] _prefabRules = new(string, SortCategory, MatchMode)[89] { ("ArmorAshlands", SortCategory.Armor, MatchMode.StartsWith), ("ArmorBerserker", SortCategory.Armor, MatchMode.StartsWith), ("ArmorBronze", SortCategory.Armor, MatchMode.StartsWith), ("ArmorCarapace", SortCategory.Armor, MatchMode.StartsWith), ("ArmorFenring", SortCategory.Armor, MatchMode.StartsWith), ("ArmorFlametal", SortCategory.Armor, MatchMode.StartsWith), ("ArmorIron", SortCategory.Armor, MatchMode.StartsWith), ("ArmorLeather", SortCategory.Armor, MatchMode.StartsWith), ("ArmorMage", SortCategory.Armor, MatchMode.StartsWith), ("ArmorPadded", SortCategory.Armor, MatchMode.StartsWith), ("ArmorRags", SortCategory.Armor, MatchMode.StartsWith), ("ArmorRoot", SortCategory.Armor, MatchMode.StartsWith), ("ArmorTroll", SortCategory.Armor, MatchMode.StartsWith), ("ArmorWolf", SortCategory.Armor, MatchMode.StartsWith), ("ArmorDress", SortCategory.Armor, MatchMode.StartsWith), ("ArmorTunic", SortCategory.Armor, MatchMode.StartsWith), ("ArmorHarvester", SortCategory.Armor, MatchMode.StartsWith), ("HelmetAshlands", SortCategory.Armor, MatchMode.StartsWith), ("HelmetBronze", SortCategory.Armor, MatchMode.StartsWith), ("HelmetCarapace", SortCategory.Armor, MatchMode.StartsWith), ("HelmetDrake", SortCategory.Armor, MatchMode.StartsWith), ("HelmetDverger", SortCategory.Armor, MatchMode.StartsWith), ("HelmetFenring", SortCategory.Armor, MatchMode.StartsWith), ("HelmetFlametal", SortCategory.Armor, MatchMode.StartsWith), ("HelmetIron", SortCategory.Armor, MatchMode.StartsWith), ("HelmetLeather", SortCategory.Armor, MatchMode.StartsWith), ("HelmetMage", SortCategory.Armor, MatchMode.StartsWith), ("HelmetOdin", SortCategory.Armor, MatchMode.StartsWith), ("HelmetPadded", SortCategory.Armor, MatchMode.StartsWith), ("HelmetRoot", SortCategory.Armor, MatchMode.StartsWith), ("HelmetTroll", SortCategory.Armor, MatchMode.StartsWith), ("HelmetYule", SortCategory.Armor, MatchMode.StartsWith), ("CapeAsh", SortCategory.Armor, MatchMode.StartsWith), ("CapeDeerHide", SortCategory.Armor, MatchMode.StartsWith), ("CapeFeather", SortCategory.Armor, MatchMode.StartsWith), ("CapeLinen", SortCategory.Armor, MatchMode.StartsWith), ("CapeLox", SortCategory.Armor, MatchMode.StartsWith), ("CapeOdin", SortCategory.Armor, MatchMode.StartsWith), ("CapeTroll", SortCategory.Armor, MatchMode.StartsWith), ("CapeWolf", SortCategory.Armor, MatchMode.StartsWith), ("ShieldAsh", SortCategory.Shield, MatchMode.StartsWith), ("ShieldBlack", SortCategory.Shield, MatchMode.StartsWith), ("ShieldBronze", SortCategory.Shield, MatchMode.StartsWith), ("ShieldCarapace", SortCategory.Shield, MatchMode.StartsWith), ("ShieldFlametal", SortCategory.Shield, MatchMode.StartsWith), ("ShieldIron", SortCategory.Shield, MatchMode.StartsWith), ("ShieldKnight", SortCategory.Shield, MatchMode.StartsWith), ("ShieldSerpent", SortCategory.Shield, MatchMode.StartsWith), ("ShieldSilver", SortCategory.Shield, MatchMode.StartsWith), ("ShieldWood", SortCategory.Shield, MatchMode.StartsWith), ("Sword", SortCategory.Weapon, MatchMode.StartsWith), ("Axe", SortCategory.Weapon, MatchMode.StartsWith), ("Club", SortCategory.Weapon, MatchMode.StartsWith), ("Knife", SortCategory.Weapon, MatchMode.StartsWith), ("Mace", SortCategory.Weapon, MatchMode.StartsWith), ("Sledge", SortCategory.Weapon, MatchMode.StartsWith), ("Spear", SortCategory.Weapon, MatchMode.StartsWith), ("Bow", SortCategory.Weapon, MatchMode.StartsWith), ("Crossbow", SortCategory.Weapon, MatchMode.StartsWith), ("Arbalest", SortCategory.Weapon, MatchMode.StartsWith), ("AtgeirHilder", SortCategory.Weapon, MatchMode.StartsWith), ("AtgeirBronze", SortCategory.Weapon, MatchMode.StartsWith), ("AtgeirIron", SortCategory.Weapon, MatchMode.StartsWith), ("AtgeirBlackmetal", SortCategory.Weapon, MatchMode.StartsWith), ("Battleaxe", SortCategory.Weapon, MatchMode.StartsWith), ("GreatSword", SortCategory.Weapon, MatchMode.StartsWith), ("THSword", SortCategory.Weapon, MatchMode.StartsWith), ("Staff", SortCategory.Weapon, MatchMode.StartsWith), ("Wand", SortCategory.Weapon, MatchMode.StartsWith), ("Pickaxe", SortCategory.Tool, MatchMode.StartsWith), ("Hammer", SortCategory.Tool, MatchMode.StartsWith), ("Hoe", SortCategory.Tool, MatchMode.StartsWith), ("Cultivator", SortCategory.Tool, MatchMode.StartsWith), ("FishingRod", SortCategory.Tool, MatchMode.StartsWith), ("Arrow", SortCategory.Arrow, MatchMode.StartsWith), ("Bolt", SortCategory.Arrow, MatchMode.StartsWith), ("Trophy", SortCategory.Trophy, MatchMode.StartsWith), ("Mead", SortCategory.Potion, MatchMode.StartsWith), ("Fish", SortCategory.Fish, MatchMode.StartsWith), ("Mushroom", SortCategory.Food, MatchMode.StartsWith), ("Cooked", SortCategory.Food, MatchMode.StartsWith), ("Seeds", SortCategory.Seed, MatchMode.Contains), ("Seed", SortCategory.Seed, MatchMode.Contains), ("Ore", SortCategory.Ore, MatchMode.Contains), ("Scrap", SortCategory.Ore, MatchMode.Contains), ("hide", SortCategory.Leather, MatchMode.Contains), ("pelt", SortCategory.Leather, MatchMode.Contains), ("bone", SortCategory.Bone, MatchMode.Contains), ("trophy", SortCategory.Trophy, MatchMode.Contains) }; public static string JsonFilePath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppDomain.CurrentDomain.BaseDirectory, "autosort_categories.json"); public static IReadOnlyList GetJsonExtraCategories() { return _jsonExtraCategories; } public static void InitJsonWatcher() { string jsonFilePath = JsonFilePath; if (!File.Exists(jsonFilePath)) { WriteDefaultJson(jsonFilePath); } LoadFromJson(); try { _jsonWatcher = new FileSystemWatcher(Path.GetDirectoryName(jsonFilePath), Path.GetFileName(jsonFilePath)) { NotifyFilter = (NotifyFilters.Size | NotifyFilters.LastWrite), EnableRaisingEvents = true }; _jsonWatcher.Changed += OnJsonChanged; _jsonWatcher.Created += OnJsonChanged; AutoSortPlugin.Log.LogInfo((object)("[AutoSort] Watching " + jsonFilePath + " for live category edits.")); } catch (Exception ex) { AutoSortPlugin.Log.LogWarning((object)("[AutoSort] Could not start JSON watcher: " + ex.Message)); } } private static void OnJsonChanged(object sender, FileSystemEventArgs e) { _reloadTimer?.Dispose(); _reloadTimer = new Timer(delegate { LoadFromJson(); AutoSortPlugin.Log.LogInfo((object)"[AutoSort] categories.json reloaded."); }, null, 400, -1); } public static void LoadFromJson() { string jsonFilePath = JsonFilePath; if (!File.Exists(jsonFilePath)) { return; } try { string input = File.ReadAllText(jsonFilePath); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); List list = new List(); foreach (Match item in new Regex("\"(?[^\"]+)\"\\s*:\\s*\\[(?[^\\]]*)\\]", RegexOptions.Singleline).Matches(input)) { string text = item.Groups["cat"].Value.Trim(); string value = item.Groups["items"].Value; if (!Enum.TryParse(text, ignoreCase: true, out var result)) { result = SortCategory.Misc; list.Add(text); } foreach (Match item2 in Regex.Matches(value, "\"([^\"]+)\"")) { string text2 = item2.Groups[1].Value.Trim(); if (!string.IsNullOrEmpty(text2)) { dictionary[text2] = result; } } } _jsonMap = dictionary; _jsonExtraCategories = list; if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)($"[AutoSort] JSON category map loaded: {dictionary.Count} items, " + $"{list.Count} custom categories.")); } } catch (Exception ex) { AutoSortPlugin.Log.LogWarning((object)("[AutoSort] Failed to parse autosort_categories.json: " + ex.Message)); } } private static void WriteDefaultJson(string path) { try { File.WriteAllText(path, "{\n \"_comment\": \"AutoSort category overrides. Edit freely — changes are detected live in-game.\",\n \"_comment2\": \"Keys are category names (see SortCategory enum). Values are prefab name lists.\",\n \"_comment3\": \"Add modded items here. Use LogDebug=true in the .cfg to print prefab names.\",\n \"_comment4\": \"You can also add CUSTOM category names — those items will sort into Misc chests.\",\n\n \"Stone\": [],\n \"Wood\": [],\n \"Ore\": [],\n \"Metal\": [],\n \"Gem\": [],\n \"Leather\": [],\n \"Bone\": [],\n \"Fiber\": [],\n \"Seed\": [],\n \"Food\": [],\n \"Potion\": [],\n \"Fish\": [],\n \"Arrow\": [],\n \"Weapon\": [],\n \"Shield\": [],\n \"Armor\": [],\n \"Tool\": [],\n \"Magic\": [],\n \"Trophy\": [],\n \"Furniture\":[],\n \"Misc\": []\n}\n"); } catch { } } public static void LoadFromConfig() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); ParseInto(dictionary, AutoSortPlugin.Cat_Stone.Value, SortCategory.Stone); ParseInto(dictionary, AutoSortPlugin.Cat_Wood.Value, SortCategory.Wood); ParseInto(dictionary, AutoSortPlugin.Cat_Ore.Value, SortCategory.Ore); ParseInto(dictionary, AutoSortPlugin.Cat_Metal.Value, SortCategory.Metal); ParseInto(dictionary, AutoSortPlugin.Cat_Gem.Value, SortCategory.Gem); ParseInto(dictionary, AutoSortPlugin.Cat_Leather.Value, SortCategory.Leather); ParseInto(dictionary, AutoSortPlugin.Cat_Bone.Value, SortCategory.Bone); ParseInto(dictionary, AutoSortPlugin.Cat_Fiber.Value, SortCategory.Fiber); ParseInto(dictionary, AutoSortPlugin.Cat_Seed.Value, SortCategory.Seed); ParseInto(dictionary, AutoSortPlugin.Cat_Food.Value, SortCategory.Food); ParseInto(dictionary, AutoSortPlugin.Cat_Potion.Value, SortCategory.Potion); ParseInto(dictionary, AutoSortPlugin.Cat_Fish.Value, SortCategory.Fish); ParseInto(dictionary, AutoSortPlugin.Cat_Arrow.Value, SortCategory.Arrow); ParseInto(dictionary, AutoSortPlugin.Cat_Weapon.Value, SortCategory.Weapon); ParseInto(dictionary, AutoSortPlugin.Cat_Shield.Value, SortCategory.Shield); ParseInto(dictionary, AutoSortPlugin.Cat_Armor.Value, SortCategory.Armor); ParseInto(dictionary, AutoSortPlugin.Cat_Tool.Value, SortCategory.Tool); ParseInto(dictionary, AutoSortPlugin.Cat_Magic.Value, SortCategory.Magic); ParseInto(dictionary, AutoSortPlugin.Cat_Trophy.Value, SortCategory.Trophy); ParseInto(dictionary, AutoSortPlugin.Cat_Furniture.Value, SortCategory.Furniture); ParseInto(dictionary, AutoSortPlugin.Cat_Misc.Value, SortCategory.Misc); _configMap = dictionary; if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)$"[AutoSort] Category config loaded: {dictionary.Count} explicit item mappings."); } } private static void ParseInto(Dictionary map, string csv, SortCategory cat) { if (string.IsNullOrWhiteSpace(csv)) { return; } string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrEmpty(text)) { map[text] = cat; } } } public static SortCategory GetCategory(ItemData item) { //IL_008e: 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_0095: 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_00f2: Expected I4, but got Unknown if (item?.m_shared == null) { return SortCategory.Unknown; } string itemKey = GetItemKey(item); if (_jsonMap.TryGetValue(itemKey, out var value)) { if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)$"[AutoSort] '{itemKey}' → {value} (json)"); } return value; } if (_configMap.TryGetValue(itemKey, out var value2)) { if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)$"[AutoSort] '{itemKey}' → {value2} (config)"); } return value2; } ItemType itemType = item.m_shared.m_itemType; switch (itemType - 3) { case 0: case 1: case 11: case 12: case 19: return SortCategory.Weapon; case 2: return SortCategory.Shield; case 3: case 4: case 8: case 14: return SortCategory.Armor; case 16: return SortCategory.Tool; case 18: return SortCategory.Fish; case 6: case 20: return SortCategory.Arrow; default: { if (_exactMap.TryGetValue(itemKey, out var value3)) { if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)$"[AutoSort] '{itemKey}' → {value3} (built-in exact)"); } return value3; } string text = itemKey.ToLowerInvariant(); (string, SortCategory, MatchMode)[] prefabRules = _prefabRules; for (int i = 0; i < prefabRules.Length; i++) { (string, SortCategory, MatchMode) tuple = prefabRules[i]; var (text2, sortCategory, _) = tuple; if (tuple.Item3 switch { MatchMode.Equals => text == text2.ToLowerInvariant(), MatchMode.StartsWith => text.StartsWith(text2.ToLowerInvariant()), MatchMode.Contains => text.Contains(text2.ToLowerInvariant()), _ => false, }) { if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)$"[AutoSort] '{itemKey}' → {sortCategory} (prefix rule)"); } return sortCategory; } } if (AutoSortPlugin.LogDebug.Value) { AutoSortPlugin.Log.LogDebug((object)("[AutoSort] '" + itemKey + "' → Misc (no match) — add to config if needed")); } return SortCategory.Misc; } } } public static string GetItemKey(ItemData item) { if (item == null) { return string.Empty; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } string text = item.m_shared?.m_name ?? string.Empty; if (!text.StartsWith("$item_")) { return text; } return text.Substring(6); } public static bool TryParseChestTag(string zdoTag, out SortCategory category) { category = SortCategory.Unknown; if (string.IsNullOrWhiteSpace(zdoTag)) { return false; } Match match = Regex.Match(zdoTag, "\\[([^\\]]+)\\]"); if (!match.Success) { return false; } return Enum.TryParse(match.Groups[1].Value.Trim(), ignoreCase: true, out category); } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData) })] public static class Patch_Inventory_MoveItemToThis { [HarmonyPostfix] public static void Postfix(Inventory __instance, ItemData item) { if (!ContainersTracker.IsRearrangingItem && AutoSortPlugin.ModEnabled.Value && (!((Object)(object)Player.m_localPlayer != (Object)null) || ((Humanoid)Player.m_localPlayer).GetInventory() != __instance)) { Container val = PatchHelpers.FindContainerForInventory(__instance); if (!((Object)(object)val == (Object)null)) { SortEngine.TriggerItemSort(item, val); } } } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] public static class Patch_Inventory_AddItem { [ThreadStatic] private static ItemData? _pendingItem; [HarmonyPrefix] public static void Prefix(ItemData item) { _pendingItem = item; } [HarmonyPostfix] public static void Postfix(Inventory __instance, bool __result) { ItemData pendingItem = _pendingItem; _pendingItem = null; if (__result && !ContainersTracker.IsRearrangingItem && AutoSortPlugin.ModEnabled.Value && (!((Object)(object)Player.m_localPlayer != (Object)null) || ((Humanoid)Player.m_localPlayer).GetInventory() != __instance) && pendingItem != null) { Container val = PatchHelpers.FindContainerForInventory(__instance); if (!((Object)(object)val == (Object)null)) { SortEngine.TriggerItemSort(pendingItem, val); } } } } internal static class PatchHelpers { internal static Container? FindContainerForInventory(Inventory inv) { foreach (Container item in ContainersTracker.All) { if ((Object)(object)item != (Object)null && item.GetInventory() == inv) { return item; } } return null; } } [HarmonyPatch(typeof(Player), "Update")] public static class Patch_Player_Update { [HarmonyPostfix] public static void Postfix(Player __instance) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && AutoSortPlugin.ModEnabled.Value) { GroundItemSucker.Tick(Time.deltaTime); if (!ContainersTracker.IsRearrangingItem && !StoreGui.IsVisible() && !Minimap.IsOpen() && IsKeybindDown(AutoSortPlugin.SortKeybind.Value)) { SortEngine.TriggerSortPass(); } } } private static bool IsKeybindDown(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0) { return false; } if (!Input.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (!Input.GetKey(modifier)) { return false; } } return true; } } [HarmonyPatch(typeof(InventoryGui), "Show")] public static class Patch_InventoryGui_Show { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnityAction <>9__6_0; public static UnityAction <>9__6_1; internal void b__6_0() { CycleCategory(forward: true); } internal void b__6_1(BaseEventData data) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 if ((int)((PointerEventData)data).button == 1) { CycleCategory(forward: false); } } } [CompilerGenerated] private sealed class d__5 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0083: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: { <>1__state = -1; if ((Object)(object)_btn == (Object)null || (Object)(object)((Component)_btn).gameObject == (Object)null) { return false; } if (!((Component)_btn).gameObject.activeSelf) { return false; } RectTransform component = ((Component)_btn).GetComponent(); if ((Object)(object)component == (Object)null) { return false; } component.anchorMin = new Vector2(1f, 1f); component.anchorMax = new Vector2(1f, 1f); component.pivot = new Vector2(1f, 1f); float num = 0f; bool flag = false; Transform parent = ((Component)_btn).transform.parent; if ((Object)(object)parent != (Object)null) { foreach (Transform item in parent) { Transform val = item; if ((Object)(object)((Component)val).gameObject == (Object)(object)((Component)_btn).gameObject || !((Component)val).gameObject.activeSelf) { continue; } string name = ((Object)val).name; if (!name.StartsWith("QuickStack", StringComparison.OrdinalIgnoreCase) && !name.StartsWith("StoreAll", StringComparison.OrdinalIgnoreCase) && !name.StartsWith("Restock", StringComparison.OrdinalIgnoreCase) && !name.StartsWith("Sort", StringComparison.OrdinalIgnoreCase)) { continue; } RectTransform component2 = ((Component)val).GetComponent(); if (!((Object)(object)component2 == (Object)null) && !(component2.anchorMin.x < 0.9f)) { float num2 = component2.anchoredPosition.y - component2.sizeDelta.y; if (num2 < num) { num = num2; flag = true; } } } } float num3 = ((!flag) ? (-6f) : (num - 4f)); component.anchoredPosition = new Vector2(-6f, num3); return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static Button? _btn = null; private static TextMeshProUGUI? _btnText = null; internal static Container? CurrentContainer = null; private static readonly SortCategory[] _cycle = (SortCategory[])Enum.GetValues(typeof(SortCategory)); private static GameObject? _pickerPanel = null; private static bool _pickerOpen = false; [HarmonyPostfix] public static void Postfix(InventoryGui __instance, Container? container) { if (AutoSortPlugin.ModEnabled.Value && AutoSortPlugin.EnableTagOverride.Value && !((Object)(object)container == (Object)null)) { CurrentContainer = container; if ((Object)(object)_btn != (Object)null && (Object)(object)((Component)_btn).gameObject == (Object)null) { _btn = null; _btnText = null; } if ((Object)(object)_btn == (Object)null) { CreateButton(__instance); } if (!((Object)(object)_btn == (Object)null)) { ((Component)_btn).gameObject.SetActive(true); RefreshButtonText(container); ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(WaitAndPositionButton()); } } } [IteratorStateMachine(typeof(d__5))] private static IEnumerator WaitAndPositionButton() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__5(0); } private static void CreateButton(InventoryGui gui) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown Button takeAllButton = GetTakeAllButton(gui); if ((Object)(object)takeAllButton == (Object)null) { AutoSortPlugin.Log.LogWarning((object)"[AutoSort] Could not find TakeAll button to clone. Category button unavailable — use 'autosort_tag' console command instead."); return; } GameObject val = Object.Instantiate(((Component)takeAllButton).gameObject, ((Component)takeAllButton).transform.parent); ((Object)val).name = "AutoSort_CategoryButton"; _btn = val.GetComponent