Decompiled source of HammerSort v1.1.3

plugins/HammerSort/HammerSort.dll

Decompiled 2 weeks ago
using 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;
		}
	}
}