Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LootSwapHelper v0.2.0
lootswaphelper.dll
Decompiled 10 months agousing 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; } }