Decompiled source of LootSwapHelper v0.2.0

lootswaphelper.dll

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

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("lootswaphelper")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("lootswaphelper")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("8115d3fa-dcf7-4e39-b63a-905f39e1f4b7")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace hoppinhauler.LootSwapHelper;

[BepInPlugin("HoppinHauler.lootswaphelper", "Loot Swap Helper", "0.2.0")]
public sealed class LootSwapHelperPlugin : BaseUnityPlugin
{
	[HarmonyPatch]
	private static class HUDPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(HUDManager), "Update")]
		private static void HUDManager_Update_Postfix(HUDManager __instance)
		{
			if (!((Object)(object)_instance == (Object)null))
			{
				_instance.OnHudUpdatePostfix(__instance);
			}
		}
	}

	private struct ScanBest
	{
		public readonly string Name;

		public readonly int Value;

		public static ScanBest Empty => new ScanBest(null, 0);

		public ScanBest(string name, int value)
		{
			Name = name;
			Value = value;
		}
	}

	private struct HeldSnapshot
	{
		public readonly int Count;

		public readonly int SumValue;

		public readonly string CheapestName;

		public readonly int CheapestValue;

		public static HeldSnapshot Empty => new HeldSnapshot(0, 0, null, 0);

		public HeldSnapshot(int count, int sumValue, string cheapestName, int cheapestValue)
		{
			Count = count;
			SumValue = sumValue;
			CheapestName = cheapestName;
			CheapestValue = cheapestValue;
		}
	}

	private sealed class ScanHintOverlay : MonoBehaviour
	{
		private Text _text;

		private Image _bg;

		private string _last;

		private int _lastFontSize;

		public static ScanHintOverlay Create(int fontSize, Action<string> logInfo)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0268: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = GameObject.Find("LootSwapHelperOverlayCanvas");
				if ((Object)(object)val != (Object)null)
				{
					ScanHintOverlay component = val.GetComponent<ScanHintOverlay>();
					if ((Object)(object)component != (Object)null)
					{
						component.SetFontSize(fontSize);
						return component;
					}
				}
			}
			catch
			{
			}
			GameObject val2 = new GameObject("LootSwapHelperOverlayCanvas");
			((Object)val2).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)val2);
			Canvas val3 = val2.AddComponent<Canvas>();
			val3.renderMode = (RenderMode)0;
			val3.sortingOrder = 32767;
			CanvasScaler val4 = val2.AddComponent<CanvasScaler>();
			val4.uiScaleMode = (ScaleMode)1;
			val4.referenceResolution = new Vector2(1920f, 1080f);
			val2.AddComponent<GraphicRaycaster>();
			GameObject val5 = new GameObject("Panel");
			((Object)val5).hideFlags = (HideFlags)61;
			val5.transform.SetParent(val2.transform, false);
			RectTransform val6 = val5.AddComponent<RectTransform>();
			val6.anchorMin = new Vector2(0f, 1f);
			val6.anchorMax = new Vector2(0f, 1f);
			val6.pivot = new Vector2(0f, 1f);
			val6.anchoredPosition = new Vector2(18f, -18f);
			val6.sizeDelta = new Vector2(1200f, 70f);
			Image val7 = val5.AddComponent<Image>();
			((Graphic)val7).raycastTarget = false;
			((Graphic)val7).color = new Color(0f, 0f, 0f, 0.6f);
			GameObject val8 = new GameObject("Text");
			((Object)val8).hideFlags = (HideFlags)61;
			val8.transform.SetParent(val5.transform, false);
			RectTransform val9 = val8.AddComponent<RectTransform>();
			val9.anchorMin = new Vector2(0f, 0f);
			val9.anchorMax = new Vector2(1f, 1f);
			val9.pivot = new Vector2(0.5f, 0.5f);
			val9.anchoredPosition = Vector2.zero;
			val9.offsetMin = new Vector2(16f, 10f);
			val9.offsetMax = new Vector2(-16f, -10f);
			Text val10 = val8.AddComponent<Text>();
			((Graphic)val10).raycastTarget = false;
			val10.horizontalOverflow = (HorizontalWrapMode)1;
			val10.verticalOverflow = (VerticalWrapMode)1;
			val10.alignment = (TextAnchor)3;
			((Graphic)val10).color = Color.white;
			try
			{
				val10.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			}
			catch
			{
			}
			ScanHintOverlay scanHintOverlay = val2.AddComponent<ScanHintOverlay>();
			scanHintOverlay._text = val10;
			scanHintOverlay._bg = val7;
			scanHintOverlay.SetFontSize(fontSize);
			scanHintOverlay.SetText("LootSwapHelper overlay OK");
			scanHintOverlay.SetVisible(visible: false);
			logInfo?.Invoke("Overlay: created standalone ScreenSpaceOverlay canvas.");
			return scanHintOverlay;
		}

		public void SetVisible(bool visible)
		{
			try
			{
				if ((Object)(object)((Component)this).gameObject != (Object)null)
				{
					((Component)this).gameObject.SetActive(visible);
				}
			}
			catch
			{
			}
		}

		public void SetText(string s)
		{
			if ((Object)(object)_text == (Object)null)
			{
				return;
			}
			s = s ?? "";
			if (s == _last)
			{
				return;
			}
			_last = s;
			try
			{
				_text.text = s;
			}
			catch
			{
			}
		}

		public void SetFontSize(int size)
		{
			if ((Object)(object)_text == (Object)null || size == _lastFontSize)
			{
				return;
			}
			_lastFontSize = size;
			try
			{
				_text.fontSize = size;
			}
			catch
			{
			}
		}
	}

	[CompilerGenerated]
	private sealed class <EnsureOverlayRoutine>d__17 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		public LootSwapHelperPlugin <>4__this;

		private Exception <ex>5__1;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <EnsureOverlayRoutine>d__17(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<ex>5__1 = null;
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				break;
			case 1:
				<>1__state = -1;
				break;
			}
			try
			{
				if ((Object)(object)<>4__this._overlay == (Object)null)
				{
					<>4__this._overlay = ScanHintOverlay.Create(Mathf.Clamp(<>4__this._fontSize.Value, 12, 60), delegate(string msg)
					{
						((BaseUnityPlugin)<>4__this).Logger.LogInfo((object)msg);
					});
				}
			}
			catch (Exception ex)
			{
				<ex>5__1 = ex;
				((BaseUnityPlugin)<>4__this).Logger.LogWarning((object)("EnsureOverlayRoutine failed: " + <ex>5__1.Message));
			}
			<>2__current = (object)new WaitForSeconds(1f);
			<>1__state = 1;
			return true;
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	public const string PluginGuid = "HoppinHauler.lootswaphelper";

	public const string PluginName = "Loot Swap Helper";

	public const string PluginVersion = "0.2.0";

	private static LootSwapHelperPlugin _instance;

	private Harmony _harmony;

	private ConfigEntry<bool> _enabled;

	private ConfigEntry<bool> _debugLog;

	private ConfigEntry<float> _pollInterval;

	private ConfigEntry<float> _showSeconds;

	private ConfigEntry<int> _fontSize;

	private ScanHintOverlay _overlay;

	private float _nextPollTime;

	private int _lastScanHash;

	private float _showUntilTime;

	private string _lastHintLine;

	private void Awake()
	{
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Expected O, but got Unknown
		_instance = this;
		_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable/disable the mod.");
		_debugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLog", false, "Write debug logs to BepInEx console.");
		_pollInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Debug", "PollInterval", 0.12f, "How often (seconds) to check scan changes.");
		_showSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "ShowSeconds", 2f, "How long to show the hint after scan changes.");
		_fontSize = ((BaseUnityPlugin)this).Config.Bind<int>("UI", "FontSize", 22, "Overlay font size.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Loot Swap Helper loaded.");
		_harmony = new Harmony("HoppinHauler.lootswaphelper");
		_harmony.PatchAll(typeof(HUDPatches));
		((MonoBehaviour)this).StartCoroutine(EnsureOverlayRoutine());
	}

	private void OnDestroy()
	{
		try
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
		catch
		{
		}
		_harmony = null;
		try
		{
			if ((Object)(object)_overlay != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_overlay).gameObject);
			}
		}
		catch
		{
		}
		_overlay = null;
		_instance = null;
	}

	[IteratorStateMachine(typeof(<EnsureOverlayRoutine>d__17))]
	private IEnumerator EnsureOverlayRoutine()
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <EnsureOverlayRoutine>d__17(0)
		{
			<>4__this = this
		};
	}

	private void OnHudUpdatePostfix(HUDManager hud)
	{
		if ((Object)(object)_overlay == (Object)null)
		{
			return;
		}
		if (!_enabled.Value)
		{
			_overlay.SetVisible(visible: false);
			return;
		}
		float unscaledTime = Time.unscaledTime;
		if (unscaledTime >= _showUntilTime)
		{
			_overlay.SetVisible(visible: false);
			_lastHintLine = null;
		}
		if (unscaledTime < _nextPollTime)
		{
			return;
		}
		_nextPollTime = unscaledTime + Mathf.Clamp(_pollInterval.Value, 0.05f, 1f);
		object obj = null;
		try
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(HUDManager), "scanNodes");
			if (fieldInfo != null)
			{
				obj = fieldInfo.GetValue(hud);
			}
		}
		catch
		{
		}
		if (obj == null)
		{
			return;
		}
		ScanBest best = ScanBest.Empty;
		ComputeScan(obj, out var total, out var priced, out var pricedSum, ref best);
		int num = ComputeScanHash(total, priced, pricedSum, best.Name, best.Value);
		bool flag = num != _lastScanHash;
		_lastScanHash = num;
		if (!flag || total <= 0)
		{
			return;
		}
		PlayerControllerB localPlayer = GetLocalPlayer();
		HeldSnapshot held = HeldSnapshot.Empty;
		if ((Object)(object)localPlayer != (Object)null)
		{
			held = ComputeHeld(localPlayer);
		}
		string text = BuildSwapHintLine(best, held);
		if (_debugLog.Value)
		{
			ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
			string[] obj3 = new string[20]
			{
				"[LootSwapHelper] scan changed; total=",
				total.ToString(),
				" priced=",
				priced.ToString(),
				" sum=",
				pricedSum.ToString(),
				" best=",
				best.Name,
				":",
				null,
				null,
				null,
				null,
				null,
				null,
				null,
				null,
				null,
				null,
				null
			};
			int value = best.Value;
			obj3[9] = value.ToString();
			obj3[10] = " held=";
			value = held.Count;
			obj3[11] = value.ToString();
			obj3[12] = " heldSum=";
			value = held.SumValue;
			obj3[13] = value.ToString();
			obj3[14] = " cheapest=";
			obj3[15] = held.CheapestName ?? "(none)";
			obj3[16] = ":";
			value = held.CheapestValue;
			obj3[17] = value.ToString();
			obj3[18] = " hint=";
			obj3[19] = text ?? "(none)";
			logger.LogInfo((object)string.Concat(obj3));
		}
		if (string.IsNullOrEmpty(text))
		{
			_overlay.SetVisible(visible: false);
			_lastHintLine = null;
			return;
		}
		_showUntilTime = Time.unscaledTime + Mathf.Clamp(_showSeconds.Value, 0.2f, 10f);
		if (_lastHintLine != text)
		{
			_lastHintLine = text;
			_overlay.SetFontSize(Mathf.Clamp(_fontSize.Value, 12, 60));
			_overlay.SetText(text);
		}
		_overlay.SetVisible(visible: true);
	}

	private static string BuildSwapHintLine(ScanBest bestScan, HeldSnapshot held)
	{
		if (bestScan.Value <= 0)
		{
			return null;
		}
		if (held.Count < 4)
		{
			return null;
		}
		if (held.CheapestValue <= 0)
		{
			return null;
		}
		int num = bestScan.Value - held.CheapestValue;
		if (num <= 0)
		{
			return null;
		}
		return $"SWAP +{num}: drop {SafeName(held.CheapestName)}:{held.CheapestValue} -> take {SafeName(bestScan.Name)}:{bestScan.Value}";
	}

	private static string SafeName(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return "?";
		}
		return s;
	}

	private static PlayerControllerB GetLocalPlayer()
	{
		try
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.localPlayerController != (Object)null)
			{
				return instance.localPlayerController;
			}
		}
		catch
		{
		}
		return null;
	}

	private static int ComputeScanHash(int total, int priced, int sum, string bestName, int bestValue)
	{
		int num = 17;
		num = num * 31 + total;
		num = num * 31 + priced;
		num = num * 31 + sum;
		num = num * 31 + bestValue;
		if (!string.IsNullOrEmpty(bestName))
		{
			num = num * 31 + bestName.GetHashCode();
		}
		return num;
	}

	private static void ComputeScan(object nodesObj, out int total, out int priced, out int pricedSum, ref ScanBest best)
	{
		total = 0;
		priced = 0;
		pricedSum = 0;
		best = ScanBest.Empty;
		try
		{
			if (!(nodesObj is IEnumerable enumerable))
			{
				return;
			}
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				Type type = item.GetType();
				PropertyInfo property = type.GetProperty("Value");
				if (property == null)
				{
					continue;
				}
				object? value = property.GetValue(item, null);
				ScanNodeProperties val = (ScanNodeProperties)((value is ScanNodeProperties) ? value : null);
				total++;
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				int num = 0;
				string text = null;
				try
				{
					num = val.scrapValue;
				}
				catch
				{
				}
				try
				{
					text = val.headerText;
				}
				catch
				{
				}
				if (num > 0)
				{
					priced++;
					pricedSum += num;
					if (num > best.Value)
					{
						best = new ScanBest(text ?? "(unknown)", num);
					}
				}
			}
		}
		catch
		{
		}
	}

	private static HeldSnapshot ComputeHeld(PlayerControllerB player)
	{
		if ((Object)(object)player == (Object)null)
		{
			return HeldSnapshot.Empty;
		}
		int num = 0;
		int num2 = 0;
		int num3 = int.MaxValue;
		string cheapestName = null;
		try
		{
			object[] array = TryGetItemSlots(player);
			if (array == null || array.Length == 0)
			{
				return HeldSnapshot.Empty;
			}
			foreach (object obj in array)
			{
				if (obj == null)
				{
					continue;
				}
				GrabbableObject val = (GrabbableObject)((obj is GrabbableObject) ? obj : null);
				if ((Object)(object)val == (Object)null)
				{
					GameObject val2 = (GameObject)((obj is GameObject) ? obj : null);
					if ((Object)(object)val2 != (Object)null)
					{
						val = val2.GetComponent<GrabbableObject>();
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				int num4 = 0;
				string text = null;
				try
				{
					num4 = val.scrapValue;
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)val.itemProperties != (Object)null)
					{
						text = val.itemProperties.itemName;
					}
				}
				catch
				{
				}
				if (num4 > 0)
				{
					num++;
					num2 += num4;
					if (num4 < num3)
					{
						num3 = num4;
						cheapestName = text ?? "(unknown)";
					}
				}
			}
		}
		catch
		{
		}
		if (num3 == int.MaxValue)
		{
			num3 = 0;
			cheapestName = null;
		}
		return new HeldSnapshot(num, num2, cheapestName, num3);
	}

	private static object[] TryGetItemSlots(PlayerControllerB player)
	{
		if ((Object)(object)player == (Object)null)
		{
			return null;
		}
		try
		{
			FieldInfo fieldInfo = AccessTools.Field(((object)player).GetType(), "ItemSlots");
			if (fieldInfo != null)
			{
				object value = fieldInfo.GetValue(player);
				if (value is Array arr)
				{
					return ToObjectArray(arr);
				}
			}
			PropertyInfo propertyInfo = AccessTools.Property(((object)player).GetType(), "ItemSlots");
			if (propertyInfo != null)
			{
				object value2 = propertyInfo.GetValue(player, null);
				if (value2 is Array arr2)
				{
					return ToObjectArray(arr2);
				}
			}
		}
		catch
		{
		}
		return null;
	}

	private static object[] ToObjectArray(Array arr)
	{
		if (arr == null)
		{
			return null;
		}
		object[] array = new object[arr.Length];
		for (int i = 0; i < arr.Length; i++)
		{
			array[i] = arr.GetValue(i);
		}
		return array;
	}
}