Decompiled source of ValheimChestSort v1.0.0

plugins/ValheimChestSort.dll

Decompiled 6 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ValheimChestSort")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ValheimChestSort")]
[assembly: AssemblyTitle("ValheimChestSort")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ValheimChestSort;

[BepInPlugin("SuperVikingDepartment.ValheimChestSort", "ValheimChestSort", "1.0.0")]
public class ChestSortPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "SuperVikingDepartment.ValheimChestSort";

	public const string PluginName = "ValheimChestSort";

	public const string PluginVersion = "1.0.0";

	internal static ManualLogSource Log;

	internal static ConfigEntry<string> SortKeyName;

	internal static ConfigEntry<string> SortModeName;

	internal static ConfigEntry<bool> MergeStacks;

	internal static ConfigEntry<bool> AddButton;

	internal static ConfigEntry<string> ButtonLabel;

	internal static ConfigEntry<bool> DebugLogging;

	internal static KeyCode SortKey = (KeyCode)114;

	private static readonly FieldInfo FItems = AccessTools.Field(typeof(Inventory), "m_inventory");

	private static readonly MethodInfo MChanged = AccessTools.Method(typeof(Inventory), "Changed", new Type[2]
	{
		typeof(bool),
		typeof(bool)
	}, (Type[])null);

	private static readonly FieldInfo FCurrentContainer = AccessTools.Field(typeof(InventoryGui), "m_currentContainer");

	private static readonly FieldInfo FDragItem = AccessTools.Field(typeof(InventoryGui), "m_dragItem");

	private static GameObject _sortButtonGo;

	private static TMP_Text _sortLabel;

	private static readonly Dictionary<ItemType, int> CategoryRank = new Dictionary<ItemType, int>
	{
		{
			(ItemType)1,
			10
		},
		{
			(ItemType)2,
			20
		},
		{
			(ItemType)9,
			30
		},
		{
			(ItemType)23,
			31
		},
		{
			(ItemType)3,
			40
		},
		{
			(ItemType)14,
			41
		},
		{
			(ItemType)22,
			42
		},
		{
			(ItemType)4,
			43
		},
		{
			(ItemType)20,
			44
		},
		{
			(ItemType)5,
			50
		},
		{
			(ItemType)15,
			51
		},
		{
			(ItemType)6,
			60
		},
		{
			(ItemType)7,
			61
		},
		{
			(ItemType)11,
			62
		},
		{
			(ItemType)12,
			63
		},
		{
			(ItemType)17,
			64
		},
		{
			(ItemType)18,
			70
		},
		{
			(ItemType)19,
			71
		},
		{
			(ItemType)24,
			72
		},
		{
			(ItemType)21,
			73
		},
		{
			(ItemType)13,
			74
		},
		{
			(ItemType)10,
			75
		},
		{
			(ItemType)16,
			80
		},
		{
			(ItemType)0,
			90
		}
	};

	private void Awake()
	{
		//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		SortKeyName = ((BaseUnityPlugin)this).Config.Bind<string>("General", "SortKey", "R", "整理当前打开箱子的快捷键(按钮之外的备用入口)。填 KeyCode 名称,如 R / T / B / LeftAlt / Mouse3。改完需重启游戏。");
		SortModeName = ((BaseUnityPlugin)this).Config.Bind<string>("General", "SortMode", "Category", "排序方式:Category = 先按类别(材料/消耗品/武器/护甲/工具…)再按名称;Name = 直接按名称。");
		MergeStacks = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "MergeStacks", true, "整理时是否合并同种物品的零散堆叠(补满已有堆叠),默认开启。");
		AddButton = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AddButton", true, "是否在箱子界面「物品堆叠」按钮左边添加一个「整理」按钮。关闭后只能靠快捷键整理。改完需重启游戏。");
		ButtonLabel = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ButtonLabel", "", "「整理」按钮上显示的文字。留空 = 自动:中文界面显示「整理」,其他语言显示「Sort」。");
		DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "DebugLogging", false, "输出整理细节日志,用于排查问题。正常游玩建议关闭。");
		SortKey = ParseKey(SortKeyName.Value, (KeyCode)114);
		LogReflectionStatus();
		Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(ApplyLabel));
		Log.LogInfo((object)"ValheimChestSort loaded (v1.0.0)");
		Log.LogInfo((object)($"[ChestSort] 快捷键 = {SortKey},排序方式 = {SortModeName.Value}," + $"合并堆叠 = {MergeStacks.Value},界面按钮 = {AddButton.Value}"));
	}

	private static KeyCode ParseKey(string raw, KeyCode fallback)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		if (!string.IsNullOrEmpty(raw) && Enum.TryParse<KeyCode>(raw.Trim(), ignoreCase: true, out KeyCode result))
		{
			return result;
		}
		ManualLogSource log = Log;
		if (log != null)
		{
			log.LogWarning((object)$"[ChestSort] 无法识别按键 \"{raw}\",回退为 {fallback}");
		}
		return fallback;
	}

	private static void LogReflectionStatus()
	{
		if (FItems == null)
		{
			Log.LogError((object)"[ChestSort][diag] Inventory.m_inventory 未找到!");
		}
		if (MChanged == null)
		{
			Log.LogError((object)"[ChestSort][diag] Inventory.Changed 未找到!");
		}
		if (FCurrentContainer == null)
		{
			Log.LogError((object)"[ChestSort][diag] InventoryGui.m_currentContainer 未找到!");
		}
		if (FDragItem == null)
		{
			Log.LogWarning((object)"[ChestSort][diag] InventoryGui.m_dragItem 未找到(拖拽保护将失效)");
		}
	}

	private void OnDestroy()
	{
		Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(ApplyLabel));
	}

	private void Update()
	{
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		InventoryGui instance = InventoryGui.instance;
		if (!((Object)(object)instance == (Object)null))
		{
			if (AddButton.Value && (Object)(object)_sortButtonGo == (Object)null)
			{
				EnsureSortButton(instance);
			}
			if ((Object)(object)_sortButtonGo != (Object)null && _sortButtonGo.activeSelf != instance.IsContainerOpen())
			{
				_sortButtonGo.SetActive(instance.IsContainerOpen());
			}
			if (instance.IsContainerOpen() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible() && ZInput.GetKeyDown(SortKey, false))
			{
				TrySortOpenedContainer();
			}
		}
	}

	private static void EnsureSortButton(InventoryGui gui)
	{
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Expected O, but got Unknown
		if ((Object)(object)gui.m_stackAllButton == (Object)null)
		{
			return;
		}
		Transform transform = ((Component)gui.m_stackAllButton).transform;
		Transform parent = transform.parent;
		if ((Object)(object)parent == (Object)null)
		{
			return;
		}
		Transform val = parent.Find("ChestSortButton");
		if ((Object)(object)val != (Object)null)
		{
			_sortButtonGo = ((Component)val).gameObject;
			_sortLabel = ((Component)val).GetComponentInChildren<TMP_Text>(true) ?? ((Component)transform).GetComponentInChildren<TMP_Text>(true);
			ApplyLabel();
			return;
		}
		GameObject val2;
		try
		{
			val2 = Object.Instantiate<GameObject>(((Component)transform).gameObject, parent);
		}
		catch (Exception ex)
		{
			Log.LogError((object)("[ChestSort] 创建界面按钮失败:" + ex));
			return;
		}
		((Object)val2).name = "ChestSortButton";
		val2.transform.SetSiblingIndex(transform.GetSiblingIndex());
		Button component = val2.GetComponent<Button>();
		if ((Object)(object)component != (Object)null)
		{
			((UnityEventBase)component.onClick).RemoveAllListeners();
			((UnityEvent)component.onClick).AddListener(new UnityAction(OnSortButtonClicked));
			((Selectable)component).interactable = true;
		}
		else
		{
			Log.LogWarning((object)"[ChestSort] 克隆出的按钮对象上没有 Button 组件");
		}
		val2.AddComponent<SortButtonPlacement>().Bind((RectTransform)(object)((transform is RectTransform) ? transform : null));
		RectTransform val3 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
		if ((Object)(object)val3 != (Object)null && (Object)(object)((Component)parent).GetComponent<LayoutGroup>() != (Object)null)
		{
			LayoutRebuilder.ForceRebuildLayoutImmediate(val3);
		}
		_sortButtonGo = val2;
		_sortLabel = val2.GetComponentInChildren<TMP_Text>(true);
		if ((Object)(object)_sortLabel == (Object)null)
		{
			Log.LogWarning((object)"[ChestSort] 未找到按钮文字组件,将沿用原按钮外观");
		}
		ApplyLabel();
		if (DebugLogging.Value)
		{
			bool flag = (Object)(object)((Component)parent).GetComponent<LayoutGroup>() != (Object)null;
			Log.LogInfo((object)($"[ChestSort] 已添加界面按钮,父节点={((Object)parent).name},父节点带布局组={flag}," + string.Format("按钮数={0},按钮文字={1}", parent.childCount, ((Object)(object)_sortLabel != (Object)null) ? _sortLabel.text : "<无>")));
		}
	}

	private static void OnSortButtonClicked()
	{
		Player localPlayer = Player.m_localPlayer;
		if (!((Object)(object)localPlayer == (Object)null) && !((Character)localPlayer).IsTeleporting())
		{
			TrySortOpenedContainer();
		}
	}

	private static void ApplyLabel()
	{
		if ((Object)(object)_sortLabel == (Object)null)
		{
			return;
		}
		string text = ((ButtonLabel != null) ? ButtonLabel.Value : null);
		if (string.IsNullOrEmpty(text))
		{
			text = (IsChinese() ? "整理" : "Sort");
		}
		_sortLabel.text = text;
		try
		{
			Localization instance = Localization.instance;
			if (instance != null)
			{
				instance.RemoveTextFromCache(_sortLabel);
			}
		}
		catch
		{
		}
	}

	private static bool IsChinese()
	{
		try
		{
			Localization instance = Localization.instance;
			string text = ((instance != null) ? instance.GetSelectedLanguage() : null);
			return text == "Chinese" || text == "Chinese_Trad";
		}
		catch
		{
			return false;
		}
	}

	internal static string L(string zh, string en)
	{
		if (!IsChinese())
		{
			return en;
		}
		return zh;
	}

	internal static bool TrySortOpenedContainer()
	{
		InventoryGui instance = InventoryGui.instance;
		if ((Object)(object)instance == (Object)null || !instance.IsContainerOpen())
		{
			return false;
		}
		Container currentContainer = GetCurrentContainer(instance);
		if ((Object)(object)currentContainer == (Object)null)
		{
			return false;
		}
		if (IsDraggingItem(instance))
		{
			Message(L("请先放下手中的物品再整理", "Put down the item you are dragging first"));
			return false;
		}
		Inventory inventory = currentContainer.GetInventory();
		if (inventory == null || inventory.NrOfItems() == 0)
		{
			return false;
		}
		if (!currentContainer.IsOwner())
		{
			Message(L("暂时无法整理,请重新打开箱子", "Cannot sort right now, reopen the container"));
			Log.LogWarning((object)"[ChestSort] 容器不是本地所有,跳过整理");
			return false;
		}
		int num;
		try
		{
			num = SortInventory(inventory, MergeStacks.Value, SortModeName.Value);
		}
		catch (Exception ex)
		{
			Log.LogError((object)("[ChestSort] 整理失败:" + ex));
			Message(L("整理失败,详见日志", "Sort failed, see log"));
			return false;
		}
		Message(L($"箱子已整理({num} 格)", $"Chest sorted ({num} slots)"));
		if (DebugLogging.Value)
		{
			Log.LogInfo((object)$"[ChestSort] sorted {num} slots");
		}
		return true;
	}

	private static Container GetCurrentContainer(InventoryGui gui)
	{
		if (!(FCurrentContainer != null))
		{
			return null;
		}
		object? value = FCurrentContainer.GetValue(gui);
		return (Container)((value is Container) ? value : null);
	}

	private static bool IsDraggingItem(InventoryGui gui)
	{
		if (FDragItem != null)
		{
			return FDragItem.GetValue(gui) != null;
		}
		return false;
	}

	internal static void Message(string msg)
	{
		try
		{
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)2, msg, 0, (Sprite)null, false);
			}
		}
		catch
		{
		}
	}

	internal static int SortInventory(Inventory inv, bool merge, string mode)
	{
		//IL_0171: Unknown result type (might be due to invalid IL or missing references)
		//IL_0176: Unknown result type (might be due to invalid IL or missing references)
		List<ItemData> list = new List<ItemData>(inv.GetAllItems());
		if (list.Count == 0)
		{
			return 0;
		}
		List<ItemData> list2 = new List<ItemData>(list.Count);
		foreach (ItemData item in list)
		{
			if (item == null)
			{
				continue;
			}
			if (merge && item.m_shared.m_maxStackSize > 1)
			{
				for (int i = 0; i < list2.Count; i++)
				{
					ItemData val = list2[i];
					if (val.m_shared.m_maxStackSize > 1 && val.m_stack < val.m_shared.m_maxStackSize && SameStackKind(val, item))
					{
						int num = Math.Min(val.m_shared.m_maxStackSize - val.m_stack, item.m_stack);
						val.m_stack += num;
						item.m_stack -= num;
						if (item.m_stack <= 0)
						{
							break;
						}
					}
				}
			}
			if (item.m_stack > 0)
			{
				list2.Add(item);
			}
		}
		bool byName = string.Equals(mode, "Name", StringComparison.OrdinalIgnoreCase);
		list2.Sort((ItemData a, ItemData b) => CompareItems(a, b, byName));
		int num2 = Math.Max(1, inv.GetWidth());
		for (int num3 = 0; num3 < list2.Count; num3++)
		{
			list2[num3].m_gridPos = new Vector2i(num3 % num2, num3 / num2);
		}
		List<ItemData> itemList = GetItemList(inv);
		itemList.Clear();
		itemList.AddRange(list2);
		InvokeChanged(inv);
		return list2.Count;
	}

	private static int CompareItems(ItemData a, ItemData b, bool byName)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		if (!byName)
		{
			int value;
			int num = (CategoryRank.TryGetValue(a.m_shared.m_itemType, out value) ? value : 85);
			int value3;
			int value2 = (CategoryRank.TryGetValue(b.m_shared.m_itemType, out value3) ? value3 : 85);
			int num2 = num.CompareTo(value2);
			if (num2 != 0)
			{
				return num2;
			}
		}
		int num3 = string.Compare(DisplayName(a), DisplayName(b), StringComparison.CurrentCultureIgnoreCase);
		if (num3 != 0)
		{
			return num3;
		}
		int num4 = b.m_quality.CompareTo(a.m_quality);
		if (num4 != 0)
		{
			return num4;
		}
		int num5 = b.m_worldLevel.CompareTo(a.m_worldLevel);
		if (num5 != 0)
		{
			return num5;
		}
		return b.m_stack.CompareTo(a.m_stack);
	}

	private static bool SameStackKind(ItemData a, ItemData b)
	{
		if (a.m_shared.m_name != b.m_shared.m_name)
		{
			return false;
		}
		if (a.m_quality != b.m_quality)
		{
			return false;
		}
		if (a.m_worldLevel != b.m_worldLevel)
		{
			return false;
		}
		if (a.m_variant != b.m_variant)
		{
			return false;
		}
		Dictionary<string, string> customData = a.m_customData;
		Dictionary<string, string> customData2 = b.m_customData;
		int num = customData?.Count ?? 0;
		int num2 = customData2?.Count ?? 0;
		if (num != num2)
		{
			return false;
		}
		if (num == 0)
		{
			return true;
		}
		foreach (KeyValuePair<string, string> item in customData)
		{
			if (!customData2.TryGetValue(item.Key, out var value) || value != item.Value)
			{
				return false;
			}
		}
		return true;
	}

	private static string DisplayName(ItemData item)
	{
		try
		{
			Localization instance = Localization.instance;
			if (instance != null)
			{
				return instance.Localize(item.m_shared.m_name);
			}
		}
		catch
		{
		}
		return item.m_shared.m_name;
	}

	private static List<ItemData> GetItemList(Inventory inv)
	{
		return (List<ItemData>)FItems.GetValue(inv);
	}

	private static void InvokeChanged(Inventory inv)
	{
		MChanged.Invoke(inv, new object[2] { false, false });
	}
}
internal class SortButtonPlacement : MonoBehaviour
{
	private RectTransform _self;

	private RectTransform _target;

	private bool _sized;

	public void Bind(RectTransform target)
	{
		ref RectTransform self = ref _self;
		Transform transform = ((Component)this).transform;
		self = (RectTransform)(object)((transform is RectTransform) ? transform : null);
		_target = target;
	}

	private void LateUpdate()
	{
		//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_0055: 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_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: 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)
		if ((Object)(object)_self == (Object)null || (Object)(object)_target == (Object)null)
		{
			return;
		}
		Transform parent = ((Transform)_self).parent;
		if (!((Object)(object)parent != (Object)null) || !((Object)(object)((Component)parent).GetComponent<LayoutGroup>() != (Object)null))
		{
			if (!_sized)
			{
				_self.sizeDelta = _target.sizeDelta;
				_sized = true;
			}
			Rect rect = _target.rect;
			float num;
			if (!(((Rect)(ref rect)).width > 1f))
			{
				num = _target.sizeDelta.x;
			}
			else
			{
				rect = _target.rect;
				num = ((Rect)(ref rect)).width;
			}
			float num2 = num;
			if (!(num2 <= 1f))
			{
				_self.anchoredPosition = new Vector2(_target.anchoredPosition.x - num2 - 6f, _target.anchoredPosition.y);
			}
		}
	}
}