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 HammerSort v1.1.3
plugins/HammerSort/HammerSort.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: AssemblyFileVersion("1.1.3.0")] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyTitle("HammerSort")] [assembly: AssemblyCompany("ERKEK2000")] [assembly: AssemblyProduct("HammerSort")] [assembly: AssemblyCopyright("Copyright (c) 2026 ERKEK2000")] [assembly: AssemblyDescription("Less searching, more building. HammerSort automatically puts pieces you can afford first in vanilla and modded build menus. Lightweight, client-side, and ready to build.")] [assembly: AssemblyVersion("1.1.3.0")] namespace HammerSort; [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("ERKEK2000.valheim.hammersort", "HammerSort", "1.1.3")] public sealed class HammerSortPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(Player), "SetSelectedPiece", new Type[] { typeof(Piece) })] private static class PieceSelectedByIdentity { [HarmonyPriority(800)] private static void Prefix(Player __instance) { if (object.ReferenceEquals(__instance, Player.m_localPlayer)) { QuickGrid.BeforeRebuild(__instance.GetBuildTool()); } } } [HarmonyPatch(typeof(Player), "OnInventoryChanged")] private static class InventoryChanged { private static void Postfix(Player __instance) { if (object.ReferenceEquals(__instance, Player.m_localPlayer)) { Invalidate(); } } } [HarmonyPatch(typeof(Player), "SetNoPlacementCost")] private static class PlacementCostChanged { private static void Postfix() { Invalidate(); } } [HarmonyPatch(typeof(PieceTable), "UpdateAvailable")] private static class PiecesChanged { private static void Prefix(PieceTable __instance) { QuickGrid.BeforeRebuild(__instance); } private static void Postfix(PieceTable __instance) { QuickGrid.AfterRebuild(__instance); Invalidate(); } } [HarmonyPatch(typeof(BuildUi), "UpdatePieceButtons")] private static class BrowserRebuilt { private static int depth; private static void Prefix() { depth++; } [HarmonyPriority(0)] private static void Postfix(BuildUi __instance, List<BuildUiPieceButton> ___m_pieceButtons) { if (depth == 1) { Browser.Capture(__instance, ___m_pieceButtons); } } private static Exception Finalizer(Exception __exception) { depth--; return __exception; } } [HarmonyPatch(typeof(BuildUi), "Update")] private static class BrowserUpdated { private static void Postfix(BuildUi __instance) { Browser.Refresh(__instance); } } [HarmonyPatch(typeof(BuildUi), "OpenBuildMenu")] private static class BrowserOpened { private static void Prefix() { Invalidate(); } } [HarmonyPatch(typeof(Hud), "UpdateBuild")] private static class QuickGridUpdated { private static void Prefix(Hud __instance, Player player, ref bool __1) { if (QuickGrid.Refresh(__instance, player)) { __1 = true; } } } public const string Author = "ERKEK2000"; public const string Guid = "ERKEK2000.valheim.hammersort"; public const string Version = "1.1.3"; internal static ConfigEntry<bool> SortEnabled; internal static ConfigEntry<bool> OtherTools; internal static int Revision; private Harmony harmony; private static readonly Func<Humanoid, ItemData> RightItem = (Func<Humanoid, ItemData>)Delegate.CreateDelegate(typeof(Func<Humanoid, ItemData>), AccessTools.Method(typeof(Humanoid), "GetRightItem", (Type[])null, (Type[])null)); private void Awake() { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown SortEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Put pieces you have materials for first, preserving the game order in both groups."); OtherTools = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "OtherBuildTools", true, "Sort the hoe, cultivator and all other build tools too. Set false to limit sorting to the vanilla hammer and active OdinArchitect tools."); SortEnabled.SettingChanged += OnSettingChanged; OtherTools.SettingChanged += OnSettingChanged; if (OdinArchitectCompatibility.Initialize()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"OdinArchitect detected; Odin's Hammer and Stone Chisel sorting is available automatically."); } harmony = new Harmony("ERKEK2000.valheim.hammersort"); harmony.PatchAll(typeof(HammerSortPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"HammerSort 1.1.3 by ERKEK2000 loaded (client-side, inventory-based sorting)."); } private static void OnSettingChanged(object sender, EventArgs args) { Invalidate(); } internal static void Invalidate() { Revision++; } private void OnDestroy() { if (SortEnabled != null) { SortEnabled.SettingChanged -= OnSettingChanged; } if (OtherTools != null) { OtherTools.SettingChanged -= OnSettingChanged; } Browser.Restore(); QuickGrid.Restore(); if (harmony != null) { harmony.UnpatchSelf(); } } internal static bool ShouldSort(Player player) { if (!SortEnabled.Value || (Object)(object)player == (Object)null || (Object)(object)player.GetBuildTool() == (Object)null) { return false; } if (OtherTools.Value) { return true; } ItemData val = RightItem((Humanoid)(object)player); if (val != null) { return ToolSelection.ShouldSort(val.m_shared.m_name, OdinArchitectCompatibility.IsActive); } return false; } } internal static class Materials { private static readonly Dictionary<string, int> counts = new Dictionary<string, int>(StringComparer.Ordinal); private static int revision = -1; private static Player owner; private static bool freePlacement; private static bool anyFreeCost; private static ZoneSystem zone; internal static readonly Predicate<Piece> HasMaterials = Check; internal static readonly Predicate<BuildUiPieceButton> ButtonHasMaterials = CheckButton; internal static void Prepare(Player player) { if (object.ReferenceEquals(owner, player) && revision == HammerSortPlugin.Revision) { return; } owner = player; revision = HammerSortPlugin.Revision; freePlacement = player.PlacementCostDisabled; zone = ZoneSystem.instance; anyFreeCost = (Object)(object)zone != (Object)null && (zone.GetGlobalKey((GlobalKeys)24) || zone.GetGlobalKey((GlobalKeys)25)); counts.Clear(); List<ItemData> allItems = ((Humanoid)player).GetInventory().GetAllItems(); for (int i = 0; i < allItems.Count; i++) { ItemData val = allItems[i]; if (val.m_worldLevel >= Game.m_worldLevel) { string name = val.m_shared.m_name; counts.TryGetValue(name, out var value); counts[name] = value + val.m_stack; } } } private static bool CheckButton(BuildUiPieceButton button) { return Check(button.Piece); } private static bool Check(Piece piece) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece == (Object)null) { return false; } if (piece.m_repairPiece || piece.m_removePiece || freePlacement) { return true; } if (anyFreeCost && zone.GetGlobalKey(piece.FreeBuildKey())) { return true; } Requirement[] resources = piece.m_resources; if (resources == null) { return true; } foreach (Requirement val in resources) { if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0 && (!counts.TryGetValue(val.m_resItem.m_itemData.m_shared.m_name, out var value) || value < val.m_amount)) { return false; } } return true; } } internal static class Browser { private static BuildUi owner; private static List<BuildUiPieceButton> buttons; private static readonly StableOrder<BuildUiPieceButton> order = new StableOrder<BuildUiPieceButton>(); private static int revision = -1; private static bool sorting; private static readonly Action<BuildUi> ConfigureNavigation = (Action<BuildUi>)Delegate.CreateDelegate(typeof(Action<BuildUi>), AccessTools.Method(typeof(BuildUi), "ConfigureButtonNavigation", (Type[])null, (Type[])null)); internal static void Capture(BuildUi ui, List<BuildUiPieceButton> source) { owner = ui; buttons = source; order.Capture(source); revision = -1; Refresh(ui); } internal static void Refresh(BuildUi ui) { if (!object.ReferenceEquals(owner, ui)) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } bool flag = HammerSortPlugin.ShouldSort(localPlayer); if (revision != HammerSortPlugin.Revision || sorting != flag) { revision = HammerSortPlugin.Revision; sorting = flag; if (flag) { Materials.Prepare(localPlayer); } if (order.Apply(buttons, flag ? Materials.ButtonHasMaterials : null)) { UpdateLayout(); } } } private static void UpdateLayout() { for (int i = 0; i < buttons.Count; i++) { ((Component)buttons[i]).transform.SetAsLastSibling(); } ConfigureNavigation(owner); } internal static void Restore() { if ((Object)(object)owner != (Object)null && buttons != null && order.Apply(buttons, null)) { UpdateLayout(); } } } internal static class QuickGrid { private sealed class Category { internal readonly StableOrder<Piece> Order = new StableOrder<Piece>(); internal List<Piece> Pieces; internal int Revision = -1; internal bool Active; } private static PieceTable table; private static Category[] categories; private static readonly FieldInfo CategoryLists = AccessTools.Field(typeof(PieceTable), "m_availablePiecesByCategory"); internal static bool Refresh(Hud hud, Player player) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected I4, but got Unknown if (!object.ReferenceEquals(player, Player.m_localPlayer)) { return false; } if (!HammerSortPlugin.ShouldSort(player)) { Restore(); return false; } if ((Object)(object)hud.m_buildUi == (Object)null || !((Component)hud.m_buildUi).gameObject.activeSelf) { return false; } PieceTable buildTool = player.GetBuildTool(); if (!object.ReferenceEquals(table, buildTool)) { Restore(); table = buildTool; Capture(); } int num = (int)table.GetSelectedCategory(); if (categories == null || num < 0 || num >= categories.Length) { return false; } Category category = categories[num]; if (category.Revision == HammerSortPlugin.Revision) { return false; } Materials.Prepare(player); bool result = Apply(num, Materials.HasMaterials); category.Revision = HammerSortPlugin.Revision; return result; } private static void Capture() { List<List<Piece>> list = (List<List<Piece>>)CategoryLists.GetValue(table); if (categories == null || categories.Length != list.Count) { categories = new Category[list.Count]; } for (int i = 0; i < list.Count; i++) { if (categories[i] == null) { categories[i] = new Category(); } categories[i].Pieces = list[i]; categories[i].Order.Capture(list[i]); categories[i].Revision = -1; categories[i].Active = false; } } private static bool Apply(int category, Predicate<Piece> preferred) { Category category2 = categories[category]; category2.Active = preferred != null; int width = Math.Max(1, 15); Piece piece = At(category2.Pieces, table.m_selectedPiece, category, width); Piece piece2 = At(category2.Pieces, table.m_lastSelectedPiece, category, width); if (!category2.Order.Apply(category2.Pieces, preferred)) { return false; } Remap(category2.Pieces, table.m_selectedPiece, category, width, piece); Remap(category2.Pieces, table.m_lastSelectedPiece, category, width, piece2); return true; } private static Piece At(List<Piece> pieces, Vector2Int[] indices, int category, int width) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (indices == null || category >= indices.Length) { return null; } Vector2Int val = indices[category]; int num = ((Vector2Int)(ref val)).y * width + ((Vector2Int)(ref val)).x; if (num < 0 || num >= pieces.Count) { return null; } return pieces[num]; } private static void Remap(List<Piece> pieces, Vector2Int[] indices, int category, int width, Piece piece) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (object.ReferenceEquals(piece, null) || indices == null || category >= indices.Length) { return; } for (int i = 0; i < pieces.Count; i++) { if (object.ReferenceEquals(pieces[i], piece)) { ref Vector2Int reference = ref indices[category]; reference = new Vector2Int(i % width, i / width); break; } } } internal static void BeforeRebuild(PieceTable rebuilding) { if (object.ReferenceEquals(table, rebuilding)) { Restore(); } } internal static void AfterRebuild(PieceTable rebuilding) { if (object.ReferenceEquals(table, rebuilding)) { Capture(); } } internal static void Restore() { if ((Object)(object)table == (Object)null || categories == null) { return; } for (int i = 0; i < categories.Length; i++) { if (categories[i].Active) { Apply(i, null); categories[i].Revision = -1; } } } } internal static class OdinArchitectCompatibility { internal const string PluginGuid = "Raelaziel.OdinArchitect"; private static BaseUnityPlugin plugin; internal static readonly Func<bool> IsActive = CheckIsActive; internal static bool Initialize() { plugin = (Chainloader.PluginInfos.TryGetValue("Raelaziel.OdinArchitect", out var value) ? value.Instance : null); return (Object)(object)plugin != (Object)null; } private static bool CheckIsActive() { if ((Object)(object)plugin != (Object)null) { return ((Behaviour)plugin).isActiveAndEnabled; } return false; } } internal sealed class StableOrder<T> where T : class { private readonly List<T> baseline = new List<T>(); private readonly List<T> pending = new List<T>(); private readonly List<T> ordered = new List<T>(); internal void Capture(List<T> source) { baseline.Clear(); baseline.AddRange(source); if (pending.Capacity < source.Count) { pending.Capacity = source.Count; } if (ordered.Capacity < source.Count) { ordered.Capacity = source.Count; } } internal bool Apply(List<T> destination, Predicate<T> preferred) { ordered.Clear(); pending.Clear(); for (int i = 0; i < baseline.Count; i++) { T val = baseline[i]; if (preferred == null || preferred(val)) { ordered.Add(val); } else { pending.Add(val); } } ordered.AddRange(pending); bool flag = destination.Count != ordered.Count; if (flag) { destination.Clear(); destination.AddRange(ordered); return true; } for (int j = 0; j < ordered.Count; j++) { if (!object.ReferenceEquals(destination[j], ordered[j])) { destination[j] = ordered[j]; flag = true; } } return flag; } } internal static class ToolSelection { internal static bool ShouldSort(string sharedName, Func<bool> isOdinArchitectActive) { switch (sharedName) { case "$odin_hammer": case "$StoneChisel": return isOdinArchitectActive(); default: return false; case "$item_hammer": return true; } } }