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 LootWindow v1.0.1
BepInEx/plugins/dannanboi_LootWindow/dannanboi_LootWindow.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; 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: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: AssemblyVersion("1.0.1.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 LootWindow { internal static class InventoryTransfer { private static bool Matches(ItemData existing, ItemData incoming) { return existing.m_shared.m_name == incoming.m_shared.m_name && existing.m_quality == incoming.m_quality && existing.m_worldLevel == incoming.m_worldLevel; } internal static int Move(Inventory inventory, ItemData item) { int stack = item.m_stack; int num = 0; long num2 = 0L; foreach (ItemData allItem in inventory.GetAllItems()) { if (Matches(allItem, item)) { num2 += allItem.m_stack; num += Math.Max(0, allItem.m_shared.m_maxStackSize - allItem.m_stack); } } num += Math.Max(0, inventory.GetWidth() * inventory.GetHeight() - inventory.GetAllItems().Count) * item.m_shared.m_maxStackSize; if (num < stack) { return 0; } inventory.AddItem(item); long num3 = 0L; foreach (ItemData allItem2 in inventory.GetAllItems()) { if (Matches(allItem2, item)) { num3 += allItem2.m_stack; } } return (int)Math.Min(stack, Math.Max(0L, num3 - num2)); } } internal sealed class LootUi : IDisposable { private readonly Plugin plugin; private const float Width = 204f; private const float Height = 396f; private const float HeaderHeight = 36f; private GameObject root; private GameObject body; private GameObject shade; private GameObject dialog; private RectTransform canvasRect; private RectTransform panel; private InventoryGui owner; private TMP_Text title; private TMP_Text allText; private TMP_Text discardText; private TMP_Text smallText; private Button all; private Button discard; private InventoryGrid grid; private Canvas nativeCanvas; private CanvasGroup bodyGroup; private bool dirty = true; private bool failed; private bool visible; private bool fallbackPromptShown; private string language; private float scale; private Vector2 screen; private Vector2 configuredPosition; private static readonly FieldInfo CurrentContainer = AccessTools.Field(typeof(InventoryGui), "m_currentContainer"); private static readonly FieldInfo Elements = AccessTools.Field(typeof(InventoryGrid), "m_elements"); private static readonly FieldInfo DragInventory = AccessTools.Field(typeof(InventoryGui), "m_dragInventory"); private static readonly FieldInfo DragObject = AccessTools.Field(typeof(InventoryGui), "m_dragGo"); private static readonly FieldInfo DragItem = AccessTools.Field(typeof(InventoryGui), "m_dragItem"); private static readonly FieldInfo Split = AccessTools.Field(typeof(InventoryGui), "m_splitDialog"); private static readonly MethodInfo SetupDrag = AccessTools.Method(typeof(InventoryGui), "SetupDragItem", (Type[])null, (Type[])null); private static readonly MethodInfo HideSplit = AccessTools.Method(typeof(InventoryGui), "HideSplitDialog", (Type[])null, (Type[])null); private static readonly MethodInfo Outside = AccessTools.Method(typeof(InventoryGui), "OnDropOutside", (Type[])null, (Type[])null); internal bool Busy { get { int result; if (Object.op_Implicit((Object)(object)owner)) { if (DragItem.GetValue(owner) == null) { object? value = Split.GetValue(owner); SplitDialog val = (SplitDialog)((value is SplitDialog) ? value : null); result = ((val != null && val.IsActive) ? 1 : 0); } else { result = 1; } } else { result = 0; } return (byte)result != 0; } } internal bool PreserveContainerDrag(InventoryGui gui) { if (!Object.op_Implicit((Object)(object)gui) || !plugin.Loot.Owns((Inventory)/*isinst with value type is only supported in some contexts*/)) { return false; } object? value = CurrentContainer.GetValue(gui); Container val = (Container)((value is Container) ? value : null); return !Object.op_Implicit((Object)(object)val) || !val.IsOwner(); } internal Inventory SuspendContainerDrag(InventoryGui gui) { if (!Object.op_Implicit((Object)(object)gui)) { return null; } object? value = DragInventory.GetValue(gui); Inventory val = (Inventory)((value is Inventory) ? value : null); if (!plugin.Loot.Owns(val)) { return null; } DragInventory.SetValue(gui, null); return val; } internal void RestoreContainerDrag(InventoryGui gui, Inventory inventory) { if (Object.op_Implicit((Object)(object)gui) && inventory != null && plugin.Loot.Owns(inventory) && DragItem.GetValue(gui) != null && DragInventory.GetValue(gui) == null) { DragInventory.SetValue(gui, inventory); } } internal bool OwnsGrid(InventoryGrid value) { return Object.op_Implicit((Object)(object)value) && (Object)(object)value == (Object)(object)grid; } internal LootUi(Plugin value) { plugin = value; } internal void Invalidate() { dirty = true; } internal void Reset() { CancelDrag(); dirty = true; if (Object.op_Implicit((Object)(object)root)) { root.SetActive(false); } visible = false; } internal void CancelDrag() { InventoryGui val = (Object.op_Implicit((Object)(object)owner) ? owner : InventoryGui.instance); if (!Object.op_Implicit((Object)(object)val)) { return; } LootController loot = plugin.Loot; if (loot != null && loot.Owns((Inventory)/*isinst with value type is only supported in some contexts*/)) { SetupDrag.Invoke(val, new object[3] { null, null, 1 }); } object? value = Split.GetValue(val); SplitDialog val2 = (SplitDialog)((value is SplitDialog) ? value : null); if (val2 != null && val2.IsActive) { LootController loot2 = plugin.Loot; if (loot2 != null && loot2.NeedsDecision) { HideSplit.Invoke(val, null); } } } internal void CloseInventory() { if (Object.op_Implicit((Object)(object)InventoryGui.instance)) { InventoryGui.instance.Hide(); } } internal static bool OtherInput() { return (Object.op_Implicit((Object)(object)Chat.instance) && Chat.instance.HasFocus()) || Console.IsVisible() || TextInput.IsVisible() || Menu.IsVisible() || Minimap.IsOpen() || UnifiedPopup.IsVisible(); } internal void Tick() { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_0384: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Unknown result type (might be due to invalid IL or missing references) //IL_03ed: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Unknown result type (might be due to invalid IL or missing references) if (Application.isBatchMode) { return; } LootController loot = plugin.Loot; if (!Object.op_Implicit((Object)(object)loot.Player) || ((Character)loot.Player).IsDead() || ((Character)loot.Player).IsTeleporting() || ((Character)loot.Player).InCutscene() || !loot.ShowWindow) { if (Object.op_Implicit((Object)(object)loot.Player)) { LootState state = loot.State; if (((state != null && state.Waiting) || loot.TriggerSlot.Filled) && InventoryGui.IsVisible() && !OtherInput() && plugin.Input.Pressed(plugin.DiscardKey.Value)) { plugin.Input.Consume(plugin.DiscardKey.Value); loot.Discard(); } } if (visible) { Reset(); } return; } InventoryGui instance = InventoryGui.instance; if (!Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)instance.m_container) || !Object.op_Implicit((Object)(object)instance.m_takeAllButton) || !Object.op_Implicit((Object)(object)instance.m_playerGrid?.m_elementPrefab) || !Object.op_Implicit((Object)(object)instance.m_containerName)) { return; } if (owner != instance) { Dispose(); owner = instance; failed = false; } if (!InventoryGui.IsVisible()) { instance.Show((Container)null, 1); } if (!OtherInput() && !ZInput.GetKeyDown((KeyCode)27, true) && !ZInput.GetKeyDown((KeyCode)9, true)) { if (!loot.Confirming && plugin.Input.Pressed(plugin.DiscardKey.Value)) { plugin.Input.Consume(plugin.DiscardKey.Value); loot.Discard(); return; } if (!loot.Confirming && !Busy && plugin.Input.Pressed(plugin.LootAllKey.Value)) { plugin.Input.Consume(plugin.LootAllKey.Value); loot.TakeAll(); } } if (!loot.ShowWindow) { Reset(); return; } if (failed) { FallbackPrompt(); return; } try { if (!Object.op_Implicit((Object)(object)root)) { Create(instance); } bool active = InventoryGui.IsVisible() && !OtherInput(); root.SetActive(active); visible = active; Localization instance2 = Localization.instance; string text = ((instance2 != null) ? instance2.GetSelectedLanguage() : null); if (language != text) { language = text; dirty = true; } Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(plugin.PositionX.Value, plugin.PositionY.Value); if (screen != new Vector2((float)Screen.width, (float)Screen.height) || scale != plugin.WindowScale.Value || configuredPosition != val) { screen = new Vector2((float)Screen.width, (float)Screen.height); scale = plugin.WindowScale.Value; configuredPosition = val; Layout(); dirty = true; } bodyGroup.interactable = !loot.Confirming; Button obj = all; LootState state2 = loot.State; ((Selectable)obj).interactable = state2 != null && state2.HasLoot && !loot.Confirming && !Busy; shade.SetActive(loot.Confirming); dialog.SetActive(loot.Confirming); Clamp(); if (!loot.Confirming) { object? value = DragObject.GetValue(instance); GameObject val2 = (GameObject)((value is GameObject) ? value : null); if (val2 != null && Object.op_Implicit((Object)(object)val2)) { val2.transform.SetAsLastSibling(); } } if (Object.op_Implicit((Object)(object)grid) && loot.PendingInventory() != null) { ? val3 = grid; Inventory obj2 = loot.PendingInventory(); object? value2 = DragItem.GetValue(instance); ((InventoryGrid)val3).UpdateInventory(obj2, (Player)null, (ItemData)((value2 is ItemData) ? value2 : null)); foreach (InventoryElement item in (List<InventoryElement>)Elements.GetValue(grid)) { Transform val4 = ((Component)item).transform.Find("binding"); if (Object.op_Implicit((Object)(object)val4) && ((Component)val4).gameObject.activeSelf) { ((Component)val4).gameObject.SetActive(false); } } } if (dirty) { Refresh(); dirty = false; } loot.AutoTick(Time.realtimeSinceStartup, visible, Busy); } catch (Exception ex) { Dispose(); failed = true; owner = instance; plugin.Warn("Loot UI could not use native chest templates. Loot remains saved. " + ex); plugin.Notice("Loot window could not open. Check the log."); FallbackPrompt(); } } private void Create(InventoryGui gui) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_0468: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_049c: Unknown result type (might be due to invalid IL or missing references) //IL_04bf: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Unknown result type (might be due to invalid IL or missing references) //IL_04e0: Unknown result type (might be due to invalid IL or missing references) //IL_04e1: Unknown result type (might be due to invalid IL or missing references) //IL_04e9: Unknown result type (might be due to invalid IL or missing references) //IL_0514: Unknown result type (might be due to invalid IL or missing references) //IL_053e: Unknown result type (might be due to invalid IL or missing references) //IL_0543: Unknown result type (might be due to invalid IL or missing references) //IL_057b: Unknown result type (might be due to invalid IL or missing references) //IL_0583: Unknown result type (might be due to invalid IL or missing references) //IL_0585: Unknown result type (might be due to invalid IL or missing references) //IL_0586: Unknown result type (might be due to invalid IL or missing references) //IL_058e: Unknown result type (might be due to invalid IL or missing references) //IL_05b0: Unknown result type (might be due to invalid IL or missing references) //IL_05bf: Unknown result type (might be due to invalid IL or missing references) //IL_05ff: Unknown result type (might be due to invalid IL or missing references) //IL_060e: Unknown result type (might be due to invalid IL or missing references) //IL_0645: Unknown result type (might be due to invalid IL or missing references) //IL_0654: Unknown result type (might be due to invalid IL or missing references) nativeCanvas = ((Component)gui).GetComponentInParent<Canvas>(); if (!Object.op_Implicit((Object)(object)nativeCanvas)) { throw new InvalidOperationException("Native inventory canvas is unavailable."); } root = new GameObject("DannanboiLootWindow", new Type[1] { typeof(RectTransform) }); canvasRect = root.GetComponent<RectTransform>(); ((Transform)canvasRect).SetParent(((Component)gui).transform, false); canvasRect.anchorMin = Vector2.zero; canvasRect.anchorMax = Vector2.one; Vector2 val = (canvasRect.offsetMin = (canvasRect.offsetMax = Vector2.zero)); panel = Rect("LootChest", (Transform)(object)canvasRect, new Vector2(204f, 396f), Vector2.zero); RectTransform obj = panel; RectTransform obj2 = panel; ((Vector2)(ref val))..ctor(0.5f, 0.5f); obj2.anchorMax = val; obj.anchorMin = val; Image val3 = ((Component)panel).gameObject.AddComponent<Image>(); ((Graphic)val3).color = Color.clear; ((Graphic)val3).raycastTarget = true; Background(gui, panel); RectTransform val4 = Rect("DragHeader", (Transform)(object)panel, new Vector2(204f, 36f), Vector2.zero); Image val5 = ((Component)val4).gameObject.AddComponent<Image>(); ((Graphic)val5).color = Color.clear; ((Graphic)val5).raycastTarget = true; LootDrag lootDrag = ((Component)val4).gameObject.AddComponent<LootDrag>(); lootDrag.Ui = this; title = Text(gui.m_containerName, (Transform)(object)val4, "Loot", 20f, (TextAlignmentOptions)4098); title.textWrappingMode = (TextWrappingModes)0; title.overflowMode = (TextOverflowModes)1; title.rectTransform.offsetMin = new Vector2(10f, 0f); title.rectTransform.offsetMax = new Vector2(-10f, 0f); body = ((Component)Rect("LootBody", (Transform)(object)panel, new Vector2(204f, 360f), new Vector2(0f, -36f))).gameObject; bodyGroup = body.AddComponent<CanvasGroup>(); RectTransform val6 = Rect("LootGrid", body.transform, new Vector2(128f, 256f), new Vector2(38f, -8f)); grid = ((Component)val6).gameObject.AddComponent<InventoryGrid>(); grid.m_gridRoot = val6; grid.m_elementPrefab = gui.m_playerGrid.m_elementPrefab; grid.m_elementSpace = 64f; grid.m_tooltipAnchor = gui.m_playerGrid.m_tooltipAnchor; grid.m_uiGroup = gui.m_playerGrid.m_uiGroup; grid.m_onSelected = Select; grid.m_onRightClick = delegate(InventoryGrid _, ItemData item, Vector2i __) { if (!Busy && item != null) { plugin.Loot.Take(plugin.Loot.Slot(item)); } }; grid.m_onReleased = delegate(InventoryGrid target, ItemData item, Vector2i pos) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)target)) { Outside.Invoke(owner, null); } else if (Busy) { Select(target, item, pos, (Modifier)0); } }; grid.CanDropDragOntoItem = (ItemData _) => false; all = Button(gui.m_takeAllButton, body.transform, "Loot All [F]", new Vector2(180f, 32f), new Vector2(12f, -276f), delegate { if (!Busy) { plugin.Loot.TakeAll(); } }); allText = ((Component)all).GetComponentInChildren<TMP_Text>(true); discard = Button(gui.m_takeAllButton, body.transform, "Discard Loot [O]", new Vector2(180f, 32f), new Vector2(12f, -312f), delegate { plugin.Loot.Discard(); }); TintRed(discard); discardText = ((Component)discard).GetComponentInChildren<TMP_Text>(true); smallText = Text(gui.m_containerName, (Transform)(object)Rect("Status", body.transform, new Vector2(180f, 18f), new Vector2(12f, -346f)), "", 12f, (TextAlignmentOptions)4098); shade = ((Component)Rect("ConfirmShade", (Transform)(object)canvasRect, Vector2.zero, Vector2.zero)).gameObject; RectTransform component = shade.GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; val = (component.offsetMin = (component.offsetMax = Vector2.zero)); Image val8 = shade.AddComponent<Image>(); ((Graphic)val8).color = new Color(0f, 0f, 0f, 0.55f); ((Graphic)val8).raycastTarget = true; dialog = ((Component)Rect("DiscardConfirmation", (Transform)(object)canvasRect, new Vector2(350f, 158f), Vector2.zero)).gameObject; RectTransform component2 = dialog.GetComponent<RectTransform>(); Vector2 val9 = default(Vector2); ((Vector2)(ref val9))..ctor(0.5f, 0.5f); component2.pivot = val9; val = (component2.anchorMin = (component2.anchorMax = val9)); Background(gui, component2); RectTransform parent = Rect("Question", (Transform)(object)component2, new Vector2(318f, 70f), new Vector2(16f, -10f)); Text(gui.m_containerName, (Transform)(object)parent, "Discard loot?", 24f, (TextAlignmentOptions)4098); Button button = Button(gui.m_takeAllButton, (Transform)(object)component2, "Discard", new Vector2(151f, 38f), new Vector2(16f, -100f), delegate { plugin.Loot.Discard(); }); TintRed(button); Button(gui.m_takeAllButton, (Transform)(object)component2, "Cancel", new Vector2(151f, 38f), new Vector2(183f, -100f), delegate { plugin.Loot.CancelDiscard(); }); Layout(); Refresh(); dirty = false; } private static RectTransform Rect(string name, Transform parent, Vector2 size, Vector2 position) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_006b: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); RectTransform component = val.GetComponent<RectTransform>(); ((Transform)component).SetParent(parent, false); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0f, 1f); component.anchorMax = val2; component.anchorMin = val2; component.pivot = new Vector2(0f, 1f); component.sizeDelta = size; component.anchoredPosition = position; return component; } private static void Background(InventoryGui gui, RectTransform parent) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016c: 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_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) RectTransform container = gui.m_container; Rect rect = container.rect; float num = Mathf.Abs(((Rect)(ref rect)).width * ((Rect)(ref rect)).height); if (num <= 0f) { throw new InvalidOperationException("Native chest layout is not ready."); } int num2 = 0; Vector3[] array = (Vector3[])(object)new Vector3[4]; Image[] componentsInChildren = ((Component)gui.m_container).GetComponentsInChildren<Image>(true); foreach (Image val in componentsInChildren) { Rect rect2 = ((Graphic)val).rectTransform.rect; float width = ((Rect)(ref rect2)).width; rect2 = ((Graphic)val).rectTransform.rect; float num3 = Mathf.Abs(width * ((Rect)(ref rect2)).height); if (!(num3 < num * 0.45f) && ((Behaviour)val).enabled && !InsideControl(((Component)val).transform, (Transform)(object)container) && ActiveLayer(((Component)val).transform, (Transform)(object)container) && !GamepadLayer(gui, ((Component)val).transform)) { ((Graphic)val).rectTransform.GetWorldCorners(array); Vector3 val2 = ((Transform)container).InverseTransformPoint(array[0]); Vector3 val3 = ((Transform)container).InverseTransformPoint(array[2]); RectTransform val4 = Rect("ChestFrameLayer" + num2++, (Transform)(object)parent, Vector2.zero, Vector2.zero); val4.anchorMin = Vector2.zero; val4.anchorMax = Vector2.one; val4.offsetMin = new Vector2(val2.x - ((Rect)(ref rect)).xMin, val2.y - ((Rect)(ref rect)).yMin); val4.offsetMax = new Vector2(val3.x - ((Rect)(ref rect)).xMax, val3.y - ((Rect)(ref rect)).yMax); Image val5 = ((Component)val4).gameObject.AddComponent<Image>(); val5.sprite = val.sprite; val5.type = val.type; ((Graphic)val5).color = ((Graphic)val).color; ((Graphic)val5).material = ((Graphic)val).material; val5.fillCenter = val.fillCenter; val5.pixelsPerUnitMultiplier = val.pixelsPerUnitMultiplier; ((Graphic)val5).raycastTarget = false; } } if (num2 == 0) { throw new InvalidOperationException("Native chest background is unavailable."); } } private static bool GamepadLayer(InventoryGui gui, Transform value) { if (gui.m_uiGroups == null) { return false; } UIGroupHandler[] uiGroups = gui.m_uiGroups; foreach (UIGroupHandler val in uiGroups) { if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.m_enableWhenActiveAndGamepad) && value.IsChildOf(val.m_enableWhenActiveAndGamepad.transform)) { return true; } } return false; } private static bool ActiveLayer(Transform value, Transform root) { Transform val = value; while (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)root) { if (!((Component)val).gameObject.activeSelf) { return false; } val = val.parent; } return true; } private static bool InsideControl(Transform value, Transform root) { Transform val = value; while (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)root) { if (Object.op_Implicit((Object)(object)((Component)val).GetComponent<Button>()) || Object.op_Implicit((Object)(object)((Component)val).GetComponent<InventoryGrid>()) || Object.op_Implicit((Object)(object)((Component)val).GetComponent<InventoryElement>())) { return true; } val = val.parent; } return false; } private static TMP_Text Text(TMP_Text template, Transform parent, string text, float size, TextAlignmentOptions alignment) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0024: 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_0036: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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) RectTransform val = Rect("Label", parent, Vector2.zero, Vector2.zero); val.anchorMin = Vector2.zero; val.anchorMax = Vector2.one; Vector2 offsetMin = (val.offsetMax = Vector2.zero); val.offsetMin = offsetMin; TextMeshProUGUI val2 = ((Component)val).gameObject.AddComponent<TextMeshProUGUI>(); ((TMP_Text)val2).font = template.font; ((TMP_Text)val2).fontSharedMaterial = template.fontSharedMaterial; ((Graphic)val2).color = ((Graphic)template).color; ((TMP_Text)val2).fontSize = size; ((TMP_Text)val2).fontStyle = template.fontStyle; ((TMP_Text)val2).alignment = alignment; ((TMP_Text)val2).text = text; ((Graphic)val2).raycastTarget = false; ((TMP_Text)val2).richText = false; return (TMP_Text)(object)val2; } private static Button Button(Button template, Transform parent, string text, Vector2 size, Vector2 position, Action click) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) Button val = Object.Instantiate<Button>(template, parent, false); ((Component)val).gameObject.SetActive(true); CleanScripts(((Component)val).gameObject, null); RectTransform component = ((Component)val).GetComponent<RectTransform>(); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0f, 1f); component.anchorMax = val2; component.anchorMin = val2; component.pivot = new Vector2(0f, 1f); component.sizeDelta = size; component.anchoredPosition = position; ((Transform)component).localScale = Vector3.one; val.onClick = new ButtonClickedEvent(); ((UnityEvent)val.onClick).AddListener((UnityAction)delegate { click(); }); Navigation navigation = default(Navigation); ((Navigation)(ref navigation)).mode = (Mode)0; ((Selectable)val).navigation = navigation; TMP_Text componentInChildren = ((Component)val).GetComponentInChildren<TMP_Text>(true); if (Object.op_Implicit((Object)(object)componentInChildren)) { componentInChildren.text = text; componentInChildren.richText = false; componentInChildren.alignment = (TextAlignmentOptions)4098; componentInChildren.textWrappingMode = (TextWrappingModes)0; componentInChildren.overflowMode = (TextOverflowModes)1; componentInChildren.enableAutoSizing = true; componentInChildren.fontSizeMin = 12f; componentInChildren.fontSizeMax = 20f; } UITooltip[] componentsInChildren = ((Component)val).GetComponentsInChildren<UITooltip>(true); foreach (UITooltip val3 in componentsInChildren) { val3.m_topic = ""; val3.m_text = ""; } return val; } private static void CleanScripts(GameObject go, InventoryElement keep) { MonoBehaviour[] componentsInChildren = go.GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val in componentsInChildren) { if (!(val is Graphic) && !(val is Button) && !(val is UITooltip) && !((Object)(object)val == (Object)(object)keep)) { ((Behaviour)val).enabled = false; Object.Destroy((Object)(object)val); } } } private static void TintRed(Button button) { //IL_0002: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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) ColorBlock colors = ((Selectable)button).colors; ((ColorBlock)(ref colors)).normalColor = new Color(1f, 0.7f, 0.7f); ((ColorBlock)(ref colors)).highlightedColor = new Color(1f, 0.84f, 0.84f); ((ColorBlock)(ref colors)).pressedColor = new Color(0.8f, 0.45f, 0.45f); ((Selectable)button).colors = colors; if (Object.op_Implicit((Object)(object)((Selectable)button).targetGraphic)) { ((Selectable)button).targetGraphic.color = new Color(1f, 0.78f, 0.78f); } } private void Select(InventoryGrid selected, ItemData item, Vector2i pos, Modifier modifier) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Invalid comparison between Unknown and I4 if (plugin.Loot.Confirming) { return; } if (DragItem.GetValue(owner) != null) { LootController loot = plugin.Loot; object? value = DragInventory.GetValue(owner); if (loot.Owns((Inventory)((value is Inventory) ? value : null))) { CancelDrag(); } } else if (item != null) { if ((int)modifier == 2) { plugin.Loot.Take(plugin.Loot.Slot(item)); return; } if ((int)modifier == 3) { ((Humanoid)plugin.Loot.Player).DropItem(plugin.Loot.PendingInventory(), item, item.m_stack); return; } SetupDrag.Invoke(owner, new object[3] { item, plugin.Loot.PendingInventory(), item.m_stack }); } } private void Refresh() { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) LootController loot = plugin.Loot; title.text = (loot.Corrupt ? "Saved loot" : Localize(loot.State?.Name ?? "Loot")); allText.text = Label(plugin.LootAllLabel.Value, plugin.LootAllKey.Value); discardText.text = Label(plugin.DiscardLabel.Value, plugin.DiscardKey.Value); Button obj = all; LootState state = loot.State; ((Selectable)obj).interactable = state != null && state.HasLoot && !loot.Confirming && !Busy; ((Selectable)discard).interactable = !loot.Confirming; smallText.text = (loot.Corrupt ? "Saved loot could not be loaded." : ""); } private unsafe static string Label(string text, KeyboardShortcut binding) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) string text2 = Localize(text); object obj; if ((int)((KeyboardShortcut)(ref binding)).MainKey != 0) { KeyboardShortcut val = binding; obj = " [" + ((object)(*(KeyboardShortcut*)(&val))/*cast due to .constrained prefix*/).ToString() + "]"; } else { obj = ""; } return text2 + (string?)obj; } private static string Localize(string text) { return (Localization.instance != null) ? Localization.instance.Localize(text) : text; } private void Layout() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)panel) && Object.op_Implicit((Object)(object)canvasRect)) { ((Transform)panel).localScale = Vector3.one * plugin.WindowScale.Value; Rect rect = canvasRect.rect; panel.anchoredPosition = new Vector2((plugin.PositionX.Value - 0.5f) * ((Rect)(ref rect)).width, (plugin.PositionY.Value - 0.5f) * ((Rect)(ref rect)).height); Clamp(); } } private void Clamp() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)panel) && Object.op_Implicit((Object)(object)canvasRect)) { panel.pivot = new Vector2(0.5f, 1f); Vector2 val = panel.sizeDelta * plugin.WindowScale.Value; Rect rect = canvasRect.rect; float num = Mathf.Max(0f, (((Rect)(ref rect)).width - val.x) / 2f); Vector2 anchoredPosition = panel.anchoredPosition; float num2 = Mathf.Min(((Rect)(ref rect)).height / 2f, (0f - ((Rect)(ref rect)).height) / 2f + val.y); panel.anchoredPosition = new Vector2(Mathf.Clamp(anchoredPosition.x, 0f - num, num), Mathf.Clamp(anchoredPosition.y, num2, ((Rect)(ref rect)).height / 2f)); } } internal void Drag(Vector2 delta) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)panel) && !plugin.Loot.Confirming) { RectTransform obj = panel; obj.anchoredPosition += delta / nativeCanvas.scaleFactor; Clamp(); } } internal void EndDrag() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)panel) && Object.op_Implicit((Object)(object)canvasRect)) { ConfigEntry<float> positionX = plugin.PositionX; float x = panel.anchoredPosition.x; Rect rect = canvasRect.rect; positionX.Value = 0.5f + x / ((Rect)(ref rect)).width; ConfigEntry<float> positionY = plugin.PositionY; float y = panel.anchoredPosition.y; rect = canvasRect.rect; positionY.Value = 0.5f + y / ((Rect)(ref rect)).height; } } private void FallbackPrompt() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_005c: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown if (plugin.Loot.Confirming && !fallbackPromptShown && UnifiedPopup.IsAvailable()) { fallbackPromptShown = true; UnifiedPopup.Push((PopupBase)new YesNoPopup("Loot Window", "Discard loot?", (PopupButtonCallback)delegate { UnifiedPopup.Pop(); fallbackPromptShown = false; plugin.Loot.Discard(); }, (PopupButtonCallback)delegate { UnifiedPopup.Pop(); fallbackPromptShown = false; plugin.Loot.CancelDiscard(); }, false, true)); } } public void Dispose() { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) CancelDrag(); if (Object.op_Implicit((Object)(object)root)) { Object.Destroy((Object)(object)root); } root = (body = (shade = (dialog = null))); panel = (canvasRect = null); owner = null; grid = null; bodyGroup = null; nativeCanvas = null; dirty = true; visible = false; screen = Vector2.zero; } } public sealed class LootDrag : MonoBehaviour, IBeginDragHandler, IEventSystemHandler, IDragHandler, IEndDragHandler { internal LootUi Ui; public void OnBeginDrag(PointerEventData eventData) { } public void OnDrag(PointerEventData eventData) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) if ((int)eventData.button == 0) { Ui?.Drag(eventData.delta); } } public void OnEndDrag(PointerEventData eventData) { Ui?.EndDrag(); } } [HarmonyPatch(typeof(InventoryGui), "Hide")] internal static class LootCloseInventory { private static bool Prefix() { LootController lootController = Plugin.Instance?.Loot; if (lootController == null || !lootController.NeedsDecision || !Object.op_Implicit((Object)(object)lootController.Player) || ((Character)lootController.Player).IsDead() || ((Character)lootController.Player).IsTeleporting() || ((Character)lootController.Player).InCutscene()) { return true; } lootController.AskDiscard(); return false; } } [HarmonyPatch(typeof(InventoryGui), "UpdateContainer")] internal static class LootPreserveRewardDrag { private static bool Prefix(InventoryGui __instance) { Plugin instance = Plugin.Instance; if (instance == null || instance.Ui?.PreserveContainerDrag(__instance) != true) { return true; } ((Component)__instance.m_container).gameObject.SetActive(false); return false; } } [HarmonyPatch(typeof(InventoryGui), "CloseContainer")] internal static class LootCloseWorldContainer { private static void Prefix(InventoryGui __instance, out Inventory __state) { __state = Plugin.Instance?.Ui?.SuspendContainerDrag(__instance); } private static Exception Finalizer(InventoryGui __instance, Inventory __state, Exception __exception) { Plugin.Instance?.Ui?.RestoreContainerDrag(__instance, __state); return __exception; } } [HarmonyPatch(typeof(InventoryGui), "ShowSplitDialog")] internal static class LootNoRewardSplitting { private static bool Prefix(Inventory __1) { Plugin instance = Plugin.Instance; return instance == null || instance.Loot?.Owns(__1) != true; } } [HarmonyPatch(typeof(InventoryGrid), "DropItem")] internal static class LootGridDrop { private static bool Prefix(InventoryGrid __instance, Inventory __0, ItemData __1, Vector2i __3, ref bool __result) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) LootController lootController = Plugin.Instance?.Loot; if (lootController != null) { Inventory inventory = __instance.GetInventory(); Inventory inventory2 = __instance.GetInventory(); if (!lootController.CanDrop(__0, inventory, __1, (inventory2 != null) ? inventory2.GetItemAt(__3.x, __3.y) : null)) { __result = false; return false; } } return true; } } [HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")] internal static class LootModalSelection { private static bool Prefix() { Plugin instance = Plugin.Instance; return instance == null || instance.Loot?.Confirming != true; } } [HarmonyPatch(typeof(InventoryGui), "OnDropOutside")] internal static class LootModalDrop { private static bool Prefix() { Plugin instance = Plugin.Instance; return instance == null || instance.Loot?.Confirming != true; } } [HarmonyPatch(typeof(InventoryGrid), "UpdateInventory")] internal static class LootHiddenTokens { private static readonly FieldInfo Elements = AccessTools.Field(typeof(InventoryGrid), "m_elements"); private static void Postfix(InventoryGrid __instance, Inventory __0) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) TriggerRegistry triggerRegistry = Plugin.Instance?.Triggers; if (triggerRegistry == null || __0 == null || !triggerRegistry.HasOwnedItems(__0)) { return; } foreach (InventoryElement item in (List<InventoryElement>)Elements.GetValue(__instance)) { Vector2i position = item.Position; if (triggerRegistry.Owns(__0.GetItemAt(position.x, position.y))) { if (Object.op_Implicit((Object)(object)item.m_icon)) { ((Behaviour)item.m_icon).enabled = false; } if (Object.op_Implicit((Object)(object)item.m_amount)) { ((Component)item.m_amount).gameObject.SetActive(false); } if (Object.op_Implicit((Object)(object)item.m_tooltip)) { item.m_tooltip.m_topic = ""; item.m_tooltip.m_text = ""; } } } } } [HarmonyPatch(typeof(InventoryGrid), "UpdateGamepad")] internal static class LootGridGamepad { private static bool Prefix(InventoryGrid __instance) { Plugin instance = Plugin.Instance; return instance == null || instance.Ui?.OwnsGrid(__instance) != true; } } [HarmonyPatch(typeof(PlayerController), "TakeInput")] internal static class LootInventoryInput { private static void Postfix(ref bool __result) { LootController lootController = Plugin.Instance?.Loot; if (lootController != null && lootController.NeedsDecision && InventoryGui.IsVisible()) { lootController.Player.m_autoRun = false; __result = false; } } } internal sealed class LootState { internal string Id = ""; internal string Trigger = ""; internal string Name = "Loot"; internal string EmptyLootText = "No loot."; internal long World; internal long GroundUser; internal uint GroundID; internal bool Waiting; internal bool Hidden; internal readonly List<Reward> Rewards = new List<Reward>(); internal bool Ground => Waiting && GroundID != 0; internal bool HasLoot => !Waiting && Rewards.Exists((Reward r) => r.Amount > 0); internal bool Active => Waiting || HasLoot; internal LootState Copy() { LootState lootState = new LootState { Id = Id, Trigger = Trigger, Name = Name, World = World, Waiting = Waiting, Hidden = Hidden, EmptyLootText = EmptyLootText, GroundUser = GroundUser, GroundID = GroundID }; foreach (Reward reward in Rewards) { lootState.Rewards.Add(reward.Copy()); } return lootState; } internal string Encode() { using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) { binaryWriter.Write(5); binaryWriter.Write(Id); binaryWriter.Write(Trigger); binaryWriter.Write(Name); binaryWriter.Write(World); binaryWriter.Write(Waiting); binaryWriter.Write(EmptyLootText); binaryWriter.Write(GroundUser); binaryWriter.Write(GroundID); binaryWriter.Write(Hidden); binaryWriter.Write(Rewards.Count); foreach (Reward reward in Rewards) { binaryWriter.Write(reward.Item); binaryWriter.Write(reward.Amount); binaryWriter.Write(reward.Quality); } } return Convert.ToBase64String(memoryStream.ToArray()); } internal static LootState Decode(string text) { if (text == null || text.Length > 8192) { throw new FormatException("Invalid saved loot size."); } using MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(text)); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8); int num = binaryReader.ReadInt32(); if (num < 1 || num > 5) { throw new FormatException("Unsupported saved loot format."); } LootState lootState = new LootState { Id = binaryReader.ReadString(), Trigger = binaryReader.ReadString(), Name = binaryReader.ReadString(), World = binaryReader.ReadInt64(), Waiting = binaryReader.ReadBoolean() }; if (num >= 2) { lootState.EmptyLootText = binaryReader.ReadString(); } if (num >= 3) { lootState.GroundUser = binaryReader.ReadInt64(); lootState.GroundID = binaryReader.ReadUInt32(); } if (num >= 4) { lootState.Hidden = binaryReader.ReadBoolean(); } if (lootState.Hidden && (!lootState.Waiting || lootState.GroundID != 0 || lootState.GroundUser != 0L || !lootState.Trigger.StartsWith("dblw_", StringComparison.Ordinal))) { throw new FormatException("Invalid hidden trigger source."); } if ((!lootState.Waiting && (lootState.GroundUser != 0L || lootState.GroundID != 0)) || (lootState.GroundID == 0 && lootState.GroundUser != 0)) { throw new FormatException("Invalid saved trigger source."); } if (lootState.EmptyLootText.Length > 256) { throw new FormatException("Invalid empty loot text."); } if (!Guid.TryParseExact(lootState.Id, "N", out var _) || lootState.Trigger.Length > 128 || lootState.Name.Length > 128) { throw new FormatException("Invalid saved loot identity."); } int num2 = binaryReader.ReadInt32(); if (num2 < 0 || num2 > 8 || (lootState.Waiting && num2 != 0)) { throw new FormatException("Invalid saved slot count."); } for (int i = 0; i < num2; i++) { Reward reward = new Reward { Item = binaryReader.ReadString(), Amount = binaryReader.ReadInt32() }; if (num >= 5) { reward.Quality = binaryReader.ReadInt32(); } if (reward.Item.Length == 0 || reward.Item.Length > 128 || reward.Amount < 0 || reward.Amount > 10000 || reward.Quality < 1 || reward.Quality > 10000) { throw new FormatException("Invalid saved reward."); } lootState.Rewards.Add(reward); } if (memoryStream.Position != memoryStream.Length) { throw new FormatException("Trailing saved loot data."); } return lootState; } } internal sealed class LootEntry { internal string Item; internal int Min; internal int Max; internal int Quality = 1; internal double Chance; internal bool Rare; } internal sealed class LootTable { internal string Trigger; internal string Name; internal string DisplayTitle = ""; internal string TriggerCall = ""; internal string EmptyLootText = "No loot."; internal bool HasDisplayTitle; internal bool AlwaysLoot; internal int MinRares; internal int MaxRares = 1; internal readonly List<LootEntry> Entries = new List<LootEntry>(); } internal sealed class Reward { internal string Item; internal int Amount; internal int Quality = 1; internal Reward Copy() { return new Reward { Item = Item, Amount = Amount, Quality = Quality }; } } internal sealed class Catalog { internal const int MaxBytes = 65536; internal const int Capacity = 8; internal readonly Dictionary<string, LootTable> Tables = new Dictionary<string, LootTable>(StringComparer.Ordinal); private static readonly Regex Prefab = new Regex("^[A-Za-z0-9_.\\-]{1,128}$", RegexOptions.CultureInvariant); private static readonly Regex Spec = new Regex("^([A-Za-z0-9_.\\-]{1,128})(?:\\((\\d+)\\))?\\[(\\d+)(?:-(\\d+))?(?::(\\d+(?:[.,]\\d+)?))?\\]$", RegexOptions.CultureInvariant); internal const string Example = "# Missing trigger prefabs are created from Wisp; reward prefabs must already exist.\n# Amount may be fixed or a range: Coins[20] or Coins[10-20].\n# Chance is a percentage and works with either: Ruby[1:5] or Ruby[1-2:5].\n# Optional (level): BronzeSword(2)[1:0.1] is one level-2 sword at 0.1% chance.\n# No range is required: [1:0.1] and [1-1:0.1] mean the same thing.\n# Without :chance, chance defaults to 100%.\n# Use one reward per indented list line; the old comma-separated string still works.\n# Prefix ! marks a rare entry; quotes are optional: !BronzeSword(2)[1:0.1].\n# minRares/maxRares count selected rare entries, not units or native stacks.\n# Entries roll once in random order at their stated chances. Rare minimums reserve slots first.\n# Defaults: minRares 0, maxRares 1. Set maxRares 0 to disable rare rewards.\n# Use 0 <= minRares <= maxRares; guaranteed rare minimums must fit the eight-slot window.\n# Any number of entries may be listed within 64 KiB. Stop at eight stacks or whole-reward overflow.\n# triggerCall is reserved for future function calls; it has no active behavior yet.\n# For a quick solo test, replace dblw_Test2 with TrophyBoar and pick one up.\n- triggerItem: dblw_Test2\n itemDisplayTitle: \"\"\n triggerCall:\n alwaysLoot: false\n emptyLootText: \"Nothing was found.\"\n lootTableName: \"Boss reward\"\n minRares: 0\n maxRares: 1\n lootTable:\n - Coins[50-100]\n - Ruby[1-2:5]\n - Amber[2]\n - !BronzeSword(2)[1:0.1]\n"; internal static Catalog Parse(string yaml) { if (yaml == null || Encoding.UTF8.GetByteCount(yaml) > 65536) { throw new FormatException("Loot tables exceed 64 KiB."); } Catalog catalog = new Catalog(); Dictionary<string, string> row = null; List<string> listedLoot = null; bool flag = false; Action action = delegate { if (row != null) { if (!row.TryGetValue("triggerItem", out var value) || !Prefab.IsMatch(value)) { throw new FormatException("triggerItem must be an exact item prefab name."); } if (!row.TryGetValue("lootTable", out var value2) || ((listedLoot == null) ? string.IsNullOrWhiteSpace(value2) : (listedLoot.Count == 0))) { throw new FormatException("lootTable must contain Item[amount] entries as a list or comma-separated string."); } if (catalog.Tables.Count >= 128 || catalog.Tables.ContainsKey(value)) { throw new FormatException("Duplicate trigger or more than 128 tables: " + value); } string value3; LootTable lootTable = new LootTable { Trigger = value, Name = ((row.TryGetValue("lootTableName", out value3) && value3.Length > 0) ? value3 : "Loot") }; lootTable.DisplayTitle = (row.TryGetValue("itemDisplayTitle", out var value4) ? value4 : ""); lootTable.HasDisplayTitle = row.ContainsKey("itemDisplayTitle"); if (lootTable.DisplayTitle.Length > 128) { throw new FormatException("itemDisplayTitle exceeds 128 characters."); } lootTable.TriggerCall = (row.TryGetValue("triggerCall", out var value5) ? value5 : ""); if (lootTable.TriggerCall.Length > 128) { throw new FormatException("triggerCall exceeds 128 characters."); } if (row.TryGetValue("alwaysLoot", out var value6)) { if (!bool.TryParse(value6, out var result)) { throw new FormatException("alwaysLoot must be true or false."); } lootTable.AlwaysLoot = result; } lootTable.EmptyLootText = ((row.TryGetValue("emptyLootText", out var value7) && !string.IsNullOrWhiteSpace(value7)) ? value7 : "No loot."); if (lootTable.EmptyLootText.Length > 256) { throw new FormatException("emptyLootText exceeds 256 characters."); } if (lootTable.Name.Length > 128) { throw new FormatException("lootTableName exceeds 128 characters."); } lootTable.MinRares = ReadRareLimit(row, "minRares", 0); lootTable.MaxRares = ReadRareLimit(row, "maxRares", 1); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); int num4 = 0; IEnumerable<string> enumerable; if (listedLoot == null) { enumerable = SplitEntries(value2); } else { IEnumerable<string> enumerable2 = listedLoot; enumerable = enumerable2; } foreach (string item in enumerable) { string text5 = item.Trim().Replace('@', ':'); bool flag3 = text5.StartsWith("!", StringComparison.Ordinal); if (flag3) { text5 = text5.Substring(1); } Match match = Spec.Match(text5); if (!match.Success) { throw new FormatException("Invalid loot entry: " + item + ". Use Item[amount:chance] or Item(level)[min-max:chance], optionally prefixed with !."); } int result2 = 1; if (match.Groups[2].Success && (!int.TryParse(match.Groups[2].Value, out result2) || result2 < 1 || result2 > 10000)) { throw new FormatException("Item levels must be 1–10000 and within the prefab's native maximum."); } if (!int.TryParse(match.Groups[3].Value, out var result3) || result3 < 1 || result3 > 10000) { throw new FormatException("Amounts must be 1–10000."); } int result4; if (!match.Groups[4].Success) { result4 = result3; } else if (!int.TryParse(match.Groups[4].Value, out result4) || result4 < result3 || result4 > 10000) { throw new FormatException("Invalid amount range."); } double num5 = (match.Groups[5].Success ? double.Parse(match.Groups[5].Value.Replace(',', '.'), CultureInfo.InvariantCulture) : 100.0); if (num5 <= 0.0 || num5 > 100.0) { throw new FormatException("Chance must be above 0 and at most 100 percent."); } string value8 = match.Groups[1].Value; if (!hashSet.Add(value8 + ":" + result2.ToString(CultureInfo.InvariantCulture))) { throw new FormatException("Duplicate item/level entry: " + value8 + "(" + result2 + ")."); } lootTable.Entries.Add(new LootEntry { Item = value8, Quality = result2, Min = result3, Max = result4, Chance = num5, Rare = flag3 }); if (flag3) { num4++; } } ValidateRareLimits(lootTable, num4); catalog.Tables.Add(value, lootTable); row = null; } }; int num = 0; string[] array = yaml.Replace("\r", "").TrimStart('\ufeff').Split('\n'); foreach (string text in array) { num++; try { if (text.Contains("\t")) { throw new FormatException("Indent with spaces, not tabs."); } bool flag2 = text.StartsWith(" - ", StringComparison.Ordinal) || text == " -"; string text2 = YamlScalar.Uncomment(text, flag2 ? 6 : (-1)).TrimEnd(); if (string.IsNullOrWhiteSpace(text2)) { continue; } if (flag2) { if (!flag || listedLoot == null) { throw new FormatException("Nested entries are only allowed immediately under an empty lootTable: field."); } listedLoot.Add(YamlScalar.Read((text2.Length > 6) ? text2.Substring(6) : "")); continue; } flag = false; if ((text2 == "---" && row == null && catalog.Tables.Count == 0) || (text2 == "[]" && row == null && catalog.Tables.Count == 0)) { continue; } if (text2.StartsWith("- ", StringComparison.Ordinal)) { action(); row = new Dictionary<string, string>(StringComparer.Ordinal); listedLoot = null; text2 = text2.Substring(2); } else { if (row == null || !text2.StartsWith(" ", StringComparison.Ordinal) || text2.StartsWith(" ", StringComparison.Ordinal)) { throw new FormatException("Use two spaces before table fields and four spaces before lootTable list entries."); } text2 = text2.Substring(2); } int num3 = text2.IndexOf(':'); if (num3 < 1 || (num3 + 1 < text2.Length && !char.IsWhiteSpace(text2[num3 + 1]))) { throw new FormatException("Expected field: value."); } string text3 = text2.Substring(0, num3); switch (text3) { case "name": text3 = "lootTableName"; break; case "loot": text3 = "lootTable"; break; case "displayTitle": text3 = "itemDisplayTitle"; break; } if (text3 != "triggerItem" && text3 != "lootTableName" && text3 != "lootTable" && text3 != "emptyLootText" && text3 != "itemDisplayTitle" && text3 != "triggerCall" && text3 != "alwaysLoot" && text3 != "minRares" && text3 != "maxRares") { throw new FormatException("Unknown field: " + text3); } if (row.ContainsKey(text3)) { throw new FormatException("Duplicate field: " + text3); } string text4 = YamlScalar.Read(text2.Substring(num3 + 1), allowEmpty: true); row.Add(text3, text4); if (text3 == "lootTable" && text4.Length == 0) { listedLoot = new List<string>(); flag = true; } } catch (FormatException ex) { throw new FormatException("loottables.yaml:" + num + ": " + ex.Message); } } action(); foreach (LootTable value9 in catalog.Tables.Values) { foreach (LootEntry entry in value9.Entries) { if (catalog.Tables.ContainsKey(entry.Item)) { throw new FormatException("Loot cannot include a mapped trigger item: " + entry.Item); } } } return catalog; } private static int ReadRareLimit(Dictionary<string, string> row, string field, int fallback) { if (!row.TryGetValue(field, out var value) || value.Length == 0) { return fallback; } if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 0) { throw new FormatException(field + " must be a nonnegative whole number."); } return result; } private static void ValidateRareLimits(LootTable table, int rareCount) { if (table.MinRares < 0 || table.MaxRares < table.MinRares) { throw new FormatException("Use 0 <= minRares <= maxRares."); } if (table.MinRares > 8) { throw new FormatException("The guaranteed minRares cannot fit the eight-slot window."); } if (table.MinRares > rareCount) { throw new FormatException("minRares exceeds the number of ! rare entries."); } if (table.AlwaysLoot && table.MaxRares == 0 && rareCount == table.Entries.Count) { throw new FormatException("alwaysLoot cannot guarantee a reward when maxRares disables every entry."); } } private static IEnumerable<string> SplitEntries(string text) { int start = 0; int bracket = 0; for (int i = 0; i < text.Length; i++) { if (text[i] == '[') { bracket++; } else if (text[i] == ']') { bracket--; } else if (text[i] == ',' && bracket == 0) { yield return text.Substring(start, i - start); start = i + 1; } } yield return text.Substring(start); } internal static void Validate(LootTable table, Func<string, int> maxStack, Func<string, int> maxQuality = null) { int num = 0; foreach (LootEntry entry in table.Entries) { if (entry.Rare) { num++; } } ValidateRareLimits(table, num); int[] array = ((table.MinRares > 0) ? new int[table.MinRares + 1] : null); foreach (LootEntry entry2 in table.Entries) { int num2 = maxStack(entry2.Item); if (num2 < 1) { throw new FormatException("Reward prefab is unavailable: " + entry2.Item); } if (entry2.Quality < 1 || entry2.Quality > 10000 || (maxQuality != null && entry2.Quality > maxQuality(entry2.Item))) { throw new FormatException("Reward item level is unavailable: " + entry2.Item + "(" + entry2.Quality + ")."); } int num3 = (entry2.Max + num2 - 1) / num2; if (array != null && entry2.Rare) { for (int num4 = table.MinRares; num4 > 0; num4--) { array[num4] = Math.Max(array[num4], array[num4 - 1] + num3); } } if (table.AlwaysLoot && table.MinRares == 0 && (!entry2.Rare || table.MaxRares > 0) && num3 > 8) { throw new FormatException("alwaysLoot requires each eligible reward to fit the eight-slot window."); } } if (array != null && array[table.MinRares] > 8) { throw new FormatException("The guaranteed minRares can exceed eight native stacks. Reduce rare quantities or minRares."); } } internal static List<Reward> Roll(LootTable table, Func<string, int> maxStack, Func<double> random, Func<string, int> maxQuality = null) { Validate(table, maxStack, maxQuality); List<Reward> list = new List<Reward>(8); int[] rewardOrder = new int[8]; byte[] array = null; int i = 0; if (table.MinRares > 0) { array = new byte[table.Entries.Count]; int[] array2 = new int[table.Entries.Count]; int num = 0; for (int j = 0; j < table.Entries.Count; j++) { if (table.Entries[j].Rare) { array2[num++] = j; } } for (int k = 0; k < num; k++) { if (i >= table.MinRares) { break; } int num2 = PickNext(array2, k, num, random); LootEntry lootEntry = table.Entries[num2]; array[num2] = 1; if (!(lootEntry.Chance < 100.0) || !(random() * 100.0 >= lootEntry.Chance)) { if (!AddReward(list, rewardOrder, lootEntry, num2, maxStack, random)) { throw new FormatException("Guaranteed rare reward exceeds available capacity."); } array[num2] = 2; i++; } } for (; i < table.MinRares; i++) { double num3 = 0.0; int num4 = -1; for (int l = 0; l < num; l++) { int num5 = array2[l]; if (array[num5] == 1) { num3 += table.Entries[num5].Chance; num4 = num5; } } double num6 = random() * num3; int num7 = num4; for (int m = 0; m < num; m++) { int num8 = array2[m]; if (array[num8] == 1) { double chance = table.Entries[num8].Chance; if (num6 < chance) { num7 = num8; break; } num6 -= chance; } } if (!AddReward(list, rewardOrder, table.Entries[num7], num7, maxStack, random)) { throw new FormatException("Guaranteed rare reward exceeds available capacity."); } array[num7] = 2; } if (list.Count == 8) { return list; } } int[] array3 = new int[table.Entries.Count]; int num9 = 0; for (int n = 0; n < table.Entries.Count; n++) { LootEntry lootEntry2 = table.Entries[n]; if (!lootEntry2.Rare || (i < table.MaxRares && (array == null || array[n] == 0))) { array3[num9++] = n; } } bool flag = table.AlwaysLoot && list.Count == 0; double[] array4 = null; if (flag) { for (int num10 = 0; num10 < num9; num10++) { PickNext(array3, num10, num9, random); } array4 = new double[num9 + 1]; for (int num11 = num9 - 1; num11 >= 0; num11--) { double chance2 = table.Entries[array3[num11]].Chance; array4[num11] = chance2 + (1.0 - chance2 / 100.0) * array4[num11 + 1]; } } for (int num12 = 0; num12 < num9; num12++) { if (list.Count >= 8) { break; } int index = (flag ? array3[num12] : PickNext(array3, num12, num9, random)); LootEntry lootEntry3 = table.Entries[index]; double num13 = ((flag && list.Count == 0) ? array4[num12] : 100.0); if (lootEntry3.Chance < num13 && random() * num13 >= lootEntry3.Chance) { continue; } if (!AddReward(list, rewardOrder, lootEntry3, index, maxStack, random)) { break; } if (!lootEntry3.Rare || ++i != table.MaxRares) { continue; } int num14 = num12 + 1; for (int num15 = num14; num15 < num9; num15++) { if (!table.Entries[array3[num15]].Rare) { array3[num14++] = array3[num15]; } } num9 = num14; } return list; } private static int PickNext(int[] order, int position, int count, Func<double> random) { if (position + 1 < count) { int num = position + (int)(random() * (double)(count - position)); int num2 = order[position]; order[position] = order[num]; order[num] = num2; } return order[position]; } private static bool AddReward(List<Reward> result, int[] rewardOrder, LootEntry entry, int index, Func<string, int> maxStack, Func<double> random) { int num = entry.Min + (int)(random() * (double)(entry.Max - entry.Min + 1)); int num2 = maxStack(entry.Item); int num3 = (num + num2 - 1) / num2; if (num3 > 8 - result.Count) { return false; } int i; for (i = 0; i < result.Count && rewardOrder[i] < index; i++) { } while (num > 0) { for (int num4 = result.Count; num4 > i; num4--) { rewardOrder[num4] = rewardOrder[num4 - 1]; } rewardOrder[i] = index; int num5 = Math.Min(num, num2); result.Insert(i++, new Reward { Item = entry.Item, Amount = num5, Quality = entry.Quality }); num -= num5; } return true; } } [BepInPlugin("dannanboi.lootwindow", "Loot Window", "1.0.1")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "dannanboi.lootwindow"; public const string ModName = "Loot Window"; public const string Version = "1.0.1"; public const string BuildVersion = "1.0.1"; internal static Plugin Instance; internal ConfigEntry<bool> EnableMod; internal ConfigEntry<bool> AutoLoot; internal ConfigEntry<bool> AutoLootAutoclose; internal ConfigEntry<bool> Autoclose; internal ConfigEntry<float> AutoLootInterval; internal ConfigEntry<float> AutoLootSleep; internal ConfigEntry<float> InputBlockInterval; internal ConfigEntry<float> TriggerPickupRadius; internal LootInput Input; internal TriggerRegistry Triggers; internal LootTokenPickup Tokens; internal ConfigEntry<KeyboardShortcut> LootAllKey; internal ConfigEntry<KeyboardShortcut> DiscardKey; internal ConfigEntry<float> WindowScale; internal ConfigEntry<float> PositionX; internal ConfigEntry<float> PositionY; internal ConfigEntry<string> LootAllLabel; internal ConfigEntry<string> DiscardLabel; internal Catalog LocalCatalog = new Catalog(); internal string LocalYaml = "[]\n"; internal LootController Loot; internal LootNetwork Network; internal LootUi Ui; private Harmony harmony; private string yamlPath; private DateTime yamlWrite; private DateTime cfgWrite; private long yamlLength = -1L; private float nextFiles; private string lastError; private bool reloading; private void Awake() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Expected O, but got Unknown //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Expected O, but got Unknown //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Expected O, but got Unknown //IL_03a1: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Expected O, but got Unknown Instance = this; EnableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Enable Mod", true, "Consume configured trigger items and open their loot tables. Server-controlled in multiplayer. Existing pending loot can still be collected or discarded when disabled."); TriggerPickupRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Settings", "Trigger pickup radius", 3f, new ConfigDescription("Horizontal pickup distance in metres for dblw_ tokens. Allows up to 2 metres of height difference for spawned/floating items. Server-controlled in multiplayer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 6f), Array.Empty<object>())); LootAllKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keybindings", "Loot All", new KeyboardShortcut((KeyCode)102, Array.Empty<KeyCode>()), "Collect all items that fit in inventory. Local binding; the button shows this binding live."); DiscardKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keybindings", "Discard Loot", new KeyboardShortcut((KeyCode)111, Array.Empty<KeyCode>()), "Ask for confirmation before destroying remaining loot. Local binding; the button shows this binding live."); AutoLoot = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Auto Loot", false, "Automatically collect one reward stack at a time while the window is visible. Local UI preference."); AutoLootInterval = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Auto Loot interval", 1f, new ConfigDescription("Seconds before the first automatic pickup and between subsequent stacks. Local UI preference.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>())); AutoLootSleep = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Sleep before auto-loot", 0.5f, new ConfigDescription("Additional seconds to wait once per loot window before the first pickup interval. Local UI preference.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>())); RemoveLegacyAutoLoot(); InputBlockInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Keybindings", "Input-block interval", 0.25f, new ConfigDescription("Seconds to reserve loot keys after an action or closing the window. Held keys stay reserved until released; the interval then applies. Local setting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); AutoLootAutoclose = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Auto Loot Autoclose", true, "Close inventory when loot is emptied or discarded while Auto Loot is enabled. Local UI preference."); Autoclose = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Autoclose", true, "Close inventory when loot is emptied or discarded while Auto Loot is disabled. Local UI preference."); WindowScale = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Window scale", 1f, new ConfigDescription("Local scale relative to the native inventory canvas.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.7f, 2f), Array.Empty<object>())); PositionX = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Window X", 0.35f, new ConfigDescription("Local horizontal position as a fraction of the screen. Dragging updates this value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); PositionY = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Window Y", 0.62f, new ConfigDescription("Local position of the window's top edge as a fraction of the screen. Dragging updates this value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); LootAllLabel = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "Loot All text", "Loot All", "Local button text. The current key binding is appended automatically."); DiscardLabel = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "Discard Loot text", "Discard Loot", "Local button text. The current key binding is appended automatically."); yamlPath = Path.Combine(Paths.ConfigPath, "LootWindow", "loottables.yaml"); Directory.CreateDirectory(Path.GetDirectoryName(yamlPath)); if (!File.Exists(yamlPath)) { File.WriteAllText(yamlPath, "# Missing trigger prefabs are created from Wisp; reward prefabs must already exist.\n# Amount may be fixed or a range: Coins[20] or Coins[10-20].\n# Chance is a percentage and works with either: Ruby[1:5] or Ruby[1-2:5].\n# Optional (level): BronzeSword(2)[1:0.1] is one level-2 sword at 0.1% chance.\n# No range is required: [1:0.1] and [1-1:0.1] mean the same thing.\n# Without :chance, chance defaults to 100%.\n# Use one reward per indented list line; the old comma-separated string still works.\n# Prefix ! marks a rare entry; quotes are optional: !BronzeSword(2)[1:0.1].\n# minRares/maxRares count selected rare entries, not units or native stacks.\n# Entries roll once in random order at their stated chances. Rare minimums reserve slots first.\n# Defaults: minRares 0, maxRares 1. Set maxRares 0 to disable rare rewards.\n# Use 0 <= minRares <= maxRares; guaranteed rare minimums must fit the eight-slot window.\n# Any number of entries may be listed within 64 KiB. Stop at eight stacks or whole-reward overflow.\n# triggerCall is reserved for future function calls; it has no active behavior yet.\n# For a quick solo test, replace dblw_Test2 with TrophyBoar and pick one up.\n- triggerItem: dblw_Test2\n itemDisplayTitle: \"\"\n triggerCall:\n alwaysLoot: false\n emptyLootText: \"Nothing was found.\"\n lootTableName: \"Boss reward\"\n minRares: 0\n maxRares: 1\n lootTable:\n - Coins[50-100]\n - Ruby[1-2:5]\n - Amber[2]\n - !BronzeSword(2)[1:0.1]\n"); } ReadLocal(); Triggers = new TriggerRegistry(this); Loot = new LootController(this); Network = new LootNetwork(this); Tokens = new LootTokenPickup(this); Input = new LootInput(this); Ui = new LootUi(this); ((BaseUnityPlugin)this).Config.SettingChanged += delegate(object _, SettingChangedEventArgs args) { Ui?.Invalidate(); if (!reloading && args.ChangedSetting.Definition.Section == "Settings") { Network?.Publish(); } }; harmony = new Harmony("dannanboi.lootwindow"); try { harmony.PatchAll(typeof(Plugin).Assembly); } catch (Exception ex) { harmony.UnpatchSelf(); ((Behaviour)this).enabled = false; ((BaseUnityPlugin)this).Logger.LogError((object)("Unsupported game API; patches removed: " + ex)); return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Loot Window 1.0.1 loaded."); } private void RemoveLegacyAutoLoot() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown Dictionary<ConfigDefinition, string> dictionary = AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(((BaseUnityPlugin)this).Config) as Dictionary<ConfigDefinition, string>; bool flag = false; string[] array = new string[3] { "Auto Loot", "Auto Loot interval", "Sleep before auto-loot" }; foreach (string text in array) { ConfigDefinition val = new ConfigDefinition("Settings", text); if (dictionary != null) { flag |= dictionary.Remove(val); } flag |= ((BaseUnityPlugin)this).Config.Remove(val); } if (flag) { ((BaseUnityPlugin)this).Config.Save(); } } private void Update() { float realtimeSinceStartup = Time.realtimeSinceStartup; Network.Tick(realtimeSinceStartup); Triggers.Tick(realtimeSinceStartup); if (realtimeSinceStartup >= nextFiles) { nextFiles = realtimeSinceStartup + 1f; try { FileInfo fileInfo = new FileInfo(((BaseUnityPlugin)this).Config.ConfigFilePath); if (fileInfo.Exists && fileInfo.LastWriteTimeUtc != cfgWrite) { cfgWrite = fileInfo.LastWriteTimeUtc; reloading = true; try { ((BaseUnityPlugin)this).Config.Reload(); } finally { reloading = false; } if (Network.Authority) { Network.Publish(); } Ui.Invalidate(); } } catch (Exception ex) { Warn("Config reload failed: " + ex.Message); } if (Network.Authority) { ReadLocal(); } } if (!Application.isBatchMode) { Input.Tick(); Loot.Tick(realtimeSinceStartup); Tokens.Tick(realtimeSinceStartup); Ui.Tick(); } } private void ReadLocal() { try { FileInfo fileInfo = new FileInfo(yamlPath); if (!fileInfo.Exists) { throw new IOException("Loot table file is missing."); } if (!(fileInfo.LastWriteTimeUtc == yamlWrite) || fileInfo.Length != yamlLength) { yamlWrite = fileInfo.LastWriteTimeUtc; yamlLength = fileInfo.Length; if (fileInfo.Length > 65536) { throw new FormatException("Loot tables exceed 64 KiB."); } string text = File.ReadAllText(yamlPath); Catalog localCatalog = Catalog.Parse(text); LocalCatalog = localCatalog; LocalYaml = text; lastError = null; Triggers?.Invalidate(); Network?.Publish(); Loot?.InventoryChanged(); } } catch (Exception ex) { if (ex is IOException) { yamlLength = -1L; } if (lastError != ex.Message) { lastError = ex.Message; Warn("Last valid loot tables retained. " + ex.Message); } } } internal static ItemDrop FindItem(string name) { if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || string.IsNullOrEmpty(name)) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name); return Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null; } internal static int MaxStack(string name) { ItemDrop val = FindItem(name); return (Object.op_Implicit((Object)(object)val) && val.m_itemData?.m_shared != null) ? val.m_itemData.m_shared.m_maxStackSize : 0; } internal static int MaxQuality(string name) { ItemDrop val = FindItem(name); return (Object.op_Implicit((Object)(object)val) && val.m_itemData?.m_shared != null) ? val.m_itemData.m_shared.m_maxQuality : 0; } internal void Warn(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); } internal void Notice(string message) { if (Object.op_Implicit((Object)(object)MessageHud.instance)) { MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false, true); } } private void OnDestroy() { Ui?.Dispose(); Loot?.Dispose(); Tokens?.Dispose(); Input?.Clear(); Network?.Dispose(); Triggers?.Dispose(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } } internal static class YamlScalar { public static string Uncomment(string text, int valueStart = -1) { if (text.TrimStart().StartsWith("#", StringComparison.Ordinal)) { return ""; } int i; for (i = ((valueStart >= 0) ? valueStart : (text.IndexOf(':') + 1)); i < text.Length && char.IsWhiteSpace(text[i]); i++) { } char c = ((i < text.Length && (text[i] == '\'' || text[i] == '"')) ? text[i] : '\0'); bool flag = c == '\0'; for (int j = ((!flag) ? (i + 1) : 0); j < text.Length; j++) { char c2 = text[j]; if (!flag) { if (c == '"' && c2 == '\\') { j++; } else if (c == '\'' && c2 == '\'' && j + 1 < text.Length && text[j + 1] == '\'') { j++; } else if (c2 == c) { flag = true; } } else if (c2 == '#' && (j == 0 || char.IsWhiteSpace(text[j - 1]))) { return text.Substring(0, j); } } if (!flag) { throw new FormatException("Unclosed YAML quote."); } return text; } public static string Read(string text, bool allowEmpty = false) { text = text.Trim(); if (text.Length == 0) { if (allowEmpty) { return ""; } throw new FormatException("Missing value."); } char c = text[0]; if (c != '\'' && c != '"') { if (allowEmpty && (text == "~" || string.Equals(text, "null", StringComparison.OrdinalIgnoreCase))) { return ""; } if (text == "|" || text == ">") { throw new FormatException("Use a quoted description with \\n for line breaks."); } return text; } if (text.Length < 2 || text[text.Length - 1] != c) { throw new FormatException("Invalid quoted value."); } StringBuilder stringBuilder = new StringBuilder(); for (int i = 1; i < text.Length - 1; i++) { char c2 = text[i]; if (c2 == c) { if (c == '\'' && i + 1 < text.Length - 1 && text[i + 1] == '\'') { stringBuilder.Append('\''); i++; continue; } throw new FormatException("Escape quotes inside a quoted value."); } if (c == '"' && c2 == '\\') { if (++i >= text.Length - 1) { throw new FormatException("Incomplete escape."); } c2 = text[i]; switch (c2) { case 'n': c2 = '\n'; break; case 't': c2 = '\t'; break; case 'r': c2 = '\r'; break; default: if (c2 != '"') { throw new FormatException("Unsupported escape; use \\n, \\t, \\r, \\\\ or \\\"."); } break; case '\\': break; } } stringBuilder.Append(c2); } if (stringBuilder.Length == 0 && !allowEmpty) { throw new FormatException("Missing value."); } return stringBuilder.ToString(); } } internal sealed class RollLedger { private readonly string folder; private readonly Dictionary<string, LootState> cache = new Dictionary<string, LootState>(); private readonly Queue<string> cachedPaths = new Queue<string>(); internal RollLedger(string path) { folder = path; Directory.CreateDirectory(folder); } private string PathFor(long player, string id) { if (player == 0L || !Guid.TryParseExact(id, "N", out var _)) { throw new FormatException("Invalid roll identity."); } return Path.Combine(folder, player.ToString(CultureInfo.InvariantCulture) + "_" + id + ".receipt"); } internal LootState GetOrCreate(long player, string id, string trigger, Func<LootState> make) { string text = PathFor(player, id); if (!cache.TryGetValue(text, out var value)) { if (File.Exists(text)) { value = LootState.Decode(File.ReadAllText(text)); } else { value = make(); string s = value.Encode(); string text2 = text + ".tmp"; using (FileStream fileStream = new FileStream(text2, FileMode.Create, FileAccess.Write, FileShare.None)) { byte[] bytes = Encoding.UTF8.GetBytes(s); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } File.Move(text2, text); } if (cache.Count >= 256) { cache.Remove(cachedPaths.Dequeue()); } cache.Add(text, value); cachedPaths.Enqueue(text); } if (value.Trigger != trigger) { throw new FormatException("A roll id cannot be used with a different trigger."); } return value.Copy(); } internal static List<Reward> Roll(LootTable table, Func<string, int> maxStack, Func<string, int> maxQuality) { byte[] bytes = new byte[4]; RandomNumberGenerator rng = RandomNumberGenerator.Create(); try { return Catalog.Roll(table, maxStack, delegate { rng.GetBytes(bytes); return (double)BitConverter.ToUInt32(bytes, 0) / 4294967296.0; }, maxQuality); } finally { if (rng != null) { ((IDisposable)rng).Dispose(); } } } } internal sealed class LootNetwork : IDisposable { internal sealed class Connection { internal ZNetPeer Peer; internal bool Compatible; internal bool HelloReceived; internal bool Rejected; internal float Started; internal float CloseAt; internal float NextRoll; } private const string Hello = "dannanboi.lootwindow.Hello"; private const string Settings = "dannanboi.lootwindow.Settings"; private const string Roll = "dannanboi.lootwindow.Roll"; private const string Reply = "dannanboi.lootwindow.Reply"; private readonly Plugin plugin; private ZNet network; private ZRpc server; private readonly Dictionary<ZRpc, Connection> connections = new Dictionary<ZRpc, Connection>(); private readonly List<ZRpc> stale = new List<ZRpc>(); private RollLedger ledger; private long ledgerWorld; private float nextCleanup; private bool serverEnabled; private bool publishedEnabled; private bool hasServerCatalog; private float serverPickupRadius = 3f; private float publishedPickupRadius; private string publishedYaml; private string serverYaml; private Catalog remoteCatalog = new Catalog(); internal bool Authority => !Object.op_Implicit((Object)(object)network) || network.IsServer(); internal bool Ready { get; private set; } = true; internal bool Enabled => Authority ? plugin.EnableMod.Value : serverEnabled; internal Catalog Catalog => Authority ? plugin.LocalCatalog : remoteCatalog; internal float PickupRadius => Authority ? LootTokenPickup.ClampRadius(plugin.TriggerPickupRadius.Value) : serverPickupRadius; internal long World => Object.op_Implicit((Object)(object)network) ? network.GetWorldUID() : 0; internal LootNetwork(Plugin value) { plugin = value; } internal void Begin(ZNet value) { if (network == value) { return; } foreach (ZRpc key in connections.Keys) { Unregister(key); } connections.Clear(); server = null; ledger = null; ledgerWorld = 0L; network = value; Ready = Authority; serverEnabled = (hasServerCatalog = false); serverYaml = null; serverPickupRadius = 3f; publishedEnabled = plugin.EnableMod.Value; publishedYaml = plugin.LocalYaml; publishedPickupRadius = PickupRadius; remoteCatalog = new Catalog(); plugin.Loot?.InventoryChanged(); plugin.Triggers?.Invalidate(); } internal void Register(ZNet value, ZNetPeer peer) { if (Object.op_Implicit((Object)(object)value) && peer != null && peer.m_rpc != null) { Begin(value); if ((Authority || peer.m_server) && !connections.ContainsKey(peer.m_rpc)) { connections.Add(peer.m_rpc, new Connection { Peer = peer, Started = Time.realtimeSinceStartup }); peer.m_rpc.Register<string>("dannanboi.lootwindow.Hello", (Action<ZRpc, string>)ReceiveHello); peer.m_rpc.Register<ZPackage>("dannanboi.lootwindow.Settings", (Action<ZRpc, ZPackage>)ReceiveSettings); peer.m_rpc.Register<ZPackage>("dannanboi.lootwindow.Roll", (Action<ZRpc, ZPackage>)ReceiveRoll); peer.m_rpc.Register<ZPackage>("dannanboi.lootwindow.Reply", (Action<ZRpc, ZPackage>)ReceiveReply); peer.m_rpc.Invoke("dannanboi.lootwindow.Hello", new object[1] { "1.0.1" }); } } } private void ReceiveHello(ZRpc rpc, string version) { if (connections.TryGetValue(rpc, out var value) && !value.HelloReceived) { value.HelloReceived = true; value.Compatible = version == "1.0.1"; if (!value.Compatible) { Reject(rpc, value, "Loot Window versions differ. Install 1.0.1 on clients and server."); } else if (Authority) { SendSettings(rpc); } else { server = rpc; } } } private void Reject(ZRpc rpc, Connection c, string message) { c.Rejected = true; c.CloseAt = Time.realtimeSinceStartup + 2f; plugin.Warn(message); if (!Authority) { Ready = false; plugin.Notice(message); } if (rpc.IsConnected()) { rpc.Invoke("Error", new object[1] { 3 }); } } private void SendSettings(ZRpc rpc) { rpc.Invoke("dannanboi.lootwindow.Settings", new object[1] { SettingsPacket(includeTables: true) }); } private ZPackage SettingsPacket(bool includeTables) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(6); val.Write(plugin.EnableMod.Value); val.Write(PickupRadius); val.Write(includeTables); if (includeTables) { val.Write(plugin.LocalYaml); } return val; } internal void Publish() { if (!Authority) { return; } bool flag = publishedYaml != plugin.LocalYaml; bool flag2 = publishedEnabled != plugin.EnableMod.Value; if (!flag2 && !flag && publishedPickupRadius == PickupRadius) { return; } publishedEnabled = plugin.EnableMod.Value; publishedYaml = plugin.LocalYaml; publishedPickupRadius = PickupRadius; ZPackage val = null; foreach (KeyValuePair<ZRpc, Connection> connection in connections) { if (connection.Value.Compatible && !connection.Value.Rejected && connection.Key.IsConnected()) { if (val == null) { val = SettingsPacket(flag); } connection.Key.Invoke("dannanboi.lootwindow.Settings", new object[1] { val }); } } if (flag) { plugin.Triggers?.Invalidate(); } if (flag || flag2) { plugin.Loot?.InventoryChanged(); } } private bool FromServer(ZRpc rpc) { Connection value; return !Authority && connections.TryGetValue(rpc, out value) && value.Peer.m_server && value.Compatible && !value.Rejected && rpc.IsConnected(); } private void ReceiveSettings(ZRpc rpc, ZPackage packet) { if (!FromServer(rpc)) { return; } try { if (packet == null || packet.Size() > 65604 || packet.ReadInt() != 6) { throw new FormatException("Invalid server settings packet."); } bool flag = packet.ReadBool(); float num = packet.ReadSingle(); bool flag2 = packet.ReadBool(); if (float.IsNaN(num) || float.IsInfinity(num) || num < 1f || num > 6f) { throw new FormatException("Invalid server trigger pickup radius."); } if (!flag2 && !hasServerCatalog) { throw new FormatException("Initial server settings must include loot tables."); } bool flag3 = false; if (flag2) { string text = packet.ReadString(); flag3 = !hasServerCatalog || text != serverYaml; if (flag3) { Catalog catalog = Catalog.Parse(text); remoteCatalog = catalog; serverYaml = text; hasServerCatalog = true; } } bool flag4 = serverEnabled != flag; serverEnabled = flag; serverPickupRadius = num; server = rpc; Ready = true; if (flag3) { plugin.Triggers?.Invalidate(); plugin.Triggers?.Apply(); } if (flag3 || flag4) { plugin.Loot.InventoryChanged(); } } catch (Exception ex) { Ready = false; plugin.Warn("Server loot settings rejected; triggers kept in inventory. " + ex.Message); } } internal void Request(LootState state, long player) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown if (!Ready || !Enabled) { plugin.Loot.RollFailed(state.Id, "Loot Window is disabled or server settings are unavailable."); return; } if (Authority) { try { plugin.Loot.Receive(Generate(player, state.Id, state.Trigger)); return; } catch (Exception ex) { plugin.Loot.RollFailed(state.Id, ex.Message); return; } } if (server != null && server.IsConnected()) { ZPackage val = new ZPackage(); val.Write(state.Id); val.Write(state.Trigger); val.Write(player); server.Invoke("dannanboi.lootwindow.Roll", new object[1] { val }); } } private LootState Generate(long player, string id, string trigger) { if (ledger == null || ledgerWorld != World) { ledgerWorld = World; ledger = new RollLedger(Path.Combine(Paths.ConfigPath, "LootWindow", "state", "server", World.ToString(CultureInfo.InvariantCulture))); } return ledger.GetOrCreate(player, id, trigger, delegate { if (!Catalog.Tables.TryGetValue(trigger, out var value)) { throw new FormatException("Trigger has no active loot table: " + trigger); } LootState lootState = new LootState { Id = id, Trigger = trigger, Name = value.Name, EmptyLootText = value.EmptyLootText, World = World }; lootState.Rewards.AddRange(RollLedger.Roll(value, Plugin.MaxStack, Plugin.MaxQuality)); return lootState; }); } private void ReceiveRoll(ZRpc rpc, ZPackage packet) { //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Expected O, but got Unknown //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown if (!Authority || !Ready || !connections.TryGetValue(rpc, out var value) || !value.Compatible || value.Rejected || !rpc.IsConnected() || packet == null || packet.Size() > 512 || Time.realtimeSinceStartup < value.NextRoll) { return; } value.NextRoll = Time.realtimeSinceStartup + 0.5f; string text = null; Guid result; try { text = packet.ReadString(); string text2 = packet.ReadString(); long num = packet.ReadLong(); if (Guid.TryParseExact(text, "N", out result) && text2.Length != 0 && text2.Length <= 128) { if (!Enabled) { throw new FormatException("Loot Window is disabled on the server."); } ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(value.Peer.m_characterID) : null); if (val != null && val.GetOwner() == value.Peer.m_uid && val.GetLong("playerID", 0L) == num && num != 0) { LootState lootState = Generate(num, text, text2); ZPackage val2 = new ZPackage(); val2.Write(text); val2.Write(true); val2.Write(lootState.Encode()); rpc.Invoke("dannanboi.lootwindow.Reply", new object[1] { val2 }); } } } catch (Exception ex) { if (text != null && Guid.TryParseExact(text, "N", out result)) { ZPackage val3 = new ZPackage(); val3.Write(text); val3.Write(false); val3.Write("The server could not prepare this loot table."); rpc.Invoke("dannanboi.lootwindow.Reply", new object[1] { val3 }); plugin.Warn("Loot roll failed: " + ex.Message); } } } private void ReceiveReply(ZRpc rpc, ZPackage packet) { if (!FromServer(rpc) || rpc != server || packet == null || packet.Size() > 12288) { return; } try { string text = packet.ReadString(); bool flag = packet.ReadBool(); string text2 = packet.ReadString(); if (!flag) { plugin.Loot.RollFailed(text, text2); return; } LootState lootState = LootState.Decode(text2); if (!(text != lootState.Id) && lootState.World == World) { plugin.Loot.Receive(lootState); } } catch (Exception ex) { plugin.Warn("Loot reply rejected; trigger kept. " + ex.Message); } } internal void Tick(float now) {