Decompiled source of Inspect Fix v1.0.2

InspectFix.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Localization;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("InspectFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2")]
[assembly: AssemblyProduct("InspectFix")]
[assembly: AssemblyTitle("InspectFix")]
[assembly: AssemblyVersion("1.0.2.0")]
namespace InspectFix;

public enum SwitchBehaviour
{
	Vanilla,
	Follow,
	Hide
}
[BepInPlugin("com.sopika.inspectfix", "Inspect Fix", "1.0.2")]
public class InspectFixPlugin : BaseUnityPlugin
{
	public const string Guid = "com.sopika.inspectfix";

	public const string Version = "1.0.2";

	internal static ManualLogSource Log;

	internal static ConfigEntry<SwitchBehaviour> OnItemSwitch;

	internal static ConfigEntry<bool> HideWhenItemGone;

	internal static ConfigEntry<float> EmptyHandsDelay;

	internal static ConfigEntry<bool> DebugLogging;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Expected O, but got Unknown
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, "inspectfix.cfg"), true, ((BaseUnityPlugin)this).Info.Metadata);
		FieldInfo fieldInfo = AccessTools.Field(typeof(BaseUnityPlugin), "<Config>k__BackingField");
		if (fieldInfo != null)
		{
			fieldInfo.SetValue(this, val);
		}
		else
		{
			Log.LogWarning((object)"could not redirect base.Config, settings may not show up in config menus");
		}
		OnItemSwitch = val.Bind<SwitchBehaviour>("Behaviour", "OnItemSwitch", SwitchBehaviour.Follow, "What the inspect panel does when you swap to a different item while it is still up.\nFollow: updates the panel to the item you just equipped.\nHide: closes the panel the moment you switch.\nVanilla: leaves it alone, stale info and all.");
		HideWhenItemGone = val.Bind<bool>("Behaviour", "HideWhenItemGone", true, "Close the panel once you are no longer holding the item it describes. Turn this off and the panel just runs out its own timer instead.");
		EmptyHandsDelay = val.Bind<float>("Behaviour", "EmptyHandsDelay", 0.25f, new ConfigDescription("Seconds to wait before deciding your hands are really empty. Mods that stack items hand the next one back through the server, and the game reports an empty inventory the whole time, so this has to outlast that. Lower it if the panel lingers, raise it if it closes mid stack.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
		DebugLogging = val.Bind<bool>("Behaviour", "DebugLogging", false, "Log what the mod sees while the panel is up. Only useful for chasing a bug report.");
		InspectPanel.Init();
		InspectText.Init();
		_harmony = new Harmony("com.sopika.inspectfix");
		Patches.ApplyAll(_harmony);
	}

	private void Update()
	{
		InspectPanel.Tick();
	}

	private void OnDestroy()
	{
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
	}
}
internal static class InspectPanel
{
	private const float HandoverWait = 3f;

	private static FieldRef<WeaponUI, bool> _isInspecting;

	private static FieldRef<WeaponUI, Item> _inspectedItem;

	private static FieldRef<WeaponUI, Purchasable> _inspectedPurchasable;

	private static WeaponUI _ui;

	private static bool _ready;

	private static float _emptySince;

	private static Item _lastPushed;

	private static int _heldSlot = -1;

	private static string _lastTrace;

	internal static void Init()
	{
		try
		{
			_isInspecting = AccessTools.FieldRefAccess<WeaponUI, bool>("_isInspecting");
			_inspectedItem = AccessTools.FieldRefAccess<WeaponUI, Item>("_inspectedItem");
			_inspectedPurchasable = AccessTools.FieldRefAccess<WeaponUI, Purchasable>("_inspectedPurchasable");
			_ready = true;
		}
		catch (Exception ex)
		{
			_ready = false;
			InspectFixPlugin.Log.LogError((object)("could not reach WeaponUI inspect state, mod disabled: " + ex.Message));
		}
	}

	internal static void CaptureUI(WeaponUI ui)
	{
		_ui = ui;
	}

	private static bool ShowingItemPanel()
	{
		if (_ready && (Object)(object)_ui != (Object)null && _isInspecting.Invoke(_ui))
		{
			return (Object)(object)_inspectedPurchasable.Invoke(_ui) == (Object)null;
		}
		return false;
	}

	private static Item HeldItem()
	{
		Player localPlayer = Player.LocalPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return null;
		}
		PlayerHolding holding = localPlayer.Holding;
		if (!((Object)(object)holding != (Object)null))
		{
			return null;
		}
		return holding.HeldItem;
	}

	private static void RememberSlot(Item held)
	{
		PlayerInventory val = LocalInventory();
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		int heldSlot = -1;
		foreach (KeyValuePair<byte, Item> item in val._items)
		{
			if ((Object)(object)item.Value == (Object)(object)held)
			{
				heldSlot = item.Key;
				break;
			}
		}
		_heldSlot = heldSlot;
	}

	private static PlayerInventory LocalInventory()
	{
		Player localPlayer = Player.LocalPlayer;
		if (!((Object)(object)localPlayer != (Object)null))
		{
			return null;
		}
		return localPlayer.Inventory;
	}

	private static bool NextOneComing(Item shown)
	{
		if (_heldSlot < 0)
		{
			return false;
		}
		PlayerInventory val = LocalInventory();
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		Item val2 = default(Item);
		if (val._items.TryGetValue((byte)_heldSlot, ref val2) && (Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)shown)
		{
			return true;
		}
		return StackBridge.HasSpares(val, _heldSlot);
	}

	private static string Describe(Item item)
	{
		if (item == null)
		{
			return "none";
		}
		if ((Object)(object)item == (Object)null)
		{
			return "destroyed";
		}
		return item.GetName() + "#" + ((Object)item).GetInstanceID();
	}

	private static void Trace(string what)
	{
		if (InspectFixPlugin.DebugLogging.Value)
		{
			string text = what + " shown=" + Describe(_inspectedItem.Invoke(_ui)) + " held=" + Describe(HeldItem()) + " heldSlot=" + _heldSlot + " next=" + NextOneComing(_inspectedItem.Invoke(_ui)) + " inspecting=" + _isInspecting.Invoke(_ui);
			if (!(text == _lastTrace))
			{
				_lastTrace = text;
				InspectFixPlugin.Log.LogInfo((object)text);
			}
		}
	}

	internal static void Tick()
	{
		if (!ShowingItemPanel())
		{
			_emptySince = 0f;
			_lastPushed = null;
			_heldSlot = -1;
			return;
		}
		Trace("tick");
		SwitchBehaviour value = InspectFixPlugin.OnItemSwitch.Value;
		if (value == SwitchBehaviour.Vanilla)
		{
			return;
		}
		Item val = _inspectedItem.Invoke(_ui);
		Item val2 = HeldItem();
		if ((Object)(object)val2 != (Object)null)
		{
			_emptySince = 0f;
			RememberSlot(val2);
			if ((Object)(object)val == (Object)(object)val2)
			{
				_lastPushed = null;
			}
			else if (value == SwitchBehaviour.Hide)
			{
				Hide();
			}
			else if (!((Object)(object)_lastPushed == (Object)(object)val2))
			{
				_lastPushed = val2;
				Trace("retarget");
				PlayerUI.ShowInspectInfo(val2, InspectText.Build(val2));
			}
		}
		else
		{
			if (_emptySince == 0f)
			{
				_emptySince = Time.unscaledTime;
			}
			float num = Time.unscaledTime - _emptySince;
			if ((!(num < 3f) || !NextOneComing(val)) && InspectFixPlugin.HideWhenItemGone.Value && !(num < InspectFixPlugin.EmptyHandsDelay.Value))
			{
				Trace("hide");
				Hide();
			}
		}
	}

	private static void Hide()
	{
		_emptySince = 0f;
		_lastPushed = null;
		PlayerUI.HideInspectInfo((Purchasable)null);
	}
}
internal static class InspectText
{
	private static FieldRef<Item, float> _weight;

	internal static void Init()
	{
		try
		{
			_weight = AccessTools.FieldRefAccess<Item, float>("_weight");
		}
		catch (Exception ex)
		{
			InspectFixPlugin.Log.LogWarning((object)("no Item._weight, creature weights will read as base: " + ex.Message));
		}
	}

	internal static string Build(Item item)
	{
		try
		{
			Weapon val = (Weapon)(object)((item is Weapon) ? item : null);
			if (val != null)
			{
				return ((Object)(object)val.Attachments != (Object)null) ? val.Attachments.GetAttachmentInfo() : "";
			}
			Melee val2 = (Melee)(object)((item is Melee) ? item : null);
			if (val2 != null)
			{
				return Sharpness(val2);
			}
			Creature val3 = (Creature)(object)((item is Creature) ? item : null);
			if (val3 != null)
			{
				return CreatureInfo(val3);
			}
		}
		catch (Exception ex)
		{
			InspectFixPlugin.Log.LogWarning((object)("could not build inspect text: " + ex.Message));
		}
		return "";
	}

	private static string Sharpness(Melee melee)
	{
		SharpnessUpgrade curSharpness = melee.GetCurSharpness();
		return $"<b>{melee.SharpnessIndex}</b> {Loc(LocalizationManager.SharpnessLocalized)}" + $"\n<b>{((curSharpness != null) ? curSharpness.Damage : 0)}</b> {Loc(LocalizationManager.DamageLocalized)}";
	}

	private static string CreatureInfo(Creature creature)
	{
		float value = ((Item)creature)._syncedRandomWeight.Value;
		float num = ((_weight != null) ? _weight.Invoke((Item)(object)creature) : 1f);
		string text = $"\n{Loc(LocalizationManager.WeightLocalized)}: {num * value:0.###} {Loc(LocalizationManager.KilogramLocalized)}";
		float num2 = -1f + value;
		char c = ((num2 < 0f) ? '-' : '+');
		if (num2 != 0f)
		{
			text += $" <color=grey>({c}{Mathf.Abs(num2):0.##})%</color>";
		}
		text += $"\n{Loc(LocalizationManager.KillscoreLocalized)}: {((Item)creature)._killScoreMultiplier.Value:0.##}x";
		if (creature.IsEndangered)
		{
			text = text + "\n\n" + Loc(LocalizationManager.EndangeredSpeciesLocalized);
		}
		return text;
	}

	private static string Loc(LocalizedString entry)
	{
		if (entry == null)
		{
			return "";
		}
		return entry.GetLocalizedString();
	}
}
internal static class Patches
{
	private static int _applied;

	private static int _attempted;

	internal static void ApplyAll(Harmony harmony)
	{
		_applied = 0;
		_attempted = 0;
		Add(harmony, AccessTools.Method(typeof(WeaponUI), "ShowInspectText", new Type[2]
		{
			typeof(Item),
			typeof(string)
		}, (Type[])null), "ShowInspectText_Post", "WeaponUI.ShowInspectText");
		InspectFixPlugin.Log.LogInfo((object)$"patches applied: {_applied}/{_attempted}");
	}

	private static void Add(Harmony harmony, MethodBase target, string postfix, string label)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Expected O, but got Unknown
		_attempted++;
		try
		{
			if (target == null)
			{
				throw new MissingMethodException(label);
			}
			MethodInfo method = typeof(Patches).GetMethod(postfix, BindingFlags.Static | BindingFlags.NonPublic);
			harmony.Patch(target, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_applied++;
		}
		catch (Exception ex)
		{
			InspectFixPlugin.Log.LogWarning((object)("could not patch " + label + ": " + ex.Message));
		}
	}

	private static void ShowInspectText_Post(WeaponUI __instance)
	{
		InspectPanel.CaptureUI(__instance);
	}
}
internal static class StackBridge
{
	private const float RetrySeconds = 5f;

	private static MethodInfo _getStackCount;

	private static float _nextLookup;

	private static bool _announced;

	internal static bool HasSpares(PlayerInventory inventory, int slot)
	{
		if ((Object)(object)inventory == (Object)null || slot < 0 || slot > 255)
		{
			return false;
		}
		if (_getStackCount == null)
		{
			if (Time.unscaledTime < _nextLookup)
			{
				return false;
			}
			_nextLookup = Time.unscaledTime + 5f;
			Resolve();
			if (_getStackCount == null)
			{
				return false;
			}
		}
		try
		{
			return (int)_getStackCount.Invoke(null, new object[2]
			{
				inventory,
				(byte)slot
			}) > 0;
		}
		catch (Exception ex)
		{
			_getStackCount = null;
			_nextLookup = float.MaxValue;
			InspectFixPlugin.Log.LogWarning((object)("stack lookup failed, giving up on it: " + ex.Message));
			return false;
		}
	}

	private static void Resolve()
	{
		Type type = AccessTools.TypeByName("StackableItems.StackManager");
		if (!(type == null))
		{
			_getStackCount = AccessTools.Method(type, "GetStackCount", new Type[2]
			{
				typeof(PlayerInventory),
				typeof(byte)
			}, (Type[])null);
			if (_getStackCount == null || _getStackCount.ReturnType != typeof(int))
			{
				_getStackCount = null;
				InspectFixPlugin.Log.LogWarning((object)"found Stackable Items but not GetStackCount, falling back to the timer");
				_nextLookup = float.MaxValue;
			}
			else if (!_announced)
			{
				_announced = true;
				InspectFixPlugin.Log.LogInfo((object)"Stackable Items found, using its stack count");
			}
		}
	}
}