using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace ExtraQuickslotsUnbound; [BepInPlugin("com.spencer4792.extraquickslotsunbound", "ExtraQuickslotsUnbound", "1.0.0")] public class ExtraQuickslotsPlugin : BaseUnityPlugin { public const string GUID = "com.spencer4792.extraquickslotsunbound"; public const string NAME = "ExtraQuickslotsUnbound"; public const string VERSION = "1.0.0"; public const int EXTRA = 8; internal static ManualLogSource Log; internal static int s_baseCount = -1; private static readonly KeyCode[] s_slotKeys; private static readonly string[] s_slotLabels; private static readonly int[] s_labelToVanillaSlot; private static readonly int[] s_vanillaSlotToLabel; private static readonly Type s_inputType; private static readonly MethodInfo s_getKey; private static readonly MethodInfo s_getKeyDown; private static GameObject s_uiRow; private static string s_uiRowOwner; private static Component[] s_uiDisplays; private static float s_nextEnforce; private static readonly Type s_panelType; private static readonly Type s_displayType; private static readonly FieldInfo s_refSlotField; private static readonly FieldInfo s_refQuickSlotField; private static readonly MethodInfo s_setQuickSlot; internal static bool ShiftHeld => Key((KeyCode)304) || Key((KeyCode)303); private static bool CtrlHeld => Key((KeyCode)306) || Key((KeyCode)305); internal static bool CtrlHeldPublic => CtrlHeld; internal void Awake() { Log = ((BaseUnityPlugin)this).Logger; try { Init(); } catch (Exception ex) { Log.LogError((object)("Startup failed: " + ex)); } } private void Init() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) new Harmony("com.spencer4792.extraquickslotsunbound").PatchAll(typeof(ExtraQuickslotsPlugin).Assembly); Log.LogMessage((object)"ExtraQuickslotsUnbound 1.0.0 ready."); } private static bool Key(KeyCode k) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) return s_getKey != null && (bool)s_getKey.Invoke(null, new object[1] { k }); } private static bool KeyDown(KeyCode k) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) return s_getKeyDown != null && (bool)s_getKeyDown.Invoke(null, new object[1] { k }); } private void Update() { try { if (KeyDown((KeyCode)291)) { DumpQuickslotUi(); } MaintainUiRow(); if ((Object)(object)s_uiRow != (Object)null && Time.unscaledTime > s_nextEnforce) { s_nextEnforce = Time.unscaledTime + 1f; Character val = ((!((Object)(object)CharacterManager.Instance != (Object)null)) ? null : CharacterManager.Instance.GetFirstLocalCharacter()); if ((Object)(object)val != (Object)null) { EnforceBindings(val); } } if (!ShiftHeld && !CtrlHeld) { return; } Character val2 = ((!((Object)(object)CharacterManager.Instance != (Object)null)) ? null : CharacterManager.Instance.GetFirstLocalCharacter()); if ((Object)(object)val2 == (Object)null || (Object)(object)val2.QuickSlotMngr == (Object)null) { return; } for (int i = 0; i < 8; i++) { if (KeyDown(s_slotKeys[i])) { if (ShiftHeld) { ActivateExtra(val2, i); } else if (CtrlHeld) { MoveUpToExtra(val2, i); } } } } catch (Exception ex) { Log.LogError((object)("Update: " + ex)); } } private static string DescribeDisplay(Component d) { //IL_001f: 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) string text = "?"; Transform transform = d.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if ((Object)(object)val != (Object)null) { text = val.anchoredPosition.x.ToString("F1"); } string text2 = "null"; string text3 = "-"; if (s_refQuickSlotField != null) { object? value = s_refQuickSlotField.GetValue(d); QuickSlot val2 = (QuickSlot)((value is QuickSlot) ? value : null); if ((Object)(object)val2 != (Object)null) { text2 = val2.Index.ToString(); text3 = ((!((Object)(object)val2.RegisteredItem != (Object)null)) ? "(empty)" : val2.RegisteredItem.Name); } } return string.Concat("x=", text, " RefSlotID=", s_refSlotField.GetValue(d), " boundSlot=", text2, " item='", text3, "' activeSelf=", d.gameObject.activeSelf, " sibling=", d.transform.GetSiblingIndex(), " name='", ((Object)d.gameObject).name, "'"); } private static void DumpQuickslotUi() { try { Character val = ((!((Object)(object)CharacterManager.Instance != (Object)null)) ? null : CharacterManager.Instance.GetFirstLocalCharacter()); if ((Object)(object)val == (Object)null || (Object)(object)val.CharacterUI == (Object)null) { Log.LogMessage((object)"No character UI."); return; } Log.LogMessage((object)("=== Quickslot UI dump (v1.0.0) baseCount=" + s_baseCount + " ===")); Component[] componentsInChildren = ((Component)val.CharacterUI).GetComponentsInChildren(s_panelType, true); Log.LogMessage((object)(componentsInChildren.Length + " KeyboardQuickSlotPanel(s):")); Component[] array = componentsInChildren; foreach (Component val2 in array) { string text = ((Object)val2.transform).name; Transform parent = val2.transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } Component[] componentsInChildren2 = val2.GetComponentsInChildren(s_displayType, true); Log.LogMessage((object)("PANEL '" + text + "' activeInHierarchy=" + val2.gameObject.activeInHierarchy + " displaysInSubtree=" + ((componentsInChildren2 != null) ? componentsInChildren2.Length : 0))); if (componentsInChildren2 != null) { Component[] array2 = componentsInChildren2; foreach (Component d in array2) { Log.LogMessage((object)(" " + DescribeDisplay(d))); } } } if ((Object)(object)s_uiRow != (Object)null && s_uiDisplays != null) { Log.LogMessage((object)("EXTRA ROW '" + ((Object)s_uiRow).name + "' active=" + s_uiRow.activeInHierarchy)); for (int k = 0; k < s_uiDisplays.Length; k++) { Component val3 = s_uiDisplays[k]; Log.LogMessage((object)(" mine[" + k + "] key=" + s_slotLabels[k] + " -> " + ((!((Object)(object)val3 == (Object)null)) ? DescribeDisplay(val3) : "NULL"))); } } else { Log.LogMessage((object)"EXTRA ROW: not built yet."); } CharacterQuickSlotManager quickSlotMngr = val.QuickSlotMngr; if ((Object)(object)quickSlotMngr != (Object)null && s_baseCount >= 0) { Log.LogMessage((object)"MANAGER extra slots:"); for (int l = 0; l < 8; l++) { QuickSlot quickSlot = quickSlotMngr.GetQuickSlot(s_baseCount + l); Log.LogMessage((object)(" slot " + (s_baseCount + l) + " (key " + s_slotLabels[l] + "): " + ((!((Object)(object)quickSlot == (Object)null)) ? ((!((Object)(object)quickSlot.RegisteredItem != (Object)null)) ? "(empty)" : ("'" + quickSlot.RegisteredItem.Name + "'")) : "NULL"))); } } Log.LogMessage((object)"=== end dump ==="); } catch (Exception ex) { Log.LogError((object)("DumpQuickslotUi: " + ex)); } } private void MaintainUiRow() { //IL_00a2: 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_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) if (s_baseCount < 0 || s_panelType == null || s_displayType == null || s_refSlotField == null) { return; } Character val = ((!((Object)(object)CharacterManager.Instance != (Object)null)) ? null : CharacterManager.Instance.GetFirstLocalCharacter()); if ((Object)(object)val == (Object)null || (Object)(object)val.CharacterUI == (Object)null || (Object)(object)val.QuickSlotMngr == (Object)null || ((Object)(object)s_uiRow != (Object)null && s_uiRowOwner == ((object)val.UID/*cast due to .constrained prefix*/).ToString())) { return; } Component val2 = null; Component[] componentsInChildren = ((Component)val.CharacterUI).GetComponentsInChildren(s_panelType, true); foreach (Component val3 in componentsInChildren) { if (val3.gameObject.activeInHierarchy) { Component[] componentsInChildren2 = val3.GetComponentsInChildren(s_displayType, false); if (componentsInChildren2 != null && componentsInChildren2.Length == 8) { val2 = val3; break; } } } if ((Object)(object)val2 == (Object)null) { return; } try { GameObject val4 = new GameObject("ExtraQuickslotHolder"); val4.SetActive(false); val4.transform.SetParent(val2.transform.parent, false); GameObject val5 = Object.Instantiate(val2.gameObject, val4.transform, false); ((Object)val5).name = "ExtraQuickslotRow"; Component[] componentsInChildren3 = val5.GetComponentsInChildren(s_panelType, true); foreach (Component val6 in componentsInChildren3) { Object.DestroyImmediate((Object)(object)val6); } Component[] componentsInChildren4 = val5.GetComponentsInChildren(s_displayType, true); Component[] array = (Component[])(object)new Component[8]; Component[] array2 = componentsInChildren4; foreach (Component val7 in array2) { int num = (int)s_refSlotField.GetValue(val7); if (num >= 0 && num < 8) { int num2 = s_vanillaSlotToLabel[num]; s_refSlotField.SetValue(val7, s_baseCount + num2); array[num2] = val7; } } val5.transform.SetParent(val2.transform.parent, false); RectTransform component = val5.GetComponent(); RectTransform component2 = val2.GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component2 != (Object)null) { Vector2 anchoredPosition = component2.anchoredPosition; float y = anchoredPosition.y; Rect rect = component2.rect; anchoredPosition.y = y + ((Rect)(ref rect)).height * 1.1f; component.anchoredPosition = anchoredPosition; } Object.Destroy((Object)(object)val4); val5.SetActive(true); s_uiRow = val5; s_uiRowOwner = ((object)val.UID/*cast due to .constrained prefix*/).ToString(); s_uiDisplays = array; EnforceBindings(val); MethodInfo method = s_displayType.GetMethod("SetInputTargetAlpha"); FieldInfo field = s_displayType.GetField("m_inputCanvasGroup", BindingFlags.Instance | BindingFlags.NonPublic); Component[] array3 = array; foreach (Component val8 in array3) { if ((Object)(object)val8 == (Object)null) { continue; } try { if (method != null) { method.Invoke(val8, new object[1] { 0f }); } if (!(field != null)) { continue; } object? value = field.GetValue(val8); Component val9 = (Component)((value is Component) ? value : null); if ((Object)(object)val9 != (Object)null) { PropertyInfo property = ((object)val9).GetType().GetProperty("alpha"); if (property != null) { property.SetValue(val9, 0f, null); } } } catch (Exception ex) { Log.LogWarning((object)("Input hint hide failed: " + ex.Message)); } } Log.LogInfo((object)("Extra quickslot row built: " + componentsInChildren4.Length + " displays.")); } catch (Exception ex2) { Log.LogError((object)("MaintainUiRow: " + ex2)); } } private static void EnforceBindings(Character character) { if (s_uiDisplays == null || s_setQuickSlot == null || (Object)(object)character.QuickSlotMngr == (Object)null) { return; } for (int i = 0; i < s_uiDisplays.Length && i < 8; i++) { QuickSlot quickSlot = character.QuickSlotMngr.GetQuickSlot(s_baseCount + i); if (!((Object)(object)s_uiDisplays[i] == (Object)null) && !((Object)(object)quickSlot == (Object)null) && (!(s_refQuickSlotField != null) || !object.ReferenceEquals(s_refQuickSlotField.GetValue(s_uiDisplays[i]), quickSlot))) { try { s_setQuickSlot.Invoke(s_uiDisplays[i], new object[1] { quickSlot }); } catch { } } } } private static void ActivateExtra(Character character, int i) { CharacterQuickSlotManager quickSlotMngr = character.QuickSlotMngr; if (s_baseCount < 0) { Log.LogWarning((object)"Extra slots not initialized yet."); return; } int num = s_baseCount + i; QuickSlot quickSlot = quickSlotMngr.GetQuickSlot(num); if ((Object)(object)quickSlot == (Object)null || quickSlot.IsFree) { Log.LogMessage((object)("Extra slot " + s_slotLabels[i] + " is empty (Ctrl+" + s_slotLabels[i] + " to assign from the vanilla bar).")); } else { quickSlotMngr.QuickSlotInput(num); } } private static void MoveUpToExtra(Character character, int i) { CharacterQuickSlotManager quickSlotMngr = character.QuickSlotMngr; if (s_baseCount < 0) { return; } QuickSlot quickSlot = quickSlotMngr.GetQuickSlot(s_labelToVanillaSlot[i]); QuickSlot quickSlot2 = quickSlotMngr.GetQuickSlot(s_baseCount + i); if (!((Object)(object)quickSlot == (Object)null) && !((Object)(object)quickSlot2 == (Object)null)) { Item registeredItem = quickSlot.RegisteredItem; if ((Object)(object)registeredItem == (Object)null) { Log.LogMessage((object)("Vanilla slot " + s_slotLabels[i] + " is empty; nothing to move up.")); return; } quickSlot.Clear(); quickSlot2.SetQuickSlot(registeredItem, true); Log.LogMessage((object)("Moved '" + registeredItem.Name + "' to extra slot " + s_slotLabels[i] + " (activate with Shift+" + s_slotLabels[i] + ").")); } } static ExtraQuickslotsPlugin() { KeyCode[] array = new KeyCode[8]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); s_slotKeys = (KeyCode[])(object)array; s_slotLabels = new string[8] { "Q", "E", "R", "1", "2", "3", "4", "5" }; s_labelToVanillaSlot = new int[8] { 5, 6, 7, 0, 1, 2, 3, 4 }; s_vanillaSlotToLabel = new int[8] { 3, 4, 5, 6, 7, 0, 1, 2 }; s_inputType = Type.GetType("UnityEngine.Input, UnityEngine.InputLegacyModule"); s_getKey = ((!(s_inputType != null)) ? null : s_inputType.GetMethod("GetKey", new Type[1] { typeof(KeyCode) })); s_getKeyDown = ((!(s_inputType != null)) ? null : s_inputType.GetMethod("GetKeyDown", new Type[1] { typeof(KeyCode) })); s_panelType = typeof(CharacterQuickSlotManager).Assembly.GetType("KeyboardQuickSlotPanel"); s_displayType = typeof(CharacterQuickSlotManager).Assembly.GetType("QuickSlotDisplay"); s_refSlotField = ((!(s_displayType != null)) ? null : s_displayType.GetField("RefSlotID")); s_refQuickSlotField = ((!(s_displayType != null)) ? null : s_displayType.GetField("m_refQuickSlot", BindingFlags.Instance | BindingFlags.NonPublic)); s_setQuickSlot = ((!(s_displayType != null)) ? null : s_displayType.GetMethod("SetQuickSlot")); } } [HarmonyPatch(typeof(CharacterQuickSlotManager), "Awake")] internal static class QuickSlotManager_Awake { [HarmonyPostfix] private static void Postfix(CharacterQuickSlotManager __instance) { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown try { FieldInfo fieldInfo = AccessTools.Field(typeof(CharacterQuickSlotManager), "m_quickSlots"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(CharacterQuickSlotManager), "m_character"); if (fieldInfo == null) { ExtraQuickslotsPlugin.Log.LogError((object)"m_quickSlots not found."); return; } if (!(fieldInfo.GetValue(__instance) is QuickSlot[] array)) { ExtraQuickslotsPlugin.Log.LogWarning((object)"Quickslot array null at Awake."); return; } int num = array.Length; if (ExtraQuickslotsPlugin.s_baseCount < 0) { ExtraQuickslotsPlugin.s_baseCount = num; } Character val = (Character)((!(fieldInfo2 != null)) ? null : /*isinst with value type is only supported in some contexts*/); GameObject val2 = new GameObject("ExtraQuickslots"); val2.transform.SetParent(((Component)__instance).transform, false); QuickSlot[] array2 = (QuickSlot[])(object)new QuickSlot[num + 8]; Array.Copy(array, array2, num); for (int i = 0; i < 8; i++) { QuickSlot val3 = val2.AddComponent(); val3.Index = num + i; if ((Object)(object)val != (Object)null) { val3.SetOwner(val); } array2[num + i] = val3; } fieldInfo.SetValue(__instance, array2); ExtraQuickslotsPlugin.Log.LogInfo((object)("Extra quickslots attached: indices " + num + ".." + (num + 8 - 1) + ".")); } catch (Exception ex) { ExtraQuickslotsPlugin.Log.LogError((object)("Awake patch: " + ex)); } } } [HarmonyPatch] internal static class SuppressVanillaWhileShift { private static IEnumerable TargetMethods() { for (int n = 1; n <= 8; n++) { MethodInfo m = AccessTools.Method(typeof(ControlsInput), "QuickSlotInstant" + n, (Type[])null, (Type[])null); if (m != null) { yield return m; } } } [HarmonyPostfix] private static void Postfix(ref bool __result) { if (__result && (ExtraQuickslotsPlugin.ShiftHeld || ExtraQuickslotsPlugin.CtrlHeldPublic)) { __result = false; } } }