Decompiled source of BetterOrganisationBetrValheimCompat v1.0.2

plugins/BetterOrganisationBetrValheimCompat.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[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("BetterOrganisationBetrValheimCompat")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BetterOrganisationBetrValheimCompat")]
[assembly: AssemblyTitle("BetterOrganisationBetrValheimCompat")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BetterOrganisationBetrValheimCompat
{
	[BepInPlugin("com.phoenixf.betterorganisation.betrvalheimcompat", "Better Organisation - BetrValheim Drawers Compat", "0.8.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string ModGuid = "com.phoenixf.betterorganisation.betrvalheimcompat";

		public const string ModName = "Better Organisation - BetrValheim Drawers Compat";

		public const string ModVersion = "0.8.3";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
		}

		private void Start()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_0069: Expected O, but got Unknown
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Expected O, but got Unknown
			try
			{
				if (!Compat.Bind())
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Compatibility bridge not enabled; Better Organisation is untouched.");
					return;
				}
				_harmony = new Harmony("com.phoenixf.betterorganisation.betrvalheimcompat");
				_harmony.Patch((MethodBase)Compat.StashAllMethod, new HarmonyMethod(typeof(Compat), "StashAllPrefix", (Type[])null), new HarmonyMethod(typeof(Compat), "StashAllPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				foreach (MethodInfo item in from m in typeof(MessageHud).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					where m.Name == "ShowMessage" && m.GetParameters().Any((ParameterInfo p) => p.ParameterType == typeof(string))
					select m)
				{
					_harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(Compat), "MessageHudShowMessagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Better Organisation - BetrValheim Drawers Compat 0.8.3 active: matching BetrValheim drawers first, then Better Organisation chests.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Failed to initialise compatibility bridge: " + ex));
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class Compat
	{
		internal static MethodInfo StashAllMethod;

		private static Assembly _boAssembly;

		private static Assembly _betrAssembly;

		private static Type _drawerType;

		private static MethodInfo _getStashableItems;

		private static MethodInfo _isFavorite;

		private static FieldInfo _boRangeField;

		private static PropertyInfo _configEntryValue;

		private static PropertyInfo _drawerItemName;

		private static PropertyInfo _drawerFreeSpace;

		private static MethodInfo _drawerDeposit;

		private static bool _inside;

		private static bool _warnedDeposit;

		private static int _inventoryCountBefore;

		private static int _drawerMoved;

		private static bool _stashCallActive;

		private static readonly HashSet<string> _drawerDestinations = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private const BindingFlags AllStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		internal static bool Bind()
		{
			_boAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "ChestCategories", StringComparison.OrdinalIgnoreCase));
			_betrAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "BetrValheim", StringComparison.OrdinalIgnoreCase));
			if (_boAssembly == null)
			{
				Plugin.Log.LogError((object)"ChestCategories.dll (Better Organisation) is not loaded.");
				return false;
			}
			if (_betrAssembly == null)
			{
				Plugin.Log.LogError((object)"BetrValheim.dll is not loaded.");
				return false;
			}
			StashAllMethod = SafeTypes(_boAssembly).FirstOrDefault((Type t) => t.Name == "StashService")?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "StashAll" && m.GetParameters().Length == 0);
			_getStashableItems = SafeTypes(_boAssembly).FirstOrDefault((Type t) => t.Name == "InventoryRules")?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetStashableItems" && m.GetParameters().Length == 1 && typeof(Player).IsAssignableFrom(m.GetParameters()[0].ParameterType));
			_isFavorite = SafeTypes(_boAssembly).FirstOrDefault((Type t) => t.Name == "ItemFavorites")?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "IsFavorite" && m.ReturnType == typeof(bool) && m.GetParameters().Length >= 1 && typeof(ItemData).IsAssignableFrom(m.GetParameters()[0].ParameterType));
			_boRangeField = SafeTypes(_boAssembly).FirstOrDefault((Type t) => t.Name == "ModConfig")?.GetField("Range", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (_boRangeField != null)
			{
				_configEntryValue = _boRangeField.GetValue(null)?.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public);
			}
			_drawerType = _betrAssembly.GetType("BetrValheim.Drawers.Drawer", throwOnError: false);
			if (_drawerType != null)
			{
				_drawerItemName = _drawerType.GetProperty("ItemName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				_drawerFreeSpace = _drawerType.GetProperty("FreeSpace", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				_drawerDeposit = _drawerType.GetMethod("Deposit", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
				{
					typeof(string),
					typeof(int)
				}, null);
			}
			if (StashAllMethod == null)
			{
				Plugin.Log.LogError((object)"Could not find Better Organisation StashService.StashAll().");
				return false;
			}
			if (_getStashableItems == null)
			{
				Plugin.Log.LogError((object)"Could not find Better Organisation InventoryRules.GetStashableItems(Player).");
				return false;
			}
			if (_drawerType == null || _drawerItemName == null || _drawerDeposit == null)
			{
				Plugin.Log.LogError((object)"Could not bind BetrValheim Drawer.ItemName / Drawer.Deposit(string,int). Better Organisation is untouched.");
				return false;
			}
			Plugin.Log.LogInfo((object)("Bound Better Organisation: " + StashAllMethod.DeclaringType.FullName + ".StashAll()"));
			Plugin.Log.LogInfo((object)("Bound BO stash filter: " + Describe(_getStashableItems)));
			Plugin.Log.LogInfo((object)("Bound BO favourite check: " + Describe(_isFavorite)));
			Plugin.Log.LogInfo((object)("Bound BetrValheim drawer: ItemName=" + _drawerItemName.Name + ", FreeSpace=" + (_drawerFreeSpace?.Name ?? "<not needed>") + ", Deposit=" + Describe(_drawerDeposit)));
			return true;
		}

		internal static bool StashAllPrefix()
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			if (_inside)
			{
				return true;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return true;
			}
			_inventoryCountBefore = CountInventoryItems(((Humanoid)localPlayer).GetInventory());
			_drawerMoved = 0;
			_drawerDestinations.Clear();
			_stashCallActive = true;
			_inside = true;
			try
			{
				List<ItemData> stashableItems = GetStashableItems(localPlayer);
				if (stashableItems == null || stashableItems.Count == 0)
				{
					return true;
				}
				float boRange = GetBoRange();
				List<object> list = FindNearbyDrawers(((Component)localPlayer).transform.position, boRange);
				if (list.Count == 0)
				{
					return true;
				}
				Inventory inventory = ((Humanoid)localPlayer).GetInventory();
				int num = 0;
				int num2 = 0;
				ItemData[] array = stashableItems.ToArray();
				foreach (ItemData val in array)
				{
					if (val == null || val.m_stack <= 0 || (Object)(object)val.m_dropPrefab == (Object)null || IsFavorite(val))
					{
						continue;
					}
					string prefabName = GetPrefabName(val);
					if (string.IsNullOrEmpty(prefabName))
					{
						continue;
					}
					string sharedName = val.m_shared?.m_name;
					foreach (object item in list)
					{
						if (val.m_stack <= 0)
						{
							break;
						}
						string assigned = ReadString(_drawerItemName, item);
						if (!MatchesAssignedDrawer(assigned, prefabName, sharedName))
						{
							continue;
						}
						int num3 = val.m_stack;
						if (_drawerFreeSpace != null)
						{
							int num4 = ReadInt(_drawerFreeSpace, item);
							if (num4 <= 0)
							{
								continue;
							}
							num3 = Math.Min(num3, num4);
						}
						int num5 = Deposit(item, prefabName, num3);
						if (num5 > 0)
						{
							num5 = Math.Min(num5, val.m_stack);
							inventory.RemoveItem(val, num5);
							num += num5;
							num2++;
							_drawerMoved += num5;
							_drawerDestinations.Add(FriendlyDrawerName(assigned, prefabName, sharedName));
						}
					}
				}
				if (num > 0)
				{
					Plugin.Log.LogInfo((object)$"Drawer-first stash moved {num} item(s) in {num2} transfer(s); Better Organisation will handle the remainder.");
					List<ItemData> stashableItems2 = GetStashableItems(localPlayer);
					if (stashableItems2 == null || stashableItems2.Count == 0)
					{
						Plugin.Log.LogInfo((object)"All stashable items were handled by matching drawers; skipping empty Better Organisation chest pass.");
						return false;
					}
				}
				return true;
			}
			catch (Exception e)
			{
				Plugin.Log.LogError((object)("Drawer-first stash failed; falling through to unmodified Better Organisation StashAll: " + RootMessage(e)));
				return true;
			}
			finally
			{
				_inside = false;
			}
		}

		internal static bool MessageHudShowMessagePrefix(object[] __args)
		{
			if (!_stashCallActive || _drawerMoved <= 0 || __args == null)
			{
				return true;
			}
			for (int i = 0; i < __args.Length; i++)
			{
				if (__args[i] is string text && !string.IsNullOrEmpty(text) && text.IndexOf("No chest nearby accepts these items", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					Plugin.Log.LogInfo((object)"Suppressed Better Organisation chest-only failure message because drawers accepted items.");
					return false;
				}
			}
			return true;
		}

		internal static void StashAllPostfix()
		{
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if (!((Object)(object)localPlayer == (Object)null) && _drawerMoved > 0)
				{
					int num = CountInventoryItems(((Humanoid)localPlayer).GetInventory());
					int num2 = Math.Max(0, _inventoryCountBefore - num);
					int num3 = Math.Max(0, num2 - _drawerMoved);
					if (num2 > 0 && !((Object)(object)MessageHud.instance == (Object)null))
					{
						string text = ((num3 > 0) ? "Drawers/Chests" : ((_drawerDestinations.Count != 1) ? (_drawerDestinations.Count + " drawers") : _drawerDestinations.First()));
						string text2 = string.Format("Stashed {0} item{1} into {2}", num2, (num2 == 1) ? "" : "s", text);
						((Character)localPlayer).Message((MessageType)2, text2, 0, (Sprite)null, false);
						Plugin.Log.LogInfo((object)$"Combined stash summary: total={num2}, drawers={_drawerMoved}, chests={num3}, destination={text}");
					}
				}
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("Could not show combined BO/drawer stash summary: " + RootMessage(e)));
			}
			finally
			{
				_stashCallActive = false;
				_inventoryCountBefore = 0;
				_drawerMoved = 0;
				_drawerDestinations.Clear();
			}
		}

		private static int CountInventoryItems(Inventory inventory)
		{
			if (inventory == null)
			{
				return 0;
			}
			int num = 0;
			try
			{
				foreach (ItemData allItem in inventory.GetAllItems())
				{
					if (allItem != null && allItem.m_stack > 0)
					{
						num += allItem.m_stack;
					}
				}
			}
			catch
			{
			}
			return num;
		}

		private static string FriendlyDrawerName(string assigned, string prefabName, string sharedName)
		{
			if (!string.IsNullOrEmpty(sharedName) && !sharedName.StartsWith("$", StringComparison.Ordinal))
			{
				return sharedName;
			}
			if (!string.IsNullOrEmpty(assigned))
			{
				return CleanPrefabName(assigned);
			}
			return CleanPrefabName(prefabName);
		}

		private static string CleanPrefabName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "Drawer";
			}
			if (name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				name = name.Substring(0, name.Length - "(Clone)".Length);
			}
			return name.Replace("_", " ");
		}

		private static bool IsFavorite(ItemData item)
		{
			if (item == null)
			{
				return false;
			}
			if (_isFavorite == null)
			{
				return false;
			}
			try
			{
				ParameterInfo[] parameters = _isFavorite.GetParameters();
				object[] array = new object[parameters.Length];
				array[0] = item;
				for (int i = 1; i < parameters.Length; i++)
				{
					if (parameters[i].HasDefaultValue)
					{
						array[i] = parameters[i].DefaultValue;
					}
					else if (parameters[i].ParameterType.IsValueType)
					{
						array[i] = Activator.CreateInstance(parameters[i].ParameterType);
					}
					else
					{
						array[i] = null;
					}
				}
				object obj = _isFavorite.Invoke(null, array);
				bool flag = default(bool);
				int num;
				if (obj is bool)
				{
					flag = (bool)obj;
					num = 1;
				}
				else
				{
					num = 0;
				}
				return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("Could not query Better Organisation favourite state; skipping this item for drawer stash: " + RootMessage(e)));
				return true;
			}
		}

		private static List<ItemData> GetStashableItems(Player player)
		{
			try
			{
				if (!(_getStashableItems.Invoke(null, new object[1] { player }) is IEnumerable enumerable))
				{
					return null;
				}
				List<ItemData> list = new List<ItemData>();
				foreach (object item in enumerable)
				{
					ItemData val = (ItemData)((item is ItemData) ? item : null);
					if (val != null && val.m_stack > 0)
					{
						list.Add(val);
					}
				}
				return list;
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("Could not obtain Better Organisation stashable items: " + RootMessage(e)));
				return null;
			}
		}

		private static float GetBoRange()
		{
			try
			{
				object obj = _boRangeField?.GetValue(null);
				object obj2 = _configEntryValue?.GetValue(obj, null);
				if (obj2 is float result)
				{
					return result;
				}
				if (obj2 is double num)
				{
					return (float)num;
				}
				if (obj2 is int num2)
				{
					return num2;
				}
			}
			catch
			{
			}
			return 15f;
		}

		private static List<object> FindNearbyDrawers(Vector3 origin, float range)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//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_0098: Unknown result type (might be due to invalid IL or missing references)
			float num = range * range;
			List<object> list = new List<object>();
			Object[] array = Resources.FindObjectsOfTypeAll(_drawerType);
			foreach (Object val in array)
			{
				if (val == (Object)null)
				{
					continue;
				}
				Component val2 = (Component)(object)((val is Component) ? val : null);
				if ((Object)(object)val2 == (Object)null || (Object)(object)val2.gameObject == (Object)null)
				{
					continue;
				}
				Scene scene = val2.gameObject.scene;
				if (((Scene)(ref scene)).IsValid() && val2.gameObject.activeInHierarchy)
				{
					Vector3 val3 = val2.transform.position - origin;
					if (((Vector3)(ref val3)).sqrMagnitude <= num)
					{
						list.Add(val);
					}
				}
			}
			list.Sort(delegate(object a, object b)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Expected O, but got Unknown
				//IL_0012: 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_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				//IL_003c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				Component val4 = (Component)a;
				Component val5 = (Component)b;
				Vector3 val6 = val4.transform.position - origin;
				float sqrMagnitude = ((Vector3)(ref val6)).sqrMagnitude;
				val6 = val5.transform.position - origin;
				float sqrMagnitude2 = ((Vector3)(ref val6)).sqrMagnitude;
				return sqrMagnitude.CompareTo(sqrMagnitude2);
			});
			return list;
		}

		private static int Deposit(object drawer, string prefabName, int amount)
		{
			if (drawer == null || amount <= 0)
			{
				return 0;
			}
			try
			{
				if (_drawerDeposit.Invoke(drawer, new object[2] { prefabName, amount }) is int val)
				{
					return Math.Max(0, Math.Min(amount, val));
				}
				return 0;
			}
			catch (Exception e)
			{
				if (!_warnedDeposit)
				{
					_warnedDeposit = true;
					Plugin.Log.LogWarning((object)("Drawer.Deposit failed: " + RootMessage(e)));
				}
				return 0;
			}
		}

		private static bool MatchesAssignedDrawer(string assigned, string prefabName, string sharedName)
		{
			if (string.IsNullOrEmpty(assigned))
			{
				return false;
			}
			if (string.Equals(assigned, prefabName, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (!string.IsNullOrEmpty(sharedName) && string.Equals(assigned, sharedName, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return false;
		}

		private static string GetPrefabName(ItemData item)
		{
			try
			{
				string text = ((Object)item.m_dropPrefab).name;
				if (text.EndsWith("(Clone)", StringComparison.Ordinal))
				{
					text = text.Substring(0, text.Length - "(Clone)".Length);
				}
				return text;
			}
			catch
			{
				return null;
			}
		}

		private static string ReadString(PropertyInfo property, object target)
		{
			try
			{
				return property?.GetValue(target, null) as string;
			}
			catch
			{
				return null;
			}
		}

		private static int ReadInt(PropertyInfo property, object target)
		{
			try
			{
				return (property?.GetValue(target, null) is int num) ? num : 0;
			}
			catch
			{
				return 0;
			}
		}

		private static string RootMessage(Exception e)
		{
			while (e is TargetInvocationException { InnerException: not null } ex)
			{
				e = ex.InnerException;
			}
			return e?.Message ?? "<unknown>";
		}

		private static string Describe(MethodInfo m)
		{
			if (m == null)
			{
				return "<not found>";
			}
			string text = string.Join(", ", from p in m.GetParameters()
				select p.ParameterType.Name + " " + p.Name);
			return m.DeclaringType.FullName + "." + m.Name + "(" + text + ") -> " + m.ReturnType.Name;
		}

		private static Type[] SafeTypes(Assembly assembly)
		{
			try
			{
				return assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				return ex.Types.Where((Type t) => t != null).ToArray();
			}
		}
	}
}