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;
}
}