using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("GroupCommandsToggle")] [assembly: AssemblyDescription("Hides the Erenshor party frame's quick-command buttons until a modifier key is held")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("GroupCommandsToggle")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("3b8f5d14-6c27-4e90-a1d3-5f80b4c2e7a9")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace GroupCommandsToggle; [BepInPlugin("com.erenshor.groupcommandstoggle", "Group Commands Toggle", "1.0.0")] public class GroupCommandsTogglePlugin : BaseUnityPlugin { public enum DumpKeyBinding { None, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12 } public const string PluginGuid = "com.erenshor.groupcommandstoggle"; public const string PluginName = "Group Commands Toggle"; public const string PluginVersion = "1.0.0"; private static readonly HashSet ButtonLabels = new HashSet(StringComparer.OrdinalIgnoreCase) { "Attack", "Assist MA", "Follow", "Pull Target", "Guard", "Run Away", "Invis Group", "Manage Roles", "Loot Distribution" }; private const string AutoPullPrefix = "Auto Pull"; private const int MinLabelsForCandidate = 7; private static ManualLogSource _log; private static ConfigEntry _cfgRevealKey; private static ConfigEntry _cfgRevealKeyAlt; private static KeyCode _revealKey; private static KeyCode _revealKeyAlt; private static ConfigEntry _cfgContainerOverride; private static ConfigEntry _cfgDumpKey; private static ConfigEntry _cfgDiagnostics; private static ConfigEntry _cfgHideKickButtons; private static readonly List _kickGroups = new List(); private static bool _kickBound; private static bool _kickHidden; private static readonly List _targets = new List(); private static string _lastCandidateSig = ""; private static readonly Dictionary _groupCache = new Dictionary(); private static RaidLootDist _groupLoot; private static GameObject _lootButton; private static bool _errorLogged; private static bool _weAreHiding; private static bool _loggedFirstDecision; private static bool _lastReveal; private static float _rediscoverTimer; private static float _statusTimer; private const int DriverObjectUpdate = 0; private const int DriverObjectLate = 1; private const int DriverHarmony = 2; private static readonly string[] DriverNames = new string[3] { "driverObject.Update", "driverObject.LateUpdate", "harmony:SimPlayerGrouping.Update" }; private static readonly bool[] _driverSeen = new bool[3]; private static bool _lateDriverAlive; private static int _lastTickFrame = -1; private static GameObject _driverObject; private static bool _driverSummaryLogged; private static float _driverSummaryTimer = 3f; private static bool _appliedOnce; private static bool _lastApplied; private static bool _interferenceLogged; private static KeyCode ToKeyCode(DumpKeyBinding binding) { if (binding == DumpKeyBinding.None) { return (KeyCode)0; } return (KeyCode)(282 + (binding - 1)); } private void Awake() { //IL_0062: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Expected O, but got Unknown _log = ((BaseUnityPlugin)this).Logger; _cfgRevealKey = ((BaseUnityPlugin)this).Config.Bind("General", "RevealKey", "LeftShift", "Hold this to reveal the group command buttons. Leave blank for none.\nLeftShift matches the key that already shows the Shift+1..8 hotkey\nlabels, so the buttons and their shortcuts appear together.\n\nAny key works. One example of each naming style:\n LeftShift a modifier (also LeftControl, LeftAlt, Tab, Space)\n X a letter\n Alpha1 the number row, NOT plain 1\n Keypad0 the number pad, NOT Numpad0\n F1 a function key\n Mouse2 a mouse button, counting from 0"); _cfgRevealKeyAlt = ((BaseUnityPlugin)this).Config.Bind("General", "RevealKeyAlt", "RightShift", "A second key that does the same thing. Leave blank for none."); _revealKey = ParseKey(_cfgRevealKey.Value, "RevealKey"); _revealKeyAlt = ParseKey(_cfgRevealKeyAlt.Value, "RevealKeyAlt"); _cfgHideKickButtons = ((BaseUnityPlugin)this).Config.Bind("General", "HideKickButtons", true, "Hide the X kick buttons beside each party member as well. These fade rather than switch off: the game turns them on and off itself as members join and leave, so switching them off here would fight it and could leave a kick button beside an empty slot."); _cfgDiagnostics = ((BaseUnityPlugin)this).Config.Bind("Troubleshooting", "Diagnostics", false, "Logs what the mod found and every time the buttons show or hide. Only worth turning on if something looks wrong and you are sending me a log."); _cfgDumpKey = ((BaseUnityPlugin)this).Config.Bind("Troubleshooting", "DumpKey", DumpKeyBinding.F9, "Press in-game to log every group-command panel found, with full paths\nand live state. Does nothing unless Diagnostics is on, so this key is\nleft alone for whatever else you use it for."); _cfgContainerOverride = ((BaseUnityPlugin)this).Config.Bind("Troubleshooting", "ContainerNameOverride", "", "Leave empty for automatic detection. To force a specific object, put its exact name here -- press DumpKey to list every candidate with its path."); _log.LogInfo((object)$"Active. Group command buttons hidden until {_revealKey} or {_revealKeyAlt} is held. Settings apply on restart."); ManualLogSource log = _log; object[] obj = new object[6] { ((Object)((Component)this).gameObject).name, ((Component)this).gameObject.activeSelf, ((Component)this).gameObject.activeInHierarchy, ((Behaviour)this).enabled, ((Object)((Component)this).gameObject).hideFlags, null }; Scene scene = ((Component)this).gameObject.scene; obj[5] = ((Scene)(ref scene)).name; log.LogInfo((object)string.Format("Host object: name=\"{0}\" activeSelf={1} activeInHierarchy={2} enabled={3} hideFlags={4} scene=\"{5}\"", obj)); try { _driverObject = new GameObject("GroupCommandsToggle_Driver"); Object.DontDestroyOnLoad((Object)(object)_driverObject); _driverObject.AddComponent(); _log.LogInfo((object)"Spawned own driver GameObject."); } catch (Exception ex) { _log.LogError((object)("Could not spawn own driver GameObject: " + ex)); } try { Harmony.CreateAndPatchAll(typeof(GroupCommandsTogglePlugin), (string)null); _log.LogInfo((object)"Harmony patch applied to SimPlayerGrouping.Update."); } catch (Exception ex2) { _log.LogError((object)("Harmony patch failed: " + ex2)); } } internal static void PumpDriverObjectUpdate() { Pump(0, isLate: false); } internal static void PumpDriverObjectLateUpdate() { Pump(1, isLate: true); } [HarmonyPatch(typeof(SimPlayerGrouping), "Update")] [HarmonyPostfix] public static void SimPlayerGrouping_Update_Postfix() { Pump(2, isLate: false); } internal static void Pump(int driver, bool isLate) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) try { if (!_driverSeen[driver]) { _driverSeen[driver] = true; _log.LogInfo((object)("Driver alive: " + DriverNames[driver])); } if (isLate) { _lateDriverAlive = true; } else if (_lateDriverAlive) { return; } if (_lastTickFrame != Time.frameCount) { _lastTickFrame = Time.frameCount; if (_cfgDiagnostics.Value && _cfgDumpKey.Value != DumpKeyBinding.None && Input.GetKeyDown(ToKeyCode(_cfgDumpKey.Value))) { DumpAllCandidates(); } Tick(); } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; _log.LogError((object)("Group Commands Toggle hit an error; reported once. " + ex)); } } } private static void Tick() { if (!_driverSummaryLogged) { _driverSummaryTimer -= Time.unscaledDeltaTime; if (_driverSummaryTimer <= 0f) { _driverSummaryLogged = true; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _driverSeen.Length; i++) { if (_driverSeen[i]) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(DriverNames[i]); } } _log.LogInfo((object)string.Format("Live drivers: {0}. LateUpdate driver alive: {1}.", (stringBuilder.Length == 0) ? "" : stringBuilder.ToString(), _lateDriverAlive)); } } bool editUIMode = GameData.EditUIMode; bool playerTyping = GameData.PlayerTyping; bool flag = IsRevealKeyHeld(); bool flag2 = editUIMode || (!playerTyping && flag); EnsureKickButtons(); ApplyKickVisibility(flag2); if (TargetsLost()) { _rediscoverTimer -= Time.unscaledDeltaTime; if (_rediscoverTimer <= 0f) { _rediscoverTimer = 1f; _loggedFirstDecision = false; Discover(); } } BindLootButton(); if (_targets.Count == 0) { LogUnboundStatus(); return; } DetectInterference(); SetTargetsVisible(flag2); _weAreHiding = !flag2; if (_cfgDiagnostics.Value && (!_loggedFirstDecision || flag2 != _lastReveal)) { _loggedFirstDecision = true; _log.LogInfo((object)string.Format("reveal={0} (editUI={1} typing={2} keyHeld={3}) -- {4} button(s), first is \"{5}\" activeInHierarchy={6}", flag2, editUIMode, playerTyping, flag, _targets.Count, ((Object)(object)_targets[0] != (Object)null) ? ((Object)_targets[0]).name : "", ((Object)(object)_targets[0] != (Object)null) ? _targets[0].activeInHierarchy.ToString() : "n/a")); } _lastReveal = flag2; } private static void EnsureKickButtons() { if (_kickBound) { for (int i = 0; i < _kickGroups.Count; i++) { if ((Object)(object)_kickGroups[i] == (Object)null) { _kickBound = false; _kickGroups.Clear(); break; } } } if (_kickBound) { return; } SimPlayerGrouping simPlayerGrouping = GameData.SimPlayerGrouping; if ((Object)(object)simPlayerGrouping == (Object)null) { return; } GameObject[] array = (GameObject[])(object)new GameObject[4] { simPlayerGrouping.D1, simPlayerGrouping.D2, simPlayerGrouping.D3, simPlayerGrouping.D4 }; foreach (GameObject val in array) { if ((Object)(object)val != (Object)null) { _kickGroups.Add(GetGroup(val)); } } if (_kickGroups.Count != 0) { _kickBound = true; _log.LogInfo((object)("Bound " + _kickGroups.Count + " kick button(s) (SimPlayerGrouping D1-D4). These fade rather than deactivate, so the game keeps full control of which slots actually have a kick button.")); } } private static void ApplyKickVisibility(bool reveal) { if (!_cfgHideKickButtons.Value) { RestoreKickButtons(); return; } float num = (reveal ? 1f : 0f); for (int i = 0; i < _kickGroups.Count; i++) { CanvasGroup val = _kickGroups[i]; if (!((Object)(object)val == (Object)null) && val.alpha != num) { val.alpha = num; val.interactable = reveal; val.blocksRaycasts = reveal; } } _kickHidden = !reveal; } private static void RestoreKickButtons() { if (!_kickHidden) { return; } for (int i = 0; i < _kickGroups.Count; i++) { CanvasGroup val = _kickGroups[i]; if (!((Object)(object)val == (Object)null)) { val.alpha = 1f; val.interactable = true; val.blocksRaycasts = true; } } _kickHidden = false; } private static void DetectInterference() { if (!_cfgDiagnostics.Value || !_appliedOnce || _interferenceLogged) { return; } for (int i = 0; i < _targets.Count; i++) { GameObject val = _targets[i]; if (!((Object)(object)val == (Object)null) && val.activeSelf != _lastApplied) { _interferenceLogged = true; _log.LogWarning((object)$"Another script is fighting us for \"{((Object)val).name}\": we set activeSelf={_lastApplied}, it is now {val.activeSelf}. Reported once. If this appears, hiding needs to happen later in the frame than whatever is re-enabling it."); break; } } } private static bool TargetsLost() { if (_targets.Count == 0) { return true; } for (int i = 0; i < _targets.Count; i++) { if ((Object)(object)_targets[i] == (Object)null) { return true; } } return false; } private static void BindLootButton() { if ((Object)(object)_lootButton != (Object)null || _targets.Count == 0) { return; } _groupLoot = GameData.GroupLootDist; if ((Object)(object)_groupLoot == (Object)null || (Object)(object)_groupLoot.LootDistButton == (Object)null) { return; } Transform transform = ((TMP_Text)_groupLoot.LootDistButton).transform; for (int i = 0; i < _targets.Count; i++) { GameObject val = _targets[i]; if ((Object)(object)val != (Object)null && transform.IsChildOf(val.transform)) { _lootButton = val; if (_cfgDiagnostics.Value) { _log.LogInfo((object)("\"" + ((Object)val).name + "\" is the Loot Distribution button, which the game reuses as its window's Cancel control. It stays visible while that window is open.")); } break; } } } private static bool IsRevealKeyHeld() { //IL_0000: 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) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((int)_revealKey != 0 && Input.GetKey(_revealKey)) { return true; } if ((int)_revealKeyAlt != 0) { return Input.GetKey(_revealKeyAlt); } return false; } private static KeyCode ParseKey(string name, string settingName) { //IL_006d: 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_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) if (string.IsNullOrEmpty(name) || name.Trim().Length == 0) { return (KeyCode)0; } try { return (KeyCode)Enum.Parse(typeof(KeyCode), name.Trim(), ignoreCase: true); } catch { _log.LogWarning((object)("\"" + name + "\" is not a key name, so " + settingName + " does nothing. Try LeftShift, RightControl, LeftAlt, Tab, and so on.")); return (KeyCode)0; } } private static List FindCandidates() { //IL_0032: 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) Dictionary dictionary = new Dictionary(); List list = new List(); TMP_Text[] array = Resources.FindObjectsOfTypeAll(); foreach (TMP_Text val in array) { if ((Object)(object)val == (Object)null) { continue; } Scene scene = ((Component)val).gameObject.scene; if (!((Scene)(ref scene)).IsValid() || !IsStackLabel(val.text)) { continue; } Transform parent = val.transform.parent; int num = 0; while ((Object)(object)parent != (Object)null && num < 4) { if (CountStackLabels(parent) >= 7) { int instanceID = ((Object)parent).GetInstanceID(); if (!dictionary.ContainsKey(instanceID)) { dictionary[instanceID] = parent; list.Add(parent); } break; } parent = parent.parent; num++; } } return list; } private static void Discover() { _targets.Clear(); _lootButton = null; List list = FindCandidates(); string text = SignatureOf(list); if (text != _lastCandidateSig) { _lastCandidateSig = text; LogCandidates(list); } if (list.Count == 0) { return; } if (!string.IsNullOrEmpty(_cfgContainerOverride.Value)) { string text2 = _cfgContainerOverride.Value.Trim(); foreach (Transform item in list) { if (string.Equals(((Object)item).name, text2, StringComparison.Ordinal)) { Accept(item, "ContainerNameOverride"); return; } } _log.LogWarning((object)("ContainerNameOverride \"" + text2 + "\" matched no candidate; using automatic detection.")); } Transform val = null; foreach (Transform item2 in list) { if (((Component)item2).gameObject.activeInHierarchy && ((Object)(object)val == (Object)null || item2.childCount < val.childCount)) { val = item2; } } if (!((Object)(object)val == (Object)null)) { Accept(val, "auto (smallest rendered)"); } } private static string SignatureOf(List candidates) { StringBuilder stringBuilder = new StringBuilder(); foreach (Transform candidate in candidates) { stringBuilder.Append(((Object)candidate).GetInstanceID()).Append(':').Append(((Component)candidate).gameObject.activeInHierarchy ? '1' : '0') .Append('|'); } return stringBuilder.ToString(); } private static void LogUnboundStatus() { if (_cfgDiagnostics.Value) { _statusTimer -= Time.unscaledDeltaTime; if (!(_statusTimer > 0f)) { _statusTimer = 5f; _log.LogInfo((object)string.Format("Not bound yet: no rendered group command container. GameData.SimPlayerGrouping is {0}. Retrying every second.", ((Object)(object)GameData.SimPlayerGrouping != (Object)null) ? "set" : "null")); } } } private static void Accept(Transform container, string how) { _log.LogInfo((object)("Group command stack container [" + how + "]: " + PathOf(container))); StringBuilder stringBuilder = new StringBuilder("Hiding these button objects individually (container, RaidLootDist and AwardLootGroup left alone):"); for (int i = 0; i < container.childCount; i++) { Transform child = container.GetChild(i); string text = LabelOf(child); if (IsStackLabel(text)) { _targets.Add(((Component)child).gameObject); stringBuilder.Append("\n \"").Append(((Object)child).name).Append("\" \"") .Append(text) .Append("\""); } } if (_targets.Count == 0) { _log.LogWarning((object)("No button children matched inside " + PathOf(container) + "; falling back to hiding the container itself.")); _targets.Add(((Component)container).gameObject); } else { _log.LogInfo((object)stringBuilder.ToString()); } } private static void LogCandidates(List candidates) { SimPlayerGrouping simPlayerGrouping = GameData.SimPlayerGrouping; Transform val = null; if ((Object)(object)simPlayerGrouping != (Object)null && (Object)(object)simPlayerGrouping.PullButton != (Object)null) { val = ((TMP_Text)simPlayerGrouping.PullButton).transform; } StringBuilder stringBuilder = new StringBuilder("Group command containers found: ").Append(candidates.Count); foreach (Transform candidate in candidates) { bool num = (Object)(object)val != (Object)null && val.IsChildOf(candidate); RaidLootDist component = ((Component)candidate).GetComponent(); stringBuilder.Append("\n ").Append(PathOf(candidate)).Append("\n labels=") .Append(CountStackLabels(candidate)) .Append(" children=") .Append(candidate.childCount) .Append(" activeInHierarchy=") .Append(((Component)candidate).gameObject.activeInHierarchy) .Append(" activeSelf=") .Append(((Component)candidate).gameObject.activeSelf); if ((Object)(object)component != (Object)null) { stringBuilder.Append(" RaidLootDist.UseGroup=").Append(component.UseGroup); } if (num) { stringBuilder.Append(" <-- holds SimPlayerGrouping.PullButton"); } } _log.LogInfo((object)stringBuilder.ToString()); } private static void DumpAllCandidates() { List list = FindCandidates(); _log.LogInfo((object)"=== DumpKey pressed ==="); if (list.Count == 0) { _log.LogInfo((object)"No group command containers found in the loaded UI."); } else { LogCandidates(list); } StringBuilder stringBuilder = new StringBuilder("Currently targeting ").Append(_targets.Count).Append(" object(s):"); foreach (GameObject target in _targets) { stringBuilder.Append("\n ").Append(((Object)(object)target != (Object)null) ? PathOf(target.transform) : ""); } _log.LogInfo((object)stringBuilder.ToString()); } private static int CountStackLabels(Transform t) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); TMP_Text[] componentsInChildren = ((Component)t).GetComponentsInChildren(true); foreach (TMP_Text val in componentsInChildren) { string text = ((val.text != null) ? val.text.Trim() : ""); if (IsStackLabel(text)) { hashSet.Add(text.StartsWith("Auto Pull", StringComparison.OrdinalIgnoreCase) ? "Auto Pull" : text); } } return hashSet.Count; } private static string LabelOf(Transform t) { TMP_Text componentInChildren = ((Component)t).GetComponentInChildren(true); if (!((Object)(object)componentInChildren != (Object)null) || componentInChildren.text == null) { return ""; } return componentInChildren.text.Trim(); } private static bool IsStackLabel(string label) { if (string.IsNullOrEmpty(label)) { return false; } label = label.Trim(); if (!ButtonLabels.Contains(label)) { return label.StartsWith("Auto Pull", StringComparison.OrdinalIgnoreCase); } return true; } private static string PathOf(Transform t) { StringBuilder stringBuilder = new StringBuilder(((Object)t).name); Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Insert(0, ((Object)parent).name + "/"); parent = parent.parent; } return stringBuilder.ToString(); } private static void SetTargetsVisible(bool visible) { bool flag = !visible && (Object)(object)_groupLoot != (Object)null && (Object)(object)_groupLoot.DistWindow != (Object)null && _groupLoot.DistWindow.activeSelf; for (int i = 0; i < _targets.Count; i++) { GameObject val = _targets[i]; if (!((Object)(object)val == (Object)null)) { bool flag2 = visible || (flag && (Object)(object)val == (Object)(object)_lootButton); if (val.activeSelf != flag2) { val.SetActive(flag2); } } } _lastApplied = visible; _appliedOnce = true; } private static CanvasGroup GetGroup(GameObject go) { int instanceID = ((Object)go).GetInstanceID(); if (_groupCache.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null) { return value; } CanvasGroup val = go.GetComponent(); if ((Object)(object)val == (Object)null) { val = go.AddComponent(); } _groupCache[instanceID] = val; return val; } } internal class ToggleDriver : MonoBehaviour { private void Update() { GroupCommandsTogglePlugin.PumpDriverObjectUpdate(); } private void LateUpdate() { GroupCommandsTogglePlugin.PumpDriverObjectLateUpdate(); } }